Files
StarPunk/TECHNOLOGY-STACK-SUMMARY.md
2025-11-18 19:21:31 -07:00

14 KiB

StarPunk Technology Stack - Quick Reference

Project Understanding

StarPunk is a minimal, single-user IndieWeb CMS for publishing notes with RSS syndication. The core philosophy is radical simplicity: "Every line of code must justify its existence."

Key Requirements

  • Publish IndieWeb-compatible notes
  • External IndieLogin authentication via indielogin.com
  • Micropub server for publishing from any client
  • RSS feed generation
  • File-based note storage (markdown files)
  • SQLite for metadata
  • Self-hostable
  • API-first architecture

Complete Technology Stack

Backend

Component Technology Version Justification
Language Python 3.11+ User's preference, excellent ecosystem
Web Framework Flask 3.0+ Minimal micro-framework, perfect for single-user
Note Storage Markdown Files - Maximum portability, user owns data directly
Metadata DB SQLite Built-in Single file, no server, perfect for single-user
Markdown Rendering markdown 3.5+ Standard Python implementation
RSS Generation feedgen 1.0+ Ensures valid RSS 2.0 output
HTTP Client httpx 0.27+ Modern API, IndieLogin communication
Configuration python-dotenv 1.0+ Standard .env file support
Testing pytest 8.0+ Python testing standard

Total Direct Dependencies: 6 packages

Frontend

Component Technology Justification
Template Engine Jinja2 Included with Flask, server-side rendering
CSS Custom CSS (~200 lines) No framework, full control, no build tools
JavaScript Vanilla JS (optional) Minimal preview feature, progressive enhancement
Build Tools NONE Zero build process, direct file serving

Authentication

Component Technology Approach
Admin Auth IndieLogin.com External OAuth 2.0 service at https://indielogin.com
Session Management HttpOnly Cookies + SQLite 30-day sessions, secure tokens
Micropub Auth IndieAuth Tokens Bearer tokens, stored in SQLite
CSRF Protection State Tokens Random tokens with 5-minute expiry

Key Point: Authentication is delegated to indielogin.com, requiring zero auth code to maintain.

Data Architecture

Hybrid File + Database Storage

Note Content: Markdown Files

data/notes/
├── 2024/
│   ├── 11/
│   │   ├── my-first-note.md
│   │   └── another-note.md
│   └── 12/
│       └── december-note.md
  • Format: Pure markdown, no frontmatter
  • Organization: Year/Month subdirectories (YYYY/MM/)
  • Naming: {slug}.md
  • Portability: Copy anywhere, read in any editor, backup with cp/rsync/git

Metadata: SQLite Database

-- Note metadata (NOT content)
CREATE TABLE notes (
    id INTEGER PRIMARY KEY,
    slug TEXT UNIQUE,
    file_path TEXT UNIQUE,
    published BOOLEAN,
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    content_hash TEXT
);

-- Authentication
CREATE TABLE sessions (...);    -- IndieLogin sessions
CREATE TABLE tokens (...);      -- Micropub tokens
CREATE TABLE auth_state (...);  -- CSRF protection
  • Location: data/starpunk.db
  • Purpose: Fast queries, indexes, referential integrity
  • Sync: Files are authoritative for content, database for metadata

How They Work Together

Creating a Note:

  1. Generate slug
  2. Write markdown file → data/notes/YYYY/MM/slug.md
  3. Calculate content hash
  4. Insert database record with metadata
  5. If database fails: delete file, rollback

Reading a Note:

  1. Query database by slug → get file_path
  2. Read markdown from file
  3. Render to HTML
  4. Return content + metadata

IndieLogin Authentication Flow

Configuration Required

# .env file
SITE_URL=https://starpunk.example.com
ADMIN_ME=https://your-website.com  # Only this URL can authenticate
SESSION_SECRET=random-secret-key

Authentication Steps

  1. User initiates login → enters their website URL
  2. StarPunk redirects → to https://indielogin.com/auth with:
    • me = user's website
    • client_id = StarPunk URL
    • redirect_uri = callback URL
    • state = random CSRF token
  3. IndieLogin verifies identity → via RelMeAuth, email, etc.
  4. User authenticates → chooses verification method
  5. IndieLogin redirects back → with authorization code
  6. StarPunk exchanges code → POST to indielogin.com API
  7. IndieLogin returns → verified "me" URL
  8. StarPunk verifies → me == ADMIN_ME (from config)
  9. Create session → generate token, store in database, set cookie
  10. Redirect to admin → user is now authenticated

API Endpoint

IndieLogin API: https://indielogin.com/api

Exchange Request:

