# Runbook

> Attempt #2's only setup instructions lived in a plan file describing an architecture
> that had already been deleted, so they were actively wrong. Keep this file honest:
> if a command here doesn't work, fix it or delete it.

## Status

The app boots and serves. Everything marked **(#2)** is carried from the previous
attempt and has **not** been re-verified against Topcoat — treat it as a sketch.

## Requirements

- **rustc ≥ 1.95** — Topcoat 0.5 requires it, and on an older toolchain `cargo add
  topcoat` silently resolves to an empty `topcoat v0.0.0` placeholder rather than
  failing. Verified on 1.97.1.
- `git` on `PATH` (from Milestone 3 — the protocol server shells out to it)

## Dev setup

```bash
cargo run                     # serves on http://127.0.0.1:3000
cargo test
```

```bash
cargo install topcoat-cli     # dev server: watch, rebuild, asset bundling
topcoat dev                   # working
```

`topcoat dev` builds, bundles assets, watches sources, and live-reloads pages that
include `topcoat::dev::script()`. Press `r` to force a rebuild.

## Configuration

`STEID_`-prefixed env vars via `dotenvy` + `envy`, read into `AppConfig`. Every value
has a default, so a bare `cargo run` works with no environment at all.

Live now:

```
STEID_DATABASE_URL=sqlite:steid.db?mode=rwc   # default
STEID_DATA_DIR=./data                         # default; bare repos, used from M2
```

The bind address is **not** a `STEID_` variable — Topcoat owns it:

```bash
HOST=0.0.0.0 PORT=8080 cargo run
```

That supersedes attempt #2's `STEID_LISTEN_ADDR`.

Arriving with Milestone 1 (#2) — owner bootstrap and registration policy:

```
STEID_REGISTRATION=personal          # personal | invite | open
STEID_OWNER_EMAIL=admin@localhost.dev
STEID_OWNER_PASSWORD=changeme
STEID_OWNER_USERNAME=admin
```

In `personal` mode the owner account is bootstrapped from `STEID_OWNER_*` on first
boot. Keep `.env` and `.env.prod` out of git — both are gitignored.

## Repo layout on disk (#2)

Bare repos at `{STEID_DATA_DIR}/{org}/{repo}.git`. Created empty — no initial commit.

## Git transport (Milestone 5)

Smart HTTP, delegated to `git http-backend`, authenticated with personal access tokens
over HTTP Basic — see [0001](decisions/0001-git-over-http-not-ssh.md). No SSH, no host
keys, no `authorized_keys`.

```bash
git clone http://host/owner/repo.git
```

Fill in the token workflow and the `body_limit` setting once this is built.

## Manual verification checklist (#2)

Attempt #2 verified these by hand each milestone but never wrote down the steps. They
are the smoke test for Milestones 4–5. The auth rows assumed SSH keys; the shape of
the check still holds with tokens substituted:

- [ ] Create a repo via the web UI → bare repo appears at
      `{data_dir}/{org}/{repo}.git`
- [ ] `git clone` an empty repo → succeeds
- [ ] `git clone` a repo with history → succeeds
- [ ] `git clone` a non-existent repo → clean error, not a hang or panic
- [ ] First push to an empty repo → succeeds
- [ ] Push to a repo with history → succeeds
- [ ] Push a repo large enough to exercise the `body_limit` cap → succeeds
- [ ] Clone with no credentials → rejected, and the prompt is comprehensible
- [ ] Clone with a valid token → succeeds
- [ ] Revoke the token → subsequent clone rejected at auth
- [ ] Clone a private repo as a non-member → rejected
- [ ] Push as a non-owner member → rejected

Worth automating as an integration test rather than re-running by hand a fourth time.

## `.gitignore`

Applied: `/target`, `/data`, `*.db*`, `.env`, `.env.prod`.

**Do not add `/plans`.** Attempt #2 did, and that is why these docs had to be
hand-carried between repos.
