Commit graph

2382 commits

Author SHA1 Message Date
Bryan Helmkamp
dd4e467bfc
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-server/tests/it/api/mod.rs
2026-04-19 13:36:54 -04:00
Bryan Helmkamp
3f21644d80
fix(install): cover follow-up edge cases
Harden the remaining install flow regressions and add the missing
coverage for startup dispatch, finish-time shutdown behavior, and
partial-state persistence after vault failures.
2026-04-19 13:32:46 -04:00
Bryan Helmkamp
e8d0f75be9
Merge remote-tracking branch 'origin/main' 2026-04-19 12:46:19 -04:00
Bryan Helmkamp
75f8ed845b
fix(install): harden web wizard against review findings
Tighten the browser-based install flow after correctness and adversarial
review, without changing the external wizard shape.

- Persist the actual bind in server.listen, not the canonical URL
- Reject concurrent /install/finish and rapid GitHub App retries
- Keep the prior GitHub Token strategy until App callback succeeds
- Recover from poisoned install locks instead of propagating panics
- Rollback both settings and vault on failed persistence
- Redirect GitHub callback errors back into the wizard UI
- Validate LLM keys via /models probe instead of a billed generate()
- Reject canonical URLs with trailing slash, path, query, or fragment
- Accept any valid install-token source, not just the first present one
- Redact the install token in structured logs
- Assert install-mode SPA marker injection at startup
- Warn on suspected concurrent operators via UA + X-Forwarded-For
- Add component-level test for the GitHub callback error banner

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:43:00 -04:00
Bryan Helmkamp
1e6543528f
fix(server): satisfy workspace clippy 2026-04-19 12:02:19 -04:00
Bryan Helmkamp
e9daf2db3a
Merge remote-tracking branch 'origin/main' 2026-04-19 11:48:15 -04:00
Bryan Helmkamp
a086a694f1
Merge remote-tracking branch 'origin/main' 2026-04-19 11:48:02 -04:00
Bryan Helmkamp
9dd792c8b8
fix(install): exclude openai-compatible from v1 setup
Restrict the browser install flow to Anthropic, OpenAI, and Gemini,
remove the unused install-time base URL surface, and reject
openai_compatible with a stable 422 response.

Also fix the finishing health poller so it only redirects after the
server comes back healthy outside install mode instead of jumping early
on transient restart failures.
2026-04-19 11:43:38 -04:00
Bryan Helmkamp
ba3e760313
fix(runs): use generated demo status reason parser 2026-04-19 11:38:52 -04:00
Bryan Helmkamp
b5bb134890
fix(runs): bound board enrichment and demo normalization
Paginate board-eligible summaries before enriching them from run state,
add safety caps to paginated web fetches, and make demo run summaries
follow the production title and status-reason normalization rules.
2026-04-19 11:35:30 -04:00
Bryan Helmkamp
ecdfdd82d8
feat(install): add browser-based setup flow
Implement the web-first install experience across the server, CLI, API spec,
web app, and packaged SPA assets.

This also removes test-side process env mutation by pushing env-dependent
decision points behind explicit helpers and test wiring.
2026-04-19 11:20:58 -04:00
Bryan Helmkamp
ec239aaf9c
fix(cli): update install test for listener tls removal 2026-04-19 11:18:34 -04:00
Bryan Helmkamp
6226858648
fix(runs): finish canonical run summary rollout
Complete the /runs and /boards/runs canonicalization work by fixing the
run-detail response shape, preserving lifecycle status separately from board
columns, loading all board pages in the web client, and aligning the shared
status_reason typing.
2026-04-19 11:12:58 -04:00
Bryan Helmkamp
79cd760cd9
refactor(server): dedupe tcp test helpers and drop narrative comment
Collapses duplicated helpers in tests/it/api/tcp.rs introduced with
the TLS-removal test suite (single start_tcp_server, single
wait_for_health), uses ServerState::env_path() in write_test_config,
and replaces the manual SystemTime-based unique-socket path with a
tempdir. Also removes a narrative comment in settings_view that the
module docstring already covers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 11:04:45 -04:00
Bryan Helmkamp
21240e2d09
fix(server): address TLS removal review follow-ups 2026-04-19 10:48:42 -04:00
Bryan Helmkamp
914778c8a8
refactor(server): remove inbound TLS termination
Remove server-side TLS listener support so Fabro only binds plain TCP
or Unix sockets, and update docs/tests around proxy-terminated HTTPS.
This also drops the removed [server.listen.tls] config shape and the
inbound TLS-specific diagnostics, fixtures, and integration coverage.
2026-04-19 10:43:57 -04:00
Bryan Helmkamp
8ab689da78
feat(runs): canonicalize paginated run list responses
Unify /api/v1/runs and /api/v1/boards/runs around a shared
paginated summary contract with additive convenience fields.

