[WRONG BRANCH] fix(windows): remove native WinSW service during fresh scheduler install #203
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Service lifecycle | |
| on: | |
| pull_request: | |
| branches: [main, dev] | |
| paths: | |
| - "src/service.ts" | |
| # Keep in sync with the release.yml service-gate regex (release.yml "Require | |
| # successful Cross-platform CI" step). src/cli.ts is the pre-restructure compat | |
| # stub that durable launchers still execute. | |
| - "src/cli.ts" | |
| - "src/cli/index.ts" | |
| - "src/lib/bun-runtime.ts" | |
| - "package.json" | |
| - "bun.lock" | |
| - ".github/workflows/service-lifecycle.yml" | |
| push: | |
| paths: | |
| - "src/service.ts" | |
| # Keep in sync with the release.yml service-gate regex (see above). | |
| - "src/cli.ts" | |
| - "src/cli/index.ts" | |
| - "src/lib/bun-runtime.ts" | |
| - "package.json" | |
| - "bun.lock" | |
| - ".github/workflows/service-lifecycle.yml" | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: service-lifecycle-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| linux-systemd: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: 1.3.14 | |
| - run: bun install --frozen-lockfile | |
| # systemd user session needs XDG_RUNTIME_DIR | |
| - name: Enable systemd user session | |
| run: | | |
| sudo loginctl enable-linger "$(whoami)" | |
| user_id="$(id -u)" | |
| export XDG_RUNTIME_DIR="/run/user/${user_id}" | |
| echo "XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" >> "$GITHUB_ENV" | |
| # Write a minimal config so ocx start doesn't fail on missing providers | |
| - name: Write minimal config | |
| run: | | |
| mkdir -p ~/.opencodex | |
| echo '{"port":10199,"providers":{}}' > ~/.opencodex/config.json | |
| - name: Install service | |
| run: bun run src/cli/index.ts service install | |
| - name: Verify service is running | |
| run: | | |
| ready=false | |
| for attempt in $(seq 1 20); do | |
| if systemctl --user is-active --quiet opencodex-proxy \ | |
| && curl --max-time 2 -sf http://127.0.0.1:10199/healthz >/dev/null; then | |
| ready=true | |
| break | |
| fi | |
| echo "waiting for systemd service health ($attempt/20)" | |
| sleep 1 | |
| done | |
| if [ "$ready" != true ]; then | |
| systemctl --user status opencodex-proxy --no-pager || true | |
| journalctl --user-unit opencodex-proxy --no-pager -n 200 || true | |
| echo "::error::systemd service did not become healthy" | |
| exit 1 | |
| fi | |
| - name: ocx stop should stop the service too | |
| run: | | |
| bun run src/cli/index.ts stop | |
| stopped=false | |
| for attempt in $(seq 1 10); do | |
| if ! systemctl --user is-active --quiet opencodex-proxy \ | |
| && ! curl --max-time 2 -sf http://127.0.0.1:10199/healthz >/dev/null; then | |
| stopped=true | |
| break | |
| fi | |
| echo "waiting for systemd service stop ($attempt/10)" | |
| sleep 1 | |
| done | |
| if [ "$stopped" != true ]; then | |
| systemctl --user status opencodex-proxy --no-pager || true | |
| journalctl --user-unit opencodex-proxy --no-pager -n 200 || true | |
| echo "::error::service or health endpoint survived ocx stop" | |
| exit 1 | |
| fi | |
| - name: Restart service and verify crash-restart | |
| run: | | |
| bun run src/cli/index.ts service start | |
| old_pid="$(systemctl --user show opencodex-proxy --property MainPID --value)" | |
| case "$old_pid" in | |
| ""|0|*[!0-9]*) | |
| echo "::error::systemd service has no positive MainPID before crash test" | |
| exit 1 | |
| ;; | |
| esac | |
| kill -9 "$old_pid" | |
| restarted=false | |
| for attempt in $(seq 1 20); do | |
| new_pid="$(systemctl --user show opencodex-proxy --property MainPID --value 2>/dev/null || true)" | |
| if systemctl --user is-active --quiet opencodex-proxy \ | |
| && [ -n "$new_pid" ] \ | |
| && [ "$new_pid" != 0 ] \ | |
| && [ "$new_pid" != "$old_pid" ] \ | |
| && curl --max-time 2 -sf http://127.0.0.1:10199/healthz >/dev/null; then | |
| restarted=true | |
| break | |
| fi | |
| echo "waiting for systemd crash restart ($attempt/20)" | |
| sleep 1 | |
| done | |
| if [ "$restarted" != true ]; then | |
| systemctl --user status opencodex-proxy --no-pager || true | |
| journalctl --user-unit opencodex-proxy --no-pager -n 200 || true | |
| echo "::error::systemd service did not restart healthy with a new MainPID" | |
| exit 1 | |
| fi | |
| - name: Uninstall service | |
| if: ${{ !cancelled() }} | |
| run: | | |
| bun run src/cli/index.ts service uninstall | |
| removed=false | |
| for attempt in $(seq 1 10); do | |
| if [ ! -e "$HOME/.config/systemd/user/opencodex-proxy.service" ] \ | |
| && ! systemctl --user is-active --quiet opencodex-proxy \ | |
| && ! curl --max-time 2 -sf http://127.0.0.1:10199/healthz >/dev/null; then | |
| removed=true | |
| break | |
| fi | |
| echo "waiting for systemd uninstall cleanup ($attempt/10)" | |
| sleep 1 | |
| done | |
| if [ "$removed" != true ]; then | |
| systemctl --user status opencodex-proxy --no-pager || true | |
| journalctl --user-unit opencodex-proxy --no-pager -n 200 || true | |
| echo "::error::systemd artifact or proxy survived uninstall" | |
| exit 1 | |
| fi | |
| macos-launchd: | |
| runs-on: macos-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: 1.3.14 | |
| - run: bun install --frozen-lockfile | |
| - name: Write minimal config | |
| run: | | |
| mkdir -p ~/.opencodex | |
| echo '{"port":10199,"providers":{}}' > ~/.opencodex/config.json | |
| - name: Install service | |
| run: bun run src/cli/index.ts service install | |
| - name: Verify launchd service is healthy | |
| run: | | |
| ready=false | |
| for attempt in $(seq 1 20); do | |
| if launchctl list | grep -q com.opencodex.proxy \ | |
| && curl --max-time 2 -sf http://127.0.0.1:10199/healthz >/dev/null; then | |
| ready=true | |
| break | |
| fi | |
| echo "waiting for launchd service health ($attempt/20)" | |
| sleep 1 | |
| done | |
| if [ "$ready" != true ]; then | |
| launchctl list | grep com.opencodex.proxy || true | |
| tail -n 200 "$HOME/.opencodex/service.log" || true | |
| echo "::error::launchd service did not become healthy" | |
| exit 1 | |
| fi | |
| - name: ocx stop should unload the agent | |
| run: | | |
| bun run src/cli/index.ts stop | |
| stopped=false | |
| for attempt in $(seq 1 10); do | |
| if ! launchctl list | grep -q com.opencodex.proxy \ | |
| && ! curl --max-time 2 -sf http://127.0.0.1:10199/healthz >/dev/null; then | |
| stopped=true | |
| break | |
| fi | |
| echo "waiting for launchd service stop ($attempt/10)" | |
| sleep 1 | |
| done | |
| if [ "$stopped" != true ]; then | |
| launchctl list | grep com.opencodex.proxy || true | |
| tail -n 200 "$HOME/.opencodex/service.log" || true | |
| echo "::error::launchd label or health endpoint survived ocx stop" | |
| exit 1 | |
| fi | |
| - name: Uninstall service | |
| if: ${{ !cancelled() }} | |
| run: | | |
| bun run src/cli/index.ts service uninstall | |
| removed=false | |
| for attempt in $(seq 1 10); do | |
| if [ ! -e "$HOME/Library/LaunchAgents/com.opencodex.proxy.plist" ] \ | |
| && ! launchctl list | grep -q com.opencodex.proxy \ | |
| && ! curl --max-time 2 -sf http://127.0.0.1:10199/healthz >/dev/null; then | |
| removed=true | |
| break | |
| fi | |
| echo "waiting for launchd uninstall cleanup ($attempt/10)" | |
| sleep 1 | |
| done | |
| if [ "$removed" != true ]; then | |
| launchctl list | grep com.opencodex.proxy || true | |
| tail -n 200 "$HOME/.opencodex/service.log" || true | |
| echo "::error::launchd artifact or proxy survived uninstall" | |
| exit 1 | |
| fi | |
| windows-schtasks: | |
| runs-on: windows-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: 1.3.14 | |
| - run: bun install --frozen-lockfile | |
| - name: Write minimal config | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.opencodex" | Out-Null | |
| Set-Content -Path "$env:USERPROFILE\.opencodex\config.json" -Value '{"port":10199,"providers":{}}' | |
| - name: Install service | |
| shell: pwsh | |
| run: bun run src/cli/index.ts service install | |
| - name: Verify scheduled task is healthy | |
| shell: pwsh | |
| run: | | |
| $ready = $false | |
| for ($attempt = 1; $attempt -le 20; $attempt++) { | |
| $task = Get-ScheduledTask -TaskName opencodex-proxy -ErrorAction SilentlyContinue | |
| $healthy = $false | |
| try { | |
| $response = Invoke-WebRequest -UseBasicParsing -Uri http://127.0.0.1:10199/healthz -TimeoutSec 2 | |
| $healthy = $response.StatusCode -eq 200 | |
| } catch {} | |
| if ($null -ne $task -and $task.State -eq "Running" -and $healthy) { | |
| $ready = $true | |
| break | |
| } | |
| Write-Host "waiting for scheduled task health ($attempt/20)" | |
| Start-Sleep -Seconds 1 | |
| } | |
| if (-not $ready) { | |
| Get-ScheduledTask -TaskName opencodex-proxy -ErrorAction SilentlyContinue | Format-List * | |
| Get-Content "$env:USERPROFILE\.opencodex\service.log" -Tail 200 -ErrorAction SilentlyContinue | |
| throw "scheduled task did not become healthy" | |
| } | |
| - name: ocx stop should end the task | |
| shell: pwsh | |
| run: | | |
| bun run src/cli/index.ts stop | |
| $stopped = $false | |
| for ($attempt = 1; $attempt -le 10; $attempt++) { | |
| $task = Get-ScheduledTask -TaskName opencodex-proxy -ErrorAction SilentlyContinue | |
| $healthy = $false | |
| try { | |
| $response = Invoke-WebRequest -UseBasicParsing -Uri http://127.0.0.1:10199/healthz -TimeoutSec 2 | |
| $healthy = $response.StatusCode -eq 200 | |
| } catch {} | |
| if ($null -ne $task -and $task.State -ne "Running" -and -not $healthy) { | |
| $stopped = $true | |
| break | |
| } | |
| Write-Host "waiting for scheduled task stop ($attempt/10)" | |
| Start-Sleep -Seconds 1 | |
| } | |
| if (-not $stopped) { | |
| Get-ScheduledTask -TaskName opencodex-proxy -ErrorAction SilentlyContinue | Format-List * | |
| Get-Content "$env:USERPROFILE\.opencodex\service.log" -Tail 200 -ErrorAction SilentlyContinue | |
| throw "scheduled task or health endpoint survived ocx stop" | |
| } | |
| - name: Uninstall service | |
| if: ${{ !cancelled() }} | |
| shell: pwsh | |
| run: | | |
| bun run src/cli/index.ts service uninstall | |
| $removed = $false | |
| for ($attempt = 1; $attempt -le 10; $attempt++) { | |
| $task = Get-ScheduledTask -TaskName opencodex-proxy -ErrorAction SilentlyContinue | |
| $healthy = $false | |
| try { | |
| $response = Invoke-WebRequest -UseBasicParsing -Uri http://127.0.0.1:10199/healthz -TimeoutSec 2 | |
| $healthy = $response.StatusCode -eq 200 | |
| } catch {} | |
| if ($null -eq $task -and -not $healthy) { | |
| $removed = $true | |
| break | |
| } | |
| Write-Host "waiting for scheduled task uninstall cleanup ($attempt/10)" | |
| Start-Sleep -Seconds 1 | |
| } | |
| if (-not $removed) { | |
| Get-ScheduledTask -TaskName opencodex-proxy -ErrorAction SilentlyContinue | Format-List * | |
| Get-Content "$env:USERPROFILE\.opencodex\service.log" -Tail 200 -ErrorAction SilentlyContinue | |
| throw "scheduled task or proxy survived uninstall" | |
| } |