# 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 ```sql -- 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 ```bash # .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**: ```http 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**: ```json { "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 /* 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 ``` ## Recommended Architectural Patterns ### 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 ```bash # 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 ```bash 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 ```bash # 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**: - https://indiewebify.me/ (microformats) - https://micropub.rocks/ (Micropub compliance) ### 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**: - https://validator.w3.org/feed/ (RSS) - https://validator.w3.org/ (HTML) ## Performance Targets - **API responses**: < 100ms - **Page loads**: < 200ms - **RSS generation**: < 300ms - **Memory usage**: < 100MB - **Startup time**: < 1 second ## Quick Start ```bash # 1. Clone and setup git clone && 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/