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>
This commit is contained in:
2026-06-15 15:47:44 -07:00
parent 5e9f9bfb13
commit ccb32e7103
7 changed files with 320 additions and 3 deletions

View File

@@ -1,3 +1,5 @@
import asyncio
import contextlib
from contextlib import asynccontextmanager
from typing import Optional
from fastapi import FastAPI, Form, Depends, HTTPException, APIRouter, WebSocket, WebSocketDisconnect
@@ -14,6 +16,7 @@ import os
from pathlib import Path
from . import crud
from . import maintenance
from .database import run_migrations, get_session, engine
from .ws import manager
@@ -73,7 +76,20 @@ async def lifespan(app: FastAPI):
)
run_migrations()
logger.info("Database migrations applied")
purge_cfg = maintenance.purge_config()
purge_task = None
if purge_cfg.enabled:
purge_task = asyncio.create_task(maintenance.purge_loop(purge_cfg))
else:
logger.info("Game purge task disabled (PIRATS_PURGE_ENABLED)")
yield
if purge_task is not None:
purge_task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await purge_task
logger.info("Shutting down")
app = FastAPI(title="Rats with Gats Remote Play API", lifespan=lifespan)