Completes the Go backend so it can fully replace Python in production: tracker-go website layer (serves the unchanged frontend): - static file serving + SPA fallback + /icons (website.go) - login/logout with itsdangerous cookie ISSUING (bcrypt, Python-interop) and the /me handler (auth.go issueSessionCookie + website.go) - admin user CRUD (website_admin.go) and the issue-board write side (website_issues.go) - request-scoped user context + requireAdmin (auth.go) cutover ingest (gated off during the parallel run, required for a clean cutover): - inventory forwarding: full_inventory -> /process-inventory, inventory_delta -> item POST/DELETE, per-character serialized, fire-and-forget (inventory_forward.go) - death/idle Discord alerts via DISCORD_ACLOG_WEBHOOK (aclog.go) - SKIP_SCHEMA_INIT so write mode against the prod DBs runs no DDL (tracker-go + inventory-go) two bugs found live and fixed: - coerceNum: the plugin sends kills_per_hour/deaths/total_deaths/prismatic_taper_count as STRINGS; pydantic coerced them, Go's number helpers wrote null/0 (reads.go/ingest.go) - telemetry is broadcast TYPELESS so the browser ignores it and uses the /live poll; broadcasting it typed flapped the per-player counters 0<->value (ingest.go stripType) docker-compose.cutover.yml: reversible override flipping the Go services to write mode against the production DBs and repointing the Discord bot at the Go /ws/live. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
212 lines
7.8 KiB
YAML
212 lines
7.8 KiB
YAML
# Compose OVERRIDE that adds the Go services alongside the live Python stack.
|
|
# It only ADDS containers; it never modifies the tracked docker-compose.yml or
|
|
# any running Python service.
|
|
#
|
|
# Invoke from the repo root so the Compose project name resolves to
|
|
# "mosswartoverlord" (same as the live stack) and the new container joins the
|
|
# existing default network — letting it reach the `db` service by name:
|
|
#
|
|
# cd /home/erik/MosswartOverlord
|
|
# export BUILD_VERSION="$(date -u +%Y.%-m.%-d.%H%M)-$(git rev-parse --short HEAD)"
|
|
# docker compose -f docker-compose.yml -f go-services/docker-compose.go.yml \
|
|
# build dereth-tracker-go
|
|
# docker compose -f docker-compose.yml -f go-services/docker-compose.go.yml \
|
|
# up -d --no-deps dereth-tracker-go
|
|
#
|
|
# --no-deps keeps Compose from touching the already-running `db` (and anything
|
|
# else). The service is loopback-bound (127.0.0.1:8770); external reach is only
|
|
# ever via the host nginx `location /go/` block (added separately).
|
|
services:
|
|
dereth-tracker-go:
|
|
build:
|
|
context: ./go-services/tracker-go
|
|
args:
|
|
BUILD_VERSION: ${BUILD_VERSION:-dev}
|
|
image: dereth-tracker-go:local
|
|
container_name: dereth-tracker-go
|
|
ports:
|
|
- "127.0.0.1:8770:8770"
|
|
environment:
|
|
PORT: "8770"
|
|
# Read-only use of the same dereth TimescaleDB the Python tracker writes.
|
|
DATABASE_URL: "postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/dereth"
|
|
# Point at the Go inventory service so the /go/ read stack is fully Go
|
|
# end-to-end (browser -> Go tracker -> Go inventory -> read-only prod DBs).
|
|
# inventory-go is read-only against the production inventory_db.
|
|
INVENTORY_SERVICE_URL: "http://inventory-go:8772"
|
|
# Same signing key as the Python tracker so the same login cookie verifies
|
|
# on both during the parallel run.
|
|
SECRET_KEY: "${SECRET_KEY}"
|
|
# Serve the (unchanged) frontend from the same static/ the Python tracker
|
|
# serves — needed for the full cutover (login, index.html, assets, icons).
|
|
STATIC_DIR: "/static"
|
|
LOG_LEVEL: "INFO"
|
|
volumes:
|
|
- ./static:/static:ro
|
|
# Issue board is a flat file the tracker writes; mount it read-write
|
|
# (more specific than the :ro static mount above, so it wins).
|
|
- ./static/openissues.json:/static/openissues.json
|
|
depends_on:
|
|
- db
|
|
restart: unless-stopped
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
|
|
# Go port of discord-rare-monitor. Consumes the SAME Python /ws/live firehose
|
|
# as the live Python bot. DRY-RUN by default (logs classifications, posts
|
|
# nothing) so it can't double-post. To parallel-test for real, set a TEST
|
|
# DISCORD_RARE_BOT_TOKEN + TEST channel IDs + DRY_RUN=0 here.
|
|
discord-rare-monitor-go:
|
|
build:
|
|
context: ./go-services/discord-go
|
|
args:
|
|
BUILD_VERSION: ${BUILD_VERSION:-dev}
|
|
container_name: discord-rare-monitor-go
|
|
environment:
|
|
DERETH_TRACKER_WS_URL: "ws://dereth-tracker:8765/ws/live"
|
|
MONITOR_CHARACTER: "Dunking Rares"
|
|
ICONS_DIR: "/icons"
|
|
LOG_LEVEL: "INFO"
|
|
# DISCORD_RARE_BOT_TOKEN: "" # set a TEST token to go live
|
|
# DRY_RUN: "0" # required (with a token) to actually post
|
|
# COMMON_RARE_CHANNEL_ID / GREAT_RARE_CHANNEL_ID / SAWATOLIFE_CHANNEL_ID /
|
|
# ACLOG_CHANNEL_ID: set TEST channels before going live
|
|
volumes:
|
|
- ./discord-rare-monitor/icons:/icons:ro
|
|
depends_on:
|
|
- dereth-tracker
|
|
restart: unless-stopped
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
|
|
# ---- Phase 2: shadow ingest (fully isolated; production never touched) ----
|
|
|
|
# A SEPARATE TimescaleDB the Go tracker owns for shadow ingest. Isolated
|
|
# volume + loopback port; the production dereth DB is never written.
|
|
dereth-go-db:
|
|
image: timescale/timescaledb:2.19.3-pg14
|
|
container_name: dereth-go-db
|
|
ports:
|
|
- "127.0.0.1:5434:5432"
|
|
environment:
|
|
POSTGRES_DB: "dereth_go"
|
|
POSTGRES_USER: "postgres"
|
|
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
|
volumes:
|
|
- dereth-go-data:/var/lib/postgresql/data
|
|
restart: unless-stopped
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
|
|
# Shadow tracker instance: same image, but OWNS dereth-go-db (read-write) and
|
|
# (once ingest lands) consumes the Python /ws/live firehose into it, so its
|
|
# ingest output can be compared against production without writing to it.
|
|
dereth-tracker-go-shadow:
|
|
image: dereth-tracker-go:local
|
|
container_name: dereth-tracker-go-shadow
|
|
ports:
|
|
- "127.0.0.1:8771:8771"
|
|
environment:
|
|
PORT: "8771"
|
|
DATABASE_URL: "postgresql://postgres:${POSTGRES_PASSWORD}@dereth-go-db:5432/dereth_go"
|
|
READ_ONLY: "false" # owns its DB; creates schema on boot
|
|
INVENTORY_SERVICE_URL: "http://inventory-service:8000"
|
|
SECRET_KEY: "${SECRET_KEY}"
|
|
SHARED_SECRET: "${SHARED_SECRET}" # /ws/position plugin auth (cutover-ready)
|
|
SHARED_SECRET_LEGACY: "${SHARED_SECRET_LEGACY:-}"
|
|
# Replay the Python /ws/live firehose into the ingest handlers (shadow).
|
|
SHADOW_INGEST_WS: "ws://dereth-tracker:8765/ws/live"
|
|
LOG_LEVEL: "INFO"
|
|
depends_on:
|
|
- dereth-go-db
|
|
restart: unless-stopped
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
|
|
# Go port of inventory-service. Phase A: read side, READ-ONLY against the
|
|
# production inventory_db, validated vs the Python service. Loopback :8772.
|
|
inventory-go:
|
|
build:
|
|
context: ./go-services/inventory-go
|
|
args:
|
|
BUILD_VERSION: ${BUILD_VERSION:-dev}
|
|
image: inventory-go:local
|
|
container_name: inventory-go
|
|
ports:
|
|
- "127.0.0.1:8772:8772"
|
|
environment:
|
|
PORT: "8772"
|
|
DATABASE_URL: "postgresql://inventory_user:${INVENTORY_DB_PASSWORD}@inventory-db:5432/inventory_db"
|
|
READ_ONLY: "true"
|
|
ENUM_DB_PATH: "/enums/comprehensive_enum_database_v2.json"
|
|
LOG_LEVEL: "INFO"
|
|
volumes:
|
|
- ./inventory-service/comprehensive_enum_database_v2.json:/enums/comprehensive_enum_database_v2.json:ro
|
|
depends_on:
|
|
- inventory-db
|
|
restart: unless-stopped
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
|
|
# Phase C: isolated inventory DB the Go ingestion writes to (never production).
|
|
inventory-go-db:
|
|
image: postgres:14
|
|
container_name: inventory-go-db
|
|
ports:
|
|
- "127.0.0.1:5435:5432"
|
|
environment:
|
|
POSTGRES_DB: "inventory_db"
|
|
POSTGRES_USER: "inventory_user"
|
|
POSTGRES_PASSWORD: "${INVENTORY_DB_PASSWORD}"
|
|
volumes:
|
|
- inventory-go-data:/var/lib/postgresql/data
|
|
restart: unless-stopped
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
|
|
# Read-write inventory-go instance: owns inventory-go-db, exposes the ingestion
|
|
# endpoints. Used to validate ingestion (POST a character's items, compare the
|
|
# resulting normalized rows to production) without touching the production DB.
|
|
inventory-go-shadow:
|
|
image: inventory-go:local
|
|
container_name: inventory-go-shadow
|
|
ports:
|
|
- "127.0.0.1:8773:8773"
|
|
environment:
|
|
PORT: "8773"
|
|
DATABASE_URL: "postgresql://inventory_user:${INVENTORY_DB_PASSWORD}@inventory-go-db:5432/inventory_db"
|
|
READ_ONLY: "false"
|
|
ENUM_DB_PATH: "/enums/comprehensive_enum_database_v2.json"
|
|
LOG_LEVEL: "INFO"
|
|
volumes:
|
|
- ./inventory-service/comprehensive_enum_database_v2.json:/enums/comprehensive_enum_database_v2.json:ro
|
|
depends_on:
|
|
- inventory-go-db
|
|
restart: unless-stopped
|
|
logging:
|
|
driver: "json-file"
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
|
|
volumes:
|
|
dereth-go-data:
|
|
inventory-go-data:
|