fix(quickstart): make curl-install and af-stack dev work from a fresh clone - #215
Merged
Conversation
`curl … install.sh | bash` died at "Resolving latest release" with `curl: (23) Failed writing body`. The script piped the GitHub API response into `grep -m1`, which exits as soon as it has its match; curl then fails its remaining write and, under `set -o pipefail`, the whole `$(...)` is treated as failed even though the tag parsed correctly. On WSL2/bash this reproduced 5/5 from a file, from stdin, and via curl|bash. Resolve the tag from the github.com/<repo>/releases/latest 302 redirect instead, which also sidesteps the 60 req/hour unauthenticated API limit that CI runners and office NATs hit, and keep the API as a fallback with the body buffered before parsing. While here, make checksum verification a plain function whose failure the caller turns into a hard error, since a `die` inside the old `( cd … )` subshell only exited the subshell. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Abir Abbas <abirabbas1998@gmail.com>
The first line of the README quickstart had no gate: nothing in CI ever executed scripts/install.sh, so it could ship broken. Add an `install-script` job, path-filtered to the script and this workflow, that lints it (bash -n + shellcheck) and runs it in the three shapes users hit — piped from stdin like the README one-liner, from a file, and pinned with AF_STACK_VERSION — asserting `af-stack version` prints a semver each time, and that a tampered checksums.txt is rejected. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Abir Abbas <abirabbas1998@gmail.com>
A fresh clone could not boot. `.env.example` set `AF_STACK_KMS_KEY=change-me-to-a-real-key`, and since the KMS boot gate (#141) a key that is set but cannot be loaded is fatal by design — so `af-stack dev`, `af-stack mode`, and `cp .env.example .env && docker compose up` all seeded that placeholder and the runtime crash-looped with "refusing to start: KMS is configured but the key could not be loaded". Maintainers never saw it because their local .env files already carried real keys, and the release-smoke compose hardcodes a hex key. Use the runtime's well-known dev sentinel `dev-secret-change-me` instead, which is also the docker-compose.yml default, so the example boots with the deterministic dev KEK and a warning exactly like a missing key does. Fix the deploy skill doc that named the auth-secret default as the KMS sentinel; setting that value would trip the same fatal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Abir Abbas <abirabbas1998@gmail.com>
TestEnvExampleBootsKMS reads the repo's .env.example the way compose and the CLI's .env seeding do, loads the KMS cipher from it, and runs the same preflight the runtime does at boot. It fails against the previous placeholder with the exact reason a fresh clone crash-looped, and passes with the dev sentinel. Add .env.example to CI's Go path filter so the test runs whenever the example changes, not only when Go code does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Abir Abbas <abirabbas1998@gmail.com>
The docs-site quickstart had readers write `AF_STACK_KMS_KEY=$(openssl rand -hex 32)` into .env. A .env file is not a shell: docker compose hands the runtime the literal string, which is not hex, and the runtime refuses to start. Show the dev sentinel as the value and say to paste the command's output for a real key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Abir Abbas <abirabbas1998@gmail.com>
…y runs Hardening from an adversarial review of the installer, each item reproduced before it was fixed: - Checksum fetch failed open: any transport error on checksums.txt was reported as "not found on the release" and the install continued unverified. Every release publishes that file, so branch on the HTTP status, refuse to install otherwise, and print the status and curl's error. AF_STACK_SKIP_CHECKSUM=1 is the explicit override. - Install dir off PATH: for any non-root user /usr/local/bin is not writable, the binary landed in ~/.local/bin, and the script printed a version banner and exited 0 — followed by `af-stack dev: command not found`. Prefer a usable candidate that is already on PATH, and when the chosen dir is not, print the exact export line and the rc-file line for the user's shell instead of a bare banner. Also flag an older copy shadowing the new one. - The post-install probe was silenced and ran after the success banner; it now gates the banner, so a noexec mount or truncated archive fails loudly. `install -m 0755` replaces `mv` so a sudo install is root-owned. mkdir gets the same sudo fallback and actionable error as the copy, and dir resolution moves before the download so a bad target fails in under a second. - AF_STACK_VERSION=0.12.4 (bare, the spelling release.yml and the prod compose use) 404'd and blamed repo visibility; try the other prefix once, keeping the checksums fetch on the tag that resolved. - AF_STACK_DOWNLOAD_BASE points both fetches at a mirror, which is what lets CI drive the real script against a local fake release. - Trailing slashes on AF_STACK_INSTALL_DIR no longer defeat the PATH check or print a doubled slash. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Abir Abbas <abirabbas1998@gmail.com>
…lock The first cut of this job could not catch the regressions it exists for: it was not in ci-success's needs (so a red run never blocked a merge), its version assertions only checked the semver shape (a resolver returning the wrong tag passed), the pinned step pinned the latest release (a no-op pin passed), and the tamper check sourced a sed-extracted copy of verify_checksum instead of running install.sh (a fail-open at the call site passed). Now: ci-success requires the job (path-filtered skips still count as success, as for every other job); the two newest release tags come from gh, independently of the redirect the script uses, and every assertion is an exact `af-stack <version>` match; the pin uses the previous release, both v-prefixed and bare, so ignoring it can never pass; and a local http.server serves the real archive as a fake release so the script itself is run against a tampered checksums.txt (must exit non-zero with the checksum message and install nothing), a missing one (must refuse with HTTP 404 unless AF_STACK_SKIP_CHECKSUM=1), and the correct one (must install). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Abir Abbas <abirabbas1998@gmail.com>
cli-distribution.md now lists bare pins, the redirect-based resolution, fail-closed checksums with AF_STACK_SKIP_CHECKSUM, the AF_STACK_DOWNLOAD_BASE mirror, and the PATH hint. README's quickstart said the only prerequisite is Docker; `af-stack dev` also uses Node for port auto-allocation and silently falls back to the defaults without it, so say so in one line. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Abir Abbas <abirabbas1998@gmail.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The README quickstart —
curl … install.sh | bashthenaf-stack dev— was broken on a fresh clone in three independent ways. All three were invisible from a long-lived maintainer checkout, which is why they shipped.1.
install.shdied before downloading anything. It piped the GitHub API response intogrep -m1, which exits on its first match; curl then fails its remaining write (curl: (23) Failed writing body) and, underset -o pipefail, the whole$(...)is treated as failed even though the tag parsed. Reproduced 5/5 from a file, from stdin, and viacurl | bash. The script now resolves the tag from thegithub.com/<repo>/releases/latest302 redirect (which also avoids the 60 req/hour unauthenticated API limit that CI runners hit) and falls back to the API with the body buffered before parsing.2. Even when the install succeeded,
af-stack devcould be "command not found". For any non-root user/usr/local/binis not writable, so the binary landed in~/.local/bin, which macOS never puts on PATH and Ubuntu only adds at the next login. The script printed a version banner via the absolute path and exited 0. It now prefers a usable directory that is already on PATH and, when the chosen one is not, prints the exactexport PATH=…line plus the rc-file line for the user's shell. It also flags an older copy shadowing the new one.3. A fresh clone crash-looped the runtime.
.env.examplesetAF_STACK_KMS_KEY=change-me-to-a-real-key. Since the KMS boot gate (#141) a key that is set but cannot be loaded is fatal by design, soaf-stack dev,af-stack mode, andcp .env.example .env && docker compose upall seeded that placeholder and the runtime refused to start. The example now ships the runtime's dev sentineldev-secret-change-me(the same valuedocker-compose.ymldefaults to), so it boots with the deterministic dev key and a warning. The docs-site quickstart also told readers to writeAF_STACK_KMS_KEY=$(openssl rand -hex 32)into.env; compose passes that through literally, same crash. Fixed.Installer hardening (from an adversarial review, each reproduced first)
checksums.txtwas reported as "not found on the release" and the install continued unverified. It now branches on the HTTP status, refuses to install otherwise with the status and curl's error, andAF_STACK_SKIP_CHECKSUM=1is the explicit override.install -m 0755replacesmvso a sudo install is root-owned.mkdirgets the same sudo fallback and actionable error as the copy, and directory resolution runs before the download so a bad target fails in under a second.AF_STACK_VERSION=0.12.4(bare, the spellingrelease.ymland the prod compose use) 404'd and blamed repo visibility; the other prefix is tried once.AF_STACK_DOWNLOAD_BASEpoints both fetches at a mirror, which is also what lets CI drive the real script against a local fake release.Gates added so this cannot regress silently
install-scriptCI job, required byci-success(path-filtered, so unrelated PRs skip it):bash -n+shellcheck; the installer run against the real latest release piped from stdin and from a file with an exactaf-stack <version>assertion (tags resolved viagh, independently of the script's own redirect); the previous release pinned both v-prefixed and bare, so a no-op pin can never pass; and a localhttp.serverserving the real archive as a fake release, against which the script must reject a tamperedchecksums.txtand install nothing, refuse a missing one with HTTP 404 unless overridden, and install with the correct one.secrets.TestEnvExampleBootsKMS: reads the committed.env.examplethe way compose does, loads the KMS cipher from it, and runs the runtime's boot preflight. It fails against the old placeholder with the exact crash-loop reason and passes with the sentinel..env.exampleis added to CI's Go path filter so the test runs when the example changes.Verification
main+ releasedaf-stackv0.12.4 +af-stack devwith no manual edits:/readyreports ready,POST /api/v1/agents/supportdesk.echoechoes back, dashboard and customer app serve their sign-in pages, and the runtime logs exactly one dev-key warning with zero restarts.go build,go vet, andgo test ./...pass; golangci-lint v2.13.1 in CI's new-issues mode reports 0 new issues.docs-lint,build-docs-site(47 pages), andvalidate-deploy-targetspass locally.Not in this PR
af-stack devsilently skips port auto-allocation when Node is missing or older than 18; the README now says Node is optional, but porting the preflight into the Go binary would remove the dependency.af-stack devbanner could print the seeded operator login, which the README never gives.af-stack devcould generate a unique random KMS key when seeding a new.envinstead of the shared dev sentinel.🤖 Generated with Claude Code