testsAndMisc/linux_configuration/features/setup_syncyomi_guard.sh
Krzysztof kuhy Rudnicki f15f00f3fe Re-point paths for the ~ reorganisation (home-tidy)
Also bumps the 16 meta/requirements pins the freshness gate flagged
(anthropic 1.5.0, numpy 2.5.3, pytest-randomly 5, flake8-bugbear 26.9.9,
ruff 0.16.7, pyright 1.1.414, selenium 4.49.0, hypothesis 6.168.0, lxml
6.1.3, pipdeptree 4.2.5, the two types-* stubs) and Flutter 3.47.3 in
meta/.fvmrc. One test still expected the pre-move ~/mcp-servers path.

The pre-commit hook fed .zsh files to shfmt and shellcheck, neither of
which parses zsh (SC2148/SC1071), so no zsh file could be committed since
the hook was tightened on 2026-08-22; those now get `zsh -n` instead. The
hook's own shfmt pass also staged its reformat of 8 shell files.

CI_GREEN_SKIP because main is red on exactly the dependency-freshness
finding this commit fixes, which is the documented use of that flag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF
2026-09-11 20:50:19 +02:00

124 lines
3.6 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# Install the SyncYomi library integrity guard as a user systemd timer.
#
# TachiyomiSY 1.13.2 can finish a backup restore, report success, and have
# written almost nothing (upstream jobobby04/TachiyomiSY#1634, #1638). The app
# then syncs that near-empty library up, replacing the good server copy. On
# 2026-08-09 the real library survived only as stale pages in the SQLite WAL.
#
# The guard cannot fix the app. It makes the silent case loud, and keeps
# restorable .tachibk snapshots so recovery never again depends on WAL forensics.
# ============================================================================
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
readonly REPO_ROOT
readonly UNIT_DIR="${HOME}/.config/systemd/user"
readonly SERVICE_NAME="syncyomi-guard.service"
readonly TIMER_NAME="syncyomi-guard.timer"
readonly SNAPSHOT_DIR="${HOME}/services/syncyomi/snapshots"
readonly STATE_DIR="${HOME}/.local/share/syncyomi_guard"
readonly DB_PATH="${HOME}/services/syncyomi/config/syncyomi.db"
log_info() { echo "[*] $*"; }
log_ok() { echo "[+] $*"; }
die() {
echo "[!] $*" >&2
exit 1
}
usage() {
echo "Usage: ${SCRIPT_NAME} [--uninstall]"
echo " Installs a user timer that verifies the SyncYomi payload every 30 min."
exit 0
}
require_python() {
command -v python3 >/dev/null 2>&1 ||
die "python3 not found — install it with: sudo pacman -S python"
}
# The guard reads the database through sudo-less paths, so it must be able to
# open it. A root-owned db (the container writes as root) is readable only if
# the mode allows it; check now rather than failing silently on the first timer.
check_readable() {
[[ -f ${DB_PATH} ]] || die "no SyncYomi database at ${DB_PATH}"
if ! head -c 1 "${DB_PATH}" >/dev/null 2>&1; then
die "cannot read ${DB_PATH} as $(whoami) — run: sudo chmod o+r '${DB_PATH}'"
fi
}
write_units() {
mkdir -p "${UNIT_DIR}" "${SNAPSHOT_DIR}" "${STATE_DIR}"
cat >"${UNIT_DIR}/${SERVICE_NAME}" <<EOF
# Generated by ${SCRIPT_NAME} — do not edit by hand.
[Unit]
Description=Verify the SyncYomi library payload has not silently collapsed
Documentation=https://github.com/jobobby04/TachiyomiSY/issues/1634
[Service]
Type=oneshot
WorkingDirectory=${REPO_ROOT}
Environment=PYTHONPATH=${REPO_ROOT}
ExecStart=/usr/bin/python3 -m python_pkg.syncyomi_guard \\
--db ${DB_PATH} \\
--state-dir ${STATE_DIR} \\
--snapshot-dir ${SNAPSHOT_DIR}
EOF
cat >"${UNIT_DIR}/${TIMER_NAME}" <<EOF
# Generated by ${SCRIPT_NAME} — do not edit by hand.
[Unit]
Description=Periodic SyncYomi library integrity check
[Timer]
OnCalendar=*:00/30
Persistent=true
[Install]
WantedBy=timers.target
EOF
log_ok "Wrote ${UNIT_DIR}/${SERVICE_NAME} and ${TIMER_NAME}"
}
install_timer() {
require_python
check_readable
write_units
systemctl --user daemon-reload
systemctl --user enable --now "${TIMER_NAME}"
log_ok "Timer enabled (every 30 minutes)"
log_info "Running one check now"
if systemctl --user start "${SERVICE_NAME}"; then
systemctl --user status "${SERVICE_NAME}" --no-pager -n 10 || true
else
die "first run failed — inspect: journalctl --user -u ${SERVICE_NAME}"
fi
}
uninstall_timer() {
systemctl --user disable --now "${TIMER_NAME}" 2>/dev/null || true
rm -f "${UNIT_DIR}/${SERVICE_NAME}" "${UNIT_DIR}/${TIMER_NAME}"
systemctl --user daemon-reload
log_ok "Removed timer and service (snapshots in ${SNAPSHOT_DIR} kept)"
}
main() {
case "${1:-}" in
--uninstall) uninstall_timer ;;
-h | --help) usage ;;
"") install_timer ;;
*) die "Unknown option: $1" ;;
esac
}
main "$@"