POST https://indielogin.com/auth
Content-Type: application/x-www-form-urlencoded

code={authorization_code}&
client_id={starpunk_url}&
redirect_uri={starpunk_url}/auth/callback

Exchange Response:

{
  "me": "https://user-website.com"
}

Security Features

  • State tokens prevent CSRF attacks
  • Only ADMIN_ME URL can authenticate (single-user enforcement)
  • Session tokens are cryptographically random (256-bit)
  • HttpOnly cookies prevent XSS theft
  • Secure flag requires HTTPS
  • 30-day session expiry

Frontend Stack Details

Server-Side Rendering (Jinja2)

Public Templates:

  • base.html - Base layout with HTML structure
  • index.html - Homepage (note list)
  • note.html - Single note permalink
  • feed.xml - RSS feed template

Admin Templates:

  • admin/base.html - Admin layout
  • admin/login.html - Login form
  • admin/dashboard.html - Note list
  • admin/new.html - Create note form
  • admin/edit.html - Edit note form

CSS Approach

Single stylesheet: static/css/style.css (~200 lines)

/* CSS custom properties for theming */
:root {
  --color-text: #333;
  --color-bg: #fff;
  --color-link: #0066cc;
  --max-width: 42rem;
  --spacing: 1rem;
}

/* Mobile-first responsive */
body { padding: 1rem; }

@media (min-width: 768px) {
  body { padding: 2rem; }
}

No framework: Custom CSS gives full control, no unused code.

JavaScript Approach

Single optional file: static/js/preview.js

Purpose: Real-time markdown preview in admin editor (progressive enhancement)

Implementation:

  • Vanilla JavaScript (no framework)
  • Uses marked.js from CDN for client-side markdown
  • Works without it (form submits to server)

Why vanilla JS?

  • Core functionality works without JavaScript
  • Single feature doesn't justify React/Vue/Svelte
  • Modern browser APIs are sufficient
  • No build tools needed

Build Process: NONE

  • No webpack, Vite, Rollup, esbuild
  • No npm, package.json, node_modules
  • No Babel transpilation
  • No CSS preprocessing
  • Direct file serving
  • Instant development setup

Advantages:

  • Zero build time
  • No dependency hell
  • Simple deployment
  • Easy debugging

API Routes

Public API

GET  /                    Homepage (recent notes)
GET  /note/{slug}         Individual note
GET  /feed.xml            RSS feed

Admin Interface

GET  /admin/login         Login form
POST /admin/login         Initiate IndieLogin flow
GET  /auth/callback       IndieLogin callback handler
GET  /admin               Dashboard (list notes)
GET  /admin/new           Create note form
GET  /admin/edit/{id}     Edit note form
POST /admin/logout        Destroy session

Notes API (Session Auth)

GET    /api/notes         List published notes (JSON)
POST   /api/notes         Create note (JSON)
GET    /api/notes/{id}    Get single note (JSON)
PUT    /api/notes/{id}    Update note (JSON)
DELETE /api/notes/{id}    Delete note (JSON)

Micropub API (Token Auth)

POST /api/micropub              Create note (h-entry)
GET  /api/micropub?q=config     Query configuration
GET  /api/micropub?q=source     Query note source

File Organization

starpunk/
├── app.py                      # Main Flask application
├── requirements.txt            # 6 dependencies
├── .env                        # Configuration (gitignored)
├── .env.example                # Template
│
├── starpunk/                   # Application package
│   ├── __init__.py
│   ├── config.py              # Load environment
│   ├── database.py            # SQLite operations
│   ├── models.py              # Data models
│   ├── auth.py                # IndieLogin logic
│   ├── micropub.py            # Micropub endpoint
│   ├── feed.py                # RSS generation
│   └── utils.py               # Helpers
│
├── static/
│   ├── css/style.css          # Single stylesheet
│   └── js/preview.js          # Optional markdown preview
│
├── templates/
│   ├── base.html              # Public base
│   ├── index.html             # Homepage
│   ├── note.html              # Note permalink
│   └── admin/
│       ├── base.html          # Admin base
│       ├── login.html         # Login form
│       ├── dashboard.html     # Note list
│       ├── new.html           # Create form
│       └── edit.html          # Edit form
│
├── data/                      # Persistent (gitignored)
│   ├── notes/YYYY/MM/slug.md  # Markdown files
│   └── starpunk.db            # SQLite
│
├── tests/                     # pytest tests
│   ├── test_auth.py
│   ├── test_database.py
│   ├── test_micropub.py
│   └── test_feed.py
│
└── docs/                      # Architecture docs
    ├── architecture/
    │   ├── overview.md
    │   └── technology-stack.md
    └── decisions/
        ├── ADR-001-python-web-framework.md
        ├── ADR-002-flask-extensions.md
        ├── ADR-003-frontend-technology.md
        ├── ADR-004-file-based-note-storage.md
        └── ADR-005-indielogin-authentication.md

