Files
pirats/README.md
Tim McCarthy ccb32e7103 Periodic purge of old finished/inactive games
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>
2026-06-15 15:47:44 -07:00

6.8 KiB
Raw Blame History

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