Skip to content

refactor: migrate CD from gha to hercules-ci effects - #1858

Merged
cameronraysmith merged 77 commits into
mainfrom
cd-via-effects
Apr 27, 2026
Merged

refactor: migrate CD from gha to hercules-ci effects#1858
cameronraysmith merged 77 commits into
mainfrom
cd-via-effects

Conversation

@cameronraysmith

Copy link
Copy Markdown
Owner

No description provided.

@cameronraysmith cameronraysmith self-assigned this Apr 22, 2026
docs(notes): revise ADR-001 v0->v1.1 (drop ntfy/set-variables, fold Phase 0, correct flake path to onPush.default, replace sidecar framing with perRepoSecretFiles pipeline, flip purity mappings, add D1-D14 resolutions)
… to writeShellApplication flake apps

Land 12 cluster-domain flake apps under modules/apps/cluster/ per M1 of
the cd.yaml -> buildbot-nix migration (ADR-001 Phase 1):

  k3d-integration-ci, k3d-full, k3d-bootstrap-secrets, k3d-configure-dns,
  k3d-wait-ready, k3d-wait-argocd-sync, k3d-test-coverage, nixidy-build,
  nixidy-bootstrap, nixidy-sync, nixidy-push, list-packages-json

Each app uses the nix-a8g pure-readFile writeShellApplication template
(no cluster app required nix-computed variable injection). runtimeInputs
are declared meticulously per app; writeShellApplication's shellcheck
hook runs at build time. Every .sh carries a --help handler that exits
0 with usage, and (where applicable) argument-validation errors exit
nonzero with a usage hint on stderr. Apps nixidy-sync and
k3d-integration-ci compose sibling apps (nixidy-build, nixidy-push,
nixidy-bootstrap, k3d-full, k3d-wait-*, k3d-test-coverage) either via
their bin names on PATH (nixidy-sync) or via just-wrapper delegation
(k3d-integration-ci, k3d-full).

Justfile recipes rewrite as thin 'nix run .#<app> -- {{ARGS}}' wrappers
with comments pointing back at the modules/apps/cluster/ source pair.
Per the mission, GHA workflows still invoke the legacy recipe paths; no
cutover in M1. Argument pass-through is preserved for k3d-test-coverage,
which previously accepted chainsaw flags via *ARGS.

scripts/k3d-test-coverage.sh is reduced to a thin backward-compat shim
that exec's 'nix run .#k3d-test-coverage'. The legacy path is retained
because .github/workflows/test-cluster.yaml:57 references it in
hash-sources; removal is deferred to M5 drift cleanup.