Update the server, demo data, generated clients, CLI pagination,
and web consumers so board views become a thin projection over the
canonical run summary surface.
2026-04-19 10:37:31 -04:00
fabro-releases[bot]
51faecf266 Bump version to 0.208.0-nightly.1 2026-04-19 14:27:05 +00:00
Bryan Helmkamp
0892c73a65
ci(release): restore musl toolchain for x86_64 musl tests
The release workflow now builds musl artifacts with cargo-zigbuild, but
x86_64 musl tests still run through plain cargo test via nextest. Restore
musl-tools and the target-specific compiler/linker env for that test path
so fabro-proc's build.rs can compile its C helper again.
2026-04-19 10:02:39 -04:00
Bryan Helmkamp
a1ad81430f
refactor(server): store GitHub meta cache under storage root
Keep GitHub /meta cache state under the resolved server storage tree by
adding a storage cache accessor and wiring the resolver to use
<storage_root>/cache.
2026-04-19 09:55:01 -04:00
Bryan Helmkamp
49ef284ed5
refactor(server): simplify IP allowlist parsing and expansion
Reuse `IpAllowEntry::parse_literal` instead of duplicating `IpNet`
parsing in the resolver, and drop the unreachable defensive branch
in `expand_ip_allow_entries` that called `unwrap_or_default` on a
value that is always `Some` once an entry needs GitHub hooks.

Adds a middleware test covering X-Forwarded-For routing with a
non-zero trusted proxy count, which previously relied on
`extract_client_ip` unit tests alone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 09:47:22 -04:00
Bryan Helmkamp
f9279d0aca
fix(server): fail closed on webhook IP allowlist resolution
Validate the effective GitHub webhook overlay for Unix listeners,
reuse cached GitHub /meta hook ranges when refresh fails, and
propagate webhook allowlist resolution errors during startup instead of
silently skipping the listener.
2026-04-19 09:47:22 -04:00
Bryan Helmkamp
1e03216161
feat(server): add IP allowlist middleware with GitHub webhook support
Introduces a configurable IP allowlist applied to the main API router
and the GitHub webhook listener. Supports CIDR literals plus a
`github_meta_hooks` keyword that resolves live against GitHub's meta
API for the webhooks override. Adds trusted-proxy handling for
X-Forwarded-For, validation that rejects Unix socket listeners without
a trusted proxy count, and deep-merge logic for the new
server.ip_allowlist and per-integration override layers.
2026-04-19 09:47:22 -04:00
fabro-releases[bot]
7933e1ed5c Bump version to 0.208.0-nightly.0
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Test (push) Waiting to run
TypeScript / Build (push) Waiting to run
TypeScript / Typecheck (push) Waiting to run
2026-04-19 00:27:15 +00:00
Bryan Helmkamp
224673bdf6
docs(install): address sixth review pass on web-install spec
Two fixes:

P0 — Container packaging blocks install mode. The published Dockerfile
bakes /etc/fabro/settings.toml and sets FABRO_CONFIG, which under the
explicit-config carveout means containers would never enter install
mode. v1 must change the Dockerfile: drop the baked settings file,
drop FABRO_CONFIG, set FABRO_STORAGE_DIR=/storage, and persist
~/.fabro across container restarts (recommendation: move FABRO_HOME
into a subdirectory of the /storage volume so one mount covers both
config and data). Spelled out as load-bearing v1 implementation work
under Orchestration config updates. New decision-log row #24.

