steid

@jamesgill /

steid/plans/runbook.md
5.7 KBCode·Blame·Raw
1# Runbook
2
3> Attempt #2's only setup instructions lived in a plan file describing an architecture
4> that had already been deleted, so they were actively wrong. Keep this file honest:
5> if a command here doesn't work, fix it or delete it.
6
7## Status
8
9The app boots and serves. Everything marked **(#2)** is carried from the previous
10attempt and has **not** been re-verified against Topcoat — treat it as a sketch.
11
12## Requirements
13
14- **rustc ≥ 1.95** — Topcoat 0.5 requires it, and on an older toolchain `cargo add
15 topcoat` silently resolves to an empty `topcoat v0.0.0` placeholder rather than
16 failing. Verified on 1.97.1.
17- `git` on `PATH` (from Milestone 3 — `git init --bare` creates repos, and from
18 Milestone 4 `git http-backend` serves the protocol). **`cargo test` needs it too**:
19 `DiskGitStorage`'s tests run real `git init`, so a machine without `git` fails the
20 suite, not just the app.
21
22## Dev setup
23
24```bash
25cargo run # serves on http://127.0.0.1:3000
26cargo test
27```
28
29```bash
30cargo install topcoat-cli # dev server: watch, rebuild, asset bundling
31topcoat dev # working
32```
33
34`topcoat dev` builds, bundles assets, watches sources, and live-reloads pages that
35include `topcoat::dev::script()`. Press `r` to force a rebuild.
36
37## Configuration
38
39`STEID_`-prefixed env vars via `dotenvy` + `envy`, read into `AppConfig`. Every value
40has a default, so a bare `cargo run` works with no environment at all.
41
42Live now:
43
44```
45STEID_DATABASE_URL=sqlite:steid.db?mode=rwc # default
46STEID_DATA_DIR=./data # default; bare repos, read from M3
47STEID_INSECURE_COOKIES=false # default; see below
48```
49
50There is deliberately **no owner password in configuration** — the owner is created
51through the claim flow instead. See
52[0002]decisions/0002-first-run-claim-not-config-bootstrap.md.
53
54The bind address is **not** a `STEID_` variable — Topcoat owns it:
55
56```bash
57HOST=0.0.0.0 PORT=8080 cargo run
58```
59
60That supersedes attempt #2's `STEID_LISTEN_ADDR`.
61
62### `STEID_INSECURE_COOKIES` — development only
63
64Topcoat's session cookie is `__Host-` prefixed and `Secure`. `Secure` means the browser
65only keeps it over a trustworthy origin, and browsers disagree about whether
66plain-HTTP `localhost` qualifies. Where it doesn't, **the failure is completely
67silent**: the server issues a session and records the row, the browser discards the
68cookie, and every page renders signed out with no error anywhere. This cost an
69afternoon; the symptom looks exactly like broken auth logic.
70
71Setting `STEID_INSECURE_COOKIES=true` swaps in `InsecureCookieTokenStore` — the same
72cookie without `Secure` and without the prefix, named `steid-dev-session` so it can
73never be confused with a hardened one. `HttpOnly` and `SameSite=Lax` are kept. Boot
74prints a warning while it's on.
75
76**Never set this on a deployed instance.** Without `Secure` the session cookie travels
77unencrypted and anyone on the network path can lift it and become that user. Behind
78TLS, leave it unset.
79
80A gitignored `.env` in the repo root sets it for local work. Keep `.env` and
81`.env.prod` out of git — both are gitignored.
82
83## First run
84
85```bash
86cargo run # or: topcoat dev
87```
88
89An unclaimed instance prints a setup token and redirects every route to `/auth/setup`.
90Paste the token, choose a handle, email, and password, and the owner is created and
91signed in.
92
93The token is **held in memory only**, so every restart mints a new one — including
94each rebuild under `topcoat dev`. Use the most recent one printed. Once claimed, no
95token is minted at all and `/auth/setup` redirects away.
96
97Sign in at `/auth/login` with the **email**, not the handle.
98
99## Repo layout on disk (Milestone 3)
100
101Bare repos at `{STEID_DATA_DIR}/{handle}/{name}.git`, created with no template (so no
102`.sample` hooks) and `HEAD` pinned to `refs/heads/main` regardless of the host's
103`init.defaultBranch`. See [0006]decisions/0006-git-binary-behind-narrow-ports.md.
104
105Created empty — no initial commit and no branch, like GitHub.
106
107Creating one refuses rather than reusing a directory that already exists, so an orphan
108left by a create that died mid-way blocks that name until it is removed by hand.
109
110## Git transport (Milestone 4)
111
112Smart HTTP, delegated to `git http-backend`, authenticated with personal access tokens
113over HTTP Basic — see [0001]decisions/0001-git-over-http-not-ssh.md. No SSH, no host
114keys, no `authorized_keys`.
115
116```bash
117git clone http://host/{handle}/repos/{name}.git
118```
119
120Fill in the token workflow and the `body_limit` setting once this is built.
121
122## Manual verification checklist (#2)
123
124Attempt #2 verified these by hand each milestone but never wrote down the steps. They
125are the smoke test for Milestones 4–5. The auth rows assumed SSH keys; the shape of
126the check still holds with tokens substituted:
127
128- [ ] Create a repo via the web UI → bare repo appears at
129 `{data_dir}/{org}/{repo}.git`
130- [ ] `git clone` an empty repo → succeeds
131- [ ] `git clone` a repo with history → succeeds
132- [ ] `git clone` a non-existent repo → clean error, not a hang or panic
133- [ ] First push to an empty repo → succeeds
134- [ ] Push to a repo with history → succeeds
135- [ ] Push a repo large enough to exercise the `body_limit` cap → succeeds
136- [ ] Clone with no credentials → rejected, and the prompt is comprehensible
137- [ ] Clone with a valid token → succeeds
138- [ ] Revoke the token → subsequent clone rejected at auth
139- [ ] Clone a private repo as a non-member → rejected
140- [ ] Push as a non-owner member → rejected
141
142Worth automating as an integration test rather than re-running by hand a fourth time.
143
144## `.gitignore`
145
146Applied: `/target`, `/data`, `*.db*`, `.env`, `.env.prod`.
147
148**Do not add `/plans`.** Attempt #2 did, and that is why these docs had to be
149hand-carried between repos.