Config: .env = projects root only; derive each project's forge from git

Reduce .env to just REPOS_HOST_PATH (the projects root); runtime bootstrap moves to compose/defaults. Projects are the subdirectories of the single root — removed the project-directories feature (store table, service methods, /api/config/project-dirs, Settings section). Each project's forge is derived from its git remote (matched to a configured forge, else the bare host) and shown as a pill next to its name (State.Forge via scanner ForgeFor + svc.ForgeDisplay). Removed first-run .env seeding; forges + identity are managed in Settings. Added forge.HostOf. AGENT.md updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-22 05:52:03 -04:00
parent e30c3b632a
commit 32ae17cc9f
15 changed files with 175 additions and 434 deletions
+33 -27
View File
@@ -124,7 +124,7 @@ values** rather than reintroducing hardcoded hex.
- **Git stays the system of record for repository data** — never mirror repo
contents into another store. The one sanctioned datastore is the **config store**
(§1.5): a private SQLite DB holding *app configuration* (forge hosts + tokens,
the project directories to scan, git identity), not git data. Anything beyond
git identity), not git data. Anything beyond
that (mirroring repo/PR data, a serverside app DB) still requires asking first.
Peruser UI state remains in browser `localStorage` (§4).
@@ -146,22 +146,24 @@ must be treated as such:
### 1.5 Configuration: bootstrap `.env` + a private config store
Configuration is split:
Configuration is split three ways:
- **Bootstrap `.env`**only what's needed to start the process and can't live in
the DB: listen address, TLS, `APP_ENV`, `LOG_FILE`, the `git` binary, scan
tuning (interval/depth/ignore/fetch), and the **config DB path**. A committed
`.env.example` documents every variable; **never commit a real `.env`**.
- **Config store (`internal/store`, SQLite)** — the domain config that used to
live in `.env`: **forge hosts + access tokens, the project directories to scan,
and the git commit identity.** Managed at runtime in the app's **Settings**
(`/settings`), not by editing files. On first run with an empty DB it is
**seeded** from the `.env` values (`GITEA_*`, `GIT_REPO_ROOTS`, `GIT_USER_*`);
after that those `.env` values are ignored.
- **The DB must not be reachable outside the container.** It lives on a **private
named Docker volume** (`/data`) — never bindmounted into the project, never on a
published port. Tokens are stored there relying on that isolation. Never hardcode
tokens/hosts/paths in code.
- **`.env`the projects root, and nothing else.** The only value a user sets in
`.env` is `REPOS_HOST_PATH`: the host directory that holds their projects, mounted
into the container as the single **projects root** (`PROJECTS_ROOT`, default
`/repos`). **Each project is a subdirectory of that root.** `.env` is gitignored;
a committed `.env.example` documents it.
- **Runtime bootstrap — compose/env defaults, not the `.env` file.** Listen
address, TLS, `APP_ENV`, `LOG_FILE`, the `git` binary, scan tuning, the config
DB path, and `PROJECTS_ROOT` come from `docker-compose.yml`'s `environment:` and
incode defaults — not from the user's `.env`.
- **Config store (`internal/store`, SQLite)** — **forge hosts + access tokens** and
the **git commit identity**, managed at runtime in the app's **Settings**
(`/settings`), not by editing files. A project's **forge is derived from its git
remote** (matched to a configured forge) — not stored per project (§8.4). The DB
must not be reachable outside the container: it lives on a **private named Docker
volume** (`/data`) — never bindmounted, never on a published port; tokens rely on
that isolation. Never hardcode tokens/hosts/paths in code.
### 1.6 Everything runs in Docker / dockercompose
@@ -227,7 +229,7 @@ without asking.
│ └── server/main.go # entrypoint: wire config, git, scanner, router
├── internal/
│ ├── config/ # bootstrap .env loading, typed config struct (§1.5)
│ ├── store/ # SQLite config store: forges+tokens, project dirs, identity (§1.5)
│ ├── store/ # SQLite config store: forges+tokens, git identity (§1.5)
│ ├── git/ # THE Git boundary: interface + os/exec impl (all git ops)
│ ├── repos/ # discovery, in-memory index/cache, refresh scanner worker
│ ├── service/ # the ONE service layer both the HTTP API and MCP call (§1.7)
@@ -456,6 +458,10 @@ later behind the same interface).
matching the forge features are simply absent (**graceful degradation**). The
service resolves a repo → provider by matching remotes (preferring `origin`)
against the stored forges, caching a client per host.
- **A project's forge is derived from git, not stored per project.** The scanner
reads each repo's `origin` remote and labels it with the matching configured
forge's name (or the bare host when unmatched); the dashboard shows that label
next to the project name (`repos.State.Forge`, via `svc.ForgeDisplay`).
- The provider is inferred from a repo's remote URL. Never send repo data to a
host the user didn't configure.
@@ -540,16 +546,16 @@ component carries its own context.
*(Claude Code: surface these to the human at the first relevant moment; don't
silently guess.)*
- ✅ **RESOLVED 2026-09-22:** **Domain config moved from `.env` to a private
SQLite store** (`internal/store`, §1.5): forges+tokens, project directories, git
identity — managed in Settings (`/settings`), seeded from `.env` on first run.
DB on a private `/data` volume (not bindmounted, no port). Forge is now
**multihost**; git auth sets an `http.extraheader` per forge.
- **Container mount constraint (multiple project dirs):** the container can only
scan host paths that are **bindmounted at `up` time**. Today `~/Projects` is
mounted to `/repos`, so project dirs added in Settings must resolve under a
mounted base. A dir outside it needs a new compose mount — surface this if asked
to add such a path.
- ✅ **RESOLVED 2026-09-22:** **`.env` holds only the projects root; forges +
identity live in a private SQLite store** (`internal/store`, §1.5), managed in
Settings. **Projects are the subdirectories** of the single root
(`REPOS_HOST_PATH` → `/repos`); each project's **forge is derived from its git
remote** and shown next to its name. DB on a private `/data` volume (not
bindmounted, no port). Forge is **multihost**; git auth sets an
`http.extraheader` per forge.
- **Mount constraint:** the container only sees host paths bindmounted at `up`
time. All projects must live under the mounted root (`REPOS_HOST_PATH`); a
project elsewhere needs its own compose mount + a widened/extra root.
- **Token encryption at rest:** tokens are stored plaintext in the private DB
(isolation is the control). Confirm before adding encryptionatrest (a key
would then need storing too).