chore: prefer BOT_TOKEN env var over secrets file

Backward-compatible reorder: env var wins, then file. This lets
multiple instances on the same host (prod + shipyard staging)
each load a distinct token via systemd EnvironmentFile, instead
of fighting over the single ~/.secrets/bigbiggerbiggestbot file.

Also documents the new two-environment workflow in README.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Danny 2026-05-10 12:43:42 +02:00
parent c0caf6cdf4
commit 967c7880fc
3 changed files with 43 additions and 22 deletions

View file

@ -60,11 +60,27 @@ nix develop --command pytest tests/ -v
## Deployment ## Deployment
Runs as a systemd service. A timer pulls this repo periodically and Two environments share one host (`sunken-ship`):
restarts the service when the remote has new commits — push to `main`
and the bot redeploys itself within ~15 minutes.
The SQLite database lives next to the code at `workouts.db` (gitignored). - **Production**`fitness-bot.service`, working dir `/home/danny/tg_fitness_bot`,
watches `origin/main`, served behind a stable URL via the VPS Caddy.
- **Shipyard staging**`fitness-bot-shipyard.service`, working dir
`/home/danny/tg_fitness_bot_shipyard`, watches `origin/staging`, separate
bot token, ephemeral cloudflared URL each restart.
Each has its own pull timer that fetches every ~15 minutes and restarts
the service when its branch has new commits.
**Workflow:**
```
# 1. land changes on a working branch (or main locally)
git push origin <branch>:staging # → shipyard auto-deploys, test there
git push origin <branch>:main # → production auto-deploys
```
Each environment keeps its own `workouts.db` next to its code (gitignored),
so testing on shipyard never touches production data.
## Architecture ## Architecture

16
bot.py
View file

@ -27,22 +27,24 @@ logging.basicConfig(
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Token resolution: secrets file → .env / environment variable # Token resolution: BOT_TOKEN env var → secrets file
# Env var wins so multiple instances on the same host (e.g. prod + shipyard
# staging) can each point to a different token without sharing a secrets file.
SECRETS_FILE = os.path.expanduser("~/.secrets/bigbiggerbiggestbot") SECRETS_FILE = os.path.expanduser("~/.secrets/bigbiggerbiggestbot")
def _load_token() -> str: def _load_token() -> str:
# 1. Try the secrets file # 1. Env var (set by systemd EnvironmentFile in multi-instance setups)
token = os.environ.get("BOT_TOKEN", "").strip()
if token:
return token
# 2. Default secrets file
if os.path.isfile(SECRETS_FILE): if os.path.isfile(SECRETS_FILE):
token = open(SECRETS_FILE).read().strip() token = open(SECRETS_FILE).read().strip()
if token: if token:
return token return token
# 2. Fall back to env var (set via .env or shell)
token = os.environ.get("BOT_TOKEN", "").strip()
if token:
return token
raise RuntimeError( raise RuntimeError(
f"No bot token found. Put it in {SECRETS_FILE} or set BOT_TOKEN env var." f"No bot token found. Set BOT_TOKEN env var or put it in {SECRETS_FILE}."
) )

View file

@ -21,15 +21,25 @@ SECRETS_FILE = pathlib.Path.home() / ".secrets" / "bigbiggerbiggestbot"
def load_token() -> str: def load_token() -> str:
"""Load bot token: secrets file → .env → BOT_TOKEN env var.""" """Load bot token: BOT_TOKEN env → secrets file → .env in cwd.
# 1. Secrets file (same path as bot.py uses)
Env var wins so multiple instances on the same host (prod + shipyard)
can each get a distinct token via systemd EnvironmentFile.
"""
# 1. Already in environment (systemd EnvironmentFile sets this for staging)
token = os.environ.get("BOT_TOKEN", "").strip()
if token:
print(" Token loaded from BOT_TOKEN env var")
return token
# 2. Secrets file (same path as bot.py uses)
if SECRETS_FILE.is_file(): if SECRETS_FILE.is_file():
token = SECRETS_FILE.read_text().strip() token = SECRETS_FILE.read_text().strip()
if token: if token:
print(f" Token loaded from {SECRETS_FILE}") print(f" Token loaded from {SECRETS_FILE}")
return token return token
# 2. .env in working directory # 3. .env in working directory
env_file = pathlib.Path.cwd() / ".env" env_file = pathlib.Path.cwd() / ".env"
if env_file.exists(): if env_file.exists():
for line in env_file.read_text().splitlines(): for line in env_file.read_text().splitlines():
@ -40,15 +50,8 @@ def load_token() -> str:
print(f" Token loaded from {env_file}") print(f" Token loaded from {env_file}")
return token return token
# 3. Already in environment
token = os.environ.get("BOT_TOKEN", "").strip()
if token:
print(" Token loaded from BOT_TOKEN env var")
return token
print("\n No bot token found!") print("\n No bot token found!")
print(f" Put it in {SECRETS_FILE}") print(f" Set BOT_TOKEN env var, or put it in {SECRETS_FILE}, or in a .env file.\n")
print(" Or create a .env file with: BOT_TOKEN=your-token\n")
sys.exit(1) sys.exit(1)