feat(sources): admin page to add and remove git sources (TODO.md L4)

This commit is contained in:
2026-08-26 18:42:28 -04:00
parent b2d8696741
commit 1925bb66a8
30 changed files with 2673 additions and 110 deletions
+49 -23
View File
@@ -65,16 +65,21 @@ uv run python -m scripts.import_docs # import the configured sources (below)
Two source modes:
- **Git sources (recommended)** — set `BOR_GIT_SOURCES` in `.env` to a
comma-separated list of git repo URLs. `import_docs` clones each repo
- **Git sources (recommended)** — the list is managed on the **admin
Git sources page** (`/git-sources.html`) and stored in Postgres (see
[Git-based sources](#git-based-sources)). `import_docs` clones each repo
(first run) or pulls it (subsequent runs) into
`BOR_SOURCES_DIR/<repo-name>/` (default `~/bor-sources`) and indexes the
checkouts — see [Git-based sources](#git-based-sources).
checkouts. While the stored list is empty, the `BOR_GIT_SOURCES` variable
in `.env` is the fallback — the moment the page stores a source, the
variable is ignored.
- **Manual directories** — `--source <path>` (repeatable) imports local
directories directly and *always wins* over `BOR_GIT_SOURCES`.
- If neither is set, the import falls back to the **previous** default,
directories directly and *always wins* over the git sources (stored list
or env).
- If neither is set (stored list, `--source`, and `BOR_GIT_SOURCES` all
empty), the import falls back to the **previous** default,
`~/Homelab` + `~/Deployments` — kept only for backwards compatibility,
now replaced by `BOR_GIT_SOURCES`.
now replaced by the git sources list.
### 6. Run the app
```bash
@@ -102,6 +107,12 @@ uv run uvicorn app.main:app --reload
new tab). **Admin-only** — anonymous visitors see a sign-in gate instead
(the catalog is what the login locks; the document viewer itself stays
open to everyone).
- **Git sources** (`/git-sources.html`) — the list of git repositories the
**Sync sources** button clones and indexes; **admin-only** (the same
sign-in gate as Sources). Add or remove repositories here — no `.env`
editing, no restart. Adding/removing does not clone or prune on its
own: the Sync button performs that, and a removed repository's documents
leave the index on the next sync.
## Thinking
@@ -262,26 +273,38 @@ embedded.
Rather than pointing the import at local folders, point it at **git
repositories** — the notes live in the repos and `import_docs` keeps local
checkouts of them up to date for you:
checkouts of them up to date for you.
**Where the list lives (phase 35):** the primary management surface is
the **admin Git sources page** (`/git-sources.html`) — add or remove
repositories there and the list is stored in Postgres (the `git_sources`
table). `BOR_GIT_SOURCES` in `.env` is the **empty-table fallback**: it
only applies while the stored list is empty, and is ignored once the page
has any row (the page becomes the source of truth — no `.env` editing, no
restart needed afterwards).
```env
# .env
# .env — the fallback list (fresh setups, or until the admin page
# stores a source; phase 35 demotes this variable, it does not remove it)
BOR_GIT_SOURCES=https://git.reeseapps.com/reese/homelab.git,git@github.com:reese/deployments.git
BOR_SOURCES_DIR=~/bor-sources # default; each repo lands in <dir>/<repo-name>/
```
- `BOR_GIT_SOURCES` is a **comma-separated list** of URLs. Auth is whatever
the machine supplies — `https://…` via the OS credential helper, or
`git@host:repo.git` via your SSH key; no credentials are stored in the
app or `.env`.
- The effective list (stored rows, else `BOR_GIT_SOURCES` while the stored
list is empty) is a set of git repo URLs. Auth is whatever the machine
supplies — `https://…` via the OS credential helper, or `git@host:repo.git`
via your SSH key; no credentials are stored in the app or `.env`.
Stored URLs are shape-validated on the page (`https://`, `ssh://`,
`git@…` — scp-style `host:repo` is rejected).
- Every run **clones** each repo (first time, shallow `--depth 1`) or
**pulls** it (`git pull --ff-only` — fast-forward only, so a diverged or
broken checkout fails loudly instead of merging) into
`BOR_SOURCES_DIR/<repo-name>/`, then indexes the checkouts exactly like
any local directory (A9 format filter, hidden-dir skip, sha256 delta).
`documents.source` is the repo directory name (e.g. `homelab`).
- **`--source <path>` overrides**: when the flag is given,
`BOR_GIT_SOURCES` is ignored and the manual directory(ies) are imported.
- **`--source <path>` overrides**: when the flag is given, the git
sources (stored list *and* `BOR_GIT_SOURCES`) are ignored and the
manual directory(ies) are imported.
- **A failed sync aborts the run**: if any repo cannot be cloned/pulled,
`import_docs` exits non-zero naming the failing repo and imports
**nothing** (no partial junk). Fix the URL/connectivity and re-run — the
@@ -293,9 +316,10 @@ The **Sync sources** button on the **Sources** page — visible to the
**admin only** (anonymous visitors never see it) — runs the whole
git-source refresh in one click, in-process:
1. **clone/pull** every `BOR_GIT_SOURCES` repo (the same
`clone_or_pull` the CLI uses — shallow clone on first run,
`git pull --ff-only` afterwards);
1. **clone/pull** every configured git source — the admin-managed list
(the `git_sources` table; `BOR_GIT_SOURCES` only while that list is
empty), through the same `clone_or_pull` the CLI uses (shallow clone
on first run, `git pull --ff-only` afterwards);
2. **re-import with prune** — the `--prune` equivalent, so files deleted
upstream leave the index (the button is the canonical "mirror the
repos" action); the sha256 delta still skips unchanged files, so an
@@ -304,10 +328,12 @@ git-source refresh in one click, in-process:
chat turn injects) — but only when the import actually changed the
knowledge base.
- **Prerequisites:** `BOR_GIT_SOURCES` must be set — an unset/empty list
fails the sync loudly ("no git sources configured"), because the button
targets the git repos only (manual `--source` directories have no repo
to clone) — and `git` must be on the app's `PATH`.
- **Prerequisites:** at least one git source must be configured — a row
on the admin Git sources page, or `BOR_GIT_SOURCES` in `.env` while the
stored list is empty; **both** empty fails the sync loudly ("no git
sources configured"), because the button targets the git repos only
(manual `--source` directories have no repo to clone) — and `git` must
be on the app's `PATH`.
- **States:** clicking starts the run (`202`) and the button goes
disabled with **Syncing…** (spinning icon) while the page polls
`GET /api/sync/status` every 2 s. There is deliberately **no
@@ -534,8 +560,8 @@ served locally (no CDN), `BOR_ENVIRONMENT=production`.
| `BOR_HYBRID_LEXICAL_CANDIDATES` | `30` | FTS list width for the RRF fusion |
| `BOR_RRF_K` | `60` | RRF damping constant (`1/(k + rank)`) |
| `BOR_IMPORT_EXTENSIONS` | `md,markdown,txt,yaml,yml,json,py` | csv of importable formats (may only narrow the A9 set) |
| `BOR_GIT_SOURCES` | — (empty) | csv of git repo URLs; `import_docs` clones/pulls them into `BOR_SOURCES_DIR` and indexes the checkouts (see *Git-based sources*) |
| `BOR_SOURCES_DIR` | `~/bor-sources` | where the `BOR_GIT_SOURCES` repos are cloned/pulled (one subdirectory per repo) |
| `BOR_GIT_SOURCES` | — (empty) | csv of git repo URLs — **fallback while the admin Git sources page's list (Postgres `git_sources`) is empty**; the page is the primary management surface (see *Git-based sources*) |
| `BOR_SOURCES_DIR` | `~/bor-sources` | where the git source repos are cloned/pulled (one subdirectory per repo) |
| `BOR_STEERING_MAX_CHARS` | `8000` | char budget for the `<tuning>` (steering notes) prompt section |
| `BOR_SUMMARY_MAX_CHARS` | `12000` | cap on document content sent to the `lite` summary model at import (see *Document summaries*) |
| `BOR_KB_OVERVIEW_MAX_CHARS` | `4000` | char budget for the `<knowledge_base>` (KB overview) prompt section |