atxbbs.com › specs

Specs

Technical reference for ATXbbs v0.1. Last updated 2026-04-19.

Architecture

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

Stack

LayerComponentNotes
LanguagePython 3.12Standard CPython, no compiled deps
Web frameworkFlask 3Vanilla; no Flask-WTF / Flask-Login extensions
TemplatesJinja29 templates total. Static archive pages are pre-rendered, not Jinja.
WSGIgunicorn 221 worker, 4 threads, max-requests=200
Reverse proxynginxSSL via certbot/Let's Encrypt
DatabaseSQLiteSingle file at /opt/springbbs/spring.db
EmailResend HTTPS APISMTP blocked by hosting (DigitalOcean); Resend's HTTPS endpoint works
Inbound mailForwardEmail.netCatch-all forward to a real Gmail inbox
BackupBackblaze B2 + rcloneNightly sync + dated 30-day snapshots
OSUbuntu 24.04 LTSSingle $6/mo droplet, 1 vCPU, 1GB RAM

Data model

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)

URL conventions

PathWhat
/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>/newStart new topic (Flask, login required)
/bbs/live/archive/<conf>/<num>/Archived thread + live comments (Flask)
/bbs/live/signup / /login / /welcomeAuth flows
/bbs/live/forgot / /reset/<token>Password reset
/bbs/live/settingsProfile + 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/membersMember directory
/bbs/live/adminAdmin queue (admin-only)
/bbs/live/stats.jsonJSON stats endpoint (homepage live strip)
/bbs/live/rss/<conf|all>.xmlRSS feed
/sitemap.xml / /bbs-sitemap.xmlSEO sitemaps (3,180+ URLs)

Security

Performance

Backups + integrity

Reconstruction pipeline (Wayback → site)

  1. CDX scan — query Wayback CDX API for all /yapp-bin/public/read/<conf>/<num> URLs ever captured.
  2. Per-thread fetch — for each (conf, num), fetch the LATEST capture using the id_ Wayback prefix (raw content, no toolbar).
  3. Cache — write to archive/threads/<conf>/<num>.html.
  4. Conference index synthesis — for confs without a usable Wayback browse-page, regenerate index from cache by parsing each thread's <H3> + author + response count.
  5. Static renderbuild-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/.
  6. Cross-link — auto-inject banners between thematic conferences and their reference sites (austen ↔ austen.com, drool ↔ firth network).
  7. OG / Schema.org — inject <meta property="og:..."> + JSON-LD DiscussionForumPosting on every rendered thread.
  8. Sitemap regen — per-conference sitemaps + sitemap index.

What ATXbbs is NOT

What's NOT yet shipped

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.