diff --git a/docs/architecture/auth.md b/docs/architecture/auth.md new file mode 100644 index 0000000..cb3fe9d --- /dev/null +++ b/docs/architecture/auth.md @@ -0,0 +1,115 @@ +# Authentication & Invites + +## Shared database + +BincioWiki shares its user database with `bincio_activity`. Both services read from and write to the same SQLite file at `/var/bincio/data/instance.db`. There is no separate user store for the wiki. + +The relevant tables: + +### `users` + +```sql +CREATE TABLE users ( + handle TEXT PRIMARY KEY, + display_name TEXT NOT NULL DEFAULT '', + password_hash TEXT NOT NULL, + is_admin INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL, + wiki_access INTEGER NOT NULL DEFAULT 1, + activity_access INTEGER NOT NULL DEFAULT 0 +); +``` + +`wiki_access = 1` grants access to BincioWiki. `activity_access = 1` grants access to BincioActivity. The two flags are independent; a user can have one, both, or neither. + +### `sessions` + +```sql +CREATE TABLE sessions ( + token TEXT PRIMARY KEY, + handle TEXT NOT NULL REFERENCES users(handle) ON DELETE CASCADE, + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL +); +``` + +Sessions expire after **30 days**. The cookie is `bincio_session`, HTTP-only, `SameSite=Lax`, domain `.bincio.org` (shared across all subdomains). + +### `invites` + +```sql +CREATE TABLE invites ( + code TEXT PRIMARY KEY, + created_by TEXT NOT NULL REFERENCES users(handle) ON DELETE CASCADE, + used_by TEXT REFERENCES users(handle) ON DELETE SET NULL, + created_at INTEGER NOT NULL, + used_at INTEGER, + grants_activity INTEGER NOT NULL DEFAULT 0, + grants_wiki INTEGER NOT NULL DEFAULT 0 +); +``` + +Wiki invites are created with `grants_wiki = 1, grants_activity = 0`. The `grants_wiki` column was added by a migration in `edit/server.py` (`_migrate_db()` runs at sidecar startup). + +## Session flow + +``` +POST /api/auth/login → bcrypt.checkpw → INSERT sessions → Set-Cookie: bincio_session +GET /api/me → SELECT sessions JOIN users → 200 + user JSON | 401 +POST /api/auth/logout → DELETE sessions → Clear cookie +``` + +Every protected Astro page calls `GET /api/me` on load via inline JavaScript. A 401 or 403 response triggers an immediate `window.location.replace('/login/')`. + +## Security model + +This is **client-side enforcement only**. The static HTML is served publicly by nginx without any HTTP-level authentication. A user without JavaScript, or one who crafts direct HTTP requests, can read the raw HTML. + +This is an intentional, documented tradeoff. The content is community memories (cycling routes, gear notes, trip reports) — not financial or medical data. The goal is keeping search engine crawlers and casual visitors out, not resisting determined attackers. All members of the community are trusted. + +For future pages that require stricter enforcement, the architecture supports adding an Astro SSR mode with server-side cookie validation before any HTML is rendered. This would require migrating from `output: "static"` to `output: "server"`. + +## Invite system + +### Creating an invite + +Any wiki user can generate an invite link from `/invites/`. The sidecar creates a single-use token: + +``` +POST /api/invites +→ { "code": "abc123def456ghi7" } +``` + +The invite URL is `https://wiki.bincio.org/join/?code=`. + +### Registration + +The invitee visits the URL and fills in a handle, optional display name, and password. On submit: + +``` +POST /api/auth/register +{ code, handle, display_name, password } +``` + +The sidecar: + +1. Validates the code exists and `used_by` is null. +2. Checks `grants_wiki = 1`. +3. Validates the handle (lowercase, 2–20 chars, `[a-z][a-z0-9_-]{1,19}`). +4. Validates password length ≥ 8 characters. +5. Checks handle uniqueness. +6. Inserts the user (`wiki_access=1, activity_access=0`) and marks the invite as used — in a single transaction. +7. Creates a session and sets the cookie. The user is immediately logged in. + +### Revoking an invite + +A user can revoke any of their own unused invites via the `/invites/` page. Used invites cannot be revoked. + +### User limits + +The `settings` table stores a `max_wiki_users` value (currently `100`). The `/api/auth/register` endpoint reads this value and rejects registration if the current wiki user count has reached the limit. + +```sql +SELECT value FROM settings WHERE key = 'max_wiki_users'; +-- 100 +``` diff --git a/docs/architecture/content-model.md b/docs/architecture/content-model.md new file mode 100644 index 0000000..d91d40c --- /dev/null +++ b/docs/architecture/content-model.md @@ -0,0 +1,83 @@ +# Content Model + +## Collections + +Astro 6 content collections are defined in `site/src/content.config.ts`. All three load files from **outside** the `site/` submodule — Vite is configured with `server.fs.allow: ['..']` to allow this. + +| Collection | Source directory | URL pattern | +|------------|-----------------|-------------| +| `entries` | `pages/*.md` | `/entries/{slug}/` | +| `blog` | `blog/*.md` | `/blog/{slug}/` | +| `index` | `config/*.md` | internal only (wikibonsai tree) | + +### `entries` — wiki pages + +Plain Markdown with minimal frontmatter: + +```markdown +--- +title: Nome della pagina +--- + +Contenuto in markdown... +``` + +Slugs are derived from the filename minus extension. Subdirectory slugs are supported: `pages/bincio-tech/gps-e-ciclocomputer.md` → slug `bincio-tech/gps-e-ciclocomputer` → URL `/entries/bincio-tech/gps-e-ciclocomputer/`. + +A `generateId` override in `content.config.ts` strips the `.md` extension and preserves slashes, so Astro does not apply any githubSlug mangling. + +### `blog` — stories and posts + +Blog frontmatter is richer: + +```markdown +--- +title: Titolo del post +description: Breve descrizione +pubDate: 2024-05-01 +heroImage: /assets/foto.jpg # optional +--- +``` + +### `index` — wikibonsai semantic tree + +`config/i.bonsai.md` defines the hierarchical semantic tree used by the wikibonsai integration. It is not rendered as a page; Astro reads it to resolve `[[WikiLink]]` relationships and generate forward/back reference data. + +## Sections + +`config/sections.json` defines the top-level wiki sections (e.g. BincioTech, BincioOfficina) and their subsections. This file is imported by: + +- The Astro pages/entries index to group and display pages +- The `PageEditor.svelte` component to populate the section picker when creating a new page + +Current sections: `bincio-tech`, `bincio-officina`, `bincio-tour`, `bincio-corsa`, `bincio-abbigliamento`. + +## WikiLinks and wikibonsai + +Pages can reference each other with `[[WikiLink]]` syntax. The remark pipeline resolves these at build time: + +1. `remark-caml` parses CAML attribute syntax in frontmatter. +2. `remark-wikirefs` resolves `[[target]]` to `/entries/{slug}/` links using `resolveHtmlHref` and `resolveHtmlText` from `site/src/wikibonsai/wikirefs.ts`. +3. `generateForeRefsRemarkPlugin` collects all forward references so they can be stored in the content store. +4. Back-references (pages that link to the current page) are computed at build time in `site/src/wikibonsai/backrefs.ts` and rendered by the `BackRefs.astro` component. + +Invalid wikilinks (targets that don't exist) are rendered as dimmed grey text rather than broken links. + +## Assets + +User-uploaded images live in `assets/` at the repo root. This directory is: + +- **Gitignored** — not versioned, not part of any git commit. +- **Served** by FastAPI's `StaticFiles` mount at `/assets/{filename}` (nginx proxies `/assets/` to FastAPI in production). +- **Uploaded** via `POST /api/assets` (multipart, images only, max 10 MB). The editor inserts `![name](/assets/filename)` into the Markdown on upload. + +On the VPS, assets are separate from the git push/pull cycle and must be managed independently if ever migrated. + +## Delete behaviour + +Deleting a page via the editor calls `DELETE /pages/{slug}` or `DELETE /stories/{slug}`. The sidecar: + +1. Unlinks the file. +2. Commits the deletion with `git rm`. + +In Astro dev mode, a `contentDeleteWatcher` Vite plugin watches `pages/` and `blog/` for `unlink` events. When a `.md` file is deleted it clears `.astro/data-store.json` and restarts the Astro dev server, forcing a full re-scan. Without this, Astro's persisted data store would keep serving the deleted entry. diff --git a/docs/architecture/edit-sidecar.md b/docs/architecture/edit-sidecar.md new file mode 100644 index 0000000..d7b7ed2 --- /dev/null +++ b/docs/architecture/edit-sidecar.md @@ -0,0 +1,129 @@ +# Edit Sidecar + +The edit sidecar (`edit/server.py`) is a FastAPI application that handles all write operations. It runs alongside the static Astro build and is the only process that modifies content on disk. + +## API endpoints + +### Auth + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| `GET` | `/api/me` | session | Return current user info | +| `POST` | `/api/auth/login` | — | Create session, set cookie | +| `POST` | `/api/auth/logout` | session | Destroy session, clear cookie | +| `POST` | `/api/auth/register` | — | Register via invite token | + +### Wiki pages + +| Method | Path | Description | +|--------|------|-------------| +| `GET` | `/pages` | List all page slugs | +| `GET` | `/pages/{slug}` | Get page content + `base_hash` | +| `POST` | `/pages/{slug}` | Save page (with 3-way merge) | +| `DELETE` | `/pages/{slug}` | Delete page | + +### Blog / stories + +Same as pages, with `/stories` prefix. + +### Invites + +| Method | Path | Description | +|--------|------|-------------| +| `GET` | `/api/invites` | List own invites | +| `POST` | `/api/invites` | Create a new wiki invite | +| `DELETE` | `/api/invites/{code}` | Revoke an unused invite | + +### Other + +| Method | Path | Description | +|--------|------|-------------| +| `POST` | `/api/assets` | Upload image (multipart) | +| `POST` | `/rebuild` | Trigger Astro rebuild + rsync | +| `GET` | `/api/log` | Last 50 web-editor commits | + +## Git attribution + +Every page save or delete is committed to git with the editor's handle as the author: + +``` +git add pages/bincio-tech/gps-e-ciclocomputer.md +git commit -m "brut: edited gps-e-ciclocomputer" \ + --author="brut " +git update-ref refs/heads/main HEAD +``` + +The committer is always `bincio-wiki `. The author field carries the human attribution. + +`GIT_COMMITTER_NAME` and `GIT_COMMITTER_EMAIL` are set in the environment passed to each subprocess. On the VPS, `GIT_DIR` points to the bare repo at `/opt/bincio-wiki-repo.git` and `GIT_WORK_TREE` to `/opt/bincio_wiki`. + +### The `update-ref` fix + +The VPS post-receive hook runs `git checkout -f ` to check out the new tree. This detaches `HEAD` in the bare repo: subsequent sidecar commits advance the detached HEAD pointer but not `refs/heads/main`. On the next `git push` from a developer's machine, those sidecar commits become unreachable and are eventually garbage-collected. + +The fix: after every `git commit`, the sidecar immediately runs: + +``` +git update-ref refs/heads/main HEAD +``` + +This keeps `refs/heads/main` in sync with the detached HEAD, so sidecar commits are always reachable via the branch and survive a future push. + +### Serialisation + +All git operations are serialised with a module-level `asyncio.Lock()`. This prevents two concurrent saves from interleaving `git add` + `git commit` calls and producing a corrupted index or commit history. + +## Optimistic concurrency + +When a page is opened for editing, the sidecar returns the current HEAD commit hash alongside the content: + +```json +{ "slug": "gps-e-ciclocomputer", "content": "...", "base_hash": "a85a2ee" } +``` + +The editor holds this hash. On save, it sends it back: + +```json +{ "content": "...", "base_hash": "a85a2ee" } +``` + +The sidecar compares `base_hash` against the current HEAD for that file. If they differ, another edit landed while the user was writing. + +## 3-way merge + +Rather than rejecting the save with a 409, the sidecar attempts a 3-way merge using `git merge-file`: + +``` +base = content at base_hash (what the user started with) +current = content at HEAD (what's on disk now) +user = content from request (what the user wrote) +``` + +If the edits touched different lines, `git merge-file` produces a clean merged result and the save proceeds transparently. If the same lines were changed by both, `git merge-file` exits with a non-zero code and embeds conflict markers (`<<<<<<< attuale / ======= / >>>>>>> handle`). The sidecar returns a 409 with the conflict-marked content so the user can resolve manually. + +```python +# exit code 0 → clean merge; > 0 → conflicts present +proc = await asyncio.create_subprocess_exec( + "git", "merge-file", + "-L", "attuale", "-L", "base", "-L", handle, + str(p_current), str(p_base), str(p_user), + ... +) +await proc.wait() +merged_content = p_current.read_text() +has_conflict = proc.returncode > 0 +``` + +## Rebuild + +`POST /rebuild` does three things: + +1. Deletes `.astro/data-store.json` — Astro's persisted content cache. Without this, changes to files in `pages/` and `blog/` (which live outside `site/`) are not picked up by a cached build. +2. Runs `npm run build -- --force` in `site/`. The `--force` flag tells Astro to ignore any remaining stale cache. +3. If `WIKI_WEBROOT` is set, rsyncs `site/dist/` to the webroot so nginx serves the updated build immediately. + +The rebuild is triggered automatically by the `PageEditor.svelte` after every successful save. + +## Slug validation + +Slugs are validated against `^[a-zA-Z0-9_][a-zA-Z0-9_\-/]*$` and checked for path traversal before any file operation. The resolved path must be a child of the configured base directory. diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md new file mode 100644 index 0000000..3facd7f --- /dev/null +++ b/docs/architecture/overview.md @@ -0,0 +1,57 @@ +# System Overview + +## Components + +``` +Browser + │ + │ HTTPS (443) + ▼ +nginx ─── static files ──► /var/www/bincio-wiki/ (Astro build output) + │ + │ proxy_pass (127.0.0.1:4042) + ▼ +FastAPI sidecar (edit/server.py) + ├── reads/writes ──► pages/ (wiki markdown) + ├── reads/writes ──► blog/ (blog markdown) + ├── reads/writes ──► assets/ (uploaded images) + ├── git add + commit ──► /opt/bincio-wiki-repo.git (bare repo) + └── reads/writes ──► /var/bincio/data/instance.db (shared SQLite) +``` + +## Request flow + +1. Browser requests `wiki.bincio.org/entries/some-page/`. +2. nginx serves the pre-built static HTML from `/var/www/bincio-wiki/`. +3. The page's inline JavaScript calls `GET /api/me` on load. nginx proxies this to FastAPI. +4. If FastAPI returns 401/403 (no valid session), JS redirects to `/login/`. +5. On a page edit, the browser `POST /pages/{slug}` with new Markdown content. FastAPI writes the file, runs `git commit`, then `POST /rebuild` triggers `astro build --force`. +6. nginx serves the rebuilt static output immediately on next request. + +## Technology stack + +| Layer | Technology | +|-------|------------| +| Frontend framework | [Astro 6](https://astro.build) (static output) | +| CSS | Tailwind CSS | +| Interactive editor | Svelte (PageEditor component) | +| Markdown extensions | `remark-wikirefs`, `remark-caml` | +| Wikibonsai integration | Custom TypeScript (`site/src/wikibonsai/`) | +| API / sidecar | FastAPI + uvicorn | +| Python packaging | uv | +| Database | SQLite (shared with `bincio_activity`) | +| Passwords | bcrypt | +| VCS | Git (two bare repos on VPS) | +| Web server | nginx + Let's Encrypt (Certbot) | +| Process manager | systemd | +| VPS | Hetzner, Debian 12 | + +## Port map + +| Port | Service | +|------|---------| +| 443 | nginx (HTTPS, wiki.bincio.org) | +| 4042 | bincio-wiki FastAPI (production) | +| 8001 | bincio-wiki FastAPI (local dev) | +| 4321 | Astro dev server (local dev only) | +| 4041 | bincio_activity FastAPI | diff --git a/docs/content-location.md b/docs/content-location.md deleted file mode 100644 index 3eedc09..0000000 --- a/docs/content-location.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -title: Content location and structure ---- - -# Content location and structure - -Two open design questions about where wiki content lives and how it can be -organised on disk. - ---- - -## Q1 — Content in `site/` vs `bincio_wiki/` - -### The tension - -Ideally wiki content (the `.md` files the community edits) would live in -`bincio_wiki/`, the container repo. The Astro app is a reusable engine; -content is wiki-specific. Mixing them means content commits go into the -`astro-bloomz` submodule history, and updating the engine requires care not -to disturb content. - -Currently content is at `site/src/content/entries/` (inside the submodule). - -### Why symlinks failed - -Symlinking `bincio_wiki/pages/` → `site/src/content/entries/` was tried but -abandoned: macOS's fsevents (used by Vite's file watcher) does not follow -symlinks reliably, causing Astro's hot-reload and content layer to miss -changes or crash. - -### Options - -**Option A — Astro glob `base` pointing outside `site/`** - -Change `content.config.ts` to use a relative or absolute path that escapes -the `site/` directory: - -```ts -loader: glob({ pattern: '**/*.md', base: '../pages' }) -``` - -`../pages` from the Astro project root (`site/`) resolves to -`bincio_wiki/pages/`. No symlinks. Content stays in the container repo. - -Risk: Vite (which Astro uses internally) may refuse to watch files outside -the project root by default. This can be overridden with -`server.watch.ignored` / `server.fs.allow` in `astro.config.mjs`, but needs -testing. Build-time collection (no watch) is likely fine regardless. - -**Option B — Copy/sync at dev time** - -Keep content source at `bincio_wiki/pages/` (source of truth). Add a step to -`dev.sh` that watches `pages/` and syncs changes to -`site/src/content/entries/` (gitignored there). Build step copies before -`astro build`. - -Edit server already reads `WIKI_PAGES_DIR` from the environment, so pointing -it at `bincio_wiki/pages/` is a one-line change. The sync is the only new -piece. - -Downside: a two-directory sync is extra moving parts; a crash or missed sync -during dev means stale content in the browser. - -**Option C — Absorb Astro app into `bincio_wiki`** - -Remove the submodule entirely. Move Astro source into `bincio_wiki/src/`. -Content and engine live together, content at `src/content/entries/`. - -Cleanest at runtime, but loses `astro-bloomz` as a reusable starting point -for other sites. Only worth it if we never intend to fork the engine again. - -### Recommendation - -Try **Option A** first — it is a two-line change and, if Vite's file watcher -cooperates, gives us exactly the right separation with minimal risk. If the -watcher proves problematic in dev, fall back to **Option B**. - ---- - -## Q2 — Subdirectory support in the edit panel - -### Current state - -The backend already handles subdirectory slugs fully: -- `_SAFE_SLUG` regex allows `/` in slugs -- Routes use `{slug:path}` so FastAPI preserves slashes -- `_list()` uses `rglob("*.md")` — nested files are already listed -- `_save()` calls `path.parent.mkdir(parents=True, exist_ok=True)` - -The frontend edit panel, however, only offers flat slug input. Work needed: -- Allow the "new page" field to accept a slug with `/` (e.g. `archivio/2024/festa`) -- Show nested entries grouped by prefix in the page list -- Possibly add a folder picker or breadcrumb - -### Pictures - -Not yet addressed. Open questions: -- Where are images stored? (options: `site/public/img/`, alongside content, - or a dedicated `bincio_wiki/assets/` tree) -- How does the edit panel upload/reference them? -- How are they referenced from markdown (`/img/foo.jpg` vs relative `./foo.jpg`)? -- Are images versioned in git or stored out-of-band? diff --git a/docs/decisions/content-location.md b/docs/decisions/content-location.md new file mode 100644 index 0000000..ebd4977 --- /dev/null +++ b/docs/decisions/content-location.md @@ -0,0 +1,42 @@ +# Content Location (resolved) + +!!! success "Resolved" + Option A was implemented. Content lives in `pages/` and `blog/` at the container repo root. Astro loads them via glob loaders with `base: '../pages'` and `base: '../blog'`. Vite is configured with `server.fs.allow: ['..']`. + +--- + +This document records the design question that was open during early development and the options considered. + +## The question + +Where should wiki content (the `.md` files the community edits) live, and how should Astro load them? + +The constraint: Astro's rendering engine lives in the `site/` submodule (`brutsalvadi/astro-bloomz`). Content is wiki-specific and should not be committed to the engine's history. + +## Why symlinks failed + +Symlinking `bincio_wiki/pages/` → `site/src/content/entries/` was tried and abandoned. macOS's `fsevents` (used by Vite's file watcher) does not follow symlinks reliably, causing Astro's hot-reload and content layer to miss changes or crash in dev mode. + +## Options considered + +**Option A — Astro glob `base` pointing outside `site/`** ✓ *chosen* + +```ts +loader: glob({ pattern: '**/*.md', base: '../pages' }) +``` + +`../pages` relative to `site/` resolves to `bincio_wiki/pages/`. No symlinks. Content stays in the container repo. Vite's `server.fs.allow: ['..']` is required to permit serving files outside the project root in dev. + +**Option B — Copy/sync at dev time** + +Keep source at `bincio_wiki/pages/`. Add a watcher step to `dev.sh` that syncs changes to `site/src/content/entries/` (gitignored there). Edit server already uses `WIKI_PAGES_DIR`. + +Downside: two-directory sync is extra moving parts; a crash means stale content. + +**Option C — Absorb Astro into `bincio_wiki`** + +Remove the submodule. Move Astro source into `bincio_wiki/src/`. Cleanest at runtime but loses the reusable engine submodule. + +## Outcome + +Option A works correctly. Both Vite's file watcher and Astro's content layer see changes to `pages/` and `blog/` in dev. The `contentDeleteWatcher` plugin handles the edge case of deleted files (which require clearing the data store to prevent stale entries). diff --git a/docs/lingua.md b/docs/decisions/lingua.md similarity index 100% rename from docs/lingua.md rename to docs/decisions/lingua.md diff --git a/docs/development/getting-started.md b/docs/development/getting-started.md new file mode 100644 index 0000000..2d1e4b3 --- /dev/null +++ b/docs/development/getting-started.md @@ -0,0 +1,88 @@ +# Getting Started + +## Prerequisites + +- **Node.js** ≥ 18 and **npm** +- **Python** ≥ 3.11 +- **uv** — Python package manager (`curl -LsSf https://astro.sh/uv/install.sh | sh` or `pip install uv`) +- A running instance of **bincio_activity** with a seeded dev database, or a manually created `instance.db` + +## Shared database + +The wiki shares its user/session/invite database with `bincio_activity`. For local development, the default path is `/tmp/bincio_dev_test/instance.db`. + +The easiest way to seed it is to run `bincio_activity`'s dev setup script: + +```bash +cd ../bincio_activity +uv run python scripts/dev_test.py --fresh +``` + +Alternatively, set `SHARED_DB_PATH` to point to an existing `instance.db`: + +```bash +export SHARED_DB_PATH=/path/to/instance.db +``` + +## Running locally + +```bash +# Clone with submodule +git clone --recurse-submodules +cd bincio_wiki + +# Astro dev server only (port 4321) +bash scripts/dev.sh + +# Astro + FastAPI sidecar (ports 4321 + 8001) +bash scripts/dev.sh --edit +``` + +`uv sync` is called automatically by `dev.sh --edit` — no manual Python setup needed. + +The Astro dev server proxies all `/api/`, `/pages/`, `/stories/`, `/assets/`, and `/rebuild` requests to `http://localhost:8001`. + +## Adding Python dependencies + +```bash +uv add # updates pyproject.toml and uv.lock +``` + +## Adding JS dependencies + +```bash +cd site +npm install +``` + +## Submodule notes + +The `site/` directory is a git submodule pointing to `brutsalvadi/astro-bloomz`. To update it: + +```bash +cd site +git pull origin main +cd .. +git add site +git commit -m "Update site submodule" +``` + +To push submodule changes to the VPS independently: + +```bash +cd site && git push vps main +``` + +## Environment variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `SHARED_DB_PATH` | `/tmp/bincio_dev_test/instance.db` | Path to shared SQLite DB | +| `WIKI_PAGES_DIR` | `pages` | Pages directory (relative to repo root) | +| `WIKI_STORIES_DIR` | `blog` | Blog directory | +| `WIKI_ASSETS_DIR` | `assets` | Assets directory | +| `WIKI_WEBROOT` | _(unset)_ | If set, rsync `dist/` here after rebuild | +| `SESSION_DOMAIN` | _(unset)_ | Cookie domain (e.g. `.bincio.org` in prod) | +| `GIT_DIR` | _(unset)_ | Bare repo path (VPS only) | + +In production all of these are set in the systemd service file (`deploy/vps/bincio-wiki.service`). diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..db39adb --- /dev/null +++ b/docs/index.md @@ -0,0 +1,38 @@ +# BincioWiki — Developer Documentation + +BincioWiki is a private, invite-only wiki for the Bincio group of friends. It is built around a small set of deliberate constraints: + +- **Git-native content.** Every page edit is a git commit with author attribution. The wiki's history is the edit history. +- **No CMS complexity.** Content is plain Markdown files on disk. No database for content, no object storage, no CDN. +- **Shared identity.** Authentication is shared with `bincio_activity` via a single SQLite database. One account, two apps, one session cookie. +- **Client-side auth enforcement.** The HTML is publicly served by nginx; JavaScript redirects unauthenticated users to `/login/`. This is an intentional tradeoff — the content is community memories, not financial data, and the goal is keeping crawlers and casual visitors out, not resisting determined attackers. +- **Self-hosted on a cheap VPS.** Everything runs on a single Hetzner Debian 12 VPS. + +## Repository structure + +``` +bincio_wiki/ + pages/ wiki content (*.md — edited by community) + blog/ blog/stories content (*.md) + config/ bincio-specific config (outside the submodule) + sections.json wiki section + subsection definitions + i.bonsai.md wikibonsai semantic tree + assets/ user-uploaded images (gitignored, rsync'd separately) + site/ Astro 6 app (git submodule → brutsalvadi/astro-bloomz) + edit/ FastAPI edit sidecar (Python, port 4042 prod / 8001 dev) + docs/ this documentation + scripts/ dev.sh, sync-vps.sh + deploy/ VPS config files (nginx, systemd, post-receive hook) + pyproject.toml Python dependencies (managed by uv) +``` + +## Two-repo model + +The project uses two git repositories: + +| Repo | Purpose | +|------|---------| +| `bincio_wiki` (container) | Content (`pages/`, `blog/`), sidecar, config, scripts | +| `brutsalvadi/astro-bloomz` (submodule at `site/`) | Astro rendering engine | + +This separation keeps content history out of the engine submodule and allows the engine to be reused or forked independently. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..a4429f1 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,39 @@ +site_name: BincioWiki — Developer Docs +site_description: Technology stack, architecture, and operational notes for bincio_wiki +site_url: "" +docs_dir: docs + +nav: + - Overview: index.md + - Architecture: + - System overview: architecture/overview.md + - Content model: architecture/content-model.md + - Edit sidecar: architecture/edit-sidecar.md + - Authentication & invites: architecture/auth.md + - Deployment: + - VPS setup: deployment/vps.md + - Daily operations: deployment/operations.md + - Development: + - Getting started: development/getting-started.md + - Design decisions: + - Language / i18n: decisions/lingua.md + - Content location: decisions/content-location.md + +theme: + name: material + palette: + scheme: slate + primary: blue + features: + - navigation.sections + - navigation.expand + - content.code.copy + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.highlight: + anchor_linenums: true + - toc: + permalink: true