From fb99ef3cffc111c8172c4eb75ebea8d3955c5bd0 Mon Sep 17 00:00:00 2001 From: Danny Date: Sun, 10 May 2026 12:48:26 +0200 Subject: [PATCH] sunken-ship: add fitness-bot-shipyard staging instance Mirrors the prod fitness-bot setup but watches origin/staging, runs in /home/danny/tg_fitness_bot_shipyard, listens on port 8081, and loads its bot token from /home/danny/.secrets/bigbiggerbiggestbot-shipyard.env via EnvironmentFile (separate from prod's secrets file). ConditionPathExists keeps the service from start-looping until the secrets file is written. No WEBAPP_URL set, so start.py boots an ephemeral cloudflared Quick Tunnel; the bot updates its Telegram menu button to that URL on every start (same as prod was originally). Pull-timer fires every 15 min on the :13/28/43/58 offset to spread load against the existing fitness-bot-pull (:07/15) and mulbo-server-pull (:11/15) timers. Co-Authored-By: Claude Opus 4.7 (1M context) --- nixos/hosts/sunken-ship.nix | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/nixos/hosts/sunken-ship.nix b/nixos/hosts/sunken-ship.nix index 4c0f13b..4188a41 100644 --- a/nixos/hosts/sunken-ship.nix +++ b/nixos/hosts/sunken-ship.nix @@ -210,6 +210,71 @@ timerConfig.RandomizedDelaySec = "2min"; }; + # ── Shipyard staging — second instance for verifying changes pre-prod ─ + # Working dir: /home/danny/tg_fitness_bot_shipyard (separate clone of the same repo). + # Branch: origin/staging (push there to deploy here; push to origin/main for prod). + # Bot token (separate from prod): /home/danny/.secrets/bigbiggerbiggestbot-shipyard.env + # File contents: BOT_TOKEN= + # Service won't start until this file exists (ConditionPathExists). + # Mini App URL: ephemeral cloudflared Quick Tunnel (no VPS Caddy). + # Workflow: git push origin :staging → wait ~15 min → /start the + # shipyard bot in Telegram → test → git push origin :main. + systemd.services.fitness-bot-shipyard = let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ + python-telegram-bot + python-dotenv + aiohttp + ]); + in { + description = "BigBiggerBiggestBot — SHIPYARD STAGING instance"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pythonEnv pkgs.cloudflared ]; + environment.API_HOST = "::"; + environment.API_PORT = "8081"; + # No WEBAPP_URL — start.py spins up its own ephemeral cloudflared tunnel. + unitConfig.ConditionPathExists = "/home/danny/.secrets/bigbiggerbiggestbot-shipyard.env"; + serviceConfig = { + WorkingDirectory = "/home/danny/tg_fitness_bot_shipyard"; + EnvironmentFile = "/home/danny/.secrets/bigbiggerbiggestbot-shipyard.env"; + ExecStart = "${pythonEnv}/bin/python start.py"; + Restart = "on-failure"; + RestartSec = 10; + User = "danny"; + }; + }; + + systemd.services.fitness-bot-shipyard-pull = { + description = "Pull shipyard fitness bot from origin/staging and restart if changed"; + path = with pkgs; [ git systemd ]; + environment.GIT_CONFIG_COUNT = "1"; + environment.GIT_CONFIG_KEY_0 = "safe.directory"; + environment.GIT_CONFIG_VALUE_0 = "/home/danny/tg_fitness_bot_shipyard"; + script = '' + set -euo pipefail + if [ ! -d /home/danny/tg_fitness_bot_shipyard/.git ]; then + echo "Shipyard working dir not bootstrapped yet — skipping pull." + exit 0 + fi + cd /home/danny/tg_fitness_bot_shipyard + git fetch origin + if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/staging)" ]; then + exit 0 + fi + git pull origin staging + systemctl restart fitness-bot-shipyard + ''; + serviceConfig.Type = "oneshot"; + }; + + systemd.timers.fitness-bot-shipyard-pull = { + wantedBy = [ "timers.target" ]; + # Offset from prod (07/15), mulbo (11/15), and dotfiles-rebuild. + timerConfig.OnCalendar = "*-*-* *:13/15:00"; + timerConfig.RandomizedDelaySec = "2min"; + }; + # Mulbo companion service (Phase 5: uploads + dedup index + folders). # Wire spec: ~danny/python-projects/20_mulbo/SERVER_API.md. # Bootstrap (one-time): git clone git@github.com:DannyDannyDanny/python-projects.git /home/danny/python-projects