Files
Ferrous-Solitaire/README_SERVER.md
T
funman300 d87397b382
Test / test (pull_request) Successful in 10m15s
feat: in-game theme store — server catalog + verified downloads + install UI
Phase 1+2 of the theme-store roadmap: a free catalog served by
solitaire_server and an in-app browse/install flow, making custom
themes installable on Android for the first time (the manual
drop-a-zip flow can't reach the app-private themes dir there).

- solitaire_sync: ThemeCatalogEntry/ThemeCatalogResponse wire types
  (additive module; SyncPayload and SyncProvider untouched)
- solitaire_server: THEME_STORE_DIR scan at startup (meta-only
  theme.ron parse, sha256, 20 MiB cap, best-effort per archive);
  public GET /api/themes, /api/themes/{id}/download, /{id}/preview;
  compose volume + README_SERVER docs
- solitaire_data: ThemeStoreClient — catalog fetch + download with
  mandatory size/sha256 verification before bytes are released
- solitaire_engine: ThemeStorePlugin — 'Browse theme store' button in
  Settings → Cosmetic, modal catalog (leaderboard-style rebuild),
  download on AsyncComputeTaskPool, atomic .tmp+rename write into
  user_theme_dir, then the existing hardened import_theme pipeline and
  an in-place registry refresh

New deps: sha2 (workspace, server+data); ron/zip reused in server;
serde_json added to solitaire_sync dev-deps for DTO round-trip tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:12:41 -07:00

2.8 KiB

Ferrous Solitaire — Self-Hosting Guide

Prerequisites

  • Docker and Docker Compose
  • openssl for generating a JWT secret

Quick start

  1. Clone the repo and enter it.
  2. Copy the example environment file and fill in your values:
    cp .env.example .env
    # Edit .env: set JWT_SECRET and SOLITAIRE_DOMAIN
    
  3. (First time only) Generate the sqlx query cache so the server builds without a live database:
    cargo install sqlx-cli --no-default-features --features rustls,sqlite
    export DATABASE_URL=sqlite://solitaire.db
    sqlx database create
    sqlx migrate run --source solitaire_server/migrations
    cargo sqlx prepare --workspace
    rm solitaire.db   # the real DB lives in ./data/ at runtime
    
  4. Start everything:
    docker compose up -d
    
  5. The server is now reachable at https://<SOLITAIRE_DOMAIN>.

Backups

The entire server state is one SQLite file at ./data/solitaire.db. Back it up with:

sqlite3 ./data/solitaire.db ".backup backup_$(date +%Y%m%d).db"

Updating

git pull
docker compose build
docker compose up -d

Theme store

The server can offer card-art themes for in-game download. Drop theme .zip archives (the same format the game's Settings → Import accepts: a theme.ron manifest plus 53 SVGs) into the directory named by THEME_STORE_DIR (default: theme_store/ next to the binary), then restart the server — the catalog is scanned once at startup. An optional <theme-id>.png in the same directory becomes the theme's store preview.

Endpoints (public, no auth):

  • GET /api/themes — catalog JSON (id, name, author, size, sha256)
  • GET /api/themes/<id>/download — the archive
  • GET /api/themes/<id>/preview — the preview PNG, if present

Archives that are oversized (> 20 MiB), unreadable, or have a malformed theme.ron are skipped with a warning in the server log; they never fail startup.

Admin — Password Reset

If a player loses access to their account, the server binary includes a built-in password reset command. Run it on the host (or inside the container) with DATABASE_URL pointing at your database:

# Interactive (prompts for the new password):
DATABASE_URL=sqlite://./data/solitaire.db \
  ./solitaire_server --reset-password <username>

# Non-interactive (piped from a script or password manager):
echo "new_password" | \
  DATABASE_URL=sqlite://./data/solitaire.db \
    ./solitaire_server --reset-password <username>

# Inside a running Docker container:
docker compose exec server sh -c \
  'echo "new_password" | ./solitaire_server --reset-password alice'

On success the user's password_hash is updated and all active refresh tokens are deleted, so every open session must log in again with the new password. JWT_SECRET does not need to be set for this command.