Note content here
# StarPunk Technology Stack ## Project Summary StarPunk is a minimal, single-user IndieWeb CMS for publishing notes with RSS syndication. The project emphasizes radical simplicity, standards compliance, and user data ownership. Every technology choice is driven by the principle: "Every line of code must justify its existence. When in doubt, leave it out." ### Core Requirements - Publish IndieWeb-compatible notes (https://indieweb.org/note) - IndieAuth authentication using external provider (indielogin.com) - Micropub server endpoint for publishing from any client - RSS feed generation for syndication - File-based note storage for maximum portability - SQLite for metadata and structured data - Self-hostable single-user system - API-first architecture - Markdown support ## Complete Technology Stack ### Backend Stack #### Web Framework: Flask 3.0+ **Purpose**: HTTP server, routing, templating **Justification**: - Minimal micro-framework (< 1000 lines core code) - Perfect for single-user applications - Native support for both JSON APIs and HTML rendering - Mature, stable, well-documented (13+ years) - Built-in Jinja2 templating for server-side rendering - Standard WSGI interface for deployment flexibility **Alternatives Rejected**: - FastAPI: Async complexity unnecessary for single-user CMS - Django: Massive framework with ORM, admin, multi-user features we don't need - Bottle: Too minimal, smaller ecosystem **Reference**: ADR-001 #### Python Version: 3.11+ **Purpose**: Programming language **Justification**: - User's preferred language - Excellent standard library (sqlite3, hashlib, secrets, etc.) - Rich ecosystem for web development - Strong typing support (type hints) - Mature dependency management (pip, venv) #### Data Persistence: Hybrid File + Database ##### Note Storage: Markdown Files on Disk **Purpose**: Store note content **Format**: Plain markdown files (.md) **Structure**: ``` data/notes/ ├── 2024/ │ ├── 11/ │ │ ├── my-first-note.md │ │ └── another-note.md │ └── 12/ │ └── december-note.md └── 2025/ └── 01/ └── new-year-note.md ``` **Naming Convention**: `{slug}.md` **Organization**: Year/Month subdirectories (`YYYY/MM/`) **File Format**: Pure markdown, no frontmatter **Justification**: - Maximum portability (user requirement) - Human-readable, editable in any text editor - Easy backup (cp, rsync, git) - User owns data directly - No vendor lock-in - Future-proof format **Reference**: ADR-004 ##### Metadata Storage: SQLite **Purpose**: Store note metadata, sessions, tokens **Database**: `data/starpunk.db` **Schema**: ```sql -- Note metadata (NOT content) CREATE TABLE notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, slug TEXT UNIQUE NOT NULL, file_path TEXT UNIQUE NOT NULL, published BOOLEAN DEFAULT 0, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, content_hash TEXT ); -- Authentication sessions (IndieLogin) CREATE TABLE sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_token TEXT UNIQUE NOT NULL, me TEXT NOT NULL, created_at TIMESTAMP NOT NULL, expires_at TIMESTAMP NOT NULL, last_used_at TIMESTAMP ); -- Micropub access tokens CREATE TABLE tokens ( token TEXT PRIMARY KEY, me TEXT NOT NULL, client_id TEXT, scope TEXT, created_at TIMESTAMP NOT NULL, expires_at TIMESTAMP ); -- CSRF state tokens CREATE TABLE auth_state ( state TEXT PRIMARY KEY, created_at TIMESTAMP NOT NULL, expires_at TIMESTAMP NOT NULL ); ``` **Justification**: - Single file, perfect for single-user - No separate server process - Excellent for read-heavy workloads (blog) - Fast indexing and querying - Built into Python standard library - Enables efficient metadata queries without parsing files - Atomic transactions for data integrity **Hybrid Strategy**: Files are authoritative for content; database is authoritative for metadata. This gives us portability AND performance. **Reference**: ADR-004 #### Core Dependencies ##### markdown (3.5+) **Purpose**: Convert markdown to HTML **Usage**: Render note content for display and RSS feed **Justification**: - Pure Python, standard markdown implementation - Simple API: `markdown.markdown(text)` - Sufficient performance for single-user system - More standard than alternatives (mistune) ##### feedgen (1.0+) **Purpose**: Generate RSS 2.0 feeds **Usage**: Create valid RSS feed from published notes **Justification**: - High-level API ensures RSS 2.0 compliance - Handles date formatting (RFC-822) automatically - CDATA wrapping for HTML content - Better than manual XML generation (error-prone) ##### httpx (0.27+) **Purpose**: HTTP client library **Usage**: - Communication with indielogin.com API - Verify Micropub client metadata - Fetch remote URLs for verification **Justification**: - Modern, clean API - Synchronous and async support - Better than requests (async capability) and urllib (too low-level) - Proper timeout handling - SSL verification built-in ##### python-dotenv (1.0+) **Purpose**: Environment configuration **Usage**: Load settings from `.env` file **Justification**: - Industry standard for configuration - Keeps secrets out of code - Simple API: `load_dotenv()` - Minimal overhead ##### pytest (8.0+) **Purpose**: Testing framework **Usage**: Unit and integration tests **Justification**: - Current Python testing standard - Minimal boilerplate - Clear assertions - Built-in fixtures - Better than unittest (verbose) and nose2 (unmaintained) **Reference**: ADR-002 #### Dependencies Explicitly REJECTED - **Flask-SQLAlchemy**: ORM abstraction unnecessary, adds complexity - **Flask-Login**: Session-based auth, we need token-based for Micropub - **Flask-CORS**: Single decorator, don't need full extension (5 lines of code) - **Flask-WTF**: Form library overkill for simple note creation - **Flask-Limiter**: Rate limiting deferred to V2 or reverse proxy **Decision**: Use Python standard library and explicit code instead of extensions where possible. Each dependency must justify its existence. **Reference**: ADR-002 ### Frontend Stack #### Template Engine: Jinja2 **Purpose**: Server-side HTML rendering **Included With**: Flask (no additional dependency) **Usage**: - Public interface (homepage, note permalinks) - Admin interface (dashboard, note editor) - Microformats markup (h-entry, h-card) **Justification**: - Zero build process - Server-side rendering for better performance - Works without JavaScript (progressive enhancement) - Easy microformats implementation - Familiar syntax - Stable and mature **Reference**: ADR-003 #### CSS: Custom Stylesheet **Purpose**: Visual styling **Approach**: Single custom CSS file, no framework **File**: `static/css/style.css` **Size**: ~200 lines for entire site **Features**: - CSS custom properties (variables) for theming - Mobile-first responsive design - Simple media queries for tablet/desktop - Semantic HTML5 + minimal classes **Justification**: - No framework overhead (Bootstrap, Tailwind, etc.) - No build tools required - Full control over appearance - Minimal single theme fits project scope - Faster than loading framework CSS **Example**: ```css :root { --color-text: #333; --color-bg: #fff; --max-width: 42rem; --spacing: 1rem; } ``` **Frameworks Rejected**: - Tailwind: Requires build process, utility-first doesn't fit - Bootstrap/Bulma: Too many unused features - PicoCSS: Good but custom CSS gives more control **Reference**: ADR-003 #### JavaScript: Minimal Vanilla JS **Purpose**: Markdown preview in admin (optional enhancement) **Approach**: Single vanilla JavaScript file, no framework **File**: `static/js/preview.js` **Dependency**: marked.js via CDN (client-side markdown) **Usage**: - Optional real-time markdown preview in note editor - Progressive enhancement (works without JS) **Justification**: - Core functionality works without JavaScript - Single optional feature doesn't justify framework - Vanilla JS sufficient for simple preview - Modern browser APIs (fetch, DOM manipulation) are enough - No build tools required **Frameworks Rejected**: - React/Vue/Svelte: Massive overkill for one preview feature - htmx: Interesting but not needed for V1 - Alpine.js: Too much for minimal JS needs **Reference**: ADR-003 #### Build Tools: NONE **Decision**: No build process whatsoever **Justification**: - Server-side rendering eliminates need for bundling - Custom CSS served directly - Vanilla JS served directly - Modern browsers support ES6+ natively - Zero npm dependencies - Instant development setup **This means**: - No webpack, Vite, Rollup, esbuild - No Babel transpilation - No PostCSS processing - No minification (premature optimization) - No asset pipeline **Reference**: ADR-003 ### Authentication Stack #### Admin Authentication: IndieLogin.com **Purpose**: Authenticate the admin user via their personal website **Provider**: External service at https://indielogin.com **API**: https://indielogin.com/api **Protocol**: OAuth 2.0 / IndieAuth **Flow**: 1. User enters their website URL 2. StarPunk redirects to indielogin.com with state token 3. indielogin.com verifies user's identity (RelMeAuth, email, etc.) 4. indielogin.com redirects back with authorization code 5. StarPunk exchanges code for verified identity 6. StarPunk creates session cookie **Session Management**: - HttpOnly, Secure cookies - 30-day expiry - Stored in SQLite sessions table - CSRF protection via state tokens **Configuration**: ```bash ADMIN_ME=https://your-website.com # Only this URL can authenticate SESSION_SECRET=random-secret-key ``` **Justification**: - Extremely simple (< 100 lines of code) - No authentication code to maintain - No password management needed - True IndieWeb authentication (user owns identity) - Secure by default (delegated to trusted service) - Community-maintained, stable service **Alternatives Rejected**: - Self-hosted IndieAuth: Too complex for V1 - Password auth: Not IndieWeb-compatible, security burden - OAuth (GitHub/Google): User doesn't own identity **Reference**: ADR-005 #### Micropub Authentication: IndieAuth Tokens **Purpose**: Authenticate Micropub API clients **Protocol**: IndieAuth bearer tokens **Flow**: Standard IndieAuth authorization code grant **Storage**: Tokens table in SQLite **Note**: Micropub token endpoint is separate from admin authentication. Users authenticate their Micropub clients (e.g., mobile apps) separately via IndieAuth flow. This will be detailed in a future ADR for Micropub implementation. ### Development Tools #### Code Quality ``` pytest-cov # Test coverage reporting black # Code formatting (standard: 88 char line length) flake8 # Linting mypy # Type checking (optional but recommended) ``` **Justification**: - Automated formatting prevents style debates - Linting catches common errors - Test coverage ensures quality - Type hints improve maintainability #### Development Workflow ```bash # Setup python -m venv venv source venv/bin/activate pip install -r requirements.txt # Run flask run # Test pytest # Format black . flake8 . ``` **No additional tools required**: No npm, no build scripts, no containers (optional for deployment). ### Deployment Stack #### WSGI Server: Gunicorn (Production) **Purpose**: Production HTTP server **Justification**: - Standard Python WSGI server - Production-ready - Better performance than Flask dev server - Simple configuration **Alternative**: uWSGI (more complex, not needed for single-user) #### Reverse Proxy: Nginx or Caddy (Recommended) **Purpose**: HTTPS termination, static file serving **Justification**: - Handle SSL/TLS certificates - Serve static files efficiently - Rate limiting (optional) - Proven deployment pattern #### Process Manager: systemd (Recommended) **Purpose**: Keep application running **Justification**: - Standard on modern Linux - Auto-restart on failure - Log management #### Deployment Package: Single Unit **Structure**: ``` starpunk/ ├── app.py # Main application ├── requirements.txt # Dependencies ├── .env.example # Configuration template ├── static/ # CSS, JS ├── templates/ # Jinja2 templates ├── data/ # Notes + SQLite (persistent) │ ├── notes/ │ └── starpunk.db └── README.md # Setup instructions ``` **Deployment**: - Clone repository - Create virtual environment - Install dependencies - Configure .env file - Run with Gunicorn + systemd - (Optional) Nginx for HTTPS **Justification**: Single self-contained package, easy to deploy and backup. ## File Organization ### Project Structure ``` starpunk/ ├── app.py # Main Flask application ├── requirements.txt # Python dependencies ├── .env # Environment configuration (gitignored) ├── .env.example # Configuration template ├── README.md # Setup documentation ├── CLAUDE.MD # Project requirements │ ├── starpunk/ # Application package │ ├── __init__.py │ ├── config.py # Configuration loading │ ├── database.py # SQLite operations │ ├── models.py # Data models │ ├── auth.py # Authentication logic │ ├── micropub.py # Micropub endpoint │ ├── feed.py # RSS generation │ └── utils.py # Helper functions │ ├── static/ # Static assets │ ├── css/ │ │ └── style.css # Single stylesheet │ └── js/ │ └── preview.js # Optional markdown preview │ ├── templates/ # Jinja2 templates │ ├── base.html # Base layout │ ├── index.html # Homepage (note list) │ ├── note.html # Single note │ ├── feed.xml # RSS template │ └── admin/ │ ├── base.html # Admin layout │ ├── login.html # Login form │ ├── dashboard.html # Admin dashboard │ ├── new.html # Create note │ └── edit.html # Edit note │ ├── data/ # Persistent data (gitignored) │ ├── notes/ # Markdown files │ │ └── YYYY/MM/ │ │ └── slug.md │ └── starpunk.db # SQLite database │ ├── tests/ # Test suite │ ├── test_auth.py │ ├── test_database.py │ ├── test_micropub.py │ ├── test_feed.py │ └── test_notes.py │ └── docs/ # Architecture documentation ├── architecture/ │ ├── overview.md │ ├── components.md │ ├── data-flow.md │ ├── security.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 ``` ## Architecture Patterns ### API-First Design **Pattern**: All functionality exposed via API, web interface consumes API **Routes**: ``` # Public API GET /api/notes # List published notes GET /api/notes/{slug} # Get single note GET /feed.xml # RSS feed # Admin API (session auth) POST /api/notes # Create note PUT /api/notes/{id} # Update note DELETE /api/notes/{id} # Delete note # Micropub API (token auth) POST /api/micropub # Create via Micropub GET /api/micropub?q=config # Query config # Auth API GET /admin/login # Login form POST /admin/login # Initiate IndieLogin GET /auth/callback # IndieLogin callback POST /admin/logout # Logout ``` ### Data Flow: File + Database Sync #### Creating a Note ``` User submits note ↓ Generate slug ↓ Create file: data/notes/YYYY/MM/{slug}.md ↓ Calculate content hash ↓ Insert database record (slug, file_path, hash, timestamps) ↓ If database insert fails: delete file, return error ↓ Return success ``` #### Reading a Note ``` Request note by slug ↓ Query database for file_path ↓ Read markdown from file ↓ Render to HTML (if needed) ↓ Return content + metadata ``` #### Updating a Note ``` User submits changes ↓ Atomic write: new content to temp file ↓ Calculate new hash ↓ Update database (timestamp, hash) ↓ If database update succeeds: atomic rename temp → actual ↓ If database update fails: delete temp, return error ↓ Return success ``` **Benefits**: - Files provide portability - Database provides fast queries - Content hash detects external changes - Atomic operations prevent corruption **Reference**: ADR-004 ### IndieLogin Authentication Flow ``` ┌─────────┐ ┌──────────┐ ┌─────────────┐ │ User │ │ StarPunk │ │ IndieLogin │ └────┬────┘ └────┬─────┘ └──────┬──────┘ │ │ │ │ 1. Click "Login" │ │ ├─────────────────────────>│ │ │ │ │ │ 2. Enter website URL │ │ ├─────────────────────────>│ │ │ │ │ │ 3. Generate state token │ │ │ │ │ 4. Redirect to IndieLogin with: │ │ - me=user_website │ │ - client_id=starpunk_url │ │ - redirect_uri=starpunk/callback │ │ - state=random_token │ │ ├──────────────────────────>│ │ │ │ │ │ 5. Verify user's │ │ │ identity │ │ <────────────────────────────────────────────────── │ │ (User authenticates via │ │ chosen method) │ │ ──────────────────────────────────────────────────> │ │ │ │ │ 6. Redirect back with code + state │ │ <──────────────────────────────────────────────────│ ├─────────────────────────>│ │ │ │ │ │ 7. Verify state │ │ │ │ │ 8. POST to IndieLogin: │ │ - code │ │ - client_id │ │ - redirect_uri │ │ ├──────────────────────────>│ │ │ │ │ │ 9. Return verified "me" │ │ │<──────────────────────────│ │ │ │ │ 10. Verify me == ADMIN_ME │ │ │ │ │ 11. Create session │ │ │ │ │ 12. Set session cookie │ │ │ <───────────────────────│ │ │ │ │ │ 13. Redirect to admin │ │ │ <───────────────────────│ │ │ │ │ ``` **Security Features**: - State token prevents CSRF - Session tokens are cryptographically random - HttpOnly cookies prevent XSS - Only ADMIN_ME URL can authenticate - Sessions expire after 30 days **Reference**: ADR-005 ### Progressive Enhancement Pattern **Principle**: Core functionality works without JavaScript #### Public Interface - **Without JS**: Full functionality (view notes, RSS feed) - **With JS**: No difference (no JS used on public pages) #### Admin Interface - **Without JS**: - Create/edit notes via HTML forms - Submit to server, server renders markdown - Full page refresh on submit - **With JS**: - Real-time markdown preview - No page refresh for preview - Still submits via form (progressive enhancement) **Implementation**: ```html
``` **Reference**: ADR-003 ## Standards Compliance ### IndieWeb Standards #### Microformats2 **Required Classes**: - `h-entry`: Mark up notes - `h-card`: Mark up author information - `e-content`: Note content - `dt-published`: Publication timestamp - `u-url`: Permalink URL **Example**: ```htmlNote content here