Skip to main content

max / multithreaded

11.2 KB · 229 lines History Blame Raw
1 # Multithreaded Architecture
2
3 Forum-first community software integrated with Makenot.work. Users authenticate via MNW OAuth (PKCE flow). Each forum community maps to an MNW project.
4
5 ## High-Level Overview
6
7 ```
8 ┌──────────────────────────────────────────────┐
9 │ Browser (Askama HTML + HTMX) │
10 └──────────────────┬───────────────────────────┘
11 │ HTTP
12 ┌──────────────────▼───────────────────────────┐
13 │ Axum HTTP Server │
14 │ │
15 │ ┌─────────┐ ┌────────┐ ┌──────┐ ┌────────┐ │
16 │ │ routes │ │ auth │ │ csrf │ │ seed │ │
17 │ └────┬────┘ └────┬───┘ └──────┘ └────────┘ │
18 │ │ │ │
19 │ ┌────▼───────────▼──────────────────────┐ │
20 │ │ mt-db (PostgreSQL queries/mutations) │ │
21 │ │ └── mt-core (domain models) │ │
22 │ └───────────────────────────────────────┘ │
23 └───────────────────────────────────────────────┘
24 │ │
25 ┌────────▼─────────┐ ┌─────────▼────────────┐
26 │ PostgreSQL │ │ MNW API │
27 │ (forum data) │ │ (OAuth, userinfo, │
28 │ │ │ project directory) │
29 └──────────────────┘ └──────────────────────┘
30 ```
31
32 ## Workspace Structure
33
34 ```
35 multithreaded/
36 ├── Cargo.toml # Workspace (v0.1.1, Rust 2024)
37 ├── src/
38 │ ├── main.rs # Entry point, server setup, migrations
39 │ ├── lib.rs # Module exports, AppState
40 │ ├── routes.rs # 30+ route handlers
41 │ ├── auth.rs # MNW OAuth callback, userinfo fetch
42 │ ├── csrf.rs # Token generation, constant-time comparison
43 │ ├── config.rs # MNW_BASE_URL, OAUTH_CLIENT_ID from env
44 │ ├── markdown.rs # pulldown-cmark with HTML stripping
45 │ ├── seed.rs # Idempotent demo data seeding
46 │ └── templates/ # Askama view models
47 ├── crates/
48 │ ├── mt-core/ # Domain models, error types
49 │ └── mt-db/ # PostgreSQL queries and mutations
50 ├── templates/ # HTML templates (Askama)
51 ├── static/ # CSS, fonts, htmx.min.js
52 ├── migrations/ # 10 PostgreSQL migrations
53 ├── tests/ # Integration tests
54 └── deploy/ # deploy.sh, systemd unit, env template
55 ```
56
57 ## Crate Dependencies
58
59 ```
60 multithreaded (src/)
61 ├── mt-db
62 │ └── mt-core
63 └── mt-core (for models in templates/routes)
64 ```
65
66 ## Domain Models (`mt-core`)
67
68 | Model | Fields |
69 |-------|--------|
70 | `User` | mnw_account_id, username, display_name, avatar_url |
71 | `Community` | id, name, slug, description, created_at |
72 | `Category` | id, community_id, name, slug, description, sort_order |
73 | `Thread` | id, category_id, author_id, title, pinned, locked, timestamps |
74 | `Post` | id, thread_id, author_id, body, edited_at, deleted_at |
75 | `Membership` | user_id, community_id, role (owner/moderator/member) |
76 | `CommunityBan` | user_id, community_id, reason, expires_at, is_mute |
77 | `ModLogEntry` | community_id, actor_id, action, target, details, timestamp |
78
79 ## Database (PostgreSQL)
80
81 10 migrations:
82
83 | Migration | Purpose |
84 |-----------|---------|
85 | 001 | Users table (MNW account references) |
86 | 002 | Communities |
87 | 003 | Categories (with sort_order) |
88 | 004 | Threads (title, pinned, locked, last_activity_at) |
89 | 005 | Posts (body, soft delete support) |
90 | 006 | Memberships (role enum: owner, moderator, member) |
91 | 007 | Soft delete columns on threads and posts |
92 | 008 | Community bans (with expiry, mute flag) |
93 | 009 | Mod log (action audit trail) |
94 | 010 | Platform-level suspensions (communities + users) |
95
96 ## Routes
97
98 ### Forum (public)
99 | Method | Path | Purpose |
100 |--------|------|---------|
101 | GET | `/` | Forum directory (fetches projects from MNW API) |
102 | GET | `/p/{slug}` | Project forum (categories + recent threads) |
103 | GET | `/p/{slug}/members` | Community member list with roles |
104 | GET | `/p/{slug}/{category}` | Category (threads paginated, 25/page) |
105 | GET | `/p/{slug}/{category}/new` | Create thread form |
106 | POST | `/p/{slug}/{category}/new` | Create thread |
107 | GET | `/p/{slug}/{category}/{thread_id}` | Thread (posts paginated, 50/page) |
108 | POST | `/p/{slug}/{category}/{thread_id}/reply` | Reply to thread |
109
110 ### Thread/Post Management (authenticated)
111 | Method | Path | Purpose |
112 |--------|------|---------|
113 | GET/POST | `/p/{slug}/{category}/{thread_id}/edit` | Edit thread |
114 | POST | `/p/{slug}/{category}/{thread_id}/delete` | Soft delete thread |
115 | POST | `/p/{slug}/{category}/{thread_id}/pin` | Pin/unpin thread |
116 | POST | `/p/{slug}/{category}/{thread_id}/lock` | Lock/unlock thread |
117 | GET/POST | `.../posts/{post_id}/edit` | Edit post (15-min window) |
118 | POST | `.../posts/{post_id}/delete` | Soft delete post |
119
120 ### Community Settings (owner only)
121 | Method | Path | Purpose |
122 |--------|------|---------|
123 | GET/POST | `/p/{slug}/settings` | Community name/description |
124 | POST | `/p/{slug}/settings/categories/new` | Create category |
125 | GET/POST | `/p/{slug}/settings/categories/{id}/edit` | Edit category |
126 | POST | `/p/{slug}/settings/categories/{id}/move` | Reorder category |
127
128 ### Moderation (moderator+)
129 | Method | Path | Purpose |
130 |--------|------|---------|
131 | GET | `/p/{slug}/moderation` | Moderation dashboard |
132 | POST | `/p/{slug}/moderation/ban` | Ban user (with expiry) |
133 | POST | `/p/{slug}/moderation/unban` | Unban user |
134 | POST | `/p/{slug}/moderation/mute` | Mute user (write-only restriction) |
135 | POST | `/p/{slug}/moderation/unmute` | Unmute user |
136 | GET | `/p/{slug}/moderation/log` | Mod log (paginated) |
137
138 ### Platform Admin (PLATFORM_ADMIN_ID only)
139 | Method | Path | Purpose |
140 |--------|------|---------|
141 | GET | `/_admin` | Platform admin dashboard |
142 | POST | `/_admin/communities/{id}/suspend` | Suspend community |
143 | POST | `/_admin/communities/{id}/unsuspend` | Unsuspend community |
144 | POST | `/_admin/users/{id}/suspend` | Suspend user |
145 | POST | `/_admin/users/{id}/unsuspend` | Unsuspend user |
146
147 ### Auth & System
148 | Method | Path | Purpose |
149 |--------|------|---------|
150 | GET | `/auth/login` | Initiate MNW OAuth (PKCE) |
151 | GET | `/auth/callback` | OAuth callback (exchange code, set session) |
152 | GET | `/auth/logout` | Clear session |
153 | GET | `/api/health` | Health check (monitored by PoM) |
154
155 ## Security
156
157 - **CSRF**: SHA256 tokens on all POST/PUT/DELETE, constant-time comparison, middleware layer
158 - **Sessions**: tower-sessions with PostgresStore, 7-day expiry, SameSite::Lax
159 - **XSS**: Markdown rendered via pulldown-cmark with HTML tags stripped
160 - **Auth**: MNW OAuth PKCE — no passwords stored locally
161 - **Moderation**: Role hierarchy enforced on all read/write handlers
162 - **Suspensions**: Platform-level suspension checks on all handlers
163
164 ## Deployment
165
166 Deployed to two targets. Both run on port 3400, use systemd, and share the same service unit (`deploy/multithreaded.service`). Public domain: `forums.makenot.work` (Cloudflare-proxied, points to hetzner).
167
168 ### Hetzner (production)
169
170 - **Host**: `alpha-west-1` (Tailscale `100.120.174.96`, public `5.78.144.244`)
171 - **SSH**: `root@100.120.174.96` (via Tailscale)
172 - **Install path**: `/opt/multithreaded/`
173 - **Build**: cross-compiled on macOS via `cargo zigbuild --release --target x86_64-unknown-linux-gnu`
174 - **Deploy script**: `deploy/deploy-hetzner.sh` (build, upload binary + static + migrations, restart)
175 - `--setup` -- first-time: create system user, dirs, database, build, install, seed
176 - `--config` -- upload systemd unit, static assets, migrations only
177 - **Env file**: `deploy/env.hetzner` -> `/opt/multithreaded/.env`
178 - **Reverse proxy**: Caddy (TLS termination via Cloudflare Origin CA, `forums.makenot.work`)
179 - **Bind address**: `127.0.0.1:3400` (Caddy fronts it; no direct external access)
180 - **OAuth**: `client_id=mt-forums-6378957b452bbbc906c3db8edd072d64`, redirect to `https://forums.makenot.work/auth/callback`, `MNW_BASE_URL=https://makenot.work`
181
182 ### Astra (staging/dev)
183
184 - **Host**: `astra` (Tailscale `100.106.221.39`)
185 - **SSH**: `max@100.106.221.39` (via Tailscale)
186 - **Install path**: `/opt/multithreaded/`
187 - **Build**: native build on astra (aarch64). Source rsynced to `~/src/multithreaded/`, built with `cargo build --release`, binary copied to `/opt/multithreaded/`.
188 - **Deploy script**: `deploy/deploy.sh` (rsync source, build remote, deploy files, restart)
189 - `--setup` -- first-time: create system user, dirs, database, build, install, seed
190 - **Env file**: `deploy/env.production` -> `/opt/multithreaded/.env`
191 - **No reverse proxy**: direct access on port 3400 via Tailscale IP
192 - **Bind address**: `0.0.0.0:3400`
193 - **OAuth**: `MNW_BASE_URL=http://127.0.0.1:3000` (local MNW instance), redirect to `http://100.106.221.39:3400/auth/callback`
194
195 ### Shared Details
196
197 - **systemd unit**: `deploy/multithreaded.service`
198 - Runs as `multithreaded` system user
199 - `EnvironmentFile=/opt/multithreaded/.env`
200 - Security hardening: `NoNewPrivileges`, `ProtectSystem=strict`, `ProtectHome`, `PrivateTmp`, `MemoryMax=512M`
201 - Depends on `postgresql.service`
202 - **Migrations**: auto-applied on boot (`sqlx::migrate!()`)
203 - **Seeding**: `./multithreaded --seed` (idempotent, run once after first deploy)
204 - **reqwest TLS**: uses `rustls-tls` feature (not `native-tls`). Required for cross-compilation to x86_64 Linux from macOS -- native-tls depends on OpenSSL which complicates cross builds.
205 - **Prerequisites for hetzner cross-compile**: `brew install zig`, `cargo install cargo-zigbuild`, `rustup target add x86_64-unknown-linux-gnu`
206
207 ### Monitoring
208
209 - PoM monitors `forums.makenot.work` -- health check on `/api/health`, TLS validation, route probes, DNS verification
210 - PoM targets: MNW, MT (`forums.makenot.work`), htpy.app
211
212 ### Deploy Files
213
214 ```
215 deploy/
216 ├── deploy.sh # Astra deploy (rsync + native build)
217 ├── deploy-hetzner.sh # Hetzner deploy (cross-compile + upload)
218 ├── multithreaded.service # systemd unit (shared)
219 ├── env.production # Env vars for astra
220 └── env.hetzner # Env vars for hetzner
221 ```
222
223 ## Testing
224
225 90 tests total:
226 - 56 integration tests (CSRF, auth, CRUD, permissions, moderation, bans, admin)
227 - 18 unit tests in mt-core (error types, helpers)
228 - 16 unit tests in mt-db (query builders, model conversions)
229