Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
c0842a9
feat: K8s agent self-update — agent loop, helm chart, manager API (AL…
Jun 2, 2026
d940ead
test(helm): re-baseline data_layer + manager_only_pure_worker snapshots
Jun 2, 2026
b069199
chore(sdk): refresh platform OpenAPI with target-agent-version endpoint
Jun 2, 2026
e78e9a9
fix(helm): enable agent runtime-data persistence by default
Jun 3, 2026
b859bdf
feat(agent,manager): /v1/rejoin endpoint for emptyDir-wipe recovery
Jun 3, 2026
9f7f962
fix(agent): preserve 409 from initialize so rejoin fall-through fires
Jun 3, 2026
1822481
Merge remote-tracking branch 'origin/main' into yonatan/alien-59-self…
Jun 3, 2026
762de2b
Merge origin/main into yonatan/alien-59-self-update (ALIEN-59)
Jun 29, 2026
b466bfb
chore: re-sync platform openapi spec from merged platform API
Jun 29, 2026
2ef69b3
Merge origin/main into self-update branch
Jun 30, 2026
148c096
Merge remote-tracking branch 'origin/main' into yonatan/alien-59-self…
Jul 5, 2026
a82ab97
fix: sync pnpm-lock with regenerated platform SDK package.json
Jul 5, 2026
f6db604
test(alien-helm): pass manager-fetch values to manager_only render tests
Jul 5, 2026
8bcabad
feat(agent): report self-update outcomes to the manager (ALIEN-59)
Jul 5, 2026
a9f85ae
Merge remote-tracking branch 'origin/main' into yonatan/alien-59-self…
Jul 5, 2026
8bbba6a
Merge remote-tracking branch 'origin/main' into yonatan/alien-59-self…
Jul 5, 2026
4c47ea0
Regenerate platform SDK openapi for operator naming
Jul 5, 2026
6ce0252
Restore pnpm-lock.yaml to match package.json (fix CI Fast)
Jul 5, 2026
9dfd4f0
Restore platform TS SDK to main's clean version (fix CI Fast OOM)
Jul 5, 2026
c09c187
Fix regime→packaging in test-only constructions (fix CI Fast rust)
Jul 5, 2026
c030a86
Rename regime→packaging internally; fix sqlite column enum mismatch
Jul 7, 2026
262679b
Merge origin/main (58 commits); resolve conflicts
Jul 7, 2026
5ff46c9
Regenerate platform SDK openapi for operator naming (post main merge)
Jul 7, 2026
e581434
feat: type operator os/arch on the sync wire (CR #5/#6)
Jul 8, 2026
d6a05bb
docs: explain the helm upgrade --atomic/--wait/--reuse-values flags (CR)
Jul 8, 2026
35e777c
fix: operator_target floor + semver validation (CR greptile P2s)
Jul 8, 2026
72809d3
feat: white-label the operator self-update Job labels (CR)
Jul 8, 2026
4df401c
refactor: use alien-k8s-clients for the self-update K8s calls (CR)
Jul 8, 2026
b6e5d60
Merge remote-tracking branch 'origin/main' into yonatan/alien-59-self…
Jul 8, 2026
602bf1f
feat: os-service operator self-update core (wire, launcher, actuator)
Jul 9, 2026
3bacffe
feat: Linux launcher shims + os-service self-update E2E (phase 1)
Jul 9, 2026
9fae4c6
fix: gate launcher systemd READY on /livez, not /readyz
Jul 9, 2026
1159cec
test: update manager integration tests for the launcher_version field
Jul 9, 2026
52bd9e9
chore(client-sdks): regenerate Alien Platform API spec with operator …
Jul 9, 2026
ede3643
feat: enable operator self-update on macOS (launchd) and harden die-w…
Jul 9, 2026
39b8894
feat: surface "redeploy required" when the launcher is below the targ…
Jul 9, 2026
223a1cc
fix(operator): terminate the managed app on shutdown
Jul 11, 2026
077bd54
feat(deploy): default to the launcher-supervised layout on Linux
Jul 11, 2026
35249ce
test(e2e): guard against an orphaned workload after a self-update swap
Jul 11, 2026
add4ebc
test(e2e): ignore the orphan guard pending the workload-seed fix
Jul 11, 2026
cd09762
test(e2e): seed the workload before the deployment and enable the orp…
Jul 11, 2026
d00aaea
feat(launcher): Windows ServiceHost (SCM) — Phase 3 T3.1
Jul 11, 2026
b4db556
feat(launcher): Windows ChildSupervisor (Job Object) — Phase 3 T3.2
Jul 11, 2026
88ebd17
feat(operator): die-with-parent app child + Windows graceful stop — P…
Jul 11, 2026
9690dd0
feat(launcher): Windows VersionStore (junctions + locked-file gc) — P…
Jul 11, 2026
06a173d
feat(deploy-cli): install alien-launcher as the Windows service — Pha…
Jul 11, 2026
f6dbb4c
feat(launcher): wire Windows run_supervisor (SCM + console) — Phase 3…
Jul 11, 2026
1999320
ci(launcher): Windows OS-service job (platform tests + SCM smoke) — P…
Jul 11, 2026
6d34246
fix(launcher): assert the health probe's timeout contract, not Unix f…
Jul 11, 2026
5463a7c
test(alien-helm): render the log-collector test with the manager-fetc…
Jul 11, 2026
bf557d9
Merge origin/main (v1.13.2) into yonatan/alien-59-self-update
Jul 12, 2026
4106195
test(alien-helm): regenerate stale data_layer + manager_only_pure_wor…
Jul 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 259 additions & 0 deletions .github/workflows/e2e-os-service.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
name: E2E OS-Service Self-Update

# The os-service self-update flow end-to-end: a real alien-launcher
# supervising a real alien-operator against an in-process manager —
# promote, rollback, backoff, crash recovery, die-with-parent, and the
# min-launcher gate. Linux now; macOS and Windows jobs join with their
# platform phases.

on:
pull_request:
paths:
- "crates/alien-launcher/**"
- "crates/alien-operator/**"
- "crates/alien-deploy-cli/**"
- "crates/alien-core/src/sync.rs"
- "crates/alien-core/src/self_update.rs"
- "crates/alien-manager/src/release_manifest.rs"
- "crates/alien-test/src/os_service.rs"
- "crates/alien-test/tests/e2e_os_service.rs"
- ".github/workflows/e2e-os-service.yml"
schedule:
# Nightly on main, catching drift from changes outside the path filter.
- cron: "17 3 * * *"
workflow_dispatch:

permissions:
contents: read

env:
CARGO_INCREMENTAL: "0"
CARGO_NET_GIT_FETCH_WITH_CLI: "true"

jobs:
linux-os-service:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork != true
runs-on: depot-ubuntu-24.04-arm-16
timeout-minutes: 60

steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- name: Install protoc (opentelemetry-proto build dependency)
run: sudo apt-get update -qq && sudo apt-get install -y -qq protobuf-compiler

- name: Build the launcher and the test-hooks operator
run: |
cargo build -p alien-launcher
cargo build -p alien-operator --features test-hooks

- name: Launcher unit + platform-guard tests
run: cargo test -p alien-launcher

- name: Install zig + cargo-zigbuild (the orphan-guard test builds a real workload OCI)
run: |
set -euo pipefail
curl -fsSL https://ziglang.org/download/0.13.0/zig-linux-aarch64-0.13.0.tar.xz -o /tmp/zig.tar.xz
sudo mkdir -p /opt/zig-0.13.0
sudo tar xJf /tmp/zig.tar.xz -C /opt/zig-0.13.0 --strip-components=1
sudo ln -sf /opt/zig-0.13.0/zig /usr/local/bin/zig
zig version
rustup target add aarch64-unknown-linux-musl
cargo install cargo-zigbuild --locked

- name: E2E scenarios
env:
ALIEN_LAUNCHER_BINARY: ${{ github.workspace }}/target/debug/alien-launcher
ALIEN_OPERATOR_BINARY: ${{ github.workspace }}/target/debug/alien-operator
RUST_LOG: info
run: |
cargo test -p alien-test --features e2e-os-service --test e2e_os_service -- --test-threads=4

- name: systemd smoke — unit template under a real init
run: |
set -euo pipefail
# Prove the Type=notify + watchdog wiring under real systemd: the
# launcher must reach "active" (which requires it to send READY=1)
# and stop cleanly. The operator here is a stub HTTP server that
# serves /livez -> 200 (the launcher gates its startup READY on
# liveness); the real sync/update flow is covered by the E2E above.
DATA_DIR=$(mktemp -d)
sudo mkdir -p "$DATA_DIR"/{versions/0.0.1,launcher,state,state-snapshots,failed,download}
# Stub operator: bind ALIEN_HEALTH_ADDR (the launcher passes it),
# answer every GET with 200, exit on SIGTERM.
sudo tee "$DATA_DIR/versions/0.0.1/alien-operator" >/dev/null <<'OP'
#!/usr/bin/env python3
import http.server, os
host, port = os.environ["ALIEN_HEALTH_ADDR"].rsplit(":", 1)
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200); self.end_headers(); self.wfile.write(b"ok")
def log_message(self, *a): pass
http.server.HTTPServer((host, int(port)), H).serve_forever()
OP
sudo chmod 755 "$DATA_DIR/versions/0.0.1/alien-operator"
sudo ln -s versions/0.0.1 "$DATA_DIR/current"
sudo ln -s versions/0.0.1 "$DATA_DIR/last-stable"
sudo cp target/debug/alien-launcher "$DATA_DIR/launcher/alien-launcher"

sudo tee /etc/systemd/system/alien-e2e-smoke.service >/dev/null <<EOF
[Unit]
Description=Alien Operator Launcher (E2E smoke)

[Service]
Type=notify
NotifyAccess=main
WatchdogSec=60
ExecStart=$DATA_DIR/launcher/alien-launcher --data-dir $DATA_DIR --probation-secs 10
Restart=always
RestartSec=2
EOF
sudo systemctl daemon-reload

dump_logs() { journalctl -u alien-e2e-smoke --no-pager | tail -60 || true; }
trap dump_logs ERR

# Type=notify: `start` blocks until READY=1, so reaching it = success.
sudo systemctl start alien-e2e-smoke
[ "$(systemctl show -p ActiveState --value alien-e2e-smoke)" = "active" ]

# Clean stop propagates through the launcher to the stub operator.
sudo systemctl stop alien-e2e-smoke
[ "$(systemctl show -p ActiveState --value alien-e2e-smoke)" = "inactive" ]
sudo systemctl disable alien-e2e-smoke 2>/dev/null || true
sudo rm /etc/systemd/system/alien-e2e-smoke.service
sudo systemctl daemon-reload

# Windows: the platform unit + state-machine tests (the real proof of the
# junction VersionStore, the Job-Object ChildSupervisor, and the SCM control
# mapping) plus an SCM smoke. The full seven-scenario E2E rig is Unix-only for
# now (symlinks, /bin/sh version wrappers, pgrep/ps, a musl workload build) —
# porting `os_service.rs` to Windows is tracked separately (doc 17 T3.7).
windows-os-service:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork != true
runs-on: depot-windows-2025-16
timeout-minutes: 60
env:
CARGO_INCREMENTAL: "0"
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
# ring / aws-lc-sys build their C with the bundled cmake builder + NASM.
AWS_LC_SYS_CMAKE_BUILDER: "1"

steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@nightly

- name: Install build tools (protoc, nasm, cmake)
shell: bash
run: choco install protoc nasm cmake -y

- name: Build the launcher
shell: bash
run: |
export PATH="/c/Program Files/NASM:$PATH"
cargo build -p alien-launcher

# The real proof of T3.1–T3.4 on native Windows: the junction VersionStore's
# flip + crash-residue reconciliation + locked-file gc, the full Phase-0
# state-machine suite over that store, the Job-Object kill-on-close
# ChildSupervisor (spawn/stop/exit-code + drop-kills-child), and the SCM
# control-signal mapping — all run here as real processes.
- name: Launcher + operator unit + platform tests
shell: bash
run: |
export PATH="/c/Program Files/NASM:$PATH"
cargo test -p alien-launcher -p alien-operator

# SCM smoke (the runner is admin): register the launcher as a real service,
# prove it starts (a tiny stub operator answers /livez so the launcher
# reports RUNNING), the doc-12 recovery config lands, and a stop propagates
# cleanly through the launcher to the operator, then delete.
- name: SCM smoke — sc create/start/qfailure/stop/delete
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$svc = "alien-e2e-scm"
$data = "C:\alien-scm" # fixed, space-free: keeps sc binPath simple
$ver = Join-Path $data "versions\0.0.1"

# Clean any residue from a previous run on this (reused) runner.
sc.exe stop $svc *> $null
sc.exe delete $svc *> $null
if (Test-Path $data) { Remove-Item -Recurse -Force $data }
foreach ($d in "versions\0.0.1","launcher","state","state-snapshots","failed","download") {
New-Item -ItemType Directory -Force -Path (Join-Path $data $d) | Out-Null
}

# A tiny stub "operator": bind ALIEN_HEALTH_ADDR (the launcher passes it)
# and answer every GET 200 — the launcher gates its SCM 'Running' on the
# operator's /livez. Compiled to a real .exe so the launcher can
# CreateProcess it (the spawn name is alien-operator.exe).
$stub = @'
use std::io::{Read, Write};
use std::net::TcpListener;
fn main() {
let addr = std::env::var("ALIEN_HEALTH_ADDR").expect("ALIEN_HEALTH_ADDR");
let listener = TcpListener::bind(&addr).expect("bind health addr");
for stream in listener.incoming() {
let mut stream = match stream { Ok(s) => s, Err(_) => continue };
let mut buf = [0u8; 1024];
let _ = stream.read(&mut buf);
let _ = stream.write_all(
b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok",
);
}
}
'@
$stubRs = Join-Path $env:RUNNER_TEMP "stub_operator.rs"
Set-Content -Path $stubRs -Value $stub -Encoding ascii
& rustc $stubRs -O -o (Join-Path $ver "alien-operator.exe")
if ($LASTEXITCODE -ne 0) { throw "rustc stub operator failed" }

Copy-Item "target\debug\alien-launcher.exe" (Join-Path $data "launcher\alien-launcher.exe")

# Junction pointers, exactly as the installer seeds them.
New-Item -ItemType Junction -Path (Join-Path $data "current") -Target $ver | Out-Null
New-Item -ItemType Junction -Path (Join-Path $data "last-stable") -Target $ver | Out-Null

$launcher = Join-Path $data "launcher\alien-launcher.exe"
$bin = "$launcher --data-dir $data --probation-secs 10 --health-port 7799"

Write-Host "== sc create =="
sc.exe create $svc binPath= $bin start= demand
if ($LASTEXITCODE -ne 0) { throw "sc create failed" }

Write-Host "== sc failure (doc-12 recovery config) =="
sc.exe failure $svc reset= 86400 actions= restart/5000/restart/5000/restart/5000
if ($LASTEXITCODE -ne 0) { throw "sc failure failed" }
$qf = (sc.exe qfailure $svc | Out-String)
Write-Host $qf
if ($qf -notmatch "86400") { throw "recovery reset period (86400) not applied" }
if ($qf -notmatch "(?i)restart") { throw "restart actions not applied" }

Write-Host "== sc start =="
sc.exe start $svc | Out-Null
$running = $false
foreach ($i in 1..30) {
Start-Sleep -Seconds 1
if ((sc.exe query $svc | Out-String) -match "RUNNING") { $running = $true; break }
}
if (-not $running) {
sc.exe query $svc
sc.exe delete $svc *> $null
throw "service never reached RUNNING (launcher did not gate on the operator /livez)"
}
Write-Host "service RUNNING"

Write-Host "== sc stop (must propagate to the operator) =="
sc.exe stop $svc | Out-Null
$stopped = $false
foreach ($i in 1..30) {
Start-Sleep -Seconds 1
if ((sc.exe query $svc | Out-String) -match "STOPPED") { $stopped = $true; break }
}
sc.exe delete $svc *> $null
if (-not $stopped) { throw "service did not stop cleanly" }
Write-Host "service STOPPED cleanly — stop propagated through to the operator"
Loading
Loading