1. API-First Design

All functionality exposed via API, web interface consumes it.

2. Progressive Enhancement

Core works without JavaScript, JS adds optional enhancements.

3. File-Database Sync

Write files first, then database. Rollback on failure.

4. Atomic Operations

Use temp files and atomic renames to prevent corruption.

5. Token-Based Auth

Sessions for humans (cookies), tokens for APIs (bearer).

Potential Risks & Considerations

Risk 1: IndieLogin.com Dependency

Impact: Cannot authenticate if service is down Mitigation:

  • Sessions last 30 days (brief outages don't lock out user)
  • IndieLogin.com is stable, community-run service
  • V2: Consider fallback auth method

Risk 2: File/Database Sync Issues

Impact: Data inconsistency between files and database Mitigation:

  • Atomic operations (write file → insert DB, rollback on error)
  • Content hashing detects external modifications
  • Optional integrity check on startup

Risk 3: SQLite Limitations

Impact: Limited concurrency (but this is single-user) Consideration: SQLite is perfect for single-user, would need PostgreSQL for multi-user

Risk 4: No Built-in Backup

Impact: User must manage backups Mitigation:

  • Document backup procedures clearly
  • Backup is simple (cp -r data/ backup/)
  • Consider adding automated backup script

Deployment Stack

Development

# Setup
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# Configure
cp .env.example .env
# Edit .env with your settings

# Run
flask run

# Test
pytest

Production

WSGI Server: Gunicorn

gunicorn -w 4 -b 127.0.0.1:8000 app:app

Reverse Proxy: Nginx or Caddy

  • HTTPS termination (Let's Encrypt)
  • Static file serving
  • Rate limiting (optional)

Process Manager: systemd

  • Auto-restart on failure
  • Log management
  • Run on boot

Backup: Cron job

# Daily backup via rsync
rsync -av /opt/starpunk/data /backup/starpunk-$(date +%Y%m%d)

Standards Compliance

IndieWeb

  • Microformats2: h-entry, h-card, e-content, dt-published, u-url
  • IndieAuth: OAuth 2.0 flow (delegated to indielogin.com)
  • Micropub: JSON and form-encoded, 201 Created responses

Validation:

Web Standards

  • RSS 2.0: Valid XML, RFC-822 dates, CDATA for HTML
  • HTML5: Semantic elements, accessible, mobile-responsive
  • HTTP: Proper status codes (200, 201, 400, 401, 404)

Validation:

Performance Targets

  • API responses: < 100ms
  • Page loads: < 200ms
  • RSS generation: < 300ms
  • Memory usage: < 100MB
  • Startup time: < 1 second

Quick Start

# 1. Clone and setup
git clone <repo> && cd starpunk
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt

# 2. Configure
cp .env.example .env
# Edit .env:
#   SITE_URL=https://your-domain.com
#   ADMIN_ME=https://your-website.com
#   SESSION_SECRET=$(python -c "import secrets; print(secrets.token_hex(32))")

# 3. Run
flask run

# 4. Visit http://localhost:5000/admin/login
#    Enter your website URL (must match ADMIN_ME)
#    Authenticate via indielogin.com
#    Start publishing!

Summary

StarPunk uses a radically simple technology stack:

  • Backend: Flask + Python stdlib + 5 small libraries
  • Storage: Markdown files (content) + SQLite (metadata)
  • Frontend: Jinja2 templates + custom CSS + optional vanilla JS
  • Auth: Delegated to indielogin.com (zero maintenance)
  • Build: None (zero build tools)
  • Deploy: Gunicorn + nginx/Caddy + systemd

Total Dependencies: 6 direct packages Lines of Code: ~1500 LOC estimate for V1 Setup Time: < 5 minutes Build Time: 0 seconds (no build process)

This stack embodies the project philosophy: every technology choice is justified by simplicity, fitness for purpose, and maintainability.

Further Reading

  • Project Requirements: /home/phil/Projects/starpunk/CLAUDE.MD
  • Full Tech Stack: /home/phil/Projects/starpunk/docs/architecture/technology-stack.md
  • Architecture Overview: /home/phil/Projects/starpunk/docs/architecture/overview.md
  • All ADRs: /home/phil/Projects/starpunk/docs/decisions/ADR-*.md
  • IndieLogin API: https://indielogin.com/api
  • IndieWeb: https://indieweb.org/