summaryrefslogtreecommitdiff
path: root/internal/release
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-08-06 23:22:03 -0500
committerClaude <noreply@anthropic.com>2026-08-06 23:22:03 -0500
commitf34ba20fc566ab861179a8ac2fd55eaabbc6a327 (patch)
treef3b421a59cff06d1fa83ae3f75065971dcc750df /internal/release
parentfc0c6951d7ac36dd8f30ace929991197dc99ae54 (diff)
Add skiff config set/unset/list, backed by /app/env in the app's dataset
Config vars are plain env vars, injected into both the running app (via rc.d) and the release phase, and versioned on the same ZFS snapshot timeline as deploys - a bad config change gets the same automatic snapshot/restart/health-check/rollback guarantee a bad deploy already gets. Extracts deploy.go's health check into internal/health so both paths share it. Verified end-to-end against a FreeBSD VM: config set/unset, automatic rollback on a health-check failure, and explicit `skiff rollback` all correctly carry the env file along with the rest of the dataset.
Diffstat (limited to 'internal/release')
-rw-r--r--internal/release/release.go16
1 files changed, 12 insertions, 4 deletions
diff --git a/internal/release/release.go b/internal/release/release.go
index fe48aa4..a40cfb1 100644
--- a/internal/release/release.go
+++ b/internal/release/release.go
@@ -37,16 +37,24 @@ func Declared(ctx context.Context, dir string) (bool, error) {
return false, fmt.Errorf("make -n release: %w\n%s", err, out)
}
+// releaseEnvWrapper sources /app/env (if present) before running the
+// release recipe, so config vars set via `skiff config` reach the release
+// phase the same way they reach the running app. $1 is the release's own
+// checkout, passed positionally rather than string-interpolated so there's
+// no manual shell-quoting surface.
+const releaseEnvWrapper = `if [ -r /app/env ]; then set -a; . /app/env; set +a; fi; exec make -B -C "$1" release`
+
// Run executes the `release` Makefile target as a one-off inside app's
// already-running jail (the jail holds the process's filesystem and any
// state the target needs to reach - e.g. the app's own data.db).
// releaseDirInJail must be the new release's own checkout, jail-relative -
// not `current`, which still points at the previous release until cutover
-// happens after this returns successfully. -B forces the recipe to run
-// even if a stray file/directory named `release` already exists in the
-// checkout. A non-nil error means the deploy must abort before
+// happens after this returns successfully. -B is bmake's compatibility/
+// execution-mode flag, not an unconditional-rebuild flag; what actually
+// guarantees the recipe always runs is `.PHONY: release` in the app's own
+// Makefile. A non-nil error means the deploy must abort before
// restart/cutover.
func Run(ctx context.Context, out io.Writer, app, releaseDirInJail string) error {
fmt.Fprintf(out, "==> release: make release (in %s)\n", releaseDirInJail)
- return sh.Run(ctx, out, "", "jexec", app, "make", "-B", "-C", releaseDirInJail, "release")
+ return sh.Run(ctx, out, "", "jexec", app, "sh", "-c", releaseEnvWrapper, "sh", releaseDirInJail)
}