diff --git a/.gitignore b/.gitignore index 06c48c3..3101b6d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,8 @@ site/.astro/ # Wiki database (runtime data, not versioned) data/ +# Deployment notes (contain VPS addresses — keep local only) +deployment/ + # OS .DS_Store diff --git a/deployment/plan.md b/deployment/plan.md deleted file mode 100644 index e381552..0000000 --- a/deployment/plan.md +++ /dev/null @@ -1,208 +0,0 @@ -# bincio_wiki — deployment plan - -## Architecture overview - -Three domains, one shared user database: - -``` -bincio.org — auth hub: login, registration, links to the two apps -activity.bincio.org — bincio_activity (moved from bincio.org) -wiki.bincio.org — bincio_wiki (new) - -Shared DB: /var/bincio/data/instance.db - ↑ used by all three, lives with bincio_activity's data -``` - -Login happens at `bincio.org`. The session cookie is set with `domain=.bincio.org` -so it is automatically valid on `activity.bincio.org` and `wiki.bincio.org`. No -per-app login page needed. Each app's FastAPI validates the shared session token. - -After login, the `bincio.org` home page shows the apps the user has access to -(based on their access flags). If not authenticated, the landing page IS the -login form. - ---- - -## User model - -One unified `users` table with two access flags: - -| flag | meaning | cap | -|------------------|-----------------------------------|-----| -| `wiki_access` | can log in to wiki.bincio.org | 100 | -| `activity_access`| can log in to activity.bincio.org | 30 | - -A user can have one or both. Registration is always for wiki first; activity -access is granted separately (invite flag or admin toggle). The caps are -independent: 100 wiki users total, 30 activity users total. - -All existing bincio_activity users get `wiki_access=1, activity_access=1`. -New wiki-only users get `wiki_access=1, activity_access=0`. - ---- - -## Schema changes (bincio_activity DB) - -### New columns on `users` - -```sql -ALTER TABLE users ADD COLUMN wiki_access INTEGER NOT NULL DEFAULT 1; -ALTER TABLE users ADD COLUMN activity_access INTEGER NOT NULL DEFAULT 0; -``` - -Migration for existing users: -```sql -UPDATE users SET wiki_access = 1, activity_access = 1; -``` - -### New column on `invites` - -```sql -ALTER TABLE invites ADD COLUMN grants_activity INTEGER NOT NULL DEFAULT 0; -``` - -The invite creator chooses whether the invite grants activity access, subject -to this rule: **you can only grant access you yourself have.** - -| Inviter type | Can create wiki invite | Can set grants_activity=1 | -|---------------------|------------------------|---------------------------| -| Wiki-only member | Yes (up to 3) | No | -| Activity member | Yes (up to 3) | Yes — their choice | -| Admin | Yes, unlimited | Yes | - -The API enforces this: `POST /api/invites` returns 403 if the caller tries to -set `grants_activity=1` without having `activity_access=1` themselves. The UI -hides the toggle entirely for wiki-only users. - -Caps are enforced at registration time regardless of who issued the invite: if -the wiki is at 100 users or activity is at 30 users, registration fails even -with a valid unused code. - -### Settings table - -```sql -INSERT OR REPLACE INTO settings VALUES ('max_wiki_users', '100'); -INSERT OR REPLACE INTO settings VALUES ('max_activity_users', '30'); --- remove or ignore the old generic 'max_users' key -``` - ---- - -## What needs to be built - -### 1. bincio.org — auth hub (changes to bincio_activity) - -**FastAPI (`bincio_activity`)** - -- `POST /api/auth/login`: after bcrypt check, also verify the user's access flag - for the app they're logging in from (sent as `app` parameter, or inferred from - `Referer`). Actually: login at bincio.org grants a general session; the flag - check happens at `/api/me` on each subdomain. -- `GET /api/me`: add `wiki_access` and `activity_access` to the response. -- `POST /api/invites`: accepts optional `grants_activity: bool` field. -- Session cookie: change `domain` from unset (host-only) to `.bincio.org` so it - propagates to subdomains. **This is the key change.** -- Cap logic: registration checks `max_wiki_users` (total users with `wiki_access=1`) - and optionally `max_activity_users` if the invite has `grants_activity=1`. - -**Astro (`bincio.org` landing page)** - -- The landing page (`/`) becomes: login form if not authenticated, app selector - if authenticated. -- App selector shows links to `activity.bincio.org` and `wiki.bincio.org` based - on the user's access flags returned by `/api/me`. -- **Invite management** moves here from bincio_activity. The `/invites/` page - stays at `bincio.org` (not at either subdomain) so admins can issue both - wiki-only and wiki+activity invites from one place. The invite creation form - gets a toggle: "wiki only" (default) vs "wiki + activity". -- Invite links always point to `bincio.org/register/?code=XXXXXXXX`. After - registration the user gets `wiki_access=1` always, and `activity_access=1` - only if the invite had `grants_activity=1`. -- The existing `/register/`, `/reset-password/` pages stay at bincio.org. -- Remove the activity app content from bincio.org (it moves to the subdomain). - -**bincio_activity moves to `activity.bincio.org`** - -- nginx: add `activity.bincio.org` server block (same webroot and proxy as - current `bincio.org` block). -- bincio.org nginx: strip activity routes (`/u/`, `/activity/`, `/data/`) and - serve only the auth hub static files + proxy `/api/` to port 4041. -- All internal links in bincio_activity site that are root-relative (`/u/dave`, - `/activity/123`) stay as-is since the app now owns its own domain. - -### 2. bincio_wiki auth (`edit/server.py`) - -- **Shared DB**: connect to `/var/bincio/data/instance.db` (configurable via - `SHARED_DB_PATH` env var, defaults to `../bincio_activity/data/instance.db` - locally). -- **`GET /api/me`**: validate session token from `bincio_session` cookie, check - `wiki_access=1`, return `{handle, display_name, is_admin}` or 401. -- **`POST /api/auth/logout`**: delete session from shared DB. -- No `/api/auth/login` in wiki: login happens at `bincio.org`. -- All CRUD endpoints (`/pages`, `/stories`) require a valid session with - `wiki_access=1`. - -### 3. bincio_wiki auth wall (Astro) - -- **`Base.astro`**: add `fetch('/api/me')` on load → on 401, redirect to - `https://bincio.org/login/?next=https://wiki.bincio.org` (or just bincio.org - with no next param, since the app selector handles it). -- No login page in bincio_wiki — login is centralised at bincio.org. -- The `?next=` redirect is optional / nice-to-have for first iteration. - ---- - -## Phase plan - -### Phase 0 — Schema migration (local + VPS) -- Add `wiki_access`, `activity_access` to users; add `grants_activity` to invites. -- Update settings: `max_wiki_users=100`, `max_activity_users=30`. -- Migration script: `deploy/migrate.sql`. - -### Phase 1 — bincio_activity auth changes -- Cookie domain → `.bincio.org`. -- `/api/me` response: include access flags. -- Login: no flag check (session is general), flag check is per-app at `/api/me`. -- Registration: enforce `max_wiki_users` (wiki_access count). - If invite has `grants_activity=1`, also enforce `max_activity_users`. -- Invite creation: add `grants_activity` field. -- On registration: set `wiki_access=1` always, `activity_access=invite.grants_activity`. - -### Phase 2 — bincio_wiki FastAPI auth -- Connect to shared DB. -- Implement `GET /api/me` with `wiki_access` check. -- Implement `POST /api/auth/logout`. -- Add `require_session()` dependency to all CRUD endpoints. - -### Phase 3 — Astro auth wall (bincio_wiki) -- `Base.astro`: `/api/me` check → redirect to `bincio.org` on 401. -- No login page in wiki. - -### Phase 4 — bincio.org landing page -- Update home page: login form (unauthenticated) / app selector (authenticated). -- Invite form: add activity toggle. -- Keep existing register/reset-password pages. - -### Phase 5 — nginx migration -- Add `activity.bincio.org` server block (certbot for the new subdomain). -- Update `bincio.org` block: serve only auth hub, strip activity routes. -- Add `wiki.bincio.org` server block. - -### Phase 6 — Deploy & verify -- Push both apps to VPS. -- Run migration SQL on the live DB. -- Restart services. -- Smoke test: login at bincio.org, verify cookie reaches both subdomains. - ---- - -## Notes - -- **Local dev**: both apps set `SESSION_DOMAIN` env var; if unset, cookie is - host-only (fine for localhost). In production always set `.bincio.org`. -- **bincio_activity data dir**: stays at `/var/bincio/data/`. The wiki just - opens the DB there; it doesn't own it. -- **Wiki content**: lives at `/var/bincio/wiki/` (pages and stories markdown). -- **Admin tools**: `is_admin=1` users can toggle access flags on other users - via an admin endpoint. First iteration: do it directly in sqlite on the VPS - if needed. diff --git a/deployment/vps.md b/deployment/vps.md deleted file mode 100644 index 71cc0dc..0000000 --- a/deployment/vps.md +++ /dev/null @@ -1,277 +0,0 @@ -# bincio_wiki — VPS configuration - -## Server layout - -``` -/opt/bincio/ bincio_activity code (existing) -/opt/bincio_wiki/ bincio_wiki code (new) - -/var/bincio/data/ bincio_activity data + shared DB - instance.db shared user/session/invite database - / per-user activity data - -/var/bincio/wiki/ bincio_wiki content - pages/ wiki markdown pages - stories/ blog markdown stories - -/var/www/bincio/ bincio_activity static build (existing, bincio.org) -/var/www/bincio/wiki/ bincio_wiki static build (wiki.bincio.org) -``` - -Ports: -- `4041` — bincio_activity FastAPI (existing) -- `4042` — bincio_wiki FastAPI (new) - ---- - -## Deploy procedure - -Builds run **locally**. We push the results to the VPS. - -### bincio_wiki deploy script: `deploy/vps/deploy.sh` - -```bash -#!/usr/bin/env bash -set -e -VPS=root@95.216.55.151 -REMOTE_CODE=/opt/bincio_wiki -REMOTE_WEB=/var/www/bincio/wiki - -echo "Building Astro..." -cd "$(dirname "$0")/../.." -cd site && npm ci --silent && npm run build -cd .. - -echo "Pushing code..." -rsync -az --delete \ - --exclude='.git' \ - --exclude='site/node_modules' \ - --exclude='site/.astro' \ - --exclude='site/dist' \ - --exclude='__pycache__' \ - --exclude='*.pyc' \ - . "$VPS:$REMOTE_CODE/" - -echo "Pushing static build..." -rsync -az --delete site/dist/ "$VPS:$REMOTE_WEB/" - -echo "Restarting service..." -ssh "$VPS" systemctl restart bincio-wiki - -echo "Done." -``` - -Run with: `bash deploy/vps/deploy.sh` - ---- - -## Environment variables - -### bincio_wiki FastAPI (`edit/server.py`) - -| Variable | Production value | Local default | -|---|---|---| -| `SHARED_DB_PATH` | `/var/bincio/data/instance.db` | `../bincio_activity/data/instance.db` | -| `WIKI_PAGES_DIR` | `/var/bincio/wiki/pages` | `site/src/content/entries` | -| `WIKI_STORIES_DIR` | `/var/bincio/wiki/stories` | `site/src/content/blog` | -| `SESSION_DOMAIN` | `.bincio.org` | *(unset — host-only cookie)* | - -### bincio_activity FastAPI (`bincio/serve/server.py`) - -| Variable | Production value | Local default | -|---|---|---| -| `SESSION_DOMAIN` | `.bincio.org` | *(unset — host-only cookie)* | - -### bincio_activity Astro build - -| Variable | Production value | Purpose | -|---|---|---| -| `PUBLIC_WIKI_URL` | `https://wiki.bincio.org` | Wiki nav link + login redirect for wiki-only users | -| `PUBLIC_EDIT_ENABLED` | `true` | Enables edit UI in production | - -### bincio_activity → bincio_activity (moved to activity subdomain) - -| Variable | Production value | -|---|---| -| `PUBLIC_WIKI_URL` | `https://wiki.bincio.org` | -| `SESSION_DOMAIN` | `.bincio.org` | - ---- - -## systemd service - -`deploy/vps/bincio-wiki.service` — copy to `/etc/systemd/system/` on the VPS. - -```ini -[Unit] -Description=BincioWiki API -After=network.target - -[Service] -WorkingDirectory=/opt/bincio_wiki -ExecStart=/root/.local/bin/uv run uvicorn edit.server:app \ - --host 127.0.0.1 \ - --port 4042 -Environment=SHARED_DB_PATH=/var/bincio/data/instance.db -Environment=WIKI_PAGES_DIR=/var/bincio/wiki/pages -Environment=WIKI_STORIES_DIR=/var/bincio/wiki/stories -Environment=SESSION_DOMAIN=.bincio.org -Restart=always -RestartSec=5 - -[Install] -WantedBy=multi-user.target -``` - -On the VPS: -```bash -cp /opt/bincio_wiki/deploy/vps/bincio-wiki.service /etc/systemd/system/ -systemctl daemon-reload -systemctl enable bincio-wiki -systemctl start bincio-wiki -``` - ---- - -## nginx - -### wiki.bincio.org — `deploy/vps/nginx-wiki.conf` - -```nginx -server { - server_name wiki.bincio.org; - root /var/www/bincio/wiki; - index index.html; - - location /api/ { - proxy_pass http://127.0.0.1:4042; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - location /pages/ { - proxy_pass http://127.0.0.1:4042; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - location /stories/ { - proxy_pass http://127.0.0.1:4042; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - location /rebuild/ { - proxy_pass http://127.0.0.1:4042; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - - location / { - try_files $uri $uri/ $uri.html =404; - } - - listen 443 ssl; # managed by Certbot - ssl_certificate /etc/letsencrypt/live/wiki.bincio.org/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/wiki.bincio.org/privkey.pem; - include /etc/letsencrypt/options-ssl-nginx.conf; - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; -} - -server { - if ($host = wiki.bincio.org) { - return 301 https://$host$request_uri; - } - listen 80; - server_name wiki.bincio.org; - return 404; -} -``` - -### activity.bincio.org (bincio_activity moves here) - -Add this block to the existing bincio_activity nginx config. The current -`bincio.org` block keeps the `/api/` proxy but loses the activity-specific -routes (see plan.md Phase 5). - -```nginx -server { - server_name activity.bincio.org; - root /var/www/bincio; - index index.html; - - client_max_body_size 2G; - client_body_timeout 300s; - - location /api/ { - proxy_pass http://127.0.0.1:4041; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_read_timeout 120s; - } - location /data/ { - alias /var/bincio/data/; - add_header Cache-Control "no-cache, must-revalidate"; - } - location /activity/ { - try_files $uri $uri/ /activity/index.html; - } - location /u/ { - try_files $uri $uri/ /index.html; - } - location / { - try_files $uri $uri/ $uri.html =404; - } - - listen 443 ssl; # managed by Certbot - ssl_certificate /etc/letsencrypt/live/activity.bincio.org/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/activity.bincio.org/privkey.pem; - include /etc/letsencrypt/options-ssl-nginx.conf; - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; -} -``` - ---- - -## First-time VPS setup (wiki) - -```bash -# 1. Create directories -mkdir -p /var/bincio/wiki/pages /var/bincio/wiki/stories -mkdir -p /var/www/bincio/wiki - -# 2. Push initial deploy -bash deploy/vps/deploy.sh - -# 3. Install and start service -cp /opt/bincio_wiki/deploy/vps/bincio-wiki.service /etc/systemd/system/ -systemctl daemon-reload && systemctl enable --now bincio-wiki - -# 4. SSL certificate for wiki subdomain -certbot --nginx -d wiki.bincio.org - -# 5. Install nginx config -cp /opt/bincio_wiki/deploy/vps/nginx-wiki.conf /etc/nginx/sites-available/bincio-wiki -ln -s /etc/nginx/sites-available/bincio-wiki /etc/nginx/sites-enabled/ -nginx -t && systemctl reload nginx - -# 6. Run DB migration (after schema changes to bincio_activity) -sqlite3 /var/bincio/data/instance.db < /opt/bincio_wiki/deploy/migrate.sql -``` - ---- - -## DB migration script: `deploy/migrate.sql` - -```sql --- Add access flags to users -ALTER TABLE users ADD COLUMN wiki_access INTEGER NOT NULL DEFAULT 1; -ALTER TABLE users ADD COLUMN activity_access INTEGER NOT NULL DEFAULT 0; - --- All existing users (bincio_activity members) get both flags -UPDATE users SET wiki_access = 1, activity_access = 1; - --- Add activity flag to invites -ALTER TABLE invites ADD COLUMN grants_activity INTEGER NOT NULL DEFAULT 0; - --- Set caps -INSERT OR REPLACE INTO settings VALUES ('max_wiki_users', '100'); -INSERT OR REPLACE INTO settings VALUES ('max_activity_users', '30'); -```