steid

@jamesgill /

steid/plans/current.md
10.5 KBCode·Blame·Raw
ab7fea9chore: plans setup1mo
1# Current
2
3> Keep this file short. One active step, one ordered backlog. Completed work moves to
4> [progress.md]progress.md. If this file starts reading like a changelog, it has
5> drifted — that's exactly what went wrong last time.
6
3dd7293feat: ship Steid as an installable binary8d
7## Active: Milestone 5b — Deployable by anyone
a814db5feat: push and clone private repositories with a token8d
8
3dd7293feat: ship Steid as an installable binary8d
9**Goal:** a stranger can install Steid on a fresh Linux box in one command and reach it
10over HTTPS, and the author can publish an instance and push Steid's own source to it.
11Distributed as **a binary plus its assets**, not a container.
a814db5feat: push and clone private repositories with a token8d
12
3dd7293feat: ship Steid as an installable binary8d
13**Out of scope:** push-to-release and any CI/CD (8+ — see
14[ROADMAP.md]ROADMAP.md#milestone-ladder), multi-user registration (7), and hosting
15release artefacts on Steid itself, which it has no feature for.
16
17### Steps
18
19- [x] Release build producing `steid-<version>-<target>.tar.gz` — binary, `assets/`,
20 README — and verified to boot from a clean extraction
21- [x] `install.sh` — download, systemd unit, Caddy with automatic HTTPS, idempotent
22- [x] `README.md` — the repo has none, and it is the front door of a portfolio project
23- [x] Operability: `/healthz`, graceful shutdown on SIGTERM
24- [x] `STEID_SETUP_TOKEN` as an optional override, so claiming is not a race with
25 `journalctl`
26- [x] Rate limiting on `/auth/login` and `/auth/setup`
27- [x] Backup and restore, documented — two paths, rsync is enough
28
29### Done when
30
31`curl … | sh -s -- --domain git.example.com` on a fresh VPS yields a working HTTPS
32instance, and Steid's own source is pushed to it and browsable there.
33
34### Settled
35
36- **A binary plus `assets/`, not a container.** Topcoat can embed the asset *manifest*
37 (`Manifest::parse` + `include_str!`, for WASM) but not the asset *bytes* — that path
38 pairs with `AssetConfig::hosted_at`, which expects a CDN. A single self-contained file
39 would mean fighting the framework, so the artifact is a directory. The Dockerfile stays
40 as a secondary path.
41- **A TLS proxy is mandatory, not conventional.** Topcoat 0.5 has no TLS: no rustls, no
42 ACME, no HTTPS listener — checked, not assumed. Since git authenticates over HTTP
43 Basic, without TLS a personal access token crosses the network in cleartext on every
44 push. Caddy is chosen because automatic certificates are its headline feature and the
45 config is two lines.
46- **glibc, built on Debian bullseye — not static musl.** Two reasons, the second
47 decisive. musl failed on `ring` (Debian's `musl-gcc` wrapper rejects `-m64`) and would
48 need a real cross toolchain. But **musl's whole point is a binary with no runtime
49 dependencies, and Steid hard-requires `git` on `PATH`** — anyone installing it already
50 has a package manager, so the portability musl buys cannot be used. Building on
51 bullseye pins the glibc floor at 2.31, covering Debian 11+ and Ubuntu 20.04+; building
52 on bookworm would need 2.36 and silently exclude Ubuntu 22.04, which is still
53 everywhere. Verified: a release build in `rust:1.97-slim-bullseye` succeeds, 11.5 MB.
54- **The runtime binary links a TLS stack it never uses.** `ring``rustls``ureq`
55 Topcoat's `icon-iconify`/`tailwind` features, whose `ureq` exists to download the
56 Tailwind CLI *at build time*. Those features are enabled on the normal dependency as
57 well as the build one, so the crypto comes along for the ride. Moving them to
58 build-dependencies only would shrink the binary and drop an unused dependency from the
59 attack surface — worth trying, not yet attempted, and it is what made the musl attempt
60 fail where it did.
61- **Rsync is the deployment mechanism for now**, and that is enough while the artifact is
62 two paths. Push-to-release is the eventual answer and is parked at 8+.
8608574feat: personal access tokens, as a domain type8d
63
be4fac5docs: correct the Topcoat guidance in CLAUDE.md24d
64### Open
65
3dd7293feat: ship Steid as an installable binary8d
66- **Whether to add `STEID_TRUSTED_PROXY`.** The rate limiter keys on `X-Forwarded-For`
67 because **Topcoat 0.5 discards the peer address at accept time and never exposes it**
68 a handler sees headers and nothing else. Behind a proxy that is fine. Exposed directly,
69 a caller can vary the header for a fresh budget, leaving only the global cap. An
70 explicit "trust forwarded headers" flag defaulting to off would close it, at the cost
71 of one more thing an operator must get right. Not picked.
4ef3945feat: repository settings, README rendering, branch switcher, raw files7d
72- **`MAX_RAW_BYTES` is 10 MiB**, chosen rather than derived. It bounds what one raw
73 request can hold in memory, because `GitQuery` reads bytes rather than streaming them.
74 Raising it means a handful of concurrent requests can hold that much each.
75- **Renaming a repository** is still impossible, and now needs its own use case plus an
76 answer for moving the directory under every existing clone.
3dd7293feat: ship Steid as an installable binary8d
77- **A licence.** A public portfolio repository probably wants one, and the README
78 deliberately says nothing about licensing rather than guessing.
4ef3945feat: repository settings, README rendering, branch switcher, raw files7d
79- **Blocked on a domain transfer** (noted 2026-08-29). `git.jpgilldev.com` is the
80 intended host for both the instance and the release downloads; the transfer is in
81 flight. Phases 1–6 of the deployment runbook cannot start until DNS resolves, because
82 Caddy requests a certificate on startup. Nothing else is blocked by it — the artifact
83 is built and verified, and `install.sh` still needs its container dry-run.
3dd7293feat: ship Steid as an installable binary8d
84- **A domain.** Caddy needs a real hostname to obtain a certificate. This is the one
85 blocker that is DNS rather than code.
be4fac5docs: correct the Topcoat guidance in CLAUDE.md24d
86
e0856ebfeat: clone a public repository over HTTP8d
87### Carried over — small, unblocked
d7b99d9docs: record milestone 0 progress and routing findings1mo
88
47db238feat: serve the git protocol through http-backend, behind a port8d
89- **A client that disappears mid-request leaves the body-copy task waiting.** The copy
e0856ebfeat: clone a public repository over HTTP8d
90 into git's stdin runs in its own task and nothing cancels it if the connection drops.
47db238feat: serve the git protocol through http-backend, behind a port8d
91 Bounded by the backend exiting and closing the pipe, but not by anything deliberate.
a814db5feat: push and clone private repositories with a token8d
92- **`REMOTE_USER` is not set on the backend**, so a push is recorded in the repository's
93 reflog without naming who made it. Steid knows the actor by then; it simply is not
94 passed through. Small, and worth doing before anything reads reflogs.
95- **No rate limiting on token authentication.** A token is 256 bits so guessing is not
96 the worry; unbounded hashing on an open endpoint is.
97- **Tokens have no expiry and no last-used timestamp.** Both deliberate omissions for
98 now — see [0007]decisions/0007-tokens-over-http-basic.md — but a token list with no
99 "last used" makes it hard to know which are safe to revoke.
e0856ebfeat: clone a public repository over HTTP8d
100- **A subprocess per git request.** Unlike Milestone 3's once-per-creation, this is on a
101 hot path and has not been measured. Milestone 5 is where that bill comes due.
102- **Streaming is by construction, not by measurement.** The response body is never
103 collected, but no clone large enough to prove it has been run.
6a2a925docs: close Milestone 3, plan Milestone 4a8d
104- **An orphaned repo directory is possible** if the process dies between the record
105 write and the filesystem write, and it then blocks re-creating that name. The durable
106 fix is a reconciliation sweep on boot
107 ([architecture.md]architecture.md#db-plus-filesystem-writes); clearing one is a
108 manual `rm` today, since repo deletion does not exist.
109- **The duplicate-name check races.** The loser is caught by `init_bare` or the unique
110 constraint, but surfaces as an opaque storage error rather than "name taken".
111- **Bare repos created on macOS carry `ignorecase = true`.** A migration gotcha if the
112 data directory ever moves to Linux.
dce0bf3feat: browse a repository's files and history8d
113- **Light mode is still untested**, and now there is much more surface to get it wrong
114 on — the file tree, the blob view and the log all shipped without anyone looking at
115 them in light mode.
116- **Submodule rendering was never seen**, only compiled: no fixture contained one.
4ef3945feat: repository settings, README rendering, branch switcher, raw files7d
117- **The `/log` page's switcher opens with nothing marked current** when no revision is
118 in the URL, because `repo_log` still does not report the revision it resolved. Now more
119 visible than before, since there is a switcher to look wrong.
120- **Task-list items keep their bullet** and footnotes render in place rather than
121 collected at the end. Both cosmetic.
dce0bf3feat: browse a repository's files and history8d
122- **A per-file last-commit column is still absent**, deliberately — see
123 [0006]decisions/0006-git-binary-behind-narrow-ports.md#amendment--20260829-the-milestone-5-read-path.
124 Wanting it is the trigger to move to a kept-alive `cat-file --batch`, not to reopen
125 `gix`.
2eb8681docs: put git next, plan the repo model24d
126- **Fonts are not loaded.** The theme names Geist and IBM Plex Mono; both fall back
127 today. Topcoat's `font-fontsource` feature handles it.
128- **Light mode is untested.** The palette defines it; nobody has looked at it.
f0444b7docs: plan milestone 2 in two phases1mo
129- **No rate limiting** on `/auth/login` or `/auth/setup`.
076dbc9docs: close milestone 1, open milestone 21mo
130- **`sweep_expired` is never called**, so expired session rows accumulate. Expiry is
131 enforced on read, so this is tidiness, not a hole.
2eb8681docs: put git next, plan the repo model24d
132- **CSRF.** `SameSite=Lax` covers the common case. Forms now exist, so this is decidable
133 rather than hypothetical.
ab7fea9chore: plans setup1mo
134
135## Backlog
136
137Ordered. Pull from the top.
138
3dd7293feat: ship Steid as an installable binary8d
1391. **Milestone 6 — Writing.** Posts, markdown, `/{handle}/posts/{slug}`. Open when it
140 starts: which markdown crate, and whether raw HTML in markdown is trusted — safe for a
141 single author, a stored-XSS hole the moment Milestone 7 adds a second user. A
142 repository's README rendering on its page falls out of the same pipeline.
ab7fea9chore: plans setup1mo
143
144## Open questions
145
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
146- **Topcoat is early** (v0.5.0, first released 2026-07-22, breaking changes expected
147 by its own authors). Expect churn that isn't feature work.
148- Topcoat ships Tailwind without Node, which reopens the design system attempt #1
149 dropped purely to avoid an npm build step — see [ui.md]ui.md.
150
151## Routing findings (Milestone 0)
152
153- **Topcoat 0.5 requires rustc ≥ 1.95.** On an older toolchain `cargo add topcoat`
154 silently resolves to an empty `topcoat v0.0.0` placeholder instead of failing. Local
155 stable is now 1.97.1.
156- `Router::builder().discover()` collects `#[page]`-annotated items **at link time**,
157 so pages can live in any module. Layering is our choice, not the framework's.
158- `module_router!` derives each URL from the module tree rather than a path string.
aaefaabfeat: root handles, grouped routes, reserved-handle denylist1mo
159 Still deferred. Application routes now group cleanly (`auth/login`, `api/me`), but
160 handles sit at the root ([0004]decisions/0004-root-handles-grouped-routes.md), so a
161 parameterised root segment still has to coexist with static ones. Worth checking how
162 `module_router!` handles that before committing to it.
bd48b4bdocs: serve git over smart HTTP, reorder roadmap portfolio-first1mo
163- Path and query params are read from `Cx` via `path_param!` / `#[query_params]`, not
164 injected as handler arguments. Parses are memoized per request.
165- Layouts wrap by path prefix and nest outermost-first, and a layout can catch a page's
166 `NotFoundError` to render a branded 404.
167- `HOST` / `PORT` configure the bind address, so `STEID_LISTEN_ADDR` is gone.
168- `Body` is a boxed `http_body::Body` used for both requests and responses, with
169 `into_data_stream()` to read and `Body::new()` to wrap a stream — pack data can
ca76e1bdocs: fix milestone cross-references after the reorder24d
170 stream both directions without buffering. This is what makes Milestone 4 viable.