From 5d837c55010baa7f8eba704a07aca3c687739f6e Mon Sep 17 00:00:00 2001 From: th3r00t Date: Sun, 7 Sep 2025 13:05:08 -0400 Subject: [PATCH] Restore missing project configuration files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restored all configuration and documentation files that were missing: - devenv.nix and devenv.lock for development environment - README.md, CLAUDE.md, DOCKER.md, WARP.md for documentation - alembic.ini for database migrations - requirements.txt for Python dependencies - Dockerfile, docker-compose.yml, entrypoint.sh for containerization - build.sh for build automation - pytest.ini for test configuration All database concurrency improvements and logging fixes remain intact. ๐Ÿค– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CLAUDE.md | 100 +++++++++++++++++++++++ DOCKER.md | 112 ++++++++++++++++++++++++++ Dockerfile | 51 ++++++++++++ README.md | 197 +++++++++++++++++++++++++++++++++++++++++++++ WARP.md | 1 + alembic.ini | 94 +++++++++++++++++++++ build.sh | 2 + devenv.lock | 103 ++++++++++++++++++++++++ devenv.nix | 103 ++++++++++++++++++++++++ docker-compose.yml | 29 +++++++ entrypoint.sh | 64 +++++++++++++++ pytest.ini | 3 + requirements.txt | 24 ++++++ 13 files changed, 883 insertions(+) create mode 100644 CLAUDE.md create mode 100644 DOCKER.md create mode 100644 Dockerfile create mode 100644 README.md create mode 120000 WARP.md create mode 100644 alembic.ini create mode 100755 build.sh create mode 100644 devenv.lock create mode 100644 devenv.nix create mode 100644 docker-compose.yml create mode 100755 entrypoint.sh create mode 100644 pytest.ini create mode 100644 requirements.txt diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..51b8a5d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,100 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Development Commands + +The project uses devenv.nix for development environment management. All commands should be run from the repository root: + +- `tests` - Run pytest with coverage +- `lint` - Check code with ruff and black +- `fix` - Auto-fix code issues with ruff and black +- `typecheck` - Run pyright type checking +- `run` - Execute the main ROM scraper application +- `serve` - Start the FastAPI web server +- `create-admin` - Create initial admin user for web interface +- `migrate` - Database migration management (see Migration Commands) +- `db-init` - Initialize database schema (first-time setup) +- `db-upgrade` - Apply pending database migrations +- `db-create` - Create new migration (requires message argument) +- `build` - Build the application using build.sh (creates zipapp in release/) + +## Architecture + +This is a Python ROM metadata scraper and web-based ROM management system for DOS games: + +### Core Components + +- **Main Application** (`src/__main__.py`): Async scraper that scans ROM directories, fetches metadata from IGDB API, and stores everything in SQLite +- **Web Application** (`src/webapp.py`): FastAPI server with user authentication, ROM browsing, downloads, and admin interface +- **Configuration** (`src/libs/config.py`): XDG-compliant config management with automatic setup prompts +- **Database Layer** (`src/libs/database.py`): SQLAlchemy models with many-to-many relationships for games, metadata, genres, tags, and users +- **Authentication** (`src/libs/auth.py`): JWT-based auth with bcrypt password hashing and role-based access control +- **Data Models** (`src/libs/objects.py`): Dataclasses for Game, Metadata, and Roms collections +- **API Integration** (`src/libs/apis.py`): IGDB API client with Twitch OAuth authentication +- **Utilities** (`src/libs/functions.py`): Title cleaning and year extraction from ROM filenames + +### Data Flow + +**ROM Scraping:** +1. Compares filesystem ROMs with database entries to avoid re-indexing +2. Authenticates with IGDB via Twitch OAuth using client credentials +3. Scrapes metadata for new games only with rate limiting (4 concurrent requests) +4. Stores normalized data in SQLite with proper foreign key relationships +5. Handles duplicate games and metadata updates gracefully + +**Web Interface:** +1. FastAPI serves modern responsive web interface with Tailwind CSS +2. JWT-based authentication with three user roles: demo, normal, super +3. Demo users can browse but not download; normal users get full access; super users can manage everything +4. Pagination, favorites system, and file downloads for authorized users +5. Admin interface for user management and metadata editing + +### Key Technical Details + +- Uses asyncio with semaphore-based rate limiting for API requests +- SQLAlchemy with declarative base and proper naming conventions +- FastAPI with Jinja2 templates, JWT authentication, and role-based access control +- Configuration supports both environment variables and .env files +- Custom PathType for storing pathlib.Path objects in database +- Batch processing for database operations with configurable batch sizes +- Modern responsive UI with Tailwind CSS and Alpine.js for interactivity + +## Database Migrations + +The project uses Alembic for database schema versioning and migrations: + +### First-Time Setup +```bash +db-init # Initialize database with current schema +migrate stamp # Mark database as up-to-date with migrations +``` + +### Migration Management +```bash +migrate create "description" # Create new migration file +migrate upgrade # Apply all pending migrations +migrate current # Show current database revision +migrate history # Show migration history +migrate check # Check database migration status +``` + +### Schema Changes +1. Modify models in `src/libs/database.py` +2. Create migration: `migrate create "description of changes"` +3. Review generated migration file in `migrations/versions/` +4. Apply migration: `migrate upgrade` + +### Migration Files +- Located in `migrations/versions/` +- Named with revision ID and description +- Contain `upgrade()` and `downgrade()` functions +- Support batch operations for SQLite compatibility + +## Environment Setup + +Requires IGDB API credentials: +- `IGDB_CLIENT_ID` - Twitch client ID +- `IGDB_SECRET_KEY` - Twitch client secret + +Can be provided via environment variables or `.env` file in project root. diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..5061537 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,112 @@ +# DosVault Docker Deployment + +## Quick Start + +1. **Copy the environment template:** + ```bash + cp .env.example .env + ``` + +2. **Edit `.env` with your configuration:** + - Set `IGDB_CLIENT_ID` and `IGDB_SECRET_KEY` (required) + - Set `ROMS_PATH` to your ROM collection directory + - Set `DOSVAULT_ADMIN_USERNAME` to you admin username + - Set `DOSVAULT_ADMIN_EMAIL` to your admin email + - Set `DOSVAULT_ADMIN_PASSWORD` to your admin password + - Optionally customize host/port settings + +3. **Start the application:** + ```bash + docker-compose up -d + ``` + +4. **Create admin user:** + ```bash + docker-compose exec dosvault python src/create_admin.py + ``` + +5. **Access the application:** + - Web interface: http://localhost:8080 + - Admin panel: http://localhost:8080/admin + +## Configuration + +### Environment Variables + +| Variable | Required | Description | +|----------|----------|-------------| +| `IGDB_CLIENT_ID` | Yes | Twitch API Client ID | +| `IGDB_SECRET_KEY` | Yes | Twitch API Client Secret | +| `ROMS_PATH` | No | Path to ROM collection (default: ./roms) | +| `DOSFRONTEND_CONFIG_DIR` | No | Application data directory (default: /app/data) | + +### Configuration Persistence + +Configuration changes made through the web interface are automatically persisted to the mounted volume: + +- **In Docker**: Configuration is stored in `/app/data/config.json` (mounted volume) +- **Regular install**: Configuration is stored in `~/.config/dosfrontend/config.json` +- **File structure**: All application data uses the same base directory: + - `config.json` - Main configuration file + - `roms.db` - SQLite database + - `images/` - Downloaded game artwork + - `logs/` - Application logs + +### Volume Mounts + +- `dosvault_data:/app/data` - Application data (database, images, logs) +- `${ROMS_PATH}:/app/data/roms:ro` - ROM collection (read-only) + +## Database Management + +### Initialize Database +```bash +docker-compose exec dosvault python src/migrate.py init +``` + +### Run Migrations +```bash +docker-compose exec dosvault python src/migrate.py upgrade +``` + +### Scrape ROM Metadata +```bash +docker-compose exec dosvault python -m src +``` + +## Maintenance + +### View Logs +```bash +docker-compose logs -f dosvault +``` + +### Backup Database +```bash +docker-compose exec dosvault cp /app/data/roms.db /app/data/backup.db +docker cp $(docker-compose ps -q dosvault):/app/data/backup.db ./backup.db +``` + +### Update Application +```bash +docker-compose pull +docker-compose up -d +``` + +## Troubleshooting + +### Check Container Health +```bash +docker-compose ps +``` + +### Access Container Shell +```bash +docker-compose exec dosvault bash +``` + +### Reset Data +```bash +docker-compose down -v +docker-compose up -d +``` diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9b1c0fc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +# Multi-stage Docker build for DosVault +FROM python:3.11-slim as base + +# Set working directory +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + curl \ + gcc \ + g++ \ + && rm -rf /var/lib/apt/lists/* + +# Copy Python dependencies +COPY requirements.txt* pyproject.toml* ./ + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt || \ + pip install --no-cache-dir fastapi uvicorn sqlalchemy alembic \ + aiohttp bcrypt python-jose python-multipart jinja2 + +# Copy application code +COPY src/ ./src/ +COPY templates/ ./templates/ +COPY migrations/ ./migrations/ +COPY alembic.ini ./ +COPY CLAUDE.md README.md ./ +COPY entrypoint.sh ./ + +# Create necessary directories +RUN mkdir -p /app/data/logs /app/data/images /app/data/roms /app/data/metadata + +# Set environment variables +ENV PYTHONPATH=/app/src +ENV DOSFRONTEND_CONFIG_DIR=/app/data + +# Expose ports +EXPOSE 8080 8081 + +# Create non-root user +RUN useradd -m -u 1000 dosvault && \ + chown -R dosvault:dosvault /app +USER dosvault + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8080/health || exit 1 + +# Default command +# CMD ["python", "-m", "uvicorn", "src.webapp:app", "--host", "0.0.0.0", "--port", "8080"] +ENTRYPOINT ["./entrypoint.sh"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..e4a23f5 --- /dev/null +++ b/README.md @@ -0,0 +1,197 @@ +# ๐ŸŽฎ DosVault + +**Your Personal DOS Game Collection Manager** + +DosVault is a modern, web-based collection manager for DOS games that combines powerful metadata scraping with an intuitive browsing experience. Built with Python and FastAPI, it helps you organize, discover, and manage your retro gaming library with style. + +## โœจ Features + +### ๐ŸŽฏ Core Functionality +- **Automatic Metadata Scraping** - Pulls game information, cover art, and screenshots from IGDB API +- **Local Image Storage** - Downloads and caches all images locally for fast loading +- **Intelligent ROM Detection** - Scans directories and avoids re-indexing existing games +- **Advanced Search & Filtering** - Find games by title, genre, developer, or description +- **Genre & Tag Browsing** - Organized categorization with alphabetical sorting + +### ๐ŸŒ Modern Web Interface +- **Responsive Design** - Works beautifully on desktop, tablet, and mobile +- **Multiple View Modes** - Switch between grid and list views +- **Interactive Screenshots** - Click to view full-screen image galleries +- **Smart Pagination** - Navigate large collections with ease +- **Real-time Favorites** - Heart games to build your personal collection + +### ๐Ÿ” User Management +- **Role-Based Access Control** - Demo, Normal, and Super Admin roles +- **Secure Authentication** - JWT-based auth with bcrypt password hashing +- **Personal Favorites** - Each user maintains their own favorites list +- **Admin Dashboard** - User management and system overview + +### ๐Ÿ“ฑ Mobile-First +- **Hamburger Navigation** - Clean mobile menu system +- **Touch-Optimized** - Large buttons and smooth interactions +- **Responsive Controls** - Pagination and filters work great on mobile + +## ๐Ÿš€ Quick Start + +### Prerequisites +- Python 3.11+ +- [Devenv](https://devenv.sh/) (recommended) or manual dependency management +- IGDB API credentials (free from Twitch Developer Console) + +### Installation + +1. **Clone the repository:** + ```bash + git clone + cd dosfrontend + ``` + +2. **Set up environment:** + ```bash + # With devenv (recommended) + devenv shell + + # Or manually install dependencies + pip install fastapi uvicorn sqlalchemy alembic bcrypt python-jose aiohttp + ``` + +3. **Configure IGDB API:** + Create a `.env` file with your IGDB credentials: + ```env + IGDB_CLIENT_ID=your_twitch_client_id + IGDB_SECRET_KEY=your_twitch_client_secret + ``` + +4. **Initialize database:** + ```bash + db-init + create-admin # Create your first admin user + ``` + +5. **Run the application:** + ```bash + serve # Starts web server + run # Runs ROM scraper (optional) + ``` + +6. **Access DosVault:** + Open http://localhost:8080 in your browser + +## ๐Ÿ“ Project Structure + +``` +dosfrontend/ +โ”œโ”€โ”€ src/ +โ”‚ โ”œโ”€โ”€ __main__.py # ROM scraper application +โ”‚ โ”œโ”€โ”€ webapp.py # FastAPI web server +โ”‚ โ””โ”€โ”€ libs/ +โ”‚ โ”œโ”€โ”€ config.py # XDG-compliant configuration +โ”‚ โ”œโ”€โ”€ database.py # SQLAlchemy models +โ”‚ โ”œโ”€โ”€ auth.py # JWT authentication +โ”‚ โ”œโ”€โ”€ apis.py # IGDB API integration +โ”‚ โ””โ”€โ”€ functions.py # Utility functions +โ”œโ”€โ”€ templates/ # Jinja2 HTML templates +โ”œโ”€โ”€ migrations/ # Database schema versions +โ”œโ”€โ”€ devenv.nix # Development environment +โ””โ”€โ”€ CLAUDE.md # Development guidance +``` + +## ๐ŸŽฎ Usage + +### Scraping ROMs +```bash +# Scan ROM directories and fetch metadata +run +``` + +### Web Interface +```bash +# Start the web server +serve +``` + +### Database Management +```bash +# Create migrations +migrate create "description of changes" + +# Apply migrations +migrate upgrade + +# Check migration status +migrate current +``` + +### Administration +```bash +# Create admin user +create-admin + +# Run tests +tests + +# Code quality +lint +typecheck +``` + +## โš™๏ธ Configuration + +DosVault uses XDG-compliant configuration stored in: +- **Linux/Mac:** `~/.config/dosfrontend/` +- **Windows:** `%APPDATA%/dosfrontend/` + +Key configuration options: +- ROM directories to scan +- Image storage location +- Database path +- Web server host/port +- IGDB API credentials + +## ๐Ÿ—๏ธ Architecture + +### Backend +- **FastAPI** - Modern Python web framework +- **SQLAlchemy** - Database ORM with proper relationships +- **Alembic** - Database migration management +- **AsyncIO** - Concurrent API requests with rate limiting +- **JWT + BCrypt** - Secure authentication + +### Frontend +- **Jinja2** - Server-side templating +- **Tailwind CSS** - Utility-first styling +- **Alpine.js** - Lightweight JavaScript framework +- **Responsive Design** - Mobile-first approach + +### Data Flow +1. **Scraper** scans ROM directories and compares with database +2. **IGDB API** provides metadata via Twitch OAuth +3. **Images** are downloaded and cached locally +4. **Web interface** serves games with fast local assets +5. **Users** browse, search, and manage favorites + +## ๐Ÿค Contributing + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Make your changes +4. Run tests and linting (`tests`, `lint`) +5. Commit your changes (`git commit -m 'Add amazing feature'`) +6. Push to the branch (`git push origin feature/amazing-feature`) +7. Open a Pull Request + +## ๐Ÿ“ License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## ๐Ÿ™ Acknowledgments + +- **IGDB** for providing comprehensive game metadata +- **Twitch** for OAuth authentication to IGDB API +- **FastAPI** for the excellent modern Python web framework +- **Tailwind CSS** for making responsive design a breeze +- **DOSBox** community for keeping retro gaming alive + +--- + +**Built with โค๏ธ for retro gaming enthusiasts** \ No newline at end of file diff --git a/WARP.md b/WARP.md new file mode 120000 index 0000000..681311e --- /dev/null +++ b/WARP.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000..927da65 --- /dev/null +++ b/alembic.ini @@ -0,0 +1,94 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = migrations + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version number format to use with the --rev-id parameter +# to specify a starting revision +# version_num_format = %04d + +# version_path_separator = : +# version_path_separator = os # Use os.pathsep. Default configuration used on new projects. + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..20edfe0 --- /dev/null +++ b/build.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +python -m zipapp src --compress --output=release/dfe --python="/usr/bin/env python" diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..7dce35e --- /dev/null +++ b/devenv.lock @@ -0,0 +1,103 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1756415044, + "owner": "cachix", + "repo": "devenv", + "rev": "c570189b38b549141179647da3ddde249ac50fec", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1747046372, + "owner": "edolstra", + "repo": "flake-compat", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1755960406, + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "e891a93b193fcaf2fc8012d890dc7f0befe86ec2", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1755783167, + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "4a880fb247d24fbca57269af672e8f78935b0328", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "git-hooks": "git-hooks", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": [ + "git-hooks" + ] + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..d947692 --- /dev/null +++ b/devenv.nix @@ -0,0 +1,103 @@ +{ pkgs, lib, config, inputs, ... }: + +{ + # https://devenv.sh/basics/ + + # https://devenv.sh/packages/ + packages = with pkgs; [ + git + curl + pkg-config + sqlite + pyright + pre-commit + ]; + languages.python = { + enable = true; + package = pkgs.python313; + libraries = with pkgs.python313Packages; [ ]; + venv = { + enable = true; + requirements = '' + pudb + ptpython + ipython + pytest + pytest-cov + flake8 + ptpython + ipython + isort + pynvim + ruff + black + sqlalchemy + requests + fastapi + uvicorn + jinja2 + python-multipart + bcrypt + python-jose + passlib + alembic + aiohttp + ''; + }; + # uv = { + # enable = false; + # sync.enable = true; + # }; + }; + env = { + PYTHONBREAKPOINT = "pudb.set_trace"; + }; + + # https://devenv.sh/variables/ + # variables = { + # GREET = "world"; + # }; + + # https://devenv.sh/scripts/ + scripts = { + "tests".exec = "cd $REPO_ROOT && python -m pytest --rootdir=$REPO_ROOT -c $REPO_ROOT/pytest.ini"; + "lint".exec = "cd $REPO_ROOT && ${pkgs.ruff}/bin/ruff check . && black --check ."; + "fix".exec = "cd $REPO_ROOT && ${pkgs.ruff}/bin/ruff check . --fix && black ."; + "typecheck".exec = "cd $REPO_ROOT && pyright"; + "run".exec = ''cd $REPO_ROOT && ./src/__main__.py "$@"''; + "serve".exec = "cd $REPO_ROOT && python src/webapp.py"; + "create-admin".exec = "cd $REPO_ROOT && python src/create_admin.py"; + "migrate".exec = "cd $REPO_ROOT && python src/migrate.py"; + "db-init".exec = "cd $REPO_ROOT && python src/migrate.py init"; + "db-upgrade".exec = "cd $REPO_ROOT && python src/migrate.py upgrade"; + "db-create".exec = "cd $REPO_ROOT && python src/migrate.py create"; + "build".exec = "cd $REPO_ROOT && ./build.sh"; + "backfill-images".exec = "cd $REPO_ROOT && python src/backfill_images.py"; + "export-requirements".exec = "pip freeze > requirements.txt"; + "export-reqs".exec = '' + cd "$REPO_ROOT" + printf "%s\n" '${config.languages.python.venv.requirements}' > requirements.txt + echo "Wrote requirements.txt" + ''; + }; + enterShell = '' + export REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" + ''; + + # https://devenv.sh/tasks/ + # tasks = { + # "myproj:setup".exec = "mytool build"; + # "devenv:enterShell".after = [ "myproj:setup" ]; + # }; + + # https://devenv.sh/tests/ + enterTest = '' + echo "Running tests" + pytest -q + ''; + + # https://devenv.sh/git-hooks/ + # git-hooks.hooks.shellcheck.enable = true; + + # See full reference at https://devenv.sh/reference/options/ +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..74ace96 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +services: + dosvault: + image: tty303/dosvault:latest + ports: + - "${DOSVAULT_PORT:-8080}:8080" + - "${DOSVAULT_WEBSOCKET_PORT:-8081}:8081" + volumes: + - dosvault_data:/app/data + - "${ROMS_PATH:-./roms}:/app/data/roms:ro" + environment: + # IGDB API Configuration + - IGDB_CLIENT_ID=${IGDB_CLIENT_ID} + - IGDB_SECRET_KEY=${IGDB_SECRET_KEY} + # Application Configuration + - DOSFRONTEND_CONFIG_DIR=/app/data + - DOSVAULT_ADMIN_USERNAME=${DOSVAULT_ADMIN_USERNAME:-} + - DOSVAULT_ADMIN_EMAIL=${DOSVAULT_ADMIN_EMAIL:-} + - DOSVAULT_ADMIN_PASSWORD=${DOSVAULT_ADMIN_PASSWORD:-} + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +volumes: + dosvault_data: + driver: local diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..4a6db6c --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail + +DATA_DIR="${DOSFRONTEND_CONFIG_DIR:-/app/data}" +DB_PATH="${DATA_DIR}/roms.db" + +# Make sure data dir exists & is writable for uid 1000 +mkdir -p "$DATA_DIR" "$DATA_DIR/images" "$DATA_DIR/logs" "$DATA_DIR/roms" "$DATA_DIR/metadata" +# Attempt to fix perms when volume mounted as root +if [ "$(id -u)" -ne 0 ]; then + # we're not root; try a writable touch to detect perms + if ! touch "$DATA_DIR/.permcheck" 2>/dev/null; then + echo "WARNING: $DATA_DIR not writable by current user. Consider running as root or fixing volume ownership." + else + rm -f "$DATA_DIR/.permcheck" + fi +else + chown -R 1000:1000 "$DATA_DIR" || true +fi + +# Initialize / migrate DB +if [ ! -f "$DB_PATH" ]; then + echo "No database found. Initializingโ€ฆ" + python /app/src/migrate.py init || true +fi +# Always try to move to latest +python /app/src/migrate.py upgrade || true + +# Non-interactive admin creation (optional) +if [ "${DOSVAULT_ADMIN_USERNAME:-}" ] && [ "${DOSVAULT_ADMIN_EMAIL:-}" ] && [ "${DOSVAULT_ADMIN_PASSWORD:-}" ]; then +python - <<'PY' +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from os import getenv +from libs.config import Config +from libs.database import User_table, UserRole +from libs.auth import AuthManager + +cfg=Config() +engine=create_engine(f"sqlite+pysqlite:///{cfg.database_path}") +Session=sessionmaker(bind=engine) +db=Session() +try: + existing = db.query(User_table).filter(User_table.role==UserRole.SUPER.value).first() + if not existing: + AuthManager.create_user( + db, + getenv("DOSVAULT_ADMIN_USERNAME"), + getenv("DOSVAULT_ADMIN_EMAIL"), + getenv("DOSVAULT_ADMIN_PASSWORD"), + UserRole.SUPER.value + ) + print("Admin user created.") + else: + print(f"Admin already exists: {existing.username}") +finally: + db.close() +PY +fi +# Execute rom scan +exec python ./src/__main__.py || true & +# Run app +exec python -m uvicorn src.webapp:app --host 0.0.0.0 --port 8080 + diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..d90f291 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +addopts = --cov=src --cov-report=term-missing --ignore=src/__main__.py +testpaths = tests/ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4b53187 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,24 @@ + pudb + ptpython + ipython + pytest + pytest-cov + flake8 + ptpython + ipython + isort + pynvim + ruff + black + sqlalchemy + requests + fastapi + uvicorn + jinja2 + python-multipart + bcrypt + python-jose + passlib + alembic + aiohttp +