P2 — Force-foreground decision was not carried through to all sections.
Two leftover references to a `__serve` daemon child contradicted the
"install mode never daemonizes" decision — one in the local-lifecycle
prose, one in the manual smoke test. Updated both to describe the
foreground process exiting cleanly. Decision-log row for #21 also
updated to reflect that the install process IS the operator's `fabro
server start` invocation under foreground mode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:50:44 -04:00
Bryan Helmkamp
d34f338e33
docs(install): address fifth review pass on web-install spec
Two fixes:

P1 — Install token surfacing in local daemon mode. Today's
`fabro server start` daemonizes by default; the daemon parent prints
its own summary but the child's stderr (where the install token would
print) is redirected to server.log. Operator wouldn't see the token
without tail-ing the log file. Decision: install mode forces foreground
regardless of how `fabro server start` was invoked; the token then
lands on the operator's terminal directly. Documented as "--foreground
is implicit during install." Daemon path is bypassed entirely for
install mode; restored on the supervisor restart.

P2 — `--no-web` contract hole. The flag is accepted by `server start`
and `server restart` but the spec didn't say what install mode does
with it. Decision: ignore during install with an explicit stderr
warning ("will be respected on next start"); respected after the
supervisor restart. Rejecting would force supervised-deployment
operators to either drop the flag or `docker exec` to run the CLI
wizard, defeating the point. Warning makes the override visible.

Added integration tests for both behaviors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:50:44 -04:00
Bryan Helmkamp
d21d9fa62f
docs(install): address fourth review pass on web-install spec
Three more fixes (all P2):

P2 — Goal wording said "using the same persistence helpers" but the
body explicitly carves out a separate install-mode vault path. Reword
to "same on-disk state, sharing the TOML/env primitives" so the
implementer isn't misled about how much of the CLI path is reused.

P2 — Summary said only `fabro server start` enters install mode but
the process model says start and restart. Reconcile: name both
commands explicitly in the summary.

P2 — Test plan covered the GitHub App `state` rejection path but not
the happy-path roundtrip (POST /install/github/app/manifest → GET
/install/github/app/redirect with stubbed conversion). Add an
integration test that covers the riskiest new path: code-exchange
wiring, session population, redirect-with-token handling, and that
the canonical-URL ordering decision actually flows through to the
manifest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:50:44 -04:00
Bryan Helmkamp
0a576a5f74
docs(install): address third review pass on web-install spec
Three more fixes:

P0 — Local restart UX. Today's `fabro server start` does not stay
around to supervise the `__serve` child it spawns (`start.rs:318`), so
no auto-restart happens locally after `/install/finish` exits. Spec the
two cases honestly: supervised deployments rely on the supervisor;
local laptops show the operator a "run `fabro server start` to launch
your configured server" message after a 30s polling timeout. A built-in
local supervisor is named as a follow-up. Updated the manual-test
section to cover both cases and the orchestration-docs section to
detect supervised vs. local at boot time.

P1 — Auto-start callers must not enter install mode. `connect_server`
→ `connect_api_client_bundle` → `start::ensure_server_running_for_storage`
is used by `run attach`, `server runs`, etc. Add an explicit *Auto-start
callers* subsection and a new decision: only the explicit `fabro server
start` (or `restart`) command enters install mode. Auto-start callers
fail with a clear "configure first" message pointing the operator at
either `fabro server start` or `fabro install`.

P2 — Process-model rationale corrected. The previous draft claimed the
existing dispatch path "would error on missing settings.toml" but that
is false: `user.rs:77` returns defaults, `serve.rs:820-824` falls back
to a Unix socket, and `tests/it/cmd/server_start.rs:111` is a passing
test of `fabro server start` with no config. Reworded to say what is
actually true — without the fork, `fabro server start` cheerfully boots
a non-functional default server, and install mode displaces that
default. Summary line now mentions the explicit-config caveat too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:50:44 -04:00
Bryan Helmkamp
d70bc7a166
docs(install): address second review pass on web-install spec
Five more fixes:

P0 — Reorder wizard: Server config now precedes GitHub. The GitHub
manifest bakes <canonical_url> into redirect_url and callback_urls;
creating the App with a misdetected URL is a real-world side effect we
cannot unmake on github.com.

P0 — `persist_install_outputs` cannot be reused as-is from install
mode. Its vault path goes through `connect_api_client(storage_dir)`,
which calls back into the install-mode server itself (which doesn't
mount /api/v1/*) and would 404. Add an explicit decision: install mode
writes vault secrets directly to disk via Vault::load(...).set(...),
the same pattern persist_github_install_changes already uses. TOML and
env-file helpers remain reusable.

P1 — Bootstrap fork narrowed. Install mode triggers only when no
explicit --config or FABRO_CONFIG was provided AND the default
~/.fabro/settings.toml is absent. A typo in --config must error, not
silently install on top of the wrong target. Matches the asymmetry the
existing config loader already enforces (user.rs:81-112).

P1 — Stop overpromising rollback. The existing helper restores
settings.toml on vault failure but leaves server.env in place (verified
by install.rs:2910). Spec out the actual partial-state semantics for
v1, justify why it's acceptable (env keys are deterministic and
idempotent on retry), and call atomic rollback a deliberate follow-up.

P2 — On-disk layout corrected. Vault path is
<storage_dir>/vaults/default/secrets.json (storage.rs:38), not
<storage_dir>/secrets/.... Added the home-level dev-token file the CLI
also writes (install.rs:1994-1999) so parity is real.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:50:44 -04:00
Bryan Helmkamp
7b9c652548
docs(install): address plan review on web-install spec
Five fixes against the v1 spec:

P0 — Reconcile GitHub App callback flow with the CLI's actual
mechanism: manifest `redirect_url` (not `callback_urls`) carries the
post-creation handoff via browser 302; the install endpoint is renamed
to `/install/github/app/redirect` and authorized by OAuth `state`
because GitHub strips Authorization across redirects.

P1 — Bootstrap fork moves from "precheck inside serve" to the dispatch
layer, since today's `commands::server::dispatch` loads settings before
`serve` is invoked. Spec out the install bootstrap path explicitly,
including skipping the eager dev-token / session-secret creation.

P1 — Clarify that the same `fabro-web` bundle hosts the wizard via a
server-injected `window.__FABRO_MODE__` flag in `index.html` controlling
which router tree mounts at boot. Without this, existing route loaders
that call `/api/v1/auth/*` would throw before the install UI renders.

P2 — Correct the dev-token path to `<storage_dir>/server.dev-token`
(matching `Storage::server_state().dev_token_path()`).

P2 — Resolve the dev-token "never exposed to the client" contradiction:
JWT keys and session secret stay on the server; the dev token is
returned in the `/install/finish` response so the operator can copy it.

P2 — Note that the existing OpenAPI conformance test only covers
`build_router(...)` and would silently miss install drift. Spec the
expansion: split spec iteration by `install` tag, route to the
appropriate router, and verify cross-mounting is rejected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:50:44 -04:00
Bryan Helmkamp
5520785723
docs(install): spec the web-based install wizard
Captures the design for browser-driven first-run configuration as an
alternative to `fabro install`. When `fabro server` boots without
`~/.fabro/settings.toml`, it enters install mode, prints a one-time
token, and serves a wizard from the existing `fabro-web` bundle.
Reaches the same on-disk end state as the CLI, then exits cleanly so
the supervisor restarts into normal mode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:50:44 -04:00
fabro-releases[bot]
769f078bc8 Bump version to 0.207.0-nightly.6 2026-04-18 21:15:59 +00:00
Bryan Helmkamp
8f48aa8b64
docs(digital-ocean): replace stub with Droplet + compose guide
App Platform has no persistent volumes, so Fabro's /storage directory
rules it out. Documents the Droplet path instead, using the existing
docker-compose.prod.yaml + Caddy setup for automatic TLS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 16:20:01 -04:00
Bryan Helmkamp
674c859a27
feat(server): emit Content-Security-Policy in Report-Only mode
Build a conservative CSP from an inventory of what the embedded SPA
actually loads today: same-origin scripts/styles, Google Fonts CSS and
font files, data: + blob: for images, blob: for workers, and WASM
(viz-js needs wasm-unsafe-eval for Graphviz rendering).

Inline `<script>` hashes are extracted at server startup from the
embedded index.html, so the theme-bootstrap script doesn't drift from
the policy when the template changes. Tests cover:

  - known-body hash stability
  - whitespace preservation (browsers hash raw bytes between tags)
  - external scripts are skipped (they're covered by script-src 'self')
  - the embedded SPA template actually yields at least one hash
  - the final policy includes the expected directives

Ships as Content-Security-Policy-Report-Only for the initial rollout.
Browsers report violations to DevTools without blocking anything, so
real-world usage surfaces any false positives before we flip to
enforcing. When reports are clean, swap the header name to
Content-Security-Policy in security_headers::apply_csp.

CSP notes:
  - 'unsafe-inline' on style-src is a pragmatic concession for React
    and Tailwind runtime-injected inline styles. Script-src remains
    strict (hash-based).
  - No 'strict-dynamic' — the entry chunks are same-origin and covered
    by 'self'. Can be added later if dynamic script injection
    violations appear.
  - No report endpoint wired up yet. DevTools console is sufficient
    for the tuning phase; add report-to + collector later.
2026-04-18 16:18:56 -04:00
Bryan Helmkamp
484a3dbc39
feat(fly): add fly.toml and full deploy guide
fly.toml points Fly directly at ghcr.io/fabro-sh/fabro:nightly (no
builder step), pins internal_port to 32276 since Fly does not inject
$PORT, declares a Volume mount at /storage, and disables autostop so
the run queue stays live under no HTTP traffic.

Replaces the deploy-fly-io.mdx stub with a CLI-first walkthrough
covering volume creation, secrets, dev token retrieval, and the
single-Machine / single-Volume caveats that apply to Fabro.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 16:15:13 -04:00
Bryan Helmkamp
36e51237d4
docs(render): replace stub with full deploy guide
Documents the render.yaml blueprint flow end-to-end: one-click deploy,
disk verification, env vars, dev token retrieval, and the same
single-replica / amd64-only caveats as the Railway guide.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 16:08:18 -04:00
Bryan Helmkamp
08cdf9220f
feat(render): add Render blueprint and deploy button
Adds render.yaml using Dockerfile.deploy (prebuilt GHCR image) with a
1 GB persistent disk at /storage and /health healthcheck. README gets
a Deploy to Render button alongside Railway.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 16:04:35 -04:00
Bryan Helmkamp
8da81fdf9e
fix(release): build musl targets via cargo-zigbuild to fix arm64 SIGSEGV
The shipped aarch64-unknown-linux-musl binary segfaulted at startup on
every arm64 runtime (Apple Silicon, Graviton, Ampere, Docker arm64).
Root cause: a glibc-vs-musl .init_array calling-convention mismatch --
a C static library in the dep graph has an __attribute__((constructor))
that expects (argc, argv, envp) per glibc, but musl on aarch64 calls
it with no args, so register garbage propagates into pointer arithmetic
and faults before main runs.

Switch the musl compile steps to cargo-zigbuild (zig 0.13.0). Zig's
bundled cc + lld produce working static-PIE binaries for both musl
targets, sidestepping Ubuntu musl-tools' -no-pie quirk and the
init_array ordering that triggered the crash. Drop the CARGO_TARGET_*
linker overrides and the musl-tools apt install -- zig handles both.

bin/dev/docker-build.sh mirrors the same toolchain so the local Docker
image build matches CI.

Verified by running fabro version from the resulting arm64 image on
ghcr.io/fabro-sh/dhi-alpine-base:3.23-dev, alpine:3.22, and
debian:stable-slim -- all print the version banner with exit 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 16:01:45 -04:00
Bryan Helmkamp
13f612b111
feat(server): emit baseline HTTP security headers on every response
fabro-server previously sent no security headers beyond content-type
and cache-control. Add a tower middleware that fills in a conservative
default set on every response, preserving any header the handler
already set so routes can still override.

Always applied:
  - X-Content-Type-Options: nosniff
  - X-Frame-Options: DENY
  - Referrer-Policy: strict-origin-when-cross-origin
  - Cross-Origin-Opener-Policy: same-origin
  - Cross-Origin-Resource-Policy: same-origin
  - Permissions-Policy: (deny sensor/payment/xr APIs)
  - X-Download-Options: noopen
  - X-Permitted-Cross-Domain-Policies: none
  - X-XSS-Protection: 0  (current OWASP guidance — the legacy filter
    has known bypasses; CSP is the proper replacement)
  - Cache-Control: no-store  (default; asset routes keep their own)
  - Pragma: no-cache
  - Vary: Accept-Encoding

Applied only when the request reached an HTTPS edge (direct TLS or
X-Forwarded-Proto: https from a reverse proxy):
  - Strict-Transport-Security: max-age=63072000; includeSubDomains

CSP is deliberately not included — it needs a dedicated audit of the
SPA's script/style/font/connect sources and isn't a drop-in header.
Filed as a separate follow-up.

Tests cover each applied header, non-override behavior against the
static-file cache-control, HSTS gating on X-Forwarded-Proto (including
the chained "https, http" leftmost-wins case), and an integration test
against a live router confirming both API and SPA responses carry the
headers.
2026-04-18 15:59:32 -04:00
Bryan Helmkamp
8e4693ad8c
chore(docker): switch base to DHI Alpine, add cross-arch build support
Runtime image now builds FROM ghcr.io/fabro-sh/dhi-alpine-base (Docker
Hardened Images mirror, Alpine 3.23) instead of alpine:3.22. Same
runtime shape, CVE-minimized base. Changelog updated to reflect the
DHI migration.

bin/dev/docker-build.sh grows --arch {amd64,arm64} and --compile-only
flags so local multi-arch verification works regardless of host arch.
Cargo target cache is now per-arch to prevent arm64/amd64 artifacts
from stomping each other in one shared volume.
2026-04-18 15:42:23 -04:00
Bryan Helmkamp
3ddc71c409
fix(server): tighten SPA fallback to HTML navigations, exclude /api/*
The static-file fallback previously served index.html (25KB of UI
shell) for any unknown non-/api/v1/ GET — including `curl /healthz`,
scripted fetches, and typos under /api/. Two problems:

1. Unregistered paths like /api/v2/foo or /api/healthz bypassed the
   router (which only matched /api/v1/) and fell through to the SPA
   fallback, silently returning HTML for API typos.
2. Non-browser clients got the UI shell back for any misspelled path,
   making deploy healthchecks, load balancer probes, and API clients
   unable to distinguish "route missing" from "server healthy".

Broaden the dispatch guard to route /api/* through the axum Router so
unknown API paths return a clean 404 from the router itself. Gate the
SPA's index.html fallback on `Accept: text/html` so only browser
navigations (which deep-link to client-side routes like /runs/abc123)
get the UI shell; curl/fetch/scripts get 404.

Asset serving is unchanged — favicon.ico, /assets/*, etc. still serve
normally regardless of Accept header; the gate only applies to the
fallback after an asset lookup misses.

Tests: unit coverage for accepts_html + integration tests for the new
404 shape on /setup without Accept and on /api/v2/nonexistent even
with Accept: text/html.
2026-04-18 15:40:47 -04:00
Bryan Helmkamp
d9596f2bc4
feat(deploy): healthcheck on /health, compose uses GHCR image
railway.toml: point Railway's healthcheck at /health. Fabro's server
returns 200 for any unknown path (SPA fallback) so /healthz would have
been a false-positive check that never catches failures. /health is
the real endpoint exposed by fabro-server and documented in the
OpenAPI spec.

docker-compose.yaml: swap `build: Dockerfile` for
`image: ghcr.io/fabro-sh/fabro:nightly`. A fresh clone's `docker
compose up` previously failed because the Dockerfile expects pre-built
binaries under `docker-context/` that only the release workflow
populates. Pulling the published image gives new users a 5-second
boot and mirrors the Railway deployment shape. Pin to linux/amd64
until the arm64 image variant is fixed.
2026-04-18 15:27:35 -04:00
Bryan Helmkamp
184cddf8e0
feat(railway): deploy from prebuilt GHCR image instead of source build
Add Dockerfile.deploy as a thin wrapper that pulls
ghcr.io/fabro-sh/fabro:nightly, and point railway.toml at it. Railway
now skips Rust compilation entirely and deploys in seconds. The
upstream image already configures entrypoint, $PORT-aware CMD, volumes,
and the unprivileged fabro user, so nothing else is needed in the
wrapper.

Update docs/administration/deploy-railway.mdx to reflect the new flow
and call out that amd64 is the supported architecture (arm64 variant
of the image is being handled separately).

Verified locally: `docker build -f Dockerfile.deploy .` succeeds,
`fabro version` prints the expected banner, and PORT override + health
check work.
2026-04-18 15:24:22 -04:00
Bryan Helmkamp
dba41c32f7
ci(release): smoke-test Docker images per-arch before pushing
Today's nightly published an arm64 image that segfaults on any
invocation (`fabro version` → SIGSEGV). The docker job only built and
pushed; the binary was never executed inside the final image layout,
so the broken arm64 manifest reached ghcr.io undetected.

Before the multi-arch push, build each platform single-arch with
load: true and run `fabro version` in the loaded image. A segfault,
missing binary, or broken entrypoint now fails the job instead of
shipping a broken image. The subsequent multi-arch push reuses buildx
cache from the per-platform builds, so the net cost is ~one short
`docker run` per arch.
2026-04-18 15:17:45 -04:00
Bryan Helmkamp
a77c45207f
test: preserve LLVM_PROFILE_FILE across env_clear in CLI tests
Integration tests spawn the fabro binary as a subprocess and call
env_clear() for isolation, which strips LLVM_PROFILE_FILE. Under
cargo-llvm-cov this dropped subprocess coverage into orphaned
default.profraw files in tempdirs instead of the merged profile.

Add a preserve_coverage_env! macro in fabro-test and call it after
each env_clear() in apply_test_isolation, LightweightCli, and the
exec.rs sites. No-op when the env var is unset (normal test runs).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:05:32 -04:00
Bryan Helmkamp
e06a47f357
ci(release): bump docker actions to Node.js 24 versions
Node.js 20 actions are deprecated on GitHub Actions runners; updating to
the latest majors silences the warning and keeps the release pipeline
working past the September 2026 removal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:05:32 -04:00
Bryan Helmkamp
b5e17ba082
ci(release): attest SLSA build provenance for binaries and image
Tarballs in each compile matrix and the multi-arch ghcr image now get
Sigstore-signed provenance attestations via GitHub's attest-build-provenance
action. Users can verify with `gh attestation verify` — covered in a new
docs/reference/verifying-releases.mdx.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:05:32 -04:00
fabro-releases[bot]
6ebe609e71 Bump version to 0.207.0-nightly.5 2026-04-18 17:26:06 +00:00
Bryan Helmkamp
977f4b4a77
deps: bump rand 0.9.2 -> 0.9.4
Patches GHSA-cq8v-f236-94qc (RUSTSEC-2026-0097) for direct rand usage.
The transitive rand 0.8.x remains in the lockfile via cookie, sentry,
slatedb, and phf_generator pending upstream bumps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 13:16:59 -04:00
Bryan Helmkamp
01dfe2a4fe
ci(release): skip tests on aarch64-musl runner
The aarch64-musl binary SIGSEGVs at startup on the ubuntu-24.04-arm
runner (empty stdout/stderr, non-zero exit), so every test that
spawns 'fabro server start' fails. The shipped binary runs natively
on Alpine via the Docker image, so skip the test step here and rely
on x86_64-musl + both gnu targets for test coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 13:04:14 -04:00