Verified locally via (aarch64-darwin):
  - nix eval .#apps.aarch64-darwin.<name>.program --raw            (12/12 OK)
  - nix run .#<name> -- --help                                     (12/12 exit 0)
  - shellcheck modules/apps/cluster/*.sh scripts/k3d-test-coverage.sh (clean)
  - just list-packages-json | jq .                                 (regression)
  - nix run .#nixidy-push (no result/) -> exit 1 with expected error

Fulfills (M1 cluster sub-area of validation-contract.md):
  VAL-WRITESHELL-CLUSTER-001..013, CLUSTER-014/015 (shellcheck),
  CLUSTER-020 (nixidy-push usage contract), CLUSTER-022 (justfile
  delegation), CLUSTER-023 (arg pass-through). PR-based assertions for
  k3d-bootstrap-secrets idempotence and nixidy-sync composition (017-019,
  021) require a live k3d cluster and are deferred to M4 validation.
…lication template bifurcation

Audit pass for m1-docs-apps: verifies existing deploy-docs, release, and
preview-version flake apps match the nix-a8g template precedent, adds
inline comments to each .nix file documenting the writeShellApplication
template bifurcation (interpolation vs pure readFile form), and brings
release.sh and preview-version.sh into conformance with the cluster
sidecar convention (#!/usr/bin/env bash + shellcheck shell=bash
directive + set -euo pipefail) so direct shellcheck runs pass.

- deploy.nix uses the interpolation form because it injects
  DOCS_PAYLOAD (config.packages.vanixiets-docs outPath) and
  SOPS_SECRETS_FILE (inputs.self path) into the script preamble at
  nix eval time.
- release.nix and preview-version.nix use the pure readFile form;
  their only nix-injected value is DOCS_NODE_MODULES, exposed via
  runtimeEnv at invocation time (not text interpolation).

runtimeInputs closure is unchanged and matches VAL-WRITESHELL-DOCS-007
(deploy: nodejs_24, sops, age, jq, coreutils, git;
release: nodejs-slim, git; preview-version: nodejs-slim, git, jq,
gnugrep, coreutils).

Fulfills VAL-WRITESHELL-DOCS-001..008.
Relocate the release flake app from modules/apps/docs/release.{nix,sh}
to modules/apps/release/release.{nix,sh} and expand it to absorb the
production-release-packages workflow body from
.github/workflows/package-release.yaml:

- Add a 'release info' subcommand that emits release-info JSON
  (version, tag, released) from the latest git tag matching the
  package (semantic-release-monorepo <pkg>-vX.Y.Z convention).
- Add a '--dry-run' flag that passes --dry-run --no-ci to
  semantic-release and filters @semantic-release/github out of the
  plugin list so GITHUB_TOKEN is not required for previews.
- Configure a default git identity (overridable via GIT_USER_NAME /
  GIT_USER_EMAIL) so semantic-release can create tags/commits
  without a separate setup step.
- Preserve the hermetic node_modules symlink guard and trap-based
  cleanup from the prior app.

Retire the docs-domain release app (duplicate apps.release attribute)
and update package-release.yaml cache hash-sources to reference the
new paths. Add a 'just release *args' recipe that delegates to
'nix run .#release --'.

Fulfills VAL-WRITESHELL-RELEASE-{001..005,007}.
…flake app

Reduce scripts/preview-version.sh to a thin shim that delegates to
'nix run .#preview-version --', and repoint the package.json
'preview-version' script to invoke the flake app directly. Removes
dual-maintenance drift between scripts/preview-version.sh and the
authoritative modules/apps/docs/preview-version.{nix,sh}
implementation.

Fulfills VAL-WRITESHELL-RELEASE-006.
…apping Makefile targets

Create modules/apps/bootstrap/{bootstrap,verify,setup-user}.{nix,sh} as
writeShellApplication flake apps mirroring the Makefile bootstrap flow
for hosts that already have nix installed:

- bootstrap: idempotent post-nix bootstrap — installs direnv via
  `nix profile install nixpkgs#direnv` only when missing
- verify: audits nix + flakes + direnv + flake-metadata, exits nonzero
  on missing nix/flakes (mirrors `make verify`, no make dependency)
- setup-user: generates age key at ~/.config/sops/age/keys.txt;
  no-op reprint of the public key when the file already exists

Each .nix uses the pure readFile template form (no nix-eval-time path
injection) and documents the chicken-and-egg scope: the flake apps
require nix to already be present (`nix run` is their entry point),
so `make bootstrap` remains the clean-host first-contact installer.

Shellcheck clean; all three apps verified idempotent on a nix-ready
host. Adds just bootstrap / just bootstrap-verify / just bootstrap-setup-user
wrappers (new `bootstrap` group) that delegate to the flake apps.
Leaves the existing `just verify` recipe (system-configuration rebuild
via scripts/verify-system.sh) untouched to avoid semantic collision.
…smoke

Smoke removed because production effects (deploy-docs, release-packages)
already exercise the same buildbot-nix + hercules-ci-effects pipeline.
flakeModule import lifted to repo-agnostic scope so future per-repo
effect domains under modules/effects/<repo>/ inherit it automatically.
Migrated to modules/effects/vanixiets/herculesCI/{deploy-docs,release-packages}.nix on magnetite buildbot-nix.
Drop deploy_enabled/deploy_environment/checkout_ref/checkout_rev/sanitized_branch/packages outputs and the package-discovery steps. Only debug (consumed by test-cluster) and force-ci (consumed by bootstrap-verification) remain. Dead push/pull_request branches removed.
…data

cd.yaml is now manual-dispatch only via gh workflow run / Actions UI. Drops push, pull_request, and workflow_call triggers; removes the deploy_enabled input (no remaining consumer); narrows permissions to contents:read; simplifies concurrency group; drops vestigial github.event_name clauses from retained job ifs.
The bun workspace filter was documented as @typescript-nix-template/docs, but the actual package name in package.json is @vanixiets/docs, so the documented commands silently matched nothing.
The SOPS_AGE_KEY env var was declared as a 'reserved passthrough' with no consumer in release.sh. No caller exports it for the release path, and dropping the documentation removes a misleading entry from the env-var contract.
…L aliases

These aliases existed to bridge a prior GHA caller that exported only the GIT_USER_* form. The GHA release path is retired; the herculesCI effect preamble exports the canonical GIT_AUTHOR_*/GIT_COMMITTER_* quartet directly. No remaining caller relies on GIT_USER_*.
…ution comment

