New maintenance.py runs a background asyncio loop (started from the app lifespan) that purges games which either finished more than PIRATS_PURGE_ FINISHED_DAYS (default 14) ago or have had no activity for PIRATS_PURGE_ INACTIVE_DAYS (default 30). "Activity" is a game's most-recent GameEvent timestamp; games with no events are undatable and left alone. Each purge is logged with the crew name, uuid, reason, idle days and an estimated byte footprint; every run that removes anything VACUUMs the SQLite file and logs the disk space actually reclaimed. Child rows go via the ORM cascade already declared on Game's relationships. Configurable via PIRATS_PURGE_ENABLED / _INTERVAL_HOURS / _FINISHED_DAYS / _INACTIVE_DAYS, exposed as services.pirats.purge.* in the NixOS module and documented in the README. Unit test covers the selection logic and cascade; smoke-tested the VACUUM/reclaim path against a file DB. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rats with Gats Remote Play (pirats)
A web-based remote play companion app for the tabletop roleplaying game Rats with Gats (where players roleplay as gunslinging pirate rats, or "Pi-Rats", in the magical land of Yeld). The application manages lobbies, character creation, card mechanics, scene phases, obstacles, challenges, and player voting. See RULEBOOK.md for the game rules.
It is built with a FastAPI + SQLModel (SQLite) backend and a Svelte 5 single-page frontend (in frontend/), which polls the backend's JSON state endpoint for near-real-time updates.
Features
- Game Lobby: Create games and join as players via a unique game ID.
- Character Creation:
- Fill out the character profile (appearance, smell, catchphrase).
- Delegate custom "Like/Hate" relationship questionnaire cards to other crewmates (answering them for each other!).
- Draft three "Secret Pirate Techniques" which are automatically shuffled and swapped among players.
- Bind swapped techniques to Face Cards (Jack, Queen, King) for play.
- Dynamic Card Mechanics:
- Automated 54-card deck shuffling and card drawing.
- Automatically matches card suits to thematic obstacles (Clubs for combat, Spades for stealth/cunning, Hearts for social/morale, Diamonds for nautical/athletics).
- Scene Management:
- Assign roles (Pi-Rats or "The Deep" narrator).
- Dynamic challenge creation linking multiple active obstacles.
- Automatically handles card matching colors/values, automatic success when playing Face Card techniques, and Joker card triggers to discard/replace obstacles.
- Between-Scenes Voting:
- Vote on crewmates to rank up.
- Toggle personal objectives (Get a Gat, Earn a Name, Die like a Pirate).
Installation & Running
This project supports standard Python package tools as well as Nix flakes.
Option 1: Standard Python Installation
Prerequisites
- Python >= 3.9
- Virtual environment tool (optional but recommended)
1. Setup Virtual Environment (Optional)
python -m venv .venv
source .venv/bin/activate
2. Install Package
Install the package and its dependencies in editable mode:
pip install -e .
3. Run the App
Launch the FastAPI server using the installed package entrypoint:
pirats --host 0.0.0.0 --port 8000 --reload
Alternatively, run it with uvicorn:
uvicorn pirats.main:app --host 0.0.0.0 --port 8000 --reload
Once running, access the web UI at http://localhost:8000.
Configuration (environment variables)
| Variable | Default | Purpose |
|---|---|---|
DATABASE_URL |
sqlite:///./rats_with_gats.db |
SQLAlchemy database URL. |
PIRATS_LOG_FILE |
(unset) | If set, also write logs to this file (rotating, 10 MB × 5 backups). Logs always go to stderr regardless. |
PIRATS_LOG_LEVEL |
INFO |
Minimum log severity (DEBUG/INFO/WARNING/ERROR/CRITICAL). |
PIRATS_DEV_MODE |
(unset = on) | Default Dev Mode for new games. Unset is treated as a local checkout (on); set to 0/false to disable. |
PIRATS_PURGE_ENABLED |
true |
Periodically purge old finished/inactive games. |
PIRATS_PURGE_INTERVAL_HOURS |
24 |
How often the purge task runs. |
PIRATS_PURGE_FINISHED_DAYS |
14 |
Purge games that finished more than this many days ago. |
PIRATS_PURGE_INACTIVE_DAYS |
30 |
Purge games with no activity for this many days. |
Option 2: Using Nix Flakes
If you use the Nix package manager, this repository provides a complete environment.
1. Enter the Development Shell
Includes Python, all runtime dependencies, and testing tools (pytest):
nix develop
2. Run the Application
Run the package directly from the flake:
nix run . -- --host 0.0.0.0 --port 8000
3. Build the Application Package
nix build .
The executable will be located in ./result/bin/pirats.
Deploying as a NixOS Service
The Nix flake exposes a NixOS module that configures the application to run as a systemd service with appropriate sandboxing.
Add this flake to your system configuration and configure the service:
services.pirats = {
enable = true;
host = "127.0.0.1";
port = 8000;
databasePath = "/var/lib/pirats/rats_with_gats.db";
logFile = "/var/log/pirats/pirats.log"; # rotating; stderr also goes to the journal
logLevel = "info";
openFirewall = false; # Set to true to open ports in the firewall
purge = {
enable = true; # periodically remove old finished/inactive games
intervalHours = 24;
finishedDays = 14; # purge games finished more than 14 days ago
inactiveDays = 30; # ...or with no activity for 30 days
};
};
The service writes its log to logFile (under the systemd-managed /var/log/pirats
LogsDirectory, so the sandboxed DynamicUser can write to it) and additionally
to the systemd journal via stderr (journalctl -u pirats).
Running Tests
Tests are written using pytest.
Running with Pip
pytest
Running with Nix
nix develop --command pytest
Project Structure
pirats/
├── pyproject.toml # Python package metadata and dependencies
├── flake.nix # Nix package (incl. frontend build), dev shell, NixOS service module
├── tests/
│ └── test_game.py # Game logic and CRUD test suite (pure-crud, no HTTP for most)
├── frontend/ # Svelte 5 + Vite SPA
│ └── src/
│ ├── pages/ # Routes: Home, Join, Dashboard (game UI), Admin
│ ├── components/ # One component per game phase, Card.svelte, and scene/ (ScenePhase sub-panels)
│ └── lib/ # api.js (fetch wrapper), cards.js (card display helpers), suggestions.js (Suggest-button pools)
└── src/
└── pirats/ # FastAPI backend package
├── main.py # App setup, /api/game create/join/state endpoints, CLI entrypoint
├── models.py # SQLModel tables (Game, Player, Obstacle, Challenge, Vote, GameEvent)
├── cards.py # Card parsing, deck building, obstacle table from the rulebook
├── crud.py # Facade re-exporting all crud_* modules
├── crud_*.py # Game logic by phase: base (deck/hands/rank), character, scene, challenge, upkeep
├── routes_*.py # Thin API routers by phase, mounted under /api
└── static/ # Built frontend lands here (gitignored; populated by the Nix build)
Frontend Development
Run the backend (pirats --reload) and the Vite dev server side by side; Vite proxies /api to port 8000:
cd frontend && npm install && npm run dev