Files
AgapHost/kanboard/restore.sh
alvis a27bae828a kb: batch from 2026-07-30 parallel run (#181 #183 #189 #192 #164 #128 #219)
Work produced by the /kb driver on 2026-07-30. Each change is recorded on its
Kanboard task; all remain Done-unverified or parked pending alvis's decisions.

#183 agap-mcp/src/gitea.js
  askpassScript() and giteaWikiWrite()'s wiki checkout both used
  /tmp/agap-mcp-wiki, so writing the askpass helper made the dir non-empty and
  git clone always failed. gitea_wiki_write had likely never succeeded in
  production. Askpass moved to its own dir.

#181 agap-mcp/src/server.js
  Initialise registeredToolCount at module load so /health reports the real
  count immediately instead of 0 until the first MCP request.

#189 kanboard/backup.sh, seafile/backup.sh, vaultwarden/backup.sh,
     users-backup.sh, openai/backup-{hindsight-adolf,llm-dbs}.sh
  Remove the dead *.ts Zabbix trapper pushes (never landed). users-backup.sh
  also pointed at localhost:81 instead of 192.168.1.4:81 and pushed a date
  string into a numeric item. Freshness monitoring now rides the .age items.

#192 RESTORE-RUNBOOK.md, {kanboard,seafile,vaultwarden}/restore.sh
  Restore path for the three services, verified in throwaway containers.
  Note: this work found Seafile backups have carried an empty ccnet_db.sql
  since 2026-07-07 -- filed as kb#222, not fixed here.

#164 openai/litellm-config.yaml
  Metered `judge` (anthropic/claude-haiku-4-5) entry removed per alvis's
  2026-07-30 decision. ANTHROPIC_API_KEY was never wired, so it could not spend.

#128 openai/agent_registry.py
  litellm_key_spec() now also grants the routing-mode aliases, gated by the
  same _reachable_tiers() check as raw grants, so a small-tier agent cannot
  acquire automatic routing that resolves to tier-large.

#219 openai/migrate-adolf-state.sh
  Migration script only; inert until run. Copies (never moves) the
  openai_adolf-state volume to /mnt/ssd/dbs/adolf, verifying a full sha256
  manifest before declaring success. Tested against a throwaway volume.

Deliberately NOT included, both awaiting alvis:
  agap-mcp/docker-compose.yml -- kb#174's contested BW_EMAIL revert (parked).
  openai/docker-compose.yml   -- kb#219's bind-mount switch; the target dirs
                                 under /mnt/ssd/dbs/adolf do not exist yet, so
                                 committing it would let a later `compose up`
                                 recreate Adolf against empty paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Y5QPagv4iun1ghpwM96Ff
2026-07-30 15:06:09 +00:00

83 lines
2.8 KiB
Bash
Executable File

#!/bin/bash
# Kanboard restore — companion to backup.sh (kb#192).
#
# Restores a snapshot produced by backup.sh (db.sqlite + optional plugins.tar.gz)
# into a running Kanboard container. Defaults to the live "kanboard" container/
# volumes, but every target is overridable via env vars so the exact same script
# can be pointed at a disposable/throwaway container for a dry-run restore test
# (see kb#192 runbook for the recommended throwaway-container recipe).
#
# Usage:
# ./restore.sh /mnt/backups/kanboard/<snapshot-dir>
#
# Env overrides (defaults = live service):
# CONTAINER=kanboard # target container name
# DATA_PATH=/var/www/app/data # data dir inside the container
# PLUGINS_PATH=/var/www/app/plugins # plugins dir inside the container
#
# WARNING: this overwrites the target container's live database. Never run
# against the "kanboard" container name unless you intend a real disaster
# recovery — for testing, point CONTAINER at a throwaway container instead.
set -euo pipefail
CONTAINER="${CONTAINER:-kanboard}"
DATA_PATH="${DATA_PATH:-/var/www/app/data}"
PLUGINS_PATH="${PLUGINS_PATH:-/var/www/app/plugins}"
if [ $# -lt 1 ]; then
echo "Usage: $0 <path-to-backup-snapshot-dir>" >&2
echo " e.g. $0 /mnt/backups/kanboard/20260728-0300" >&2
exit 1
fi
SRC="$(realpath "$1")"
DB_FILE="$SRC/db.sqlite"
PLUGINS_FILE="$SRC/plugins.tar.gz"
if [ ! -f "$DB_FILE" ]; then
echo "Error: $DB_FILE not found" >&2
exit 1
fi
if ! docker inspect "$CONTAINER" > /dev/null 2>&1; then
echo "Error: container '$CONTAINER' does not exist" >&2
exit 1
fi
echo "Restoring into container '$CONTAINER' from $SRC"
# Stop the app so the sqlite file isn't being written to concurrently.
docker stop "$CONTAINER" > /dev/null
# Replace the database file.
docker cp "$DB_FILE" "$CONTAINER:$DATA_PATH/db.sqlite"
# Restore plugins, if present in the snapshot.
if [ -f "$PLUGINS_FILE" ]; then
docker cp "$PLUGINS_FILE" "$CONTAINER:/tmp/plugins.tar.gz"
docker start "$CONTAINER" > /dev/null
# Extract inside the container so ownership matches what the app expects.
docker exec "$CONTAINER" sh -c "rm -rf '$PLUGINS_PATH'/* && tar -xzf /tmp/plugins.tar.gz -C '$PLUGINS_PATH' && rm -f /tmp/plugins.tar.gz"
else
echo "Note: no plugins.tar.gz in snapshot, skipping plugin restore"
docker start "$CONTAINER" > /dev/null
fi
echo "Waiting for Kanboard to come up..."
for i in $(seq 1 30); do
if docker exec "$CONTAINER" php -r 'exit(file_exists("'"$DATA_PATH"'/db.sqlite") ? 0 : 1);' > /dev/null 2>&1; then
break
fi
sleep 1
done
echo "Verifying restored database..."
docker exec "$CONTAINER" php -r '
$db = new PDO("sqlite:'"$DATA_PATH"'/db.sqlite");
$count = $db->query("SELECT COUNT(*) FROM tasks")->fetchColumn();
echo "tasks table row count: $count\n";
'
echo "Restore complete: $CONTAINER"