GHA release path is retired; the only remaining callers are the herculesCI effect preamble (sets RELEASE_REPO_ROOT) and local-shell invocations (leave it unset).
dryRunFlag was defined but never threaded into the dispatch. The actual non-main path delegates to preview-version (which is always dry-run by construction), so the flag is dead in both branches.
…elper

Factor the effect body into a helper parameterised on dryRun. The sole call site passes dryRun=false, preserving production behavior. The helper threads dryRun through effect name, action label, dispatch line, dispatch marker, and stale-rev guard, enabling a follow-up commit to wire a release-packages-dry-run rehearsal attribute that exercises the production plugin set under semantic-release's --dry-run mode.
…l attribute

The new effect attribute exercises release.sh's production plugin set (including @semantic-release/github) end to end against the cloned tree, but appends `-- --dry-run` so semantic-release's prepare/publish/success steps are no-ops. This is the load-bearing rehearsal stage of the verification staircase: it validates plugin loading, GITHUB_TOKEN auth surface, and per-package dispatch without performing any tag push or release publish.


Invoke manually via buildbot-effects against an arbitrary rev while declaring `--branch main` to force the main-branch dispatch; the stale-rev guard is bypassed under dryRun so HEAD need not equal origin/main.
…earsal attribute

The dryRun parameter on mkReleasePackagesEffect is itself the rehearsal toggle: flip dryRun to true on a feature branch, run buildbot-effects run release-packages, then revert. A persistent second attribute auto-fires on every qualifying push and produces no signal.
Reframes the original 2026-04-21 ADR draft as a value-extraction-oriented working note documenting what was actually migrated (deploy-docs, release-packages effects), what was deliberately retained on GHA (bootstrap-verification, test-cluster) and why, the rehearsal-toggle pattern, the snapshot-rollback pattern, the PAT identity transition, and the verification staircase.
The plan's final state remains recoverable from this branch's git history. The ci-cd/decisions/ subdir is removed if it has no other contents.
Pure orphan reusable: only callers reside in .github/deprecated/. Standalone CI behavior unchanged.
Pure orphan reusable: only callers reside in .github/deprecated/. Standalone CI behavior unchanged.
Pure orphan reusable: only callers reside in .github/deprecated/. Standalone CI behavior unchanged.
@cameronraysmith
cameronraysmith marked this pull request as ready for review April 27, 2026 03:30
@cameronraysmith
cameronraysmith merged commit b7076d3 into main Apr 27, 2026
6 checks passed
@cameronraysmith
cameronraysmith deleted the cd-via-effects branch April 27, 2026 03:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant