1083 lines
33 KiB
Markdown
1083 lines
33 KiB
Markdown
# 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
|
|
<!-- Note editor works without JS -->
|
|
<form method="POST" action="/api/notes">
|
|
<textarea name="content" required></textarea>
|
|
<button type="submit">Publish</button>
|
|
</form>
|
|
|
|
<!-- JS enhances with preview -->
|
|
<script src="/static/js/preview.js"></script>
|
|
```
|
|
|
|
**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**:
|
|
```html
|
|
<article class="h-entry">
|
|
<div class="e-content">
|
|
<p>Note content here</p>
|
|
</div>
|
|
<footer>
|
|
<a class="u-url" href="/note/abc123">
|
|
<time class="dt-published" datetime="2024-11-18T12:00:00Z">
|
|
November 18, 2024
|
|
</time>
|
|
</a>
|
|
</footer>
|
|
</article>
|
|
```
|
|
|
|
**Validation**: https://indiewebify.me/
|
|
|
|
#### IndieAuth
|
|
**Compliance**: OAuth 2.0 authorization code flow
|
|
**Endpoints**: Delegated to indielogin.com
|
|
**Token Format**: Bearer tokens
|
|
**Validation**: Token introspection
|
|
|
|
**Reference**: https://indieauth.spec.indieweb.org/
|
|
|
|
#### Micropub
|
|
**Compliance**: Full Micropub spec support
|
|
**Content Types**: JSON and form-encoded
|
|
**Required Responses**: 201 Created with Location header
|
|
**Query Support**: q=config, q=source
|
|
|
|
**Validation**: https://micropub.rocks/
|
|
|
|
**Reference**: https://micropub.spec.indieweb.org/
|
|
|
|
### Web Standards
|
|
|
|
#### RSS 2.0
|
|
**Compliance**: Valid RSS 2.0 XML
|
|
**Required Elements**: title, link, description, pubDate, guid
|
|
**Date Format**: RFC-822
|
|
**HTML Content**: CDATA-wrapped
|
|
|
|
**Validation**: https://validator.w3.org/feed/
|
|
|
|
#### HTTP
|
|
**Status Codes**: Proper use of 200, 201, 400, 401, 404, 500
|
|
**Headers**: Content-Type, Cache-Control, Location
|
|
**Methods**: GET, POST, PUT, DELETE
|
|
**CORS**: Allow cross-origin for API endpoints
|
|
|
|
#### HTML5
|
|
**Compliance**: Valid semantic HTML
|
|
**Accessibility**: ARIA labels, alt text, proper heading hierarchy
|
|
**Responsive**: Viewport meta tag, mobile-first CSS
|
|
**Forms**: Proper labels, validation attributes
|
|
|
|
**Validation**: https://validator.w3.org/
|
|
|
|
## Security Architecture
|
|
|
|
### Authentication Security
|
|
- State tokens for CSRF protection (5-minute expiry)
|
|
- Session tokens are cryptographically random (32 bytes)
|
|
- HttpOnly cookies prevent XSS theft
|
|
- Secure flag requires HTTPS
|
|
- SameSite=Lax prevents CSRF
|
|
- Single admin user (ADMIN_ME verification)
|
|
|
|
### Input Validation
|
|
- Validate all user input
|
|
- Sanitize markdown (prevent XSS in rendered HTML)
|
|
- Validate Micropub payloads against spec
|
|
- URL validation for IndieAuth
|
|
- File path validation (prevent directory traversal)
|
|
|
|
### Database Security
|
|
- Parameterized queries (prevent SQL injection)
|
|
- Input sanitization before storage
|
|
- Hash session tokens before storage
|
|
- Content hashing for integrity
|
|
|
|
### Network Security
|
|
- HTTPS required in production
|
|
- SSL certificate verification on httpx requests
|
|
- Secure headers (CSP, X-Frame-Options, etc.)
|
|
- Rate limiting via reverse proxy (nginx/Caddy)
|
|
|
|
### File System Security
|
|
- Atomic file operations
|
|
- Restricted permissions on data/ directory
|
|
- Prevent directory traversal attacks
|
|
- Validate file paths before operations
|
|
|
|
## Performance Targets
|
|
|
|
### Response Times
|
|
- API responses: < 100ms
|
|
- Page loads: < 200ms
|
|
- RSS feed: < 300ms
|
|
|
|
### Optimization Strategy
|
|
- SQLite indexes on frequently queried columns
|
|
- Cache RSS feed (5 minutes)
|
|
- Minimal dependencies = fast startup
|
|
- Server-side rendering = fast first paint
|
|
- Single CSS file = one request
|
|
- Optional JS = doesn't block rendering
|
|
|
|
### Resource Usage
|
|
- Memory: < 100MB for typical workload
|
|
- Disk: Minimal (SQLite + markdown files)
|
|
- CPU: Minimal (no heavy processing)
|
|
|
|
**Scaling**: Designed for single-user, typical load is <10 requests/minute. Over-engineering for scale would violate simplicity principle.
|
|
|
|
## Testing Strategy
|
|
|
|
### Unit Tests (pytest)
|
|
- Database operations (CRUD, queries)
|
|
- Slug generation and validation
|
|
- Markdown rendering
|
|
- File operations (atomic writes)
|
|
- Session management
|
|
- Token validation
|
|
- Content hash calculation
|
|
|
|
### Integration Tests
|
|
- IndieLogin authentication flow (mocked API)
|
|
- Micropub note creation (full flow)
|
|
- RSS feed generation (validation)
|
|
- API endpoints (request/response)
|
|
- File + database sync
|
|
- Error handling
|
|
|
|
### Manual Tests
|
|
- Real IndieLogin authentication
|
|
- Micropub client integration (e.g., Quill)
|
|
- RSS feed in actual reader
|
|
- Browser compatibility
|
|
- Mobile responsiveness
|
|
- Accessibility (screen readers)
|
|
|
|
### Validation Tests
|
|
- HTML validation (W3C validator)
|
|
- RSS validation (W3C feed validator)
|
|
- Microformats validation (indiewebify.me)
|
|
- Micropub compliance (micropub.rocks)
|
|
|
|
## Risk Assessment
|
|
|
|
### Technical Risks
|
|
|
|
#### Risk: IndieLogin.com Outage
|
|
**Impact**: Cannot authenticate new sessions
|
|
**Likelihood**: Low (stable service)
|
|
**Mitigation**:
|
|
- Sessions last 30 days (brief outages don't lock out user)
|
|
- Document manual session creation in database
|
|
- V2: Add fallback authentication method
|
|
|
|
#### Risk: File/Database Sync Failure
|
|
**Impact**: Data inconsistency
|
|
**Likelihood**: Low (atomic operations, error handling)
|
|
**Mitigation**:
|
|
- Write files first, database second
|
|
- Transaction rollback on failure
|
|
- Integrity check on startup (optional)
|
|
- Regular backups
|
|
|
|
#### Risk: File System Corruption
|
|
**Impact**: Lost notes
|
|
**Likelihood**: Very low (standard filesystem operations)
|
|
**Mitigation**:
|
|
- Atomic file writes
|
|
- Regular backups (user responsibility)
|
|
- Markdown files are recoverable
|
|
|
|
#### Risk: Dependency Vulnerabilities
|
|
**Impact**: Security breach
|
|
**Likelihood**: Medium (all software has bugs)
|
|
**Mitigation**:
|
|
- Minimal dependencies (6 direct)
|
|
- All dependencies are mature, maintained
|
|
- Regular updates
|
|
- Security scanning (optional)
|
|
|
|
### Operational Risks
|
|
|
|
#### Risk: User Misconfiguration
|
|
**Impact**: Application doesn't work
|
|
**Likelihood**: Medium (manual setup required)
|
|
**Mitigation**:
|
|
- Clear documentation
|
|
- .env.example with all settings
|
|
- Validation on startup
|
|
- Helpful error messages
|
|
|
|
#### Risk: Backup Neglect
|
|
**Impact**: Data loss
|
|
**Likelihood**: Medium (user responsibility)
|
|
**Mitigation**:
|
|
- Document backup procedures
|
|
- Make backup easy (copy data/ folder)
|
|
- Consider automated backup scripts (V2)
|
|
|
|
## Migration and Future Considerations
|
|
|
|
### V1 to V2 Migration Path
|
|
- Add features without breaking existing data
|
|
- Markdown files remain compatible
|
|
- Database schema migrations (ALTER TABLE)
|
|
- Backward compatible API changes
|
|
|
|
### Potential V2 Enhancements
|
|
- Webmentions support
|
|
- Media uploads (photos)
|
|
- Additional post types (articles, replies)
|
|
- Full-text search (SQLite FTS)
|
|
- Automated backups
|
|
- Self-hosted IndieAuth option
|
|
- Multiple IndieAuth providers
|
|
- Draft/scheduled posts
|
|
- Tags and categories
|
|
- Import/export tools
|
|
|
|
### Data Portability Strategy
|
|
**Export Formats**:
|
|
- Markdown files (already portable)
|
|
- JSON export (notes + metadata)
|
|
- RSS feed (existing notes)
|
|
- HTML archive (static site generator)
|
|
|
|
**Import Strategy** (V2):
|
|
- From other blogging platforms
|
|
- From JSON backup
|
|
- From markdown directories
|
|
|
|
## Success Criteria
|
|
|
|
The technology stack is successful if:
|
|
|
|
1. **User can publish notes from any Micropub client** ✓
|
|
- Protocol: Micropub over HTTP
|
|
- Auth: IndieAuth tokens
|
|
- Format: Stored as markdown files
|
|
|
|
2. **Notes appear in RSS readers immediately** ✓
|
|
- Format: Valid RSS 2.0
|
|
- Generator: feedgen library
|
|
- Caching: 5 minutes
|
|
|
|
3. **System runs on minimal resources** ✓
|
|
- Stack: Flask + SQLite (single process)
|
|
- Memory: < 100MB
|
|
- Dependencies: 6 direct
|
|
|
|
4. **Code is readable and maintainable** ✓
|
|
- Language: Python (user's preference)
|
|
- Framework: Flask (minimal, clear)
|
|
- Style: black formatting, type hints
|
|
|
|
5. **All IndieWeb validators pass** ✓
|
|
- Microformats: Server-side templating makes this easy
|
|
- IndieAuth: Delegated to indielogin.com
|
|
- Micropub: Spec-compliant implementation
|
|
|
|
6. **Setup takes less than 5 minutes** ✓
|
|
- Steps: Clone, venv, pip install, configure .env, run
|
|
- No build process
|
|
- No complex dependencies
|
|
|
|
7. **System runs for months without intervention** ✓
|
|
- Architecture: Stateless application
|
|
- Persistence: SQLite (reliable)
|
|
- Auth: Long-lived sessions (30 days)
|
|
|
|
## Quick Start Guide
|
|
|
|
### Development Setup
|
|
```bash
|
|
# Clone repository
|
|
git clone <repo> && cd starpunk
|
|
|
|
# Create virtual environment
|
|
python -m venv venv
|
|
source venv/bin/activate # or `venv\Scripts\activate` on Windows
|
|
|
|
# Install dependencies
|
|
pip install -r requirements.txt
|
|
|
|
# Configure
|
|
cp .env.example .env
|
|
# Edit .env: set SITE_URL, ADMIN_ME, SESSION_SECRET
|
|
|
|
# Initialize database
|
|
flask db init
|
|
|
|
# Run development server
|
|
flask run
|
|
|
|
# Visit http://localhost:5000
|
|
```
|
|
|
|
### Production Deployment
|
|
```bash
|
|
# Setup (same as development)
|
|
# ...
|
|
|
|
# Install production server
|
|
pip install gunicorn
|
|
|
|
# Run with Gunicorn
|
|
gunicorn -w 4 -b 127.0.0.1:8000 app:app
|
|
|
|
# Configure nginx/Caddy for HTTPS
|
|
# Configure systemd for process management
|
|
# Set up regular backups of data/ directory
|
|
```
|
|
|
|
### Configuration Reference
|
|
```bash
|
|
# .env file
|
|
SITE_URL=https://starpunk.example.com # Your domain
|
|
ADMIN_ME=https://your-website.com # Your IndieWeb identity
|
|
SESSION_SECRET=random-secret-key # Generate with: python -c "import secrets; print(secrets.token_hex(32))"
|
|
DATA_PATH=./data # Where to store notes and database
|
|
```
|
|
|
|
## Summary
|
|
|
|
StarPunk's technology stack achieves radical simplicity through careful technology selection:
|
|
|
|
- **Backend**: Flask (micro-framework) + SQLite (embedded DB) + Python stdlib
|
|
- **Storage**: Markdown files (portability) + SQLite metadata (performance)
|
|
- **Frontend**: Jinja2 (SSR) + custom CSS (200 lines) + optional vanilla JS
|
|
- **Auth**: IndieLogin.com (external, zero maintenance)
|
|
- **Build**: None (zero build tools, zero npm)
|
|
- **Deploy**: Single package (Gunicorn + systemd + nginx)
|
|
|
|
**Total Direct Dependencies**: 6 (Flask, markdown, feedgen, httpx, python-dotenv, pytest)
|
|
|
|
**Lines of Code Estimate**: ~1500 LOC for complete V1 implementation
|
|
|
|
**Setup Time**: < 5 minutes from clone to running
|
|
|
|
This stack embodies the project philosophy: "Every line of code must justify its existence." Each technology choice prioritizes simplicity, standards compliance, and user data ownership over features and complexity.
|
|
|
|
## References
|
|
|
|
### Architecture Decision Records
|
|
- [ADR-001: Python Web Framework Selection](/home/phil/Projects/starpunk/docs/decisions/ADR-001-python-web-framework.md)
|
|
- [ADR-002: Flask Extensions and Dependencies](/home/phil/Projects/starpunk/docs/decisions/ADR-002-flask-extensions.md)
|
|
- [ADR-003: Front-end Technology Stack](/home/phil/Projects/starpunk/docs/decisions/ADR-003-frontend-technology.md)
|
|
- [ADR-004: File-Based Note Storage Architecture](/home/phil/Projects/starpunk/docs/decisions/ADR-004-file-based-note-storage.md)
|
|
- [ADR-005: IndieLogin Authentication Integration](/home/phil/Projects/starpunk/docs/decisions/ADR-005-indielogin-authentication.md)
|
|
|
|
### Standards and Specifications
|
|
- IndieWeb: https://indieweb.org/
|
|
- IndieAuth Spec: https://indieauth.spec.indieweb.org/
|
|
- Micropub Spec: https://micropub.spec.indieweb.org/
|
|
- Microformats2: http://microformats.org/wiki/h-entry
|
|
- RSS 2.0: https://www.rssboard.org/rss-specification
|
|
- CommonMark: https://spec.commonmark.org/
|
|
|
|
### Tools and Libraries
|
|
- Flask: https://flask.palletsprojects.com/
|
|
- Jinja2: https://jinja.palletsprojects.com/
|
|
- IndieLogin.com: https://indielogin.com/
|
|
- Python Markdown: https://python-markdown.github.io/
|
|
- feedgen: https://feedgen.kiesow.be/
|
|
- httpx: https://www.python-httpx.org/
|
|
|
|
### Validation and Testing
|
|
- IndieWebify.me: https://indiewebify.me/
|
|
- Micropub Rocks: https://micropub.rocks/
|
|
- W3C Feed Validator: https://validator.w3.org/feed/
|
|
- W3C HTML Validator: https://validator.w3.org/
|