Move domain config from .env to a private SQLite store
Forges (multi-host) + tokens, project directories, and git identity now live in a private SQLite config store (internal/store, modernc.org/sqlite) on a /data named volume that is not bind-mounted or exposed, so credentials aren't reachable outside the container. New Settings page (/settings) + <settings-panel> with /api/config CRUD. Scanner reads roots fresh from the store each cycle; service resolves forges per-repo from the store and reapplies per-forge git auth on change. First run seeds the store from .env. Overturns the old no-datastore/.env-config laws (AGENT.md updated). Verified live end-to-end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -121,10 +121,12 @@ values** rather than reintroducing hardcoded hex.
|
||||
- **The background scanner is read‑only.** It may run `status`, `rev-list`,
|
||||
`for-each-ref`, `log`, and (only when explicitly enabled) `fetch`. It must never
|
||||
run a command that changes local state.
|
||||
- **Never invent local persistence for domain state.** If something must survive a
|
||||
restart and it isn't already in Git, it is either app config (`.env`, Section
|
||||
1.4) or per‑user UI state (browser `localStorage`, Section 4). Adding any other
|
||||
datastore (SQLite, a server‑side DB) requires asking first — the default is no.
|
||||
- **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
|
||||
that (mirroring repo/PR data, a server‑side app DB) still requires asking first.
|
||||
Per‑user UI state remains in browser `localStorage` (§4).
|
||||
|
||||
### 1.4 Destructive Git operations are explicit, confirmed, and never automatic
|
||||
|
||||
@@ -142,12 +144,24 @@ must be treated as such:
|
||||
destructive default. Prefer the safe variant (`--force-with-lease` over
|
||||
`--force`) and surface it as such.
|
||||
|
||||
### 1.5 Configuration via `.env`
|
||||
### 1.5 Configuration: bootstrap `.env` + a private config store
|
||||
|
||||
Repo scan roots, the `git` binary path, scan/fetch behavior, the listen address,
|
||||
and any optional forge tokens are all configurable through a single `.env` file. A
|
||||
committed `.env.example` documents every variable. **Never commit a real `.env`**
|
||||
and never hardcode paths, tokens, hosts, or the set of watched repositories.
|
||||
Configuration is split:
|
||||
|
||||
- **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 bind‑mounted into the project, never on a
|
||||
published port. Tokens are stored there relying on that isolation. Never hardcode
|
||||
tokens/hosts/paths in code.
|
||||
|
||||
### 1.6 Everything runs in Docker / docker‑compose
|
||||
|
||||
@@ -187,14 +201,15 @@ MCP handler.
|
||||
| MCP server | **`github.com/modelcontextprotocol/go-sdk`**, served over **Streamable HTTP** at `/mcp` | Claude Desktop connects as a custom connector. Tools are thin adapters over the shared service layer (§1.7). See Section 8. |
|
||||
| Real‑time (app → browser) | **Server‑Sent Events** (`net/http`, stdlib) | Push activity + handoff notifications and live repo updates to the components; replaces list polling over time. |
|
||||
| Diff rendering | Server produces unified diff from `git`; client renders it in a component | No heavy client diff lib without asking. |
|
||||
| Config | **`.env`** via `github.com/joho/godotenv` + a typed config struct | Section 1.5. |
|
||||
| Logging | **`log/slog`** → stdout/stderr (structured), optional rotating file sink | See Section 7. No database sink (there is no database). |
|
||||
| Bootstrap config | **`.env`** via `github.com/joho/godotenv` + a typed config struct | Section 1.5 (bootstrap only). |
|
||||
| Config store | **SQLite via `modernc.org/sqlite`** (pure Go, no CGO) in `internal/store`, on a private `/data` volume | Forges + tokens, project dirs, git identity. Not the git data store (§1.3). |
|
||||
| Logging | **`log/slog`** → stdout/stderr (structured), optional rotating file sink | See Section 7. |
|
||||
| Hot reload (dev) | **air** (`github.com/air-verse/air`) | Inside the app container. |
|
||||
|
||||
Anything not in this table that you want to add as a dependency: **propose it and
|
||||
wait for approval.** Keep the dependency surface small. Note in particular there is
|
||||
**no database and no auth library** — do not add one without asking (Section 1.3,
|
||||
Section 0).
|
||||
wait for approval.** Keep the dependency surface small. There is **no auth library**
|
||||
(§0) and **no datastore beyond the config store** (§1.3, §1.5) — do not add one
|
||||
without asking.
|
||||
|
||||
---
|
||||
|
||||
@@ -211,7 +226,8 @@ Section 0).
|
||||
├── cmd/
|
||||
│ └── server/main.go # entrypoint: wire config, git, scanner, router
|
||||
├── internal/
|
||||
│ ├── config/ # .env loading, typed config struct
|
||||
│ ├── config/ # bootstrap .env loading, typed config struct (§1.5)
|
||||
│ ├── store/ # SQLite config store: forges+tokens, project dirs, 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)
|
||||
@@ -231,8 +247,10 @@ Section 0).
|
||||
```
|
||||
|
||||
> If a new concern doesn't fit cleanly, **ask** before inventing a new top‑level
|
||||
> directory. Keep `components/` strictly for web components. There is deliberately
|
||||
> **no `migrations/` and no `db/`** — see Section 1.3.
|
||||
> directory. Keep `components/` strictly for web components. The only datastore is
|
||||
> the SQLite **config store** (`internal/store`) on the private `/data` volume
|
||||
> (§1.5) — schema is created in‑code (`CREATE TABLE IF NOT EXISTS`); there is no
|
||||
> `migrations/` framework yet (add one if the schema grows non‑trivially).
|
||||
|
||||
---
|
||||
|
||||
@@ -432,9 +450,12 @@ later behind the same interface).
|
||||
per §1.4**, names the PR/branch, and prefers the tidy default (squash‑merge +
|
||||
delete branch). Note a merged PR remains in the host's history; "clean up"
|
||||
means removing the **branch**, not falsifying history.
|
||||
- **Enablement is per‑host and token‑gated.** Tokens come from `.env`
|
||||
(e.g. `GITEA_TOKEN`); with no token the forge features are simply absent and
|
||||
the rest of the app works unchanged (**graceful degradation**).
|
||||
- **Multiple hosts, configured in the store.** Forges (base URL + token) live in
|
||||
the **config store** (§1.5) and are managed in Settings — not `.env`. A repo is
|
||||
forge‑enabled when a remote's host matches a configured forge; with none
|
||||
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.
|
||||
- The provider is inferred from a repo's remote URL. Never send repo data to a
|
||||
host the user didn't configure.
|
||||
|
||||
@@ -502,9 +523,10 @@ component carries its own context.
|
||||
4. If it touches repositories or forges: put the logic in the **service layer**
|
||||
(§1.7) over the `internal/git` / `internal/forge` boundaries — never in an HTTP
|
||||
or MCP handler — so both the GUI and Claude get it. Respect **Git = system of
|
||||
record** (§1.3), treat **destructive operations** per §1.4, and never add a
|
||||
datastore. A new capability generally means: service method → HTTP handler →
|
||||
MCP tool → UI control.
|
||||
record** (§1.3), treat **destructive operations** per §1.4, and don't mirror
|
||||
repo/PR data into a datastore (app *config* goes in the store, §1.5). A new
|
||||
capability generally means: service method → HTTP handler → MCP tool → UI
|
||||
control.
|
||||
5. If it needs a **new dependency** or a **new top‑level folder**, propose it and
|
||||
wait for approval.
|
||||
6. Implement, run it in the **docker‑compose dev environment**, verify hot reload
|
||||
@@ -518,9 +540,22 @@ component carries its own context.
|
||||
*(Claude Code: surface these to the human at the first relevant moment; don't
|
||||
silently guess.)*
|
||||
|
||||
- **Repo discovery strategy:** recursive scan of `GIT_REPO_ROOTS` (max depth?
|
||||
ignore globs?) vs an explicit list of repo paths. Default assumption: recursive
|
||||
scan with a configurable depth.
|
||||
- ✅ **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 bind‑mounted, no port). Forge is now
|
||||
**multi‑host**; git auth sets an `http.extraheader` per forge.
|
||||
- **Container mount constraint (multiple project dirs):** the container can only
|
||||
scan host paths that are **bind‑mounted 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.
|
||||
- **Token encryption at rest:** tokens are stored plaintext in the private DB
|
||||
(isolation is the control). Confirm before adding encryption‑at‑rest (a key
|
||||
would then need storing too).
|
||||
- **Repo discovery strategy:** recursive scan of the store's project directories
|
||||
(max depth / ignore from `.env` scan tuning). Default: recursive with a
|
||||
configurable depth.
|
||||
- **Background `fetch`:** off by default (no unsolicited network). Confirm whether
|
||||
it should be enabled, and the interval / rate limit, before turning it on.
|
||||
- **Git access library:** system `git` via `os/exec` is the locked default
|
||||
|
||||
Reference in New Issue
Block a user