#!/usr/bin/env python3 """ setup.py — First-time setup for Bastebin. What this does: 1. Checks that a supported Python version is available. 2. Creates a virtual environment at .venv/ (if absent). 3. Installs all dependencies from requirements.txt into the venv. 4. Initialises the SQLite database (creates the pastes table). 5. Warns if config.json contains the default secret key. Usage: python setup.py """ import os import subprocess import sys MIN_PYTHON = (3, 11) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) VENV_DIR = os.path.join(BASE_DIR, '.venv') REQS_FILE = os.path.join(BASE_DIR, 'requirements.txt') CONFIG = os.path.join(BASE_DIR, 'config.json') # ── Helpers ─────────────────────────────────────────────────────────────────── def _step(msg: str) -> None: print(f'\n {msg}') def _ok(msg: str = 'done') -> None: print(f' ✓ {msg}') def _warn(msg: str) -> None: print(f' ⚠ {msg}') def _fail(msg: str) -> None: print(f'\n ✗ {msg}') sys.exit(1) def _venv_python() -> str: return os.path.join(VENV_DIR, 'bin', 'python') def _venv_pip() -> str: return os.path.join(VENV_DIR, 'bin', 'pip') def _run(*args: str, capture: bool = False) -> subprocess.CompletedProcess: return subprocess.run( args, check=True, capture_output=capture, text=True, ) # ── Steps ───────────────────────────────────────────────────────────────────── def check_python() -> None: _step('Checking Python version...') if sys.version_info < MIN_PYTHON: _fail( f'Python {MIN_PYTHON[0]}.{MIN_PYTHON[1]}+ is required ' f'(found {sys.version.split()[0]}).' ) _ok(f'Python {sys.version.split()[0]}') def create_venv() -> None: _step('Setting up virtual environment...') if os.path.isfile(_venv_python()): _ok('.venv already exists — skipping creation') return _run(sys.executable, '-m', 'venv', VENV_DIR) _ok(f'Created {VENV_DIR}') def install_dependencies() -> None: _step('Installing dependencies...') if not os.path.isfile(REQS_FILE): _fail(f'requirements.txt not found at {REQS_FILE}') _run(_venv_pip(), 'install', '--quiet', '--upgrade', 'pip') _run(_venv_pip(), 'install', '--quiet', '-r', REQS_FILE) _ok('All packages installed') def initialise_database() -> None: _step('Initialising database...') # Import app inside the venv's Python so the correct packages are used. result = _run( _venv_python(), '-c', 'import sys; sys.path.insert(0, "."); from app import init_db; init_db(); print("ok")', capture=True, ) if 'ok' in result.stdout: _ok('Database ready') else: _warn('Database initialisation produced unexpected output — check manually.') def check_secret_key() -> None: _step('Checking configuration...') try: import json with open(CONFIG, encoding='utf-8') as f: cfg = json.load(f) key = cfg.get('server', {}).get('secret_key', '') if key in ('', 'change-this-to-a-long-random-secret', 'change-me'): _warn( 'config.json still has the default secret_key. ' 'Set a long, random value before deploying to production.' ) else: _ok('Secret key looks good') except (FileNotFoundError, Exception) as e: _warn(f'Could not read config.json: {e}') # ── Entry point ─────────────────────────────────────────────────────────────── def main() -> None: print('─' * 50) print(' Bastebin setup') print('─' * 50) check_python() create_venv() install_dependencies() initialise_database() check_secret_key() print() print('─' * 50) print(' Setup complete.') print() print(' Start the server:') print(' python production.py start') print() print(' Stop the server:') print(' python production.py stop') print('─' * 50) print() if __name__ == '__main__': main()