Skip to content

NUL-226: add Deploy IPAM workflow + GHCR push + SSH deploy step - #1

Merged
Snipey merged 2 commits into
mainfrom
forge/nul-226-deploy-workflow
Jul 23, 2026
Merged

NUL-226: add Deploy IPAM workflow + GHCR push + SSH deploy step#1
Snipey merged 2 commits into
mainfrom
forge/nul-226-deploy-workflow

Conversation

@NulledAgent

Copy link
Copy Markdown

NUL-226 — Add Deploy IPAM workflow + GHCR push + SSH deploy step

Parent: NUL-221. Spec: Compass's documentKey=spec on NUL-222, revision 4f3a46ec-e18d-4805-873f-9c60f7cefaa2. Risk class: deploy-workflowBlock. Sentinel review required; founder override required before merge.

What this PR adds

  1. .github/workflows/deploy.yml — workflow named Deploy IPAM (matches the rollback runbook's reference in docs/operations/ipam-rollback.md shipped under NUL-228).

    • Triggers on push.branches:[main] and workflow_dispatch.
    • jobs.builddocker/setup-buildx-action@v3docker/login-action@v3 against ghcr.iodocker/metadata-action@v5docker/build-push-action@v6. Tags: type=sha,format=long + type=raw,value=latest,enable={{is_default_branch}}. Cache: type=gha. Login defaults to GITHUB_TOKEN with packages:write (minimal secret surface); auto-falls-back to IPAM_DOCKER_USERNAME / IPAM_DOCKER_PASSWORD if both are set, for parity with release.yml.
    • jobs.deploy (depends on build) — appleboy/ssh-action@v1, executes exactly bash /srv/ipam/deploy.sh <sha> (the same command authorized_keys will whitelist on this host, see NUL-225).
    • workflow_dispatch inputs: pinned_tag (override SHA for rollback) and deploy_only (skip build/push). Acceptance item "rerun deploy step alone with override SHA" is wired here.
    • concurrency: ipam-deploy-${{ github.ref }}, cancel-in-progress: false — never kill an in-flight deploy.
  2. docs/operations/deploy-workflow.md — the operator-facing write-up: workflow overview, full repo-secrets table (IPAM_DEPLOY_HOST / IPAM_DEPLOY_USER / IPAM_DEPLOY_SSH_KEY / optional IPAM_DOCKER_*), how to generate + restrict the deploy key, the rollback quick-reference (full runbook lives in ipam-rollback.md), acceptance check status relative to NUL-225.

What this PR does NOT do

  • Does not modify release.yml (out of scope).
  • Does not modify the Dockerfile or docker-compose.yml (NUL-224 already shipped IPAM_HOST_PORT; out of scope for this PR).
  • Does not install Docker, write the nginx vhost, run certbot, or create /srv/ipam/deploy.sh. Those are NUL-225 (Relay). The end-to-end AC (crane ls, container running, https://ipam.thenull.dev/healthz) cannot be exercised until NUL-225 merges and the host-side secrets are wired.

Acceptance criteria status

Check Verifiable now?
crane ls ghcr.io/thenulldev/ipam shows new SHA tag After NUL-225 merges + first push
Container on this host running that SHA After NUL-225 merges + first push
https://ipam.thenull.dev/healthz returns {"ok":true,"db":"up"} within 5 min After NUL-225 merges + first push
workflow_dispatch can rerun deploy alone with override SHA Verifiable by reading the file — deploy_only + pinned_tag inputs are wired.

Reviewer checklist (Sentinel — please cite rules)

  • Concurrency group + cancel-in-progress: false is correct.
  • permissions: is minimal (no contents: write anywhere).
  • appleboy/ssh-action@v1 is pinned to a major version, matches repo style.
  • bash /srv/ipam/deploy.sh <tag> is the only remote command — compatible with authorized_keys command="..." restriction.
  • No secrets printed in workflow output.
  • secrets.GITHUB_TOKEN fallback is acceptable (founder call).
  • docs/operations/deploy-workflow.md matches the deploy workflow actually authored.
  • No modifications to release.yml, Dockerfile, or docker-compose.yml.

Founder override required

Per AGENTS.md §1 and the NUL-226 description ("Sentinel reviews; founder override required before Relay merges"), this PR cannot be merged without an explicit founder override comment.

cc @founder — please drop the override comment when you've reviewed.

Adds .github/workflows/deploy.yml (named 'Deploy IPAM' to match the
rollback runbook's reference):

- on.push.branches:[main] + on.workflow_dispatch
- jobs.build: buildx + ghcr.io login + build-push with tags
  type=sha,format=long and type=raw,value=latest (default-branch only),
  GHA cache. Login defaults to GITHUB_TOKEN with packages:write; falls
  back to IPAM_DOCKER_USERNAME/IPAM_DOCKER_PASSWORD if set, for parity
  with release.yml.
- jobs.deploy: needs build, runs appleboy/ssh-action@v1, executes
  bash /srv/ipam/deploy.sh <sha>. pinned_tag + deploy_only inputs let
  workflow_dispatch rerun the deploy step alone for rollback.
- concurrency: ipam-deploy-${{ github.ref }}, cancel-in-progress: false
  (never kill an in-flight deploy).

docs/operations/deploy-workflow.md is the operator-facing write-up:
what the workflow does, the full secrets table, how to wire up the
deploy key, the rollback quick-reference, and the acceptance check
status relative to NUL-225 (the on-host work that must land first).

Risk class: deploy-workflow BLOCK. Sentinel review required; founder
override required before Relay merges. This commit only adds files —
no live state changed.
GitHub Actions rejected the workflow at parse time because 'secrets'
is not in the allowed contexts for job.name (only github / inputs /
matrix / needs / strategy / vars). actionlint caught it; the previous
push resulted in a 0-second 'completed=failure' run with no jobs.

Fix: drop the dynamic hostname interpolation from job.name; the deploy
step itself still receives IPAM_DEPLOY_HOST via secrets, and the SSH
failure (if any) will name the host in its own log.

@NulledAgent NulledAgent left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentinel · Review Agent — Block-class review (NUL-233)

Reviewed per REVIEW_CHECKLIST.md §3 deploy-workflow and
review-entries/ipam-thenull-dev.md (revision authored 2026-07-23).
PR is labelled deploy-workflow and touches the Deploy IPAM workflow that
controls the ipam.thenull.dev production surface. Per AGENTS.md §1 this is
a Block-class PR.

Mechanical rule (§3, first item)

"if PR labels contain 'deploy-workflow' and no founder override comment,
Block."

There are zero comments on this PR (GET /repos/thenulldev/ipam/issues/1/comments returns []).
No founder override is present. The mechanical rule fires — verdict is Block.

The override signer, required content, and placement-after-latest-force-push
rule are documented in review-entries/ipam-thenull-dev.md §"Founder-override
signers". The signer is gWdXxYVtECa6bvJ1DFBS0qaGYqgjgw04. The override must
name the PR, name the risk class (deploy-workflow), and state "I authorize
this PR to merge despite the Block class" (or equivalent unambiguous language),
and must be posted after the latest force-push (the parse-fix commit
905cb7a4 is the current head).

Per-item verdicts (§3 deploy-workflow)

  • §3 founder override comment present — FAIL (no comment on the PR).
  • §3 push-to-main trigger only — PASS (push.branches:[main] + workflow_dispatch; no pull_request deploy step).
  • §3 workflow permissions: minimal — PASS (build: contents: read + packages: write; deploy: contents: read; no contents: write anywhere).
  • §3 registry credentials in plaintext in diff — PASS (no inline credentials; default auth uses secrets.GITHUB_TOKEN; optional IPAM_DOCKER_PASSWORD is a repo secret, not a literal).
  • §3 SSH deploy key dedicated, command-restricted, source-IP-restricted — PASS at the workflow level (the SSH step runs exactly one command — bash /srv/ipam/deploy.sh <tag> — which is compatible with command="..." lockdown in authorized_keys; appleboy/ssh-action@v1 pinned to a major version). Actual authorized_keys lockdown is NUL-225 (Relay), not this PR.
  • §3 deploy script idempotent — n/a this PR (owned by NUL-225; AC items a/b/c on the parent cannot be exercised until NUL-225 lands; tracked in NUL-225 review).
  • §3 healthcheck gate (3 retries × 10s) — n/a this PR (owned by NUL-225; command_timeout: 8m and set -euo pipefail are the workflow-side guard).
  • §3 rollback path in same PR — PASS (workflow_dispatch inputs pinned_tag + deploy_only; the "Determine deploy target tag" step honours the override; rollback does not require "revert the workflow file").
  • §3 concurrency group — PASS (concurrency: group: ipam-deploy-${{ github.ref }}, cancel-in-progress: false exactly as the rule requires).
  • §3 workflow_dispatch exists — PASS (with pinned_tag and deploy_only inputs).
  • §3 destructive ops without typed confirmation — PASS (only remote command is bash /srv/ipam/deploy.sh <tag>; no docker system prune -a, no docker volume rm, no rm -rf, no registry image delete; the cache-to: type=gha,mode=max cache prune is scoped to the repo's own GH Actions cache, not a production surface).
  • §3 final item — production hostname / service / systemd touched in diff — n/a this PR (workflow-only; nginx vhost, certbot, systemd, /srv/ipam/* are NUL-225).

Per-deploy entry (review-entries/ipam-thenull-dev.md)

  • Cert renewal playbook honoured — n/a this PR (NUL-225).
  • Production hostname ipam.thenull.dev correctly referenced in
    docs/operations/deploy-workflow.md (matches the entry's hostname list).
  • Owners correctly named (workflow owner = Forge; deploy owner = Relay; reviewer = Sentinel).

Universal gates

  • PR description names owner (Forge), parent (NUL-221), scope, acceptance
    criteria, evidence, and risk class (deploy-workflow, Block) — PASS.
  • git log --first-parent main..HEAD shows two commits, both authored by
    hermes-agent (NulledAgent), no force-push, no history rewrite — PASS.
    Heads in order: 529d0a89 (workflow + ops doc) → 905cb7a4 (parse-fix;
    removed secrets context from a name: field per the inline comment).
  • CI on the merge commit — NO CHECK RUNS. The repo currently has no CI
    configured. This is acceptable for a workflow-only PR iff the founder
    override is present, because the workflow is the thing being shipped and
    cannot self-test pre-merge. Flagged as advisory below.
  • No secrets in diffs — PASS (no tokens, no keys, no Authorization
    headers, no PATs inlined).

Advisory (non-blocking)

  • The repository has no CI on the merge commit. The deploy workflow
    Deploy IPAM is itself the artefact; there's no lint / validate-workflow
    / actionlint step on PRs. Strongly suggest a separate
    .github/workflows/ci.yml (actionlint + yamllint) in a follow-up PR so
    future deploy-workflow changes get caught pre-merge. The parse-fix
    commit 905cb7a4 is exactly the kind of thing actionlint would have
    caught on the first push.
  • appleboy/ssh-action@v1 is pinned to a major version, which is the
    current repo style, but a SHA-pinned action is stricter. Same observation
    applies to actions/checkout@v4, docker/setup-buildx-action@v3,
    docker/login-action@v3, docker/metadata-action@v5,
    docker/build-push-action@v6. Not a Block (major-pinning is acceptable);
    flagging for a future tightening PR.
  • timeout-minutes: 20 on build and timeout-minutes: 10 on deploy
    are reasonable. command_timeout: 8m on the SSH step leaves a 2-minute
    buffer under the deploy job's 10-minute job timeout; intentional and OK.

Verdict

REQUEST_CHANGES (Block) — not approvable as-is. The mechanical §3 rule
fires: no founder override comment on the PR.

Unblock path (mechanical): the founder (gWdXxYVtECa6bvJ1DFBS0qaGYqgjgw04)
posts a single comment on this PR that:

  1. Names the PR number or links to it.
  2. Names the risk class (deploy-workflow).
  3. States "I authorize this PR to merge despite the Block class" (or
    equivalent unambiguous language per review-entries/ipam-thenull-dev.md
    §"Founder-override signers").
  4. Is posted after the current head 905cb7a4 (the parse-fix commit) so
    it cannot be invalidated by a rebase.

Sentinel does not merge. Relay (per the per-deploy entry) is the merge
gate after the founder override lands and Sentinel re-verifies. Sentinel's
status on NUL-233 stays in_review until that happens.

— Sentinel · Review Agent

@Snipey
Snipey merged commit defb995 into main Jul 23, 2026
1 of 3 checks passed
@NulledAgent

Copy link
Copy Markdown
Author

Closing note: the SSH deployment shape in this merged PR is superseded by NUL-252 / PR #3 following the founder round-4 Watchtower decision a13f5a25. Do not use the SSH path; PR #3 removes it and publishes <sha> plus latest for Watchtower.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants