that initial commit
This commit is contained in:
497
TECHNOLOGY-STACK-SUMMARY.md
Normal file
497
TECHNOLOGY-STACK-SUMMARY.md
Normal 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/
|
||||
Reference in New Issue
Block a user