# Makenotwork -- Architecture 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. ## System Overview | Layer | Technology | |-------|------------| | Language | Rust (2024 edition) | | Web framework | Axum | | Database | PostgreSQL (sqlx, compile-time checked queries) | | Templates | Askama (server-rendered HTML) | | Interactivity | HTMX (no JS framework) | | Payments | Stripe Connect | | Email | Postmark (transactional + broadcast) | | Storage | Hetzner Object Storage (S3-compatible) | | Reverse proxy | Caddy (TLS, static files, custom domains) | | CDN / DDoS | Cloudflare (Full Strict SSL, mTLS) | | Hosting | Hetzner VPS (systemd, no Docker) | | Banking | Mercury (business checking) | | Auth | Argon2id passwords, tower-sessions (PostgreSQL-backed), TOTP, WebAuthn passkeys | Single binary, single process. Migrations auto-run on boot. Background tasks (health monitor, scheduler) run as spawned Tokio tasks within the same process. For capacity planning and the upgrade path from current single-VM topology through 100k creators (including cost projections), see `scaling.md`. ## Code Structure ``` MNW/server/src/ main.rs Entry point (tracing, pool, session store, app state, graceful shutdown) lib.rs Library root (AppState, build_app, route composition) config.rs Configuration from environment variables constants.rs Centralized magic numbers (limits, timeouts, rate limits) error.rs AppError enum → HTTP status + HTML/JSON response auth.rs Argon2 hashing, session management, extractors (AuthUser, MaybeUser, AdminUser) csrf.rs CSRF middleware (token generation + validation) validation/ Input validation helpers (directory module) helpers.rs Shared utility functions, rate limiter constructors payments/ Stripe Connect client wrapper (directory module) storage.rs S3 storage backend (trait + implementation, presigned URLs) synckit_auth.rs SyncKit JWT token creation + extraction build_runner.rs SSH-based remote build execution git/ Git source browser logic (git2, syntax highlighting, directory module) monitor.rs Background health monitor (DB, S3, sessions) scheduler.rs Background scheduler (scheduled publish, mailing list delivery) mt_client.rs HTTP client for Multithreaded internal API rss.rs RSS/Atom feed generation wordlist.rs Wordlist for human-readable invite codes pricing.rs Access control and pricing model trait db/ Database queries (module per domain) routes/ HTTP handlers (module per domain) templates/ Askama template structs (public, dashboard, partials) types/ Shared request/response types email/ Email composition (templates) + HMAC-signed URL tokens scanning/ 6-layer file scanning pipeline ``` ## Route Organization Routes are composed in `lib.rs::build_app()` from 13 route modules: | Module | Prefix | Purpose | |--------|--------|---------| | `pages::public` | `/`, `/u/`, `/p/`, `/i/`, `/discover`, `/docs`, `/pricing` | Public-facing HTML pages | | `pages::dashboard` | `/dashboard/` | Creator/user dashboard (HTMX tabs) | | `pages::blog` | `/p/{slug}/blog/` | Blog pages | | `pages::feeds` | `/feed/` | RSS/Atom feeds | | `pages::email_actions` | `/verify/`, `/reset/`, `/confirm-delete/` | Email link handlers | | `api` | `/api/` | JSON API (CRUD, exports, license keys, domains) | | `auth` | `/login`, `/join`, `/logout`, `/forgot-password` | Authentication pages + handlers | | `admin` | `/_admin/` | Admin panels (waitlist, users, uploads, appeals, reports) | | `storage` | `/upload/`, `/download/` | File upload (multipart) + download (presigned S3) | | `stripe` | `/stripe/` | Stripe Connect OAuth + webhooks | | `synckit` | `/synckit/` | SyncKit sync API (auth, push/pull, devices, keys, blobs) | | `oauth` | `/oauth/` | OAuth 2.0 provider (authorize, token, userinfo) | | `ota` | `/ota/` | OTA update server (Tauri-compatible protocol) | | `builds` | `/builds/` | CI/build pipeline (configs, trigger, status, logs) | | `git` | `/g/` | Git source browser (tree, blob, commits, blame, diff) | | `git_issues` | `/g/{owner}/{repo}/issues/` | Git issue tracker | | `postmark` | `/webhooks/postmark/` | Postmark inbound email + bounce/delivery webhooks | | `custom_domain` | (fallback) | Custom domain resolution (profile/project/item by Host header) | ### API Route Tiers The `/api/` routes are split into tiers with independent rate limits (tower-governor): | Tier | Rate limit | Routes | |------|------------|--------| | Write | burst 10, 2/sec per IP | All POST/PUT/DELETE (CRUD, blog, tiers, keys, codes, domains) | | Export | burst 3, 1/sec per IP | `/api/export/*` (projects, sales, purchases, followers, content) | | License keys | burst 20, 5/sec per IP | `/api/keys/*` (validate, deactivate, status) | | Validate | burst 10, 1/sec per IP | `/api/validate/*` (slug uniqueness checks) | | Read | No limit (alpha scale) | All GET endpoints | A `json_error_layer` middleware converts HTML error responses to `{"error": "..."}` on API routes. ## Database Layer 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. ### DB Modules Each `db/` submodule handles a specific domain: | Module | Tables / Purpose | |--------|-----------------| | `users` | User accounts, profiles, preferences, email verification | | `auth` | Login attempts, lockout tracking | | `sessions` | Session tracking rows (for remote revocation) | | `totp` | TOTP secrets, backup codes | | `passkeys` | WebAuthn credential storage | | `projects` | Creator projects (software, blog, etc.) | | `items` | Content items (digital goods, text, audio) | | `versions` | Item version history | | `chapters` | Text content chapters | | `blog_posts` | Project blog posts (scheduled publish, web-only flag) | | `transactions` | Purchase/payment records | | `subscriptions` | Subscription tiers + Stripe subscription tracking | | `license_keys` | Software license key generation + validation | | `promo_codes` | Discount and free-access promo codes | | `follows` | Follow relationships (users, projects, tags) | | `discover` | Discovery/browse queries (filtering, sorting, pagination) | | `analytics` | View counts, download counts, revenue aggregation | | `tags` | TagTree-based tagging (path column, per-app config) | | `categories` | Content categories | | `labels` | Creator-defined labels for content organization | | `collections` | User-curated item collections | | `custom_links` | Profile custom links (reorderable) | | `custom_domains` | Custom domain DNS verification + mapping | | `synckit` | SyncKit apps, devices, sync_log, keys, blobs | | `ota` | OTA releases + artifacts | | `builds` | Build configs + build records | | `oauth` | OAuth clients, authorization codes, tokens | | `git_repos` | Git repository metadata + visibility | | `ssh_keys` | User SSH public keys | | `issues` | Git issue tracker (issues, comments, labels) | | `patches` | Email patch handling | | `mailing_lists` | Project mailing lists + subscriber management | | `content_insertions` | Reusable content clips (S3-backed) | | `email_suppressions` | Email unsubscribe tracking | | `scanning` | File scan results + quarantine status | | `health` | Health check history | | `monitor` | Health monitoring snapshots | | `waitlist` | Creator waitlist + invite codes | | `invites` | Creator-to-creator invite system | | `reports` | User content reports | | `fan_plus` | Fan+ subscription tracking | | `creator_tiers` | Creator tier enforcement (storage limits, feature gates) | | `bundles` | Item bundling/packs | | `email_signups` | Early signup tracking | ### Type System - `define_pg_uuid_id!` macro -- newtype UUID wrappers (`UserId`, `ProjectId`, `ItemId`, etc.) - `impl_str_enum!` macro -- bidirectional enum-string conversion with sqlx Type/Encode/Decode - `validated_types` -- `Username`, `Slug`, `Email` with parse-time validation - `models` -- database row structs (`DbUser`, `DbProject`, `DbItem`, etc.) ## Key Integrations ### Stripe Connect 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. - Checkout sessions for one-time purchases and subscriptions - Webhook handlers for payment confirmation, subscription lifecycle, disputes - Creator tier subscriptions (Basic $16, Small Files $24, Big Files $36, Everything $60) - Fan+ consumer subscriptions ($8/mo) - Promo codes (percentage/fixed discount, free access) ### Postmark Transactional email (password reset, email verification, purchase receipts, new-device notifications) and broadcast email (mailing list delivery for content announcements, blog post notifications). - HMAC-signed tokens in email action URLs (password reset, verification, account deletion) - Inbound webhook for email patch processing - Bounce/delivery webhooks for suppression tracking - Dev mode: logs emails when `POSTMARK_TOKEN` is not set ### SyncKit Developer sync infrastructure hosted on MNW. JWT-authenticated API for client apps (GO, BB, AF). - Push/pull changelog sync with E2E encryption (ChaCha20-Poly1305 + Argon2) - Device management, key envelopes - Blob storage (separate S3 bucket, presigned upload/download) - OTA update server (Tauri-compatible protocol -- releases, artifacts, version checks) - CI/build pipeline (SSH-based remote builds, post-receive hooks, build configs) ### DocEngine 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. ### TagTree 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. ### File Scanning 6-layer malware scanning pipeline for uploaded files: | Layer | Type | Description | |-------|------|-------------| | 1. Content-type | In-process | Magic-byte verification against declared file type | | 2. Structural | In-process | Binary analysis (PE headers in non-download contexts) | | 3. Archive | In-process | ZIP bomb detection (ratio, depth, uncompressed size limits) | | 4. YARA | In-process | Custom rule matching (compiled at startup) | | 5. ClamAV | External | Antivirus scan via Unix socket (optional) | | 6. MalwareBazaar | External | SHA-256 hash lookup against known malware (optional) | Fail-closed: scanner errors hold files for admin review rather than allowing through. ### Git Source Browser `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`. ### Multithreaded Integration HTTP client (`mt_client`) for the Multithreaded forum instance. HMAC-signed internal API for: - Auto-provisioning forum communities when projects are created - Linking discussion threads to content items - Mailing list subscriber management ## Security Model ### Authentication - **Passwords:** Argon2id with random salt per hash - **Sessions:** `tower-sessions` with PostgreSQL-backed store, ID regeneration on login (prevents fixation), 7-day expiry on inactivity - **Session cache:** DashMap caches recent session validations (TTL from `constants::SESSION_TOUCH_CACHE_SECS`, currently 5s) to skip per-request DB touch - **2FA:** TOTP (totp-rs, 6-digit, 30-second step, +/-1 skew) + WebAuthn passkeys (webauthn-rs) - **Account lockout:** 5 failed attempts triggers 15-minute lockout - **New-device notifications:** Email alert on login from unrecognized session ### Authorization - `AuthUser` extractor -- required login, loads session user - `MaybeUser` extractor -- optional login for public pages - `AdminUser` extractor -- admin-only, returns 404 (not 403) to hide admin routes - Resource ownership verified per-request (`ensure_project_owner`, `verify_item_ownership`) ### Network Security - **Cloudflare:** Full (Strict) SSL, Authenticated Origin Pulls (mTLS), DDoS protection - **Origin CA:** Wildcard cert for `*.makenot.work`, 15-year RSA - **Caddy:** Reverse proxy, on-demand TLS for custom domains, static file serving - **Firewall:** ufw + fail2ban + hardened sshd - **Custom domains:** DNS TXT verification via Cloudflare DoH, Caddy on-demand TLS, DashMap domain cache ### Request Security - **CSRF:** Middleware-enforced tokens on all mutating requests. HTMX gets headers via `hx-headers` on body element. JS uses `csrfHeaders()`. - **Rate limiting:** tower-governor per-IP rate limits on all write endpoints (see API Route Tiers above) - **Request body limit:** 1 MB default (tower-http `RequestBodyLimitLayer`) - **Anti-enumeration:** 400ms delay on username availability checks ### Data Security - **SyncKit E2E encryption:** Server stores only encrypted blobs, never plaintext user data - **HMAC-signed email tokens:** Password reset, verification, deletion links with expiry - **File scanning:** 6-layer pipeline, fail-closed on scanner errors ## Template System Askama templates with HTMX for dynamic interactions. Three layers: | Layer | Location | Pattern | |-------|----------|---------| | Full pages | `templates/pages/`, `templates/dashboards/` | Extend `base.html`, define `{% block content %}` | | Tab content | `templates/partials/tabs/` | Standalone HTML fragments, loaded via `hx-get` | | Feedback | `templates/partials/` | Alert, form status, save status fragments | 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. 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. ## AppState Shared application state (`Arc`-wrapped by Axum): | Field | Type | Purpose | |-------|------|---------| | `db` | `PgPool` | PostgreSQL connection pool | | `config` | `Config` | Environment-based configuration | | `s3` | `Option>` | Main file storage (uploads, covers, downloads) | | `synckit_s3` | `Option>` | Separate SyncKit blob storage bucket | | `stripe` | `Option` | Stripe Connect client | | `email` | `EmailClient` | Postmark email client (logs in dev) | | `docs` | `Arc` | Documentation pages (loaded at startup) | | `scanner` | `Option>` | File scanning pipeline (YARA rules compiled at startup) | | `webauthn` | `Arc` | WebAuthn relying party | | `syntax` | `Option>` | Git source browser syntax highlighting | | `session_cache` | `Arc>` | Recent session validation cache | | `mt_client` | `Option` | Multithreaded internal API client | | `domain_cache` | `Arc>` | Verified custom domain lookup cache | 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.). ## Background Tasks Two spawned Tokio tasks, coordinated via `watch::channel` for graceful shutdown: | Task | Interval | Purpose | |------|----------|---------| | Health monitor | 60 seconds | Probes DB, S3, sessions. Records history. Alerts on status transitions. | | Scheduler | 60 seconds | Publishes scheduled items/blog posts. Sends mailing list announcements. | ## Concurrency Model - **Tokio multi-threaded runtime** drives all I/O - **`Arc>`** for session cache and domain cache (lock-free concurrent maps) - **Graceful shutdown:** SIGINT/SIGTERM triggers shutdown signal, 10-second drain window for in-flight requests, then force exit - **Connection pool:** sqlx PgPool (25 connections) handles concurrent DB access - **`into_make_service_with_connect_info`** provides peer IP for rate limiting ## Testing 1,028+ tests (486 unit + 542 integration + 28 health + 4 load). Zero failures. - **Unit tests:** In-file `#[cfg(test)]` modules, no DB required - **Integration tests:** Each test creates/drops its own PostgreSQL database. Test harness in `tests/harness/`. Workflow tests in `tests/workflows/`. - **Astra:** Dev server with `RUST_TEST_THREADS=8` (96-core machine, but PG overwhelms at full parallelism) ## Key Design Decisions - **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. - **Server-rendered HTML over SPA:** Askama templates + HTMX. No client-side routing, no JS framework, no build step. Tab content loaded as HTML fragments. - **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. - **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. - **Compile-time SQL:** sqlx checks all queries at compile time against the database schema. No runtime SQL generation. - **Fail-closed scanning:** File uploads held for admin review on scanner errors, never silently allowed through. - **Native deployment:** systemd unit, direct binary on disk. Cross-compiled via `cargo zigbuild`. No Docker. ## Key Paths | What | Where | |------|-------| | Entry point | `src/main.rs` | | Library root | `src/lib.rs` | | Configuration | `src/config.rs` | | Constants | `src/constants.rs` | | Error types | `src/error.rs` | | Authentication | `src/auth.rs` | | CSRF middleware | `src/csrf.rs` | | Database modules | `src/db/` (65 files) | | Route modules | `src/routes/` (17 submodules) | | Template structs | `src/templates/` | | Email service | `src/email/` | | File scanning | `src/scanning/` | | Stripe integration | `src/payments/` | | S3 storage | `src/storage.rs` | | SyncKit auth | `src/synckit_auth.rs` | | Build runner | `src/build_runner.rs` | | Git browser | `src/git/` | | MT client | `src/mt_client.rs` | | Health monitor | `src/monitor.rs` | | Scheduler | `src/scheduler.rs` | | Shared types | `src/types/` | | Askama templates | `templates/` | | Migrations | `migrations/` (numbered, applied in order) | | Static assets | `static/` | | Integration tests | `tests/` | | Deploy scripts | `deploy/` |