feat(self-packaging #47): 12-factor env vars FLAPI_CONFIG + FLAPI_LOG_LEVEL#57
Merged
Merged
Conversation
This was referenced May 22, 2026
Part of #40. Closes #47. A bundled flapi binary should be operable purely via environment variables (12-factor app). Two known gaps closed: - `FLAPI_CONFIG` falls back for `-c` / `--config`. The CLAUDE.md docs already mentioned it as if it worked; this PR actually wires it. - `FLAPI_LOG_LEVEL` falls back for `--log-level`. Invalid values cause flapi to exit with a single-line error message rather than silently coercing to `info` -- typos like FLAPI_LOG_LEVEL=DEBUG surface immediately. Precedence (highest wins): CLI flag > env var > built-in default. The CLI-wins rule is enforced via argparse's `is_used()` query -- we only consult the env when the user didn't pass the flag. Implementation (src/main.cpp): 24 lines after parse_args; checks `is_used("--config")` / `is_used("--log-level")`, reads `getenv()` on miss, validates log_level against the allowed enum. Integration tests (test/integration/test_env_overrides.py, 6 cases, ~4 sec on Linux x86_64 debug): - invalid FLAPI_LOG_LEVEL -> exit 1 with "invalid log level" + offending value - FLAPI_LOG_LEVEL=debug -> DEBUG log lines appear at validate-config - CLI --log-level wins over FLAPI_LOG_LEVEL - FLAPI_CONFIG used when no -c flag - CLI -c wins over FLAPI_CONFIG (env points at broken YAML, validate still succeeds against the CLI-supplied path) - default lookup of `flapi.yaml` in cwd still works when neither set Docs: - docs/CLI_REFERENCE.md: env-var rows + precedence list under `--config` and `--log-level`, plus example invocations. - docs/CONFIG_REFERENCE.md: new "12-factor checklist" section consolidating every env var flapi reads (startup vs query time), with a note that the `flapi pack` secret deny list enforces the "secrets from env, never from bundle" rule at packaging time. Out of scope (deferred): FLAPI_PORT / FLAPI_HOST and a full credential env-var audit (the user explicitly narrowed #47 to these two vars in the planning round).
1e73113 to
817ce25
Compare
jrosskopf
added a commit
that referenced
this pull request
May 22, 2026
Part of #40. Closes #49. Adds four new CI jobs that exercise the self-packaging surface on every platform flapi builds for: pack-smoke-linux-amd64 ubuntu-24.04 pack-smoke-linux-arm64 ubuntu-24.04-arm pack-smoke-macos macos-latest pack-smoke-windows windows-latest Each job: - downloads the platform's `flapi` artifact from the existing windows-build / linux-build / osx-universal-build jobs - builds a tiny fixture tree (flapi.yaml + one endpoint + sample SQL) - runs `flapi pack --in fixture --out out-a` - runs `out-a info`, asserts the entry list contains the fixture files - runs `out-a unpack --to extracted`, diffs files byte-for-byte - runs a second `flapi pack ... --out out-b` with the same `SOURCE_DATE_EPOCH=1700000000` and asserts `sha256(out-a) == sha256(out-b)` -- the reproducible-build invariant baked into archive_io (#41) The macOS leg additionally: - runs `otool -l` to confirm the unbundled binary carries the reserved `__FLAPI/__bundle` segment from link time - runs `codesign --verify --strict out-segment` after the default reserved-segment pack -- the notarisation precondition this whole approach was built for - runs `flapi pack --macos-append --out out-append` and confirms `info` still discovers the bundle (ad-hoc legacy path still works, signature is intentionally invalid -- we do not run codesign verify here) - runs an oversized-payload pack (32 MiB into a 16-MiB segment) and confirms it exits non-zero with both "FLAPI_RESERVED_BUNDLE_MIB" and "reserved|exceeds" in the error message The Windows job uses pwsh; pack/info/unpack/sha256 logic mirrors Linux via PowerShell idioms. `create-release` now `needs` the four new jobs in addition to the existing build + smoke matrix, so a broken pack on any platform blocks the release. The existing `integration-tests` job (which already runs on ubuntu-24.04 amd64) auto-picks up `test_self_packaging.py` (#56) and `test_env_overrides.py` (#57) via pytest discovery -- no change needed there. Note: on macOS we deliberately do NOT assert reproducibility because the codesign step may include non-deterministic data; on Linux and Windows the host-bytes + appended-archive layout is reproducible by construction.
jrosskopf
added a commit
that referenced
this pull request
May 22, 2026
* ci: cross-platform self-packaging smoke jobs Part of #40. Closes #49. Adds four new CI jobs that exercise the self-packaging surface on every platform flapi builds for: pack-smoke-linux-amd64 ubuntu-24.04 pack-smoke-linux-arm64 ubuntu-24.04-arm pack-smoke-macos macos-latest pack-smoke-windows windows-latest Each job: - downloads the platform's `flapi` artifact from the existing windows-build / linux-build / osx-universal-build jobs - builds a tiny fixture tree (flapi.yaml + one endpoint + sample SQL) - runs `flapi pack --in fixture --out out-a` - runs `out-a info`, asserts the entry list contains the fixture files - runs `out-a unpack --to extracted`, diffs files byte-for-byte - runs a second `flapi pack ... --out out-b` with the same `SOURCE_DATE_EPOCH=1700000000` and asserts `sha256(out-a) == sha256(out-b)` -- the reproducible-build invariant baked into archive_io (#41) The macOS leg additionally: - runs `otool -l` to confirm the unbundled binary carries the reserved `__FLAPI/__bundle` segment from link time - runs `codesign --verify --strict out-segment` after the default reserved-segment pack -- the notarisation precondition this whole approach was built for - runs `flapi pack --macos-append --out out-append` and confirms `info` still discovers the bundle (ad-hoc legacy path still works, signature is intentionally invalid -- we do not run codesign verify here) - runs an oversized-payload pack (32 MiB into a 16-MiB segment) and confirms it exits non-zero with both "FLAPI_RESERVED_BUNDLE_MIB" and "reserved|exceeds" in the error message The Windows job uses pwsh; pack/info/unpack/sha256 logic mirrors Linux via PowerShell idioms. `create-release` now `needs` the four new jobs in addition to the existing build + smoke matrix, so a broken pack on any platform blocks the release. The existing `integration-tests` job (which already runs on ubuntu-24.04 amd64) auto-picks up `test_self_packaging.py` (#56) and `test_env_overrides.py` (#57) via pytest discovery -- no change needed there. Note: on macOS we deliberately do NOT assert reproducibility because the codesign step may include non-deterministic data; on Linux and Windows the host-bytes + appended-archive layout is reproducible by construction. * fix: LocateBundle probes the macOS reserved segment too Previously LocateBundle(path) only did the EOF tail scan. Only LocateBundleInSelf() probed the __FLAPI/__bundle Mach-O section first. That asymmetry meant flapi info / flapi unpack -- both of which call LocateBundle directly against a given binary -- couldn't find bundles that flapi pack wrote into the reserved segment on macOS. Caught by CI: pack-smoke-macos reported Packed 3 entries (683 bytes) into out-segment Bundle: none (filesystem mode) The pack succeeded (3 entries went into the section), but info checked only the EOF tail and saw nothing. Fix: move the section-probe into LocateBundle(path), where every caller benefits. LocateBundleInSelf becomes a thin wrapper. The fall-through to EOF tail is preserved so --macos-append bundles (legacy ad-hoc layout) remain discoverable. Found by CI on PR #59 (#49 cross-platform smoke jobs). * fix(pack): tolerate codesign failure on --macos-append + correct pwsh array-match Two follow-up fixes surfaced by CI on PR #59: 1. macOS: `flapi pack --macos-append` always tried to codesign the output, which fails with "main executable failed strict validation" because the appended ZIP after __LINKEDIT puts the binary outside what codesign considers signable. That's the documented trade-off -- append-mode output is explicitly not notarisable. Now we warn and continue; only the reserved-segment path treats codesign failure as fatal. 2. Windows pack-smoke: `$info -notmatch "x"` on a PowerShell array returns the filtered subset, not a boolean -- so the negation in the if statement was always truthy. Pack and info both worked correctly (info actually listed flapi.yaml, sqls/...); the test logic was the bug. Join the captured lines into a scalar string before -notmatch. Re-running the macOS leg should now show pack-smoke-macos passing through the --macos-append step. Re-running the Windows leg should get all the way to the reproducibility check. Found by CI on PR #59 (#49 cross-platform smoke jobs). * fix(pack): missing <iostream> for std::cerr warning on --macos-append CI on macos-latest failed to build src/pack.cpp: error: no member named 'cerr' in namespace 'std' Caused by the previous fix that warns instead of throws on the --macos-append codesign-failure path. <ostream> doesn't drag std::cerr in; we need <iostream> explicitly. * ci: rerun (flake retry on integration-tests' sql-injection corpus)
This was referenced May 24, 2026
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.
Part of epic #40. Stacked on #56 -> #55 -> #54 -> #53 -> #52 -> #51.
Summary
A bundled flapi binary should be configurable purely via environment
variables (12-factor app). This PR closes two known gaps:
FLAPI_CONFIGfalls back for-c/--config. The CLAUDE.md docsalready mentioned it as if it worked; this PR actually wires it.
FLAPI_LOG_LEVELfalls back for--log-level. Invalid values causeflapi to exit with a clear single-line error rather than silently
coercing -- typos like
FLAPI_LOG_LEVEL=DEBUG(caps) surfaceimmediately.
Precedence
Enforced via argparse's
is_used()query -- we only consultgetenvwhen the user didn't pass the flag.
Implementation (src/main.cpp, 24 lines)
Integration tests (test/integration/test_env_overrides.py, 6 cases, ~4 sec)
FLAPI_LOG_LEVELFLAPI_LOG_LEVEL=debug--log-level error+ envdebugFLAPI_CONFIG=<path>no-c-c <valid>+FLAPI_CONFIG=<broken>flapi.yamlin cwd is loadedDocs
docs/CLI_REFERENCE.md: env-var rows + precedence lists under--configand--log-level, plus example shell invocations.docs/CONFIG_REFERENCE.md: new "12-factor checklist" subsectionconsolidating every env var flapi reads (startup vs query time),
with a pointer to the
flapi packsecret deny list as thepackaging-time enforcement of "secrets from env, never from bundle".
Out of scope (deferred -- user explicitly narrowed #47 to these two vars)
FLAPI_PORT/FLAPI_HOSTTest plan
logic is in a separate code path from
pack/info/unpack).FLAPI_LOG_LEVEL=verbose flapi --validate-config -c examples/flapi.yamlexits 1 with the documented error message.Closes #47. Part of #40.