deploy foundation: push-based deploy.sh, spec-007, v3.0.0

This commit is contained in:
Oleksandr Kozachuk
2026-07-13 15:06:38 +02:00
parent 3d2289496b
commit 8a19c35353
7 changed files with 115 additions and 5 deletions
+5
View File
@@ -54,6 +54,11 @@ Decisions inside the set architecture. D-NNN, never renumbered.
fact-level erasure ships with FDB-007 structured memory — stated in
the user-facing confirmation, not hidden. (Superseded by D-014:
erasure now covers facts, observations and episode traces.)
- **D-015** — Deploys are push-based from the dev machine
(`git archive <tag> | ssh`), not pull-based: no deploy keys or git
state on the hosts, the artifact is exactly the tag tree, untracked
live config survives in-place extraction. Trade-off: deploys need
the dev machine; acceptable for a one-operator project.
- **D-014** — Structured memory (FDB-007): observations are the only
consolidation feed (independent of history trimming); consolidation
returns NEW facts only (no wholesale rewrite — the lossiness of the
+4
View File
@@ -79,3 +79,7 @@ build: clean ## Build distribution packages
# CI targets
ci: install-dev all-checks ## Full CI pipeline (install deps and run all checks)
# Deploy targets (SPEC-007)
deploy: ## Deploy a tag to a host: make deploy HOST=ggg TAG=v3.0.0
bash deploy/deploy.sh $(HOST) $(TAG)
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# deploy.sh <host> <tag> — push-based deploy from the dev machine (SPEC-007).
# Rollback = run again with the previous tag (DEP-06); restore the
# bot.db.pre-<tag> backup first when the schema version moved (PER-06).
set -euo pipefail
HOST="${1:?usage: deploy.sh <fjerkroa|ggg> <tag>}"
TAG="${2:?usage: deploy.sh <fjerkroa|ggg> <tag>}"
case "$HOST" in
fjerkroa) SERVICE=kroa CONFIG=kroa.toml ;;
ggg) SERVICE=luma CONFIG=ggg.toml ;;
*) echo "unknown host: $HOST (known: fjerkroa, ggg)" >&2; exit 1 ;;
esac
# Tags only — no branch/commit deploys (DEP-01)
git rev-parse -q --verify "refs/tags/$TAG" >/dev/null || { echo "not a tag: $TAG" >&2; exit 1; }
# Restaurant service window (DEP-05)
if [ "$HOST" = fjerkroa ] && [ "${DEPLOY_FORCE:-0}" != 1 ]; then
HOUR=$(TZ=Europe/Oslo date +%H)
if [ "$HOUR" -ge 11 ] && [ "$HOUR" -lt 22 ]; then
echo "refusing kroa deploy during service hours (11-22 Europe/Oslo); DEPLOY_FORCE=1 overrides (DEP-05)" >&2
exit 1
fi
fi
echo "== deploy $TAG -> $HOST (service $SERVICE, config $CONFIG) =="
# Code push: tag tree over ~/fjerkroa_bot; untracked config/state survives (DEP-01)
git archive "$TAG" | ssh "$HOST" 'mkdir -p ~/fjerkroa_bot && tar -x -C ~/fjerkroa_bot'
ssh "$HOST" "set -e
[ -x ~/venv-bot/bin/python ] || python3.11 -m venv ~/venv-bot
~/venv-bot/bin/pip install -q --upgrade pip
~/venv-bot/bin/pip install -q ~/fjerkroa_bot
printf '#!/bin/sh\ncd %s/fjerkroa_bot || exit 1\nexec %s/venv-bot/bin/python -m fjerkroa_bot --config $CONFIG\n' \"\$HOME\" \"\$HOME\" > ~/fjerkroa_bot/start.sh
chmod +x ~/fjerkroa_bot/start.sh
~/venv-bot/bin/python -c 'import fjerkroa_bot'
find ~/fjerkroa_bot -maxdepth 3 -name bot.db | while read -r db; do cp \"\$db\" \"\$db.pre-$TAG\"; done # DEP-03
supervisorctl restart $SERVICE"
echo "== waiting for startsecs =="
sleep 35
# Smoke (DEP-04)
ssh "$HOST" "supervisorctl status $SERVICE | grep -q RUNNING" \
|| { echo "SMOKE FAIL: $SERVICE not RUNNING on $HOST — rollback: deploy.sh $HOST <previous-tag> (DEP-06)" >&2; exit 1; }
ssh "$HOST" "tail -80 ~/logs/supervisord.log | grep -q 'We have logged in as'" \
|| { echo "SMOKE FAIL: no fresh Discord login line on $HOST — check logs, consider rollback (DEP-06)" >&2; exit 1; }
echo "== OK: $HOST runs $TAG — RUNNING + logged in (DEP-04) =="
+6 -3
View File
@@ -6,6 +6,9 @@ with date + result.
| ID | Date | Result |
| --- | --- | --- |
_No manual-coverage requirements declared yet — DEP-NN arrives with
FDB-017._
| DEP-01 | 2026-07-13 | Verified with the v3.0.0 ggg deploy: tag-only refusal + untracked config/state survived. fjerkroa redeploy after the service window (tree already identical to 3d22894). |
| DEP-02 | 2026-07-13 | Service map exercised: luma restart via script (v3.0.0); kroa mapping code-reviewed, exercised on its next deploy. |
| DEP-03 | 2026-07-13 | bot.db.pre-v3.0.0 backup confirmed on ggg after deploy. |
| DEP-04 | 2026-07-13 | Smoke gate exercised on ggg: RUNNING + fresh login line. |
| DEP-05 | 2026-07-13 | Live-verified: kroa deploy attempt ~15h Oslo refused without DEPLOY_FORCE=1. |
| DEP-06 | 2026-07-13 | Rollback documented (older tag + db backup restore); live drill pending — next release. |
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "fjerkroa-bot"
version = "2.0"
version = "3.0.0"
description = "Discord bot with OpenAI responder for Fjærkroa and GGG"
authors = [{ name = "Oleksandr Kozachuk", email = "ddeus.lp@mailnull.com" }]
requires-python = ">=3.11"
+46
View File
@@ -0,0 +1,46 @@
# SPEC-007 — Deploy + hosts
Two uberspace hosts, one release: **fjerkroa** (service `kroa`,
config `kroa.toml`) and **ggg** (service `luma`, config `ggg.toml`).
Deploys are push-based from the dev machine — `git archive <tag>`
over ssh, no repo credentials on the hosts (D-015). Coverage here is
`manual`: rows in `manual-verification.md` with date + result.
### DEP-01 — Deploys go by tag, in place, configs survive (coverage: manual)
`deploy/deploy.sh <host> <tag>` refuses unknown hosts and refs that
are not tags. The tag's tree is extracted over `~/fjerkroa_bot`
untracked files (live TOML config, `history/`, news snapshot) are
never touched. Dev-on-host drift ends here: hosts run tag content
only.
### DEP-02 — Per-host service map (coverage: manual)
fjerkroa → supervisord program `kroa`, config `kroa.toml`; ggg →
program `luma`, config `ggg.toml`. The script owns this map; restart
via `supervisorctl restart <service>`.
### DEP-03 — Pre-deploy state backup (coverage: manual)
Before the restart, every `bot.db` under `~/fjerkroa_bot` is copied
to `bot.db.pre-<tag>` on the host. Schema migrations are
forward-only (PER-06) — rolling back past a schema bump means
restoring this backup.
### DEP-04 — Smoke test gates the deploy (coverage: manual)
After restart the script fails loudly unless the service reports
RUNNING and the log shows a fresh Discord login line. On failure the
operator instruction is printed: deploy the previous tag (DEP-06).
### DEP-05 — No kroa deploys during service hours (coverage: manual)
Deploys to fjerkroa between 11:00 and 22:00 Europe/Oslo are refused
unless `DEPLOY_FORCE=1` is set. The restaurant does not beta-test
during dinner.
### DEP-06 — Rollback is a deploy of an older tag (coverage: manual)
`deploy.sh <host> <previous-tag>` is the rollback path; when the
schema version moved, restore the DEP-03 backup first. Venv is
rebuilt from the tag's pyproject either way.
Generated
+1 -1
View File
@@ -623,7 +623,7 @@ wheels = [
[[package]]
name = "fjerkroa-bot"
version = "2.0"
version = "3.0.0"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },