# knot-static a read-only Tangled knot whose public serving path is only nginx + immutable files. `knot-static` is an offline admin/compiler CLI: provisioning mints real repo DIDs and writes knot2-compatible `meta.git` state; `generate` compiles public state into immutable generations (precomputed XRPC responses, generated nginx maps, sanitized dumb-HTTP git exports); `activate` swaps generations atomically behind an `nginx -t` gate. design packet and history live in the `tangled` monorepo under `knot2/crates/knot-static` (this repo is that crate, extracted). ## vendored core knot2 crates are consumed as **path deps into a vendored slice of the tangled monorepo** at `vendor/core` (sv-fe branch, see `vendor/core/REV`). sv-fe carries knot2 APIs (`knot_git::RefClass`, `knot_types::ContributionPolicy`, `PreparedRepoDid::operation`, `Registration.policy`) that master doesn't have yet. the slice contains the workspace root manifest, the three root crates the closure needs (`knot-capability`, `lexicons`, `trusted-proxies`), and all of `knot2/` — including the vendored `gix-pack` fork that `knot-pack` needs via `[patch.crates-io]`. no git dependencies, so no hash bookkeeping. to move to a newer sv-fe: ```text scripts/sync-core.sh /path/to/tangled-checkout # checkout must be on sv-fe cargo test && nix build .#knot-static git commit -a ``` ## build ```text nix build .#knot-static # release binary cargo test # unit + parity + plc journal tests ``` `cargo-hashes.nix` holds the nix `outputHashes` for the pinned git deps (one fetch, shared by all knot2 crates). regenerate by setting a fake hash and copying the hash nix reports on mismatch. --- ## v1 boundaries (deliberate, documented, not bugs) - **no git writes.** no receive-pack, no smart upload-pack POST, no write XRPC routes. provisioning is an explicit admin action on the build machine. - **no SSH git transport.** HTTPS clone/fetch (dumb HTTP) only. - **no shallow clones.** `git clone --depth ...` fails: dumb HTTP doesn't implement shallow negotiation. clients get full dumb-HTTP clones; partial clone/filter is not promised. - **no standard git LFS.** LFS pointer files are served as plain content. `import` detects them and refuses unless `--allow-lfs-pointers` is given; `git lfs pull` against this knot will not work. LFS batch APIs are not implemented and not advertised. - **finite revision profile.** accepted revspecs: empty (HEAD), short/full branch, short/full tag, full object-format-width commit OID. no rev expressions (`main~3`, `HEAD^2`), no abbreviated OIDs. - **bounded compare.** default-branch tip <-> live branch tips, both directions, knot2's 500-commit bound. other pairs return `CompareError` / `InvalidRequest`, not nginx 404s. - **profiled pagination.** only the page shapes the Tangled clients use (`src/project/pagination.rs` is the table); other `(limit, cursor)` tuples return `InvalidRequest`. - **blame is optional and disabled by default in v1** (`profile.blame`): knot2 itself has no blame engine in this tree; eligibility/bounds are implemented, generation is enumerated at tips. - **no archive `prefix=`.** default prefix only; custom prefixes return `InvalidRequest`. - **no firehose.** `com.atproto.sync.subscribeRepos` and `/events` are not served. mirrors are notified via configured resync triggers after provisioning/activation (`[mirror]` config). ## serving model `generate` writes a generation: `manifest.json` (routes -> CAS objects), `www/_responses` (hardlinks into the shared CAS), `www/_git` (sanitized public-only exports), `nginx/` (maps + config, generation-absolute paths). `activate` validates and atomically swaps `active.conf` + `current`. old nginx workers keep their old generation paths. until reliable worker-drain acknowledgement exists, GC retains **all** published generations regardless of `keep_generations`. storage is therefore unbounded. CAS sweep marks every valid retained manifest and aborts before deletion if any manifest cannot be validated. generation builds use private staging, a completion inventory, and atomic publication under a per-output-root admin lock. a repeated ID validates and reuses the published tree without replacing its files. nginx PID, log, and temp files live under `output/runtime/`, outside the immutable generation payload. ## commands ```text knot-static --config [default: knot-static.toml, env: KNOT_STATIC_CONFIG] init repo import --owner --rkey [--name ] [--default-branch ] [--source ] [--allow-lfs-pointers] repo create --owner --rkey [--name ] [--default-branch ] repo list repo record [--publish] [--pds-handle ] [--pds-password-env ] [default env: KNOT_STATIC_APP_PASSWORD] generate [--repo ] activate plc sync ```