that initial commit

This commit is contained in:
2025-11-18 19:21:31 -07:00
commit a68fd570c7
69 changed files with 31070 additions and 0 deletions

497
TECHNOLOGY-STACK-SUMMARY.md Normal file
View File

@@ -0,0 +1,497 @@
# 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 <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/