Technical reference for ATXbbs v0.1. Last updated 2026-04-19.
visitor / browser
│
▼ HTTPS
┌──────────────────────────────────────────────────────────┐
│ nginx 80/443 │
│ / → static HTML (homepage, etc.) │
│ /bbs/ → static HTML (conference + thread pgs) │
│ /bbs/live/* → reverse proxy to gunicorn :8920 │
└──────────────────────────────────────────────────────────┘
│ │
▼ static files ▼ proxy
/var/www/ ┌────────────────────────┐
└ austinspring.com/ │ gunicorn (1 worker, 4 │
├ index.html │ threads) :8920 │
├ bbs/ │ ↓ │
│ ├ <conf>/ │ Flask app │
│ │ ├ index.html │ ↓ │
│ │ └ <thread_num>/index.html│ SQLite (single .db) │
│ └ live/ (Flask routes) └────────────────────────┘
│
└ archive caches (raw Wayback HTML)
├ archive/threads/<conf>/<num>.html
└ archive/conferences-union/<conf>.html
| Layer | Component | Notes |
|---|---|---|
| Language | Python 3.12 | Standard CPython, no compiled deps |
| Web framework | Flask 3 | Vanilla; no Flask-WTF / Flask-Login extensions |
| Templates | Jinja2 | 9 templates total. Static archive pages are pre-rendered, not Jinja. |
| WSGI | gunicorn 22 | 1 worker, 4 threads, max-requests=200 |
| Reverse proxy | nginx | SSL via certbot/Let's Encrypt |
| Database | SQLite | Single file at /opt/springbbs/spring.db |
Resend HTTPS API | SMTP blocked by hosting (DigitalOcean); Resend's HTTPS endpoint works | |
| Inbound mail | ForwardEmail.net | Catch-all forward to a real Gmail inbox |
| Backup | Backblaze B2 + rclone | Nightly sync + dated 30-day snapshots |
| OS | Ubuntu 24.04 LTS | Single $6/mo droplet, 1 vCPU, 1GB RAM |
users (
id INTEGER PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
bio TEXT,
email TEXT, -- optional, for password reset + reply notifs
notify_replies INTEGER DEFAULT 1, -- per-user opt-out
is_admin INTEGER DEFAULT 0,
joined_at TEXT,
last_seen TEXT
)
threads (
id INTEGER PRIMARY KEY,
conf TEXT NOT NULL, -- conference slug, e.g. "drool"
author_id INTEGER REFERENCES users,
title TEXT NOT NULL,
created_at TEXT,
updated_at TEXT, -- bumped on new reply
post_count INTEGER DEFAULT 0,
pinned INTEGER DEFAULT 0, -- 1 = sticky on conf index
deleted_at TEXT, -- soft-delete (null = visible)
deleted_reason TEXT
)
posts (
id INTEGER PRIMARY KEY,
thread_id INTEGER REFERENCES threads,
author_id INTEGER REFERENCES users,
body TEXT NOT NULL,
created_at TEXT,
deleted_at TEXT,
deleted_reason TEXT
)
archive_comments ( -- comments on RECONSTRUCTED archived threads
id INTEGER PRIMARY KEY,
conf TEXT NOT NULL,
thread_num INTEGER NOT NULL, -- the original yapp topic number
author_id INTEGER REFERENCES users,
body TEXT,
created_at TEXT,
deleted_at TEXT,
deleted_reason TEXT
)
password_reset_tokens (token, user_id, email, created_at, used_at)
notification_tokens (token, user_id, created_at) -- for unsubscribe links
read_log (user_id, conf, thread_num, last_read_at) -- for unread-since-last-visit (planned)
conference_meta (slug, welcome_html, host_user_id, updated_at)
| Path | What |
|---|---|
/ | Site homepage |
/bbs/ | Conference list (static hub) |
/bbs/<conf>/ | Conference index (static, pre-rendered) |
/bbs/<conf>/<num>/ | Archived thread (static) |
/bbs/live/c/<conf>/ | Conference index (Flask, includes live threads) |
/bbs/live/c/<conf>/<tid>/ | Live thread (Flask) |
/bbs/live/c/<conf>/new | Start new topic (Flask, login required) |
/bbs/live/archive/<conf>/<num>/ | Archived thread + live comments (Flask) |
/bbs/live/signup / /login / /welcome | Auth flows |
/bbs/live/forgot / /reset/<token> | Password reset |
/bbs/live/settings | Profile + email + notification opt-out |
/bbs/live/notify/off/<token> | One-click unsubscribe (link in every notif email) |
/bbs/live/recent[?n=N] | Activity feed (default 60, max 500 — yapp's pulse) |
/bbs/live/members | Member directory |
/bbs/live/admin | Admin queue (admin-only) |
/bbs/live/stats.json | JSON stats endpoint (homepage live strip) |
/bbs/live/rss/<conf|all>.xml | RSS feed |
/sitemap.xml / /bbs-sitemap.xml | SEO sitemaps (3,180+ URLs) |
werkzeug.security.generate_password_hash (PBKDF2-SHA256 by default).<input name="csrf"> on every POST form, verified server-side.SESSION_COOKIE_HTTPONLY=True, SAMESITE=Lax, SECURE=True in production.deleted_at timestamp; data preserved for audit/undo./var/www/austinspring.com/ and /opt/springbbs/ to B2.b2:walhuswholetech-snapshots/austinspring-YYYY-MM-DD/, last 30 days kept..GOOD copy; cron sentinel restores within 2 min if live drops below locked size./yapp-bin/public/read/<conf>/<num> URLs ever captured.id_ Wayback prefix (raw content, no toolbar).archive/threads/<conf>/<num>.html.<H3> + author + response count.build-thread-pages.py --all walks the cache, parses yapp HTML with regex (1990s markup is too wild for lxml), outputs modern-styled static HTML to /var/www/austinspring.com/bbs/.<meta property="og:..."> + JSON-LD DiscussionForumPosting on every rendered thread.See the roadmap. Tier-1 items pending: full-text search across archive + live, "unread since last visit" filter, per-conference hosts tier, topic subscriptions, @mentions, Markdown in posts, mobile-responsive polish.