Skip to main content

max / makenotwork

18.6 KB · 366 lines History Blame Raw
1 # Makenotwork -- Architecture
2
3 Creator platform with 0% platform fee (only Stripe's ~3% processing fee). Rust/Axum monolith, server-rendered HTML, PostgreSQL, deployed natively on Hetzner VPS behind Caddy.
4
5 ## System Overview
6
7 | Layer | Technology |
8 |-------|------------|
9 | Language | Rust (2024 edition) |
10 | Web framework | Axum |
11 | Database | PostgreSQL (sqlx, compile-time checked queries) |
12 | Templates | Askama (server-rendered HTML) |
13 | Interactivity | HTMX (no JS framework) |
14 | Payments | Stripe Connect |
15 | Email | Postmark (transactional + broadcast) |
16 | Storage | Hetzner Object Storage (S3-compatible) |
17 | Reverse proxy | Caddy (TLS, static files, custom domains) |
18 | CDN / DDoS | Cloudflare (Full Strict SSL, mTLS) |
19 | Hosting | Hetzner VPS (systemd, no Docker) |
20 | Banking | Mercury (business checking) |
21 | Auth | Argon2id passwords, tower-sessions (PostgreSQL-backed), TOTP, WebAuthn passkeys |
22
23 Single binary, single process. Migrations auto-run on boot. Background tasks (health monitor, scheduler) run as spawned Tokio tasks within the same process.
24
25 For capacity planning and the upgrade path from current single-VM topology through 100k creators (including cost projections), see `scaling.md`.
26
27 ## Code Structure
28
29 ```
30 MNW/server/src/
31 main.rs Entry point (tracing, pool, session store, app state, graceful shutdown)
32 lib.rs Library root (AppState, build_app, route composition)
33 config.rs Configuration from environment variables
34 constants.rs Centralized magic numbers (limits, timeouts, rate limits)
35 error.rs AppError enum → HTTP status + HTML/JSON response
36 auth.rs Argon2 hashing, session management, extractors (AuthUser, MaybeUser, AdminUser)
37 csrf.rs CSRF middleware (token generation + validation)
38 validation/ Input validation helpers (directory module)
39 helpers.rs Shared utility functions, rate limiter constructors
40 payments/ Stripe Connect client wrapper (directory module)
41 storage.rs S3 storage backend (trait + implementation, presigned URLs)
42 synckit_auth.rs SyncKit JWT token creation + extraction
43 build_runner.rs SSH-based remote build execution
44 git/ Git source browser logic (git2, syntax highlighting, directory module)
45 monitor.rs Background health monitor (DB, S3, sessions)
46 scheduler.rs Background scheduler (scheduled publish, mailing list delivery)
47 mt_client.rs HTTP client for Multithreaded internal API
48 rss.rs RSS/Atom feed generation
49 wordlist.rs Wordlist for human-readable invite codes
50 pricing.rs Access control and pricing model trait
51 db/ Database queries (module per domain)
52 routes/ HTTP handlers (module per domain)
53 templates/ Askama template structs (public, dashboard, partials)
54 types/ Shared request/response types
55 email/ Email composition (templates) + HMAC-signed URL tokens
56 scanning/ 6-layer file scanning pipeline
57 ```
58
59 ## Route Organization
60
61 Routes are composed in `lib.rs::build_app()` from 13 route modules:
62
63 | Module | Prefix | Purpose |
64 |--------|--------|---------|
65 | `pages::public` | `/`, `/u/`, `/p/`, `/i/`, `/discover`, `/docs`, `/pricing` | Public-facing HTML pages |
66 | `pages::dashboard` | `/dashboard/` | Creator/user dashboard (HTMX tabs) |
67 | `pages::blog` | `/p/{slug}/blog/` | Blog pages |
68 | `pages::feeds` | `/feed/` | RSS/Atom feeds |
69 | `pages::email_actions` | `/verify/`, `/reset/`, `/confirm-delete/` | Email link handlers |
70 | `api` | `/api/` | JSON API (CRUD, exports, license keys, domains) |
71 | `auth` | `/login`, `/join`, `/logout`, `/forgot-password` | Authentication pages + handlers |
72 | `admin` | `/_admin/` | Admin panels (waitlist, users, uploads, appeals, reports) |
73 | `storage` | `/upload/`, `/download/` | File upload (multipart) + download (presigned S3) |
74 | `stripe` | `/stripe/` | Stripe Connect OAuth + webhooks |
75 | `synckit` | `/synckit/` | SyncKit sync API (auth, push/pull, devices, keys, blobs) |
76 | `oauth` | `/oauth/` | OAuth 2.0 provider (authorize, token, userinfo) |
77 | `ota` | `/ota/` | OTA update server (Tauri-compatible protocol) |
78 | `builds` | `/builds/` | CI/build pipeline (configs, trigger, status, logs) |
79 | `git` | `/g/` | Git source browser (tree, blob, commits, blame, diff) |
80 | `git_issues` | `/g/{owner}/{repo}/issues/` | Git issue tracker |
81 | `postmark` | `/webhooks/postmark/` | Postmark inbound email + bounce/delivery webhooks |
82 | `custom_domain` | (fallback) | Custom domain resolution (profile/project/item by Host header) |
83
84 ### API Route Tiers
85
86 The `/api/` routes are split into tiers with independent rate limits (tower-governor):
87
88 | Tier | Rate limit | Routes |
89 |------|------------|--------|
90 | Write | burst 10, 2/sec per IP | All POST/PUT/DELETE (CRUD, blog, tiers, keys, codes, domains) |
91 | Export | burst 3, 1/sec per IP | `/api/export/*` (projects, sales, purchases, followers, content) |
92 | License keys | burst 20, 5/sec per IP | `/api/keys/*` (validate, deactivate, status) |
93 | Validate | burst 10, 1/sec per IP | `/api/validate/*` (slug uniqueness checks) |
94 | Read | No limit (alpha scale) | All GET endpoints |
95
96 A `json_error_layer` middleware converts HTML error responses to `{"error": "..."}` on API routes.
97
98 ## Database Layer
99
100 PostgreSQL via sqlx with compile-time checked queries. Numbered migrations in `migrations/`, auto-applied on boot; the directory is the source of truth. Connection pool: 25 max connections, 3-second acquire timeout.
101
102 ### DB Modules
103
104 Each `db/` submodule handles a specific domain:
105
106 | Module | Tables / Purpose |
107 |--------|-----------------|
108 | `users` | User accounts, profiles, preferences, email verification |
109 | `auth` | Login attempts, lockout tracking |
110 | `sessions` | Session tracking rows (for remote revocation) |
111 | `totp` | TOTP secrets, backup codes |
112 | `passkeys` | WebAuthn credential storage |
113 | `projects` | Creator projects (software, blog, etc.) |
114 | `items` | Content items (digital goods, text, audio) |
115 | `versions` | Item version history |
116 | `chapters` | Text content chapters |
117 | `blog_posts` | Project blog posts (scheduled publish, web-only flag) |
118 | `transactions` | Purchase/payment records |
119 | `subscriptions` | Subscription tiers + Stripe subscription tracking |
120 | `license_keys` | Software license key generation + validation |
121 | `promo_codes` | Discount and free-access promo codes |
122 | `follows` | Follow relationships (users, projects, tags) |
123 | `discover` | Discovery/browse queries (filtering, sorting, pagination) |
124 | `analytics` | View counts, download counts, revenue aggregation |
125 | `tags` | TagTree-based tagging (path column, per-app config) |
126 | `categories` | Content categories |
127 | `labels` | Creator-defined labels for content organization |
128 | `collections` | User-curated item collections |
129 | `custom_links` | Profile custom links (reorderable) |
130 | `custom_domains` | Custom domain DNS verification + mapping |
131 | `synckit` | SyncKit apps, devices, sync_log, keys, blobs |
132 | `ota` | OTA releases + artifacts |
133 | `builds` | Build configs + build records |
134 | `oauth` | OAuth clients, authorization codes, tokens |
135 | `git_repos` | Git repository metadata + visibility |
136 | `ssh_keys` | User SSH public keys |
137 | `issues` | Git issue tracker (issues, comments, labels) |
138 | `patches` | Email patch handling |
139 | `mailing_lists` | Project mailing lists + subscriber management |
140 | `content_insertions` | Reusable content clips (S3-backed) |
141 | `email_suppressions` | Email unsubscribe tracking |
142 | `scanning` | File scan results + quarantine status |
143 | `health` | Health check history |
144 | `monitor` | Health monitoring snapshots |
145 | `waitlist` | Creator waitlist + invite codes |
146 | `invites` | Creator-to-creator invite system |
147 | `reports` | User content reports |
148 | `fan_plus` | Fan+ subscription tracking |
149 | `creator_tiers` | Creator tier enforcement (storage limits, feature gates) |
150 | `bundles` | Item bundling/packs |
151 | `email_signups` | Early signup tracking |
152
153 ### Type System
154
155 - `define_pg_uuid_id!` macro -- newtype UUID wrappers (`UserId`, `ProjectId`, `ItemId`, etc.)
156 - `impl_str_enum!` macro -- bidirectional enum-string conversion with sqlx Type/Encode/Decode
157 - `validated_types` -- `Username`, `Slug`, `Email` with parse-time validation
158 - `models` -- database row structs (`DbUser`, `DbProject`, `DbItem`, etc.)
159
160 ## Key Integrations
161
162 ### Stripe Connect
163
164 Stripe Connect Standard for creator payouts. Creators connect their Stripe account via OAuth. MNW takes 0% platform fee -- only Stripe's ~3% processing fee applies.
165
166 - Checkout sessions for one-time purchases and subscriptions
167 - Webhook handlers for payment confirmation, subscription lifecycle, disputes
168 - Creator tier subscriptions (Basic $16, Small Files $24, Big Files $36, Everything $60)
169 - Fan+ consumer subscriptions ($8/mo)
170 - Promo codes (percentage/fixed discount, free access)
171
172 ### Postmark
173
174 Transactional email (password reset, email verification, purchase receipts, new-device notifications) and broadcast email (mailing list delivery for content announcements, blog post notifications).
175
176 - HMAC-signed tokens in email action URLs (password reset, verification, account deletion)
177 - Inbound webhook for email patch processing
178 - Bounce/delivery webhooks for suppression tracking
179 - Dev mode: logs emails when `POSTMARK_TOKEN` is not set
180
181 ### SyncKit
182
183 Developer sync infrastructure hosted on MNW. JWT-authenticated API for client apps (GO, BB, AF).
184
185 - Push/pull changelog sync with E2E encryption (ChaCha20-Poly1305 + Argon2)
186 - Device management, key envelopes
187 - Blob storage (separate S3 bucket, presigned upload/download)
188 - OTA update server (Tauri-compatible protocol -- releases, artifacts, version checks)
189 - CI/build pipeline (SSH-based remote builds, post-receive hooks, build configs)
190
191 ### DocEngine
192
193 Extracted crate (`shared/docengine/`) for documentation rendering. Loads markdown files from disk at startup, renders to HTML with section hierarchy, navigation, and search index. Serves `/docs/*` routes.
194
195 ### TagTree
196
197 Shared tag standard (`shared/tagtree/`). Hierarchical tags with path column (migration 038). Per-app config: MNW allows 5 levels, 100 max tags. Used for content discovery and filtering.
198
199 ### File Scanning
200
201 6-layer malware scanning pipeline for uploaded files:
202
203 | Layer | Type | Description |
204 |-------|------|-------------|
205 | 1. Content-type | In-process | Magic-byte verification against declared file type |
206 | 2. Structural | In-process | Binary analysis (PE headers in non-download contexts) |
207 | 3. Archive | In-process | ZIP bomb detection (ratio, depth, uncompressed size limits) |
208 | 4. YARA | In-process | Custom rule matching (compiled at startup) |
209 | 5. ClamAV | External | Antivirus scan via Unix socket (optional) |
210 | 6. MalwareBazaar | External | SHA-256 hash lookup against known malware (optional) |
211
212 Fail-closed: scanner errors hold files for admin review rather than allowing through.
213
214 ### Git Source Browser
215
216 `git2`-based source browser reading bare repos from disk. Syntax highlighting via `syntect`. Features: tree/blob browsing, commit history, blame, diff, file log. Issue tracker with labels and comments. SSH clone via `ssh.makenot.work`.
217
218 ### Multithreaded Integration
219
220 HTTP client (`mt_client`) for the Multithreaded forum instance. HMAC-signed internal API for:
221
222 - Auto-provisioning forum communities when projects are created
223 - Linking discussion threads to content items
224 - Mailing list subscriber management
225
226 ## Security Model
227
228 ### Authentication
229
230 - **Passwords:** Argon2id with random salt per hash
231 - **Sessions:** `tower-sessions` with PostgreSQL-backed store, ID regeneration on login (prevents fixation), 7-day expiry on inactivity
232 - **Session cache:** DashMap caches recent session validations (TTL from `constants::SESSION_TOUCH_CACHE_SECS`, currently 5s) to skip per-request DB touch
233 - **2FA:** TOTP (totp-rs, 6-digit, 30-second step, +/-1 skew) + WebAuthn passkeys (webauthn-rs)
234 - **Account lockout:** 5 failed attempts triggers 15-minute lockout
235 - **New-device notifications:** Email alert on login from unrecognized session
236
237 ### Authorization
238
239 - `AuthUser` extractor -- required login, loads session user
240 - `MaybeUser` extractor -- optional login for public pages
241 - `AdminUser` extractor -- admin-only, returns 404 (not 403) to hide admin routes
242 - Resource ownership verified per-request (`ensure_project_owner`, `verify_item_ownership`)
243
244 ### Network Security
245
246 - **Cloudflare:** Full (Strict) SSL, Authenticated Origin Pulls (mTLS), DDoS protection
247 - **Origin CA:** Wildcard cert for `*.makenot.work`, 15-year RSA
248 - **Caddy:** Reverse proxy, on-demand TLS for custom domains, static file serving
249 - **Firewall:** ufw + fail2ban + hardened sshd
250 - **Custom domains:** DNS TXT verification via Cloudflare DoH, Caddy on-demand TLS, DashMap domain cache
251
252 ### Request Security
253
254 - **CSRF:** Middleware-enforced tokens on all mutating requests. HTMX gets headers via `hx-headers` on body element. JS uses `csrfHeaders()`.
255 - **Rate limiting:** tower-governor per-IP rate limits on all write endpoints (see API Route Tiers above)
256 - **Request body limit:** 1 MB default (tower-http `RequestBodyLimitLayer`)
257 - **Anti-enumeration:** 400ms delay on username availability checks
258
259 ### Data Security
260
261 - **SyncKit E2E encryption:** Server stores only encrypted blobs, never plaintext user data
262 - **HMAC-signed email tokens:** Password reset, verification, deletion links with expiry
263 - **File scanning:** 6-layer pipeline, fail-closed on scanner errors
264
265 ## Template System
266
267 Askama templates with HTMX for dynamic interactions. Three layers:
268
269 | Layer | Location | Pattern |
270 |-------|----------|---------|
271 | Full pages | `templates/pages/`, `templates/dashboards/` | Extend `base.html`, define `{% block content %}` |
272 | Tab content | `templates/partials/tabs/` | Standalone HTML fragments, loaded via `hx-get` |
273 | Feedback | `templates/partials/` | Alert, form status, save status fragments |
274
275 Template structs are defined in `src/templates/` (public.rs, dashboard.rs, partials.rs) with a macro (`impl_into_response!`) that implements `IntoResponse` for all template types.
276
277 Dashboard pages use HTMX tabs: each tab button triggers `hx-get` to load content into `#tab-content`. Admin actions return full table partials for HTMX swap.
278
279 ## AppState
280
281 Shared application state (`Arc`-wrapped by Axum):
282
283 | Field | Type | Purpose |
284 |-------|------|---------|
285 | `db` | `PgPool` | PostgreSQL connection pool |
286 | `config` | `Config` | Environment-based configuration |
287 | `s3` | `Option<Arc<dyn StorageBackend>>` | Main file storage (uploads, covers, downloads) |
288 | `synckit_s3` | `Option<Arc<dyn StorageBackend>>` | Separate SyncKit blob storage bucket |
289 | `stripe` | `Option<StripeClient>` | Stripe Connect client |
290 | `email` | `EmailClient` | Postmark email client (logs in dev) |
291 | `docs` | `Arc<DocLoader>` | Documentation pages (loaded at startup) |
292 | `scanner` | `Option<Arc<ScanPipeline>>` | File scanning pipeline (YARA rules compiled at startup) |
293 | `webauthn` | `Arc<Webauthn>` | WebAuthn relying party |
294 | `syntax` | `Option<Arc<SyntaxHighlighter>>` | Git source browser syntax highlighting |
295 | `session_cache` | `Arc<DashMap<UserSessionId, Instant>>` | Recent session validation cache |
296 | `mt_client` | `Option<MtClient>` | Multithreaded internal API client |
297 | `domain_cache` | `Arc<DashMap<String, UserId>>` | Verified custom domain lookup cache |
298
299 Optional fields (`Option`) allow the server to run in degraded mode when external services are not configured (no S3 = no uploads, no Stripe = no payments, etc.).
300
301 ## Background Tasks
302
303 Two spawned Tokio tasks, coordinated via `watch::channel` for graceful shutdown:
304
305 | Task | Interval | Purpose |
306 |------|----------|---------|
307 | Health monitor | 60 seconds | Probes DB, S3, sessions. Records history. Alerts on status transitions. |
308 | Scheduler | 60 seconds | Publishes scheduled items/blog posts. Sends mailing list announcements. |
309
310 ## Concurrency Model
311
312 - **Tokio multi-threaded runtime** drives all I/O
313 - **`Arc<DashMap<>>`** for session cache and domain cache (lock-free concurrent maps)
314 - **Graceful shutdown:** SIGINT/SIGTERM triggers shutdown signal, 10-second drain window for in-flight requests, then force exit
315 - **Connection pool:** sqlx PgPool (25 connections) handles concurrent DB access
316 - **`into_make_service_with_connect_info`** provides peer IP for rate limiting
317
318 ## Testing
319
320 1,028+ tests (486 unit + 542 integration + 28 health + 4 load). Zero failures.
321
322 - **Unit tests:** In-file `#[cfg(test)]` modules, no DB required
323 - **Integration tests:** Each test creates/drops its own PostgreSQL database. Test harness in `tests/harness/`. Workflow tests in `tests/workflows/`.
324 - **Astra:** Dev server with `RUST_TEST_THREADS=8` (96-core machine, but PG overwhelms at full parallelism)
325
326 ## Key Design Decisions
327
328 - **Monolith over microservices:** Single binary, single process. SyncKit, OTA, builds, git browser, and OAuth are all route modules in the same app. Simplifies deployment, testing, and state sharing.
329 - **Server-rendered HTML over SPA:** Askama templates + HTMX. No client-side routing, no JS framework, no build step. Tab content loaded as HTML fragments.
330 - **PostgreSQL sessions over Redis:** `tower-sessions-sqlx-store` keeps sessions in PostgreSQL. One fewer service to operate. Session cache (DashMap) reduces per-request DB load.
331 - **Optional everything:** S3, Stripe, ClamAV, YARA, git browser, MT integration, and custom domains are all `Option` fields. The server starts and serves pages even when external services are unavailable.
332 - **Compile-time SQL:** sqlx checks all queries at compile time against the database schema. No runtime SQL generation.
333 - **Fail-closed scanning:** File uploads held for admin review on scanner errors, never silently allowed through.
334 - **Native deployment:** systemd unit, direct binary on disk. Cross-compiled via `cargo zigbuild`. No Docker.
335
336 ## Key Paths
337
338 | What | Where |
339 |------|-------|
340 | Entry point | `src/main.rs` |
341 | Library root | `src/lib.rs` |
342 | Configuration | `src/config.rs` |
343 | Constants | `src/constants.rs` |
344 | Error types | `src/error.rs` |
345 | Authentication | `src/auth.rs` |
346 | CSRF middleware | `src/csrf.rs` |
347 | Database modules | `src/db/` (65 files) |
348 | Route modules | `src/routes/` (17 submodules) |
349 | Template structs | `src/templates/` |
350 | Email service | `src/email/` |
351 | File scanning | `src/scanning/` |
352 | Stripe integration | `src/payments/` |
353 | S3 storage | `src/storage.rs` |
354 | SyncKit auth | `src/synckit_auth.rs` |
355 | Build runner | `src/build_runner.rs` |
356 | Git browser | `src/git/` |
357 | MT client | `src/mt_client.rs` |
358 | Health monitor | `src/monitor.rs` |
359 | Scheduler | `src/scheduler.rs` |
360 | Shared types | `src/types/` |
361 | Askama templates | `templates/` |
362 | Migrations | `migrations/` (numbered, applied in order) |
363 | Static assets | `static/` |
364 | Integration tests | `tests/` |
365 | Deploy scripts | `deploy/` |
366