24 KiB
Initial Project Files Design
Purpose
This document specifies the exact contents of all initial configuration and bootstrap files needed to create a working StarPunk project from scratch. Developer agents should use this document to create the complete initial project structure with correct file contents.
Philosophy
These files represent the minimal bootstrap needed to:
- Define project dependencies
- Configure the development environment
- Provide setup instructions
- Establish git workflow
- Initialize the Flask application
Every file listed here is essential for V1.
Configuration Files
.gitignore
Location: /home/phil/Projects/starpunk/.gitignore
Purpose: Prevent committing sensitive data, user content, and build artifacts.
Contents:
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual Environment
.venv/
venv/
env/
ENV/
env.bak/
venv.bak/
# Environment Configuration (CRITICAL - CONTAINS SECRETS)
.env
*.env
!.env.example
# User Data (CRITICAL - NEVER COMMIT)
data/
*.db
*.sqlite
*.sqlite3
*.db-journal
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
*.iml
# Testing
.pytest_cache/
.coverage
htmlcov/
*.cover
.hypothesis/
.tox/
.nox/
# Logs
*.log
logs/
# OS
Thumbs.db
.directory
Critical Rules:
data/MUST be gitignored (contains user notes and database).envMUST be gitignored (contains secrets).env.exampleis explicitly allowed (template)
.env.example
Location: /home/phil/Projects/starpunk/.env.example
Purpose: Template for .env file. Committed to repository.
Contents:
# StarPunk Configuration Template
# Copy this file to .env and fill in your values
# DO NOT commit .env to version control
# =============================================================================
# SITE CONFIGURATION
# =============================================================================
# Public URL where your site is hosted (no trailing slash)
SITE_URL=http://localhost:5000
# Your site name (appears in RSS feed and page titles)
SITE_NAME=My StarPunk Site
# Your name (appears as author in RSS feed)
SITE_AUTHOR=Your Name
# Site description (appears in RSS feed)
SITE_DESCRIPTION=My personal IndieWeb site
# =============================================================================
# AUTHENTICATION
# =============================================================================
# Your IndieWeb identity URL (REQUIRED)
# This is YOUR personal website URL that you authenticate with
# Example: https://yourname.com or https://github.com/yourname
ADMIN_ME=https://your-website.com
# Session secret key (REQUIRED - GENERATE A RANDOM VALUE)
# Generate with: python3 -c "import secrets; print(secrets.token_hex(32))"
SESSION_SECRET=REPLACE_WITH_RANDOM_SECRET
# Session lifetime in days (default: 30)
SESSION_LIFETIME=30
# IndieLogin service URL (usually don't change this)
INDIELOGIN_URL=https://indielogin.com
# =============================================================================
# DATA STORAGE
# =============================================================================
# Base data directory (relative to project root)
DATA_PATH=./data
# Notes directory (where markdown files are stored)
NOTES_PATH=./data/notes
# SQLite database path
DATABASE_PATH=./data/starpunk.db
# =============================================================================
# FLASK CONFIGURATION
# =============================================================================
# Environment: development or production
FLASK_ENV=development
# Debug mode: 1 (on) or 0 (off)
# NEVER use debug mode in production
FLASK_DEBUG=1
# Flask secret key (falls back to SESSION_SECRET if not set)
FLASK_SECRET_KEY=
# =============================================================================
# DEVELOPMENT OPTIONS
# =============================================================================
# Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL
LOG_LEVEL=INFO
# Enable SQL query logging (development only)
SQL_ECHO=0
Usage:
# Copy to .env
cp .env.example .env
# Edit .env with your values
nano .env
requirements.txt
Location: /home/phil/Projects/starpunk/requirements.txt
Purpose: Production dependencies (exactly 6 packages per ADR-002).
Contents:
# StarPunk Production Dependencies
# Python 3.11+ required
# Web Framework
Flask==3.0.*
# Content Processing
markdown==3.5.*
# Feed Generation
feedgen==1.0.*
# HTTP Client (for IndieAuth)
httpx==0.27.*
# Configuration Management
python-dotenv==1.0.*
# Testing Framework
pytest==8.0.*
Version Strategy:
- Use
==X.Y.*for minor version pinning - Allows patch updates (security fixes)
- Prevents breaking changes from major/minor updates
Installation:
uv pip install -r requirements.txt
requirements-dev.txt
Location: /home/phil/Projects/starpunk/requirements-dev.txt
Purpose: Development and testing dependencies.
Contents:
# StarPunk Development Dependencies
# Includes code quality and testing tools
# Include production dependencies
-r requirements.txt
# Testing
pytest-cov>=5.0.0 # Test coverage reporting
pytest-mock>=3.12.0 # Mocking for tests
# Code Quality
black>=24.0.0 # Code formatting
flake8>=7.0.0 # Linting
mypy>=1.8.0 # Type checking
# WSGI Server (for production-like testing)
gunicorn>=21.2.0
Installation:
uv pip install -r requirements-dev.txt
README.md
Location: /home/phil/Projects/starpunk/README.md
Purpose: User-facing project documentation.
Contents:
# StarPunk
A minimal, self-hosted IndieWeb CMS for publishing notes with RSS syndication.
## Philosophy
"Every line of code must justify its existence. When in doubt, leave it out."
StarPunk is designed for a single user who wants to:
- Publish short notes to their personal website
- Own their content (notes stored as portable markdown files)
- Syndicate via RSS
- Support IndieWeb standards (Micropub, IndieAuth)
- Run on minimal resources
## Features
- **File-based storage**: Notes are markdown files, owned by you
- **IndieAuth authentication**: Use your own website as identity
- **Micropub support**: Publish from any Micropub client
- **RSS feed**: Automatic syndication
- **No database lock-in**: SQLite for metadata, files for content
- **Self-hostable**: Run on your own server
- **Minimal dependencies**: 6 core dependencies, no build tools
## Requirements
- Python 3.11 or higher
- 500MB disk space
- Linux, macOS, or Windows with WSL2
## Quick Start
```bash
# Clone repository
git clone https://github.com/YOUR_USERNAME/starpunk.git
cd starpunk
# Install uv (package manager)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create virtual environment
uv venv .venv --python 3.11
# Install dependencies
uv pip install -r requirements.txt
# Configure
cp .env.example .env
# Edit .env and set ADMIN_ME and SESSION_SECRET
# Initialize database
mkdir -p data/notes
.venv/bin/python -c "from starpunk.database import init_db; init_db()"
# Run development server
.venv/bin/flask --app app.py run --debug
# Visit http://localhost:5000
Configuration
All configuration is in the .env file. Required settings:
ADMIN_ME- Your IndieWeb identity URL (e.g., https://yoursite.com)SESSION_SECRET- Random secret key (generate withpython3 -c "import secrets; print(secrets.token_hex(32))")SITE_URL- Public URL of your site
See .env.example for all options.
Project Structure
starpunk/
├── app.py # Application entry point
├── starpunk/ # Application code
├── data/ # Your notes and database (gitignored)
│ ├── notes/ # Markdown files
│ └── starpunk.db # SQLite database
├── static/ # CSS and JavaScript
├── templates/ # HTML templates
└── tests/ # Test suite
Usage
Publishing Notes
Via Web Interface:
- Navigate to
/admin - Login with your IndieWeb identity
- Create notes in markdown
Via Micropub Client:
- Configure client with your site URL
- Authenticate via IndieAuth
- Publish from any Micropub-compatible app
Backing Up Your Data
Your notes are stored as plain markdown files in data/notes/. Back up this directory:
# Simple backup
tar -czf backup.tar.gz data/
# Or use rsync
rsync -av data/ /backup/starpunk/
Development
See docs/standards/development-setup.md for detailed setup.
# Install dev dependencies
uv pip install -r requirements-dev.txt
# Run tests
.venv/bin/pytest
# Format code
.venv/bin/black starpunk/ tests/
# Lint
.venv/bin/flake8 starpunk/ tests/
Architecture
StarPunk uses a hybrid storage approach:
- Notes content: Markdown files (portable, human-readable)
- Metadata: SQLite database (fast queries)
This gives you both portability AND performance.
See docs/architecture/ for complete documentation.
IndieWeb Compliance
StarPunk implements:
- Micropub - Publishing API
- IndieAuth - Authentication
- Microformats2 - Semantic HTML markup
- RSS 2.0 - Feed syndication
Deployment
Production Setup
# Install gunicorn
uv pip install gunicorn
# Run with gunicorn
.venv/bin/gunicorn -w 4 -b 127.0.0.1:8000 app:app
# Configure nginx/Caddy for HTTPS
# Set up systemd for process management
# Enable regular backups of data/ directory
See docs/architecture/deployment.md for details.
License
MIT License - see LICENSE file
Credits
Built with:
- Flask - Web framework
- python-markdown - Markdown processing
- feedgen - RSS generation
- httpx - HTTP client
- IndieLogin - Authentication service
Contributing
This is a personal project optimized for single-user use. If you want additional features, consider forking!
Support
- Documentation: docs/
- Issues: GitHub Issues
- IndieWeb: indieweb.org
---
## Application Files
### app.py
**Location**: `/home/phil/Projects/starpunk/app.py`
**Purpose**: Main application entry point.
**Contents**:
```python
"""
StarPunk - Minimal IndieWeb CMS
Main application entry point
"""
from starpunk import create_app
app = create_app()
if __name__ == '__main__':
# Development server
# For production, use: gunicorn app:app
app.run(debug=True)
Rationale: Minimal entry point. All logic is in the starpunk package.
starpunk/init.py
Location: /home/phil/Projects/starpunk/starpunk/__init__.py
Purpose: Package initialization and Flask app factory.
Contents:
"""
StarPunk package initialization
Creates and configures the Flask application
"""
from flask import Flask
from pathlib import Path
def create_app(config=None):
"""
Application factory for StarPunk
Args:
config: Optional configuration dict to override defaults
Returns:
Configured Flask application instance
"""
app = Flask(
__name__,
static_folder='../static',
template_folder='../templates'
)
# Load configuration
from starpunk.config import load_config
load_config(app, config)
# Initialize database
from starpunk.database import init_db
init_db(app)
# Register blueprints
# TODO: Implement blueprints in separate modules
# from starpunk.routes import public, admin, api
# app.register_blueprint(public.bp)
# app.register_blueprint(admin.bp)
# app.register_blueprint(api.bp)
# Error handlers
@app.errorhandler(404)
def not_found(error):
return {'error': 'Not found'}, 404
@app.errorhandler(500)
def server_error(error):
return {'error': 'Internal server error'}, 500
return app
# Package version
__version__ = '0.1.0'
starpunk/config.py
Location: /home/phil/Projects/starpunk/starpunk/config.py
Purpose: Configuration management and validation.
Contents:
"""
Configuration management for StarPunk
Loads settings from environment variables and .env file
"""
import os
from pathlib import Path
from dotenv import load_dotenv
def load_config(app, config_override=None):
"""
Load configuration into Flask app
Args:
app: Flask application instance
config_override: Optional dict to override config values
"""
# Load .env file
load_dotenv()
# Site configuration
app.config['SITE_URL'] = os.getenv('SITE_URL', 'http://localhost:5000')
app.config['SITE_NAME'] = os.getenv('SITE_NAME', 'StarPunk')
app.config['SITE_AUTHOR'] = os.getenv('SITE_AUTHOR', 'Unknown')
app.config['SITE_DESCRIPTION'] = os.getenv(
'SITE_DESCRIPTION',
'A minimal IndieWeb CMS'
)
# Authentication
app.config['ADMIN_ME'] = os.getenv('ADMIN_ME')
app.config['SESSION_SECRET'] = os.getenv('SESSION_SECRET')
app.config['SESSION_LIFETIME'] = int(os.getenv('SESSION_LIFETIME', '30'))
app.config['INDIELOGIN_URL'] = os.getenv(
'INDIELOGIN_URL',
'https://indielogin.com'
)
# Validate required configuration
if not app.config['SESSION_SECRET']:
raise ValueError(
"SESSION_SECRET must be set in .env file. "
"Generate with: python3 -c \"import secrets; print(secrets.token_hex(32))\""
)
# Flask secret key (uses SESSION_SECRET by default)
app.config['SECRET_KEY'] = os.getenv(
'FLASK_SECRET_KEY',
app.config['SESSION_SECRET']
)
# Data paths
app.config['DATA_PATH'] = Path(os.getenv('DATA_PATH', './data'))
app.config['NOTES_PATH'] = Path(os.getenv('NOTES_PATH', './data/notes'))
app.config['DATABASE_PATH'] = Path(
os.getenv('DATABASE_PATH', './data/starpunk.db')
)
# Flask environment
app.config['ENV'] = os.getenv('FLASK_ENV', 'development')
app.config['DEBUG'] = os.getenv('FLASK_DEBUG', '1') == '1'
# Logging
app.config['LOG_LEVEL'] = os.getenv('LOG_LEVEL', 'INFO')
# Apply overrides if provided
if config_override:
app.config.update(config_override)
# Ensure data directories exist
app.config['DATA_PATH'].mkdir(parents=True, exist_ok=True)
app.config['NOTES_PATH'].mkdir(parents=True, exist_ok=True)
starpunk/database.py
Location: /home/phil/Projects/starpunk/starpunk/database.py
Purpose: Database initialization and schema management.
Contents:
"""
Database initialization and operations for StarPunk
SQLite database for metadata, sessions, and tokens
"""
import sqlite3
from pathlib import Path
# Database schema
SCHEMA_SQL = """
-- Notes metadata (content is in files)
CREATE TABLE IF NOT EXISTS 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 DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
content_hash TEXT
);
CREATE INDEX IF NOT EXISTS idx_notes_created_at ON notes(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_notes_published ON notes(published);
CREATE INDEX IF NOT EXISTS idx_notes_slug ON notes(slug);
-- Authentication sessions (IndieLogin)
CREATE TABLE IF NOT EXISTS sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_token TEXT UNIQUE NOT NULL,
me TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL,
last_used_at TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(session_token);
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
-- Micropub access tokens
CREATE TABLE IF NOT EXISTS tokens (
token TEXT PRIMARY KEY,
me TEXT NOT NULL,
client_id TEXT,
scope TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_tokens_me ON tokens(me);
-- CSRF state tokens (for IndieAuth flow)
CREATE TABLE IF NOT EXISTS auth_state (
state TEXT PRIMARY KEY,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_auth_state_expires ON auth_state(expires_at);
"""
def init_db(app=None):
"""
Initialize database schema
Args:
app: Flask application instance (optional, for config access)
"""
if app:
db_path = app.config['DATABASE_PATH']
else:
# Fallback to default path
db_path = Path('./data/starpunk.db')
# Ensure parent directory exists
db_path.parent.mkdir(parents=True, exist_ok=True)
# Create database and schema
conn = sqlite3.connect(db_path)
try:
conn.executescript(SCHEMA_SQL)
conn.commit()
print(f"Database initialized: {db_path}")
finally:
conn.close()
def get_db(app):
"""
Get database connection
Args:
app: Flask application instance
Returns:
sqlite3.Connection
"""
db_path = app.config['DATABASE_PATH']
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row # Return rows as dictionaries
return conn
tests/conftest.py
Location: /home/phil/Projects/starpunk/tests/conftest.py
Purpose: Pytest configuration and shared fixtures.
Contents:
"""
Pytest configuration and fixtures for StarPunk tests
"""
import pytest
import tempfile
from pathlib import Path
from starpunk import create_app
from starpunk.database import init_db
@pytest.fixture
def app():
"""Create test Flask application"""
# Create temporary directory for test data
temp_dir = tempfile.mkdtemp()
temp_path = Path(temp_dir)
# Test configuration
config = {
'TESTING': True,
'DEBUG': False,
'DATA_PATH': temp_path,
'NOTES_PATH': temp_path / 'notes',
'DATABASE_PATH': temp_path / 'test.db',
'SESSION_SECRET': 'test-secret-key',
'ADMIN_ME': 'https://test.example.com',
'SITE_URL': 'http://localhost:5000',
}
# Create app with test config
app = create_app(config)
yield app
# Cleanup (optional - temp dir will be cleaned up by OS)
@pytest.fixture
def client(app):
"""Create test client"""
return app.test_client()
@pytest.fixture
def runner(app):
"""Create test CLI runner"""
return app.test_cli_runner()
LICENSE
Location: /home/phil/Projects/starpunk/LICENSE
Purpose: Project license (MIT recommended for IndieWeb projects).
Contents:
MIT License
Copyright (c) 2024 [Your Name]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Database Initialization
Initial Schema
The database schema is defined in starpunk/database.py and includes:
Tables:
notes- Note metadata (8 columns)sessions- Admin sessions (6 columns)tokens- Micropub access tokens (6 columns)auth_state- CSRF tokens (3 columns)
Indexes:
idx_notes_created_at- For chronological orderingidx_notes_published- For filtering published notesidx_notes_slug- For slug lookupsidx_sessions_token- For session validationidx_sessions_expires- For cleanupidx_tokens_me- For user token lookupidx_auth_state_expires- For cleanup
Initialization Process
# Automatic initialization on app creation
from starpunk import create_app
app = create_app() # Database initialized here
# Or manual initialization
from starpunk.database import init_db
init_db() # Creates data/starpunk.db with schema
Bootstrap Checklist
When creating a new StarPunk project from scratch:
-
Create root directory
mkdir starpunk && cd starpunk -
Create configuration files
.gitignore(from this document).env.example(from this document)requirements.txt(from this document)requirements-dev.txt(from this document)README.md(from this document)LICENSE(from this document)
-
Create application files
app.py(from this document)starpunk/__init__.py(from this document)starpunk/config.py(from this document)starpunk/database.py(from this document)
-
Create test files
tests/__init__.py(empty file)tests/conftest.py(from this document)
-
Create directory structure
mkdir -p starpunk static/css static/js templates/admin tests docs/architecture docs/decisions docs/design docs/standards -
Initialize git repository
git init git add .gitignore .env.example requirements.txt README.md LICENSE git commit -m "Initial commit" -
Setup development environment
uv venv .venv --python 3.11 uv pip install -r requirements.txt cp .env.example .env # Edit .env with your values -
Initialize database
.venv/bin/python -c "from starpunk.database import init_db; init_db()" -
Verify setup
.venv/bin/python -c "from starpunk import create_app; app = create_app(); print('✓ App created successfully')" -
Run development server
.venv/bin/flask --app app.py run --debug
File Creation Order
For developer agents, create files in this order to minimize errors:
.gitignore(prevent accidental commits).env.example(configuration template)requirements.txt(dependencies first)README.md(documentation)LICENSE(legal)starpunk/__init__.py(package structure)starpunk/config.py(configuration loading)starpunk/database.py(database setup)app.py(application entry point)tests/conftest.py(test infrastructure)
Verification
After creating all initial files, verify:
# Check all files exist
test -f .gitignore && echo "✓ .gitignore"
test -f .env.example && echo "✓ .env.example"
test -f requirements.txt && echo "✓ requirements.txt"
test -f requirements-dev.txt && echo "✓ requirements-dev.txt"
test -f README.md && echo "✓ README.md"
test -f LICENSE && echo "✓ LICENSE"
test -f app.py && echo "✓ app.py"
test -f starpunk/__init__.py && echo "✓ starpunk/__init__.py"
test -f starpunk/config.py && echo "✓ starpunk/config.py"
test -f starpunk/database.py && echo "✓ starpunk/database.py"
test -f tests/conftest.py && echo "✓ tests/conftest.py"
# Verify Python syntax
python3 -m py_compile app.py starpunk/*.py tests/*.py
echo "✓ All Python files have valid syntax"
# Verify gitignore works
git check-ignore data/ .env .venv/
echo "✓ Sensitive files are gitignored"