# 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**: ```gitignore # 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**: ```bash # 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**: ```bash # 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**: ```bash 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**: ```bash uv pip install -r requirements-dev.txt ``` --- ### README.md **Location**: `/home/phil/Projects/starpunk/README.md` **Purpose**: User-facing project documentation. **Contents**: ```markdown # 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: ```bash # Simple backup tar -czf backup.tar.gz data/ # Or use rsync rsync -av data/ /backup/starpunk/ ``` ## Development See [docs/standards/development-setup.md](docs/standards/development-setup.md) for detailed setup. ```bash # 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/](docs/architecture/) for complete documentation. ## IndieWeb Compliance StarPunk implements: - [Micropub](https://micropub.spec.indieweb.org/) - Publishing API - [IndieAuth](https://indieauth.spec.indieweb.org/) - Authentication - [Microformats2](http://microformats.org/) - Semantic HTML markup - [RSS 2.0](https://www.rssboard.org/rss-specification) - Feed syndication ## Deployment ### Production Setup ```bash # 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](docs/architecture/deployment.md) for details. ## License MIT License - see LICENSE file ## Credits Built with: - [Flask](https://flask.palletsprojects.com/) - Web framework - [python-markdown](https://python-markdown.github.io/) - Markdown processing - [feedgen](https://feedgen.kiesow.be/) - RSS generation - [httpx](https://www.python-httpx.org/) - HTTP client - [IndieLogin](https://indielogin.com/) - Authentication service ## Contributing This is a personal project optimized for single-user use. If you want additional features, consider forking! ## Support - Documentation: [docs/](docs/) - Issues: GitHub Issues - IndieWeb: [indieweb.org](https://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**: ```python """ 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**: ```python """ 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**: ```python """ 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**: ```python """ 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 ```python # 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** ```bash 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** ```bash mkdir -p starpunk static/css static/js templates/admin tests docs/architecture docs/decisions docs/design docs/standards ``` 6. **Initialize git repository** ```bash git init git add .gitignore .env.example requirements.txt README.md LICENSE git commit -m "Initial commit" ``` 7. **Setup development environment** ```bash uv venv .venv --python 3.11 uv pip install -r requirements.txt cp .env.example .env # Edit .env with your values ``` 8. **Initialize database** ```bash .venv/bin/python -c "from starpunk.database import init_db; init_db()" ``` 9. **Verify setup** ```bash .venv/bin/python -c "from starpunk import create_app; app = create_app(); print('✓ App created successfully')" ``` 10. **Run development server** ```bash .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: ```bash # 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 - [Project Structure Design](/home/phil/Projects/starpunk/docs/design/project-structure.md) - [Development Setup Standards](/home/phil/Projects/starpunk/docs/standards/development-setup.md) - [ADR-002: Flask Extensions](/home/phil/Projects/starpunk/docs/decisions/ADR-002-flask-extensions.md) - [ADR-006: Python Virtual Environment](/home/phil/Projects/starpunk/docs/decisions/ADR-006-python-virtual-environment-uv.md) - [Python Packaging Guide](https://packaging.python.org/) - [Flask Configuration Handling](https://flask.palletsprojects.com/en/3.0.x/config/)