Files
StarPunk/docs/design/initial-files.md
2025-11-18 19:21:31 -07:00

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)
  • .env MUST be gitignored (contains secrets)
  • .env.example is 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 with python3 -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:

  1. Navigate to /admin
  2. Login with your IndieWeb identity
  3. Create notes in markdown

Via Micropub Client:

  1. Configure client with your site URL
  2. Authenticate via IndieAuth
  3. 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:

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:

Contributing

This is a personal project optimized for single-user use. If you want additional features, consider forking!

Support


---

## 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:

  1. notes - Note metadata (8 columns)
  2. sessions - Admin sessions (6 columns)
  3. tokens - Micropub access tokens (6 columns)
  4. auth_state - CSRF tokens (3 columns)

Indexes:

  • idx_notes_created_at - For chronological ordering
  • idx_notes_published - For filtering published notes
  • idx_notes_slug - For slug lookups
  • idx_sessions_token - For session validation
  • idx_sessions_expires - For cleanup
  • idx_tokens_me - For user token lookup
  • idx_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:

  1. Create root directory

    mkdir starpunk && cd starpunk
    
  2. 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)
  3. 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)
  4. Create test files

    • tests/__init__.py (empty file)
    • tests/conftest.py (from this document)
  5. Create directory structure

    mkdir -p starpunk static/css static/js templates/admin tests docs/architecture docs/decisions docs/design docs/standards
    
  6. Initialize git repository

    git init
    git add .gitignore .env.example requirements.txt README.md LICENSE
    git commit -m "Initial commit"
    
  7. Setup development environment

    uv venv .venv --python 3.11
    uv pip install -r requirements.txt
    cp .env.example .env
    # Edit .env with your values
    
  8. Initialize database

    .venv/bin/python -c "from starpunk.database import init_db; init_db()"
    
  9. Verify setup

    .venv/bin/python -c "from starpunk import create_app; app = create_app(); print('✓ App created successfully')"
    
  10. 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:

  1. .gitignore (prevent accidental commits)
  2. .env.example (configuration template)
  3. requirements.txt (dependencies first)
  4. README.md (documentation)
  5. LICENSE (legal)
  6. starpunk/__init__.py (package structure)
  7. starpunk/config.py (configuration loading)
  8. starpunk/database.py (database setup)
  9. app.py (application entry point)
  10. 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"

References