git reset --hard prints 'HEAD is now at …' to stdout, which polluted read -r previous_sha target_sha < <(git_fetch_and_checkout) and wrote invalid values into .evo-deploy/state.json. Redirect git stdout to stderr inside git_fetch_and_checkout and rollback; validate SHAs before write_state and rollback. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
110 lines
5.6 KiB
Markdown
110 lines
5.6 KiB
Markdown
# Evo auto-deploy (evOo-local)
|
||
|
||
Idempotent deploy helper for the **source checkout** on the Evo host (`~/Projects/evOo-local`, user `cynar`). Pulls `main` (or a pinned SHA), runs migrations, restarts the existing stack, health-checks API `:3100` and web `:5173`, and rolls back on failure.
|
||
|
||
This is separate from production Docker deploys under `/srv/rakazo` (see `docs/self-host.md` and `.github/workflows/ci.yml` `deploy-production`).
|
||
|
||
## Prerequisites on Evo
|
||
|
||
1. Git checkout at `~/Projects/evOo-local` (or set `EVO_DEPLOY_DIR`).
|
||
2. `.env` with a working `DATABASE_URL` (Postgres reachable for `pnpm db:migrate`).
|
||
3. A restart path the script can use (pick one):
|
||
- **systemd (recommended for unattended deploy):** user unit, e.g. `evoo-local.service`, that starts `pnpm dev` or your stack wrapper. Set repo variable `EVO_SYSTEMD_UNIT`.
|
||
- **tmux:** session `evoo` running `pnpm dev` (matches `scripts/start-tmux.sh` on Mac; on Evo set `EVO_TMUX_SESSION` if different).
|
||
- **compose:** `infra/compose/docker-compose.yml` with `api` / `web` / `worker` (dev stack).
|
||
- **script:** executable hook at `EVO_RESTART_SCRIPT`.
|
||
4. Self-hosted GitHub Actions runner on Evo with labels `[self-hosted, evoo-ci, Linux]` (already used by CI).
|
||
|
||
No SSH keys or deploy secrets are stored in this repo. The workflow runs **on** the Evo runner against the local checkout.
|
||
|
||
## Enable the workflow
|
||
|
||
1. Merge this PR (or copy `ops/evo-deploy/` and `.github/workflows/evo-deploy.yml` to `main`).
|
||
2. In GitHub → **Settings → Secrets and variables → Actions → Variables** for `cynarAI/evOo-local`:
|
||
- `EVO_DEPLOY_ENABLED` = `true`
|
||
- Optional: `EVO_DEPLOY_DIR` (default `~/Projects/evOo-local` on the runner user)
|
||
- Optional: `EVO_RESTART_MODE` (`auto`, `systemd`, `tmux`, `compose`, `script`, `none`)
|
||
- Optional: `EVO_SYSTEMD_UNIT`, `EVO_TMUX_SESSION`, health URLs, timeouts
|
||
3. Confirm the runner user can read/write `EVO_DEPLOY_DIR` and restart services (`systemctl --user` or tmux).
|
||
|
||
Until `EVO_DEPLOY_ENABLED` is `true`, pushes to `main` do not deploy (workflow job is skipped).
|
||
|
||
## Manual / dry-run
|
||
|
||
On Evo (or any machine with the checkout and stack):
|
||
|
||
```bash
|
||
cd ~/Projects/evOo-local
|
||
|
||
# Show what would happen (no git reset, migrate, restart, or health wait)
|
||
bash ops/evo-deploy/deploy.sh --dry-run
|
||
|
||
# Deploy main tip
|
||
bash ops/evo-deploy/deploy.sh
|
||
|
||
# Pin a SHA
|
||
EVO_DEPLOY_REF=abc1234 bash ops/evo-deploy/deploy.sh
|
||
|
||
# Force systemd restart
|
||
EVO_RESTART_MODE=systemd EVO_SYSTEMD_UNIT=evoo-local.service bash ops/evo-deploy/deploy.sh
|
||
```
|
||
|
||
GitHub: **Actions → evo-deploy → Run workflow** (optional `ref`, `dry_run`, `skip_migrate`).
|
||
|
||
## Rollback behavior
|
||
|
||
### Regression: SHA capture vs. git stdout
|
||
|
||
`deploy.sh` captures `previousSha` and `targetSha` via process substitution (`read … < <(git_fetch_and_checkout)`). Git commands such as `reset --hard` print progress to **stdout** (`HEAD is now at …`), which previously polluted that capture and produced invalid `.evo-deploy/state.json` (e.g. `deployedSha: "is now at 84bc45e …"`, `previousSha: "HEAD"`). Rollback then became a no-op.
|
||
|
||
**Fix:** all git invocations inside `git_fetch_and_checkout` (and rollback) redirect stdout to stderr; only the final `printf '%s %s\n'` emits SHAs on stdout. `write_state` and rollback reject values that are not 7–40 hex characters.
|
||
|
||
1. Before `git fetch`, the script records `previousSha` (current `HEAD`).
|
||
2. After pull, migrate, and restart, it waits up to `EVO_HEALTH_TIMEOUT_SEC` (default 120s) for:
|
||
- `EVO_API_HEALTH_URL` (default `http://127.0.0.1:3100/health`)
|
||
- `EVO_WEB_HEALTH_URL` (default `http://127.0.0.1:5173/`)
|
||
3. On failure: `git reset --hard` to `previousSha`, restart services again, exit non-zero.
|
||
4. Last outcome is written to `.evo-deploy/state.json` under the checkout.
|
||
|
||
Manual rollback to a known good SHA:
|
||
|
||
```bash
|
||
cd ~/Projects/evOo-local
|
||
git reset --hard <good-sha>
|
||
EVO_RESTART_MODE=systemd EVO_SYSTEMD_UNIT=evoo-local.service bash ops/evo-deploy/deploy.sh --skip-migrate --ref HEAD
|
||
```
|
||
|
||
(or restart your unit/tmux session by hand, then verify health URLs).
|
||
|
||
## Autopilot / Grok Bot after merge
|
||
|
||
After a PR merges to `main`:
|
||
|
||
1. **Preferred:** rely on the `evo-deploy` workflow (once `EVO_DEPLOY_ENABLED=true`). No SSH step required.
|
||
2. **Fallback (manual):** SSH to Evo and run:
|
||
```bash
|
||
cd ~/Projects/evOo-local && bash ops/evo-deploy/deploy.sh
|
||
```
|
||
3. **Verify:** API `curl -fsS http://127.0.0.1:3100/health`, web `curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5173/`.
|
||
|
||
Do not commit host passwords, tokens, or SSH private keys. Configure restart targets via repo variables or env on the runner host only.
|
||
|
||
## Environment reference
|
||
|
||
| Variable | Default | Purpose |
|
||
|----------|---------|---------|
|
||
| `EVO_DEPLOY_DIR` | `$HOME/Projects/evOo-local` | Git checkout root |
|
||
| `EVO_DEPLOY_REF` | `main` | Branch name or commit SHA |
|
||
| `EVO_RESTART_MODE` | `auto` | `systemd` / `tmux` / `compose` / `script` / `none` |
|
||
| `EVO_SYSTEMD_UNIT` | _(empty)_ | Required for `systemd` mode |
|
||
| `EVO_SYSTEMD_SCOPE` | `user` | `user` or `system` |
|
||
| `EVO_TMUX_SESSION` | `evoo` | tmux session name |
|
||
| `EVO_TMUX_COMMAND` | `pnpm dev` | Command started in new tmux session |
|
||
| `EVO_MIGRATE` | `true` | Run `pnpm db:generate` + `pnpm db:migrate` |
|
||
| `EVO_INSTALL` | `true` | Run `pnpm install --frozen-lockfile` after checkout |
|
||
| `EVO_API_HEALTH_URL` | `http://127.0.0.1:3100/health` | API health probe |
|
||
| `EVO_WEB_HEALTH_URL` | `http://127.0.0.1:5173/` | Web health probe |
|
||
| `EVO_HEALTH_TIMEOUT_SEC` | `120` | Per-check timeout (seconds) |
|
||
|
||
If `EVO_RESTART_MODE=auto` cannot detect systemd, tmux, compose, or a restart script, deploy fails with a clear error — set `EVO_RESTART_MODE` and the matching unit/session explicitly.
|