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:
- Generate slug
- Write markdown file →
data/notes/YYYY/MM/slug.md - Calculate content hash
- Insert database record with metadata
- If database fails: delete file, rollback
Reading a Note:
- Query database by slug → get file_path
- Read markdown from file
- Render to HTML
- 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
- User initiates login → enters their website URL
- StarPunk redirects → to https://indielogin.com/auth with:
me= user's websiteclient_id= StarPunk URLredirect_uri= callback URLstate= random CSRF token
- IndieLogin verifies identity → via RelMeAuth, email, etc.
- User authenticates → chooses verification method
- IndieLogin redirects back → with authorization code
- StarPunk exchanges code → POST to indielogin.com API
- IndieLogin returns → verified "me" URL
- StarPunk verifies → me == ADMIN_ME (from config)
- Create session → generate token, store in database, set cookie
- 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 structureindex.html- Homepage (note list)note.html- Single note permalinkfeed.xml- RSS feed template
Admin Templates:
admin/base.html- Admin layoutadmin/login.html- Login formadmin/dashboard.html- Note listadmin/new.html- Create note formadmin/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
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
# 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:
- 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
# 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/