mirror of
https://github.com/kuhyx/signal-bot.git
synced 2026-11-10 17:57:02 +01:00
The registry used to be built once, inline in build_bot, so every change to the data behind it -- a person tagged in the web UI, an alias, soon a command switched off from the management page -- meant restarting the unit. That is cheap and lossless, but it is a deploy step somebody has to remember, and a 21:37 report can land in the middle of it. Now the registration sequence lives in wiring.build_registry, and RegistryHolder runs it again on POST /_reload: the new table is built whole and swapped in with one assignment, never patched in place, so a duplicate trigger is found before anything the dispatcher reads has changed. Bad data (ConfigError, DuplicateTriggerError) keeps the previous table and answers 409 with the reason; a programming error still surfaces as a 500. scripts/reload.sh wraps it, with --code for the restart a Python change needs, and an optional command to type in testbot so "reloaded" means "answered". No Python hot-reload on purpose. The loader change is what makes deploying this safe: two rows in the live tags.json name files that are no longer in media/, and load_pack refused them, so the next restart would have looped on ConfigError. A stale tag is drift in a file a page edits mid-session, not a broken album -- it is now skipped with a warning naming it. A tag that reaches outside the album is still refused. pack_load.py split at the 250-line cap; pack_tags.py is the half whose checks are not all fatal. Proven offline in scripts/e2e.sh: !delta is unknown, a fifth person is tagged into the synthetic pack, scripts/reload.sh runs, and the same process posts delta's photo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
122 lines
3.4 KiB
Bash
Executable File
122 lines
3.4 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ============================================================================
|
|
# Make the running bot pick up a change, and prove that it did.
|
|
#
|
|
# Two kinds of change, two mechanisms:
|
|
#
|
|
# scripts/reload.sh [cmd] data changed (aliases, an album, a
|
|
# command switched off): POST /_reload,
|
|
# the table is rebuilt in place, nothing
|
|
# restarts and no frame is lost.
|
|
# scripts/reload.sh --code [cmd] code changed: restart the unit, wait for
|
|
# /healthz to go green again. Lossless too
|
|
# (signal-cli replays what arrived), but
|
|
# a deploy step -- so it is the flag, not
|
|
# the default.
|
|
#
|
|
# With [cmd], the bot then types that command at itself in the test group,
|
|
# so "reloaded" means "answered", not "returned 200". E.g.
|
|
#
|
|
# scripts/reload.sh '!nowaosoba'
|
|
# scripts/reload.sh --code '!pomoc'
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
readonly SCRIPT_NAME
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
readonly SCRIPT_DIR
|
|
readonly BOT="http://127.0.0.1:${BIND_PORT:-8137}"
|
|
readonly UNIT="signal-bot"
|
|
readonly TEST_GROUP="${RELOAD_GROUP:-testbot}"
|
|
|
|
RESTART=0
|
|
PROBE=""
|
|
|
|
usage() {
|
|
echo "Usage: $SCRIPT_NAME [--code] [command-to-selftest]"
|
|
echo " --code restart the unit (code changed) instead of rebuilding the table"
|
|
exit 0
|
|
}
|
|
|
|
validate_requirements() {
|
|
if ! command -v jq >/dev/null; then
|
|
echo "Error: jq is required" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
wait_healthy() {
|
|
local attempt=0
|
|
# 150 x 0.4 s: a restart is ~1 s, but signal-cli can take a while to
|
|
# accept the websocket again after its own reconnect.
|
|
until [[ "$(curl -s "${BOT}/healthz" | jq -r '.status // "down"')" == "ok" ]]; do
|
|
attempt=$((attempt + 1))
|
|
if [[ $attempt -ge 150 ]]; then
|
|
echo "Error: ${BOT}/healthz never reported ok" >&2
|
|
curl -s "${BOT}/healthz" >&2 || true
|
|
exit 1
|
|
fi
|
|
sleep 0.4
|
|
done
|
|
}
|
|
|
|
reload_data() {
|
|
local reply status
|
|
reply="$(curl -s -X POST "${BOT}/_reload")"
|
|
status="$(jq -r '.status // "?"' <<<"$reply")"
|
|
case "$status" in
|
|
reloaded)
|
|
echo "reloaded: $(jq -r '.commands' <<<"$reply") commands live"
|
|
;;
|
|
kept)
|
|
# The old table is still answering; the new data is what is broken.
|
|
echo "Error: reload refused, previous table kept: $(jq -r '.reason' <<<"$reply")" >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
echo "Error: unexpected answer from ${BOT}/_reload: ${reply}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
restart_code() {
|
|
systemctl --user restart "$UNIT"
|
|
wait_healthy
|
|
echo "restarted: $(curl -s "${BOT}/healthz" | jq -r '"\(.version) \(.pope)"')"
|
|
}
|
|
|
|
main() {
|
|
validate_requirements
|
|
if [[ $RESTART -eq 1 ]]; then
|
|
restart_code
|
|
else
|
|
wait_healthy
|
|
reload_data
|
|
fi
|
|
if [[ -n "$PROBE" ]]; then
|
|
"${SCRIPT_DIR}/selftest.sh" "$TEST_GROUP" "$PROBE"
|
|
fi
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--code)
|
|
RESTART=1
|
|
shift
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
;;
|
|
*)
|
|
PROBE="$1"
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
main
|