Files
AIChatExporter/scheduling/install-systemd-timer.sh
JesseMarkowitzandClaude Opus 5.5 23c6e1f512 fix: push a FAILED notification when a scheduled run dies without reporting
sync pushes its result only from the end of a run it finished, so a crash or an early exit sent nothing, while the providers that survived kept pushing OK. The systemd unit now runs scheduling/run-sync.sh, which keeps the per-provider loop and pushes a high-priority failure, with the exception class only, for any run that exited non-zero without the app's own report.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbnmGHnFqDjyhPcCg1SEfF
2026-10-05 07:14:16 -04:00

110 lines
3.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Install a systemd *user* timer that runs `ai-chat-exporter sync` on a schedule.
#
# ./scheduling/install-systemd-timer.sh --provider claude-code --provider codex
# ./scheduling/install-systemd-timer.sh --provider all --time 21:30
# ./scheduling/install-systemd-timer.sh --uninstall
#
# User units (not system units) are the right scope: the archive is per-user,
# the .env holds that user's session tokens, and Joplin runs in their session.
#
# --provider is repeatable. The CLI takes one provider per run, so each one
# becomes its own ExecStart line, executed in order. Prefer listing only the
# providers that actually work unattended on this machine — a web provider with
# an expired session token will fail the unit every single day, which trains you
# to ignore the failure you actually want to see.
set -euo pipefail
REPO="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
UNIT_DIR="$HOME/.config/systemd/user"
NAME="aichat-sync"
TIME="09:00"
PROVIDERS=()
UNINSTALL=0
while [ $# -gt 0 ]; do
case "$1" in
--provider) PROVIDERS+=("$2"); shift 2 ;;
--time) TIME="$2"; shift 2 ;;
--name) NAME="$2"; shift 2 ;;
--uninstall) UNINSTALL=1; shift ;;
-h|--help) sed -n '2,20p' "${BASH_SOURCE[0]}"; exit 0 ;;
*) echo "unknown argument: $1" >&2; exit 1 ;;
esac
done
if [ "$UNINSTALL" -eq 1 ]; then
systemctl --user disable --now "$NAME.timer" 2>/dev/null || true
rm -f "$UNIT_DIR/$NAME.timer" "$UNIT_DIR/$NAME.service"
systemctl --user daemon-reload
echo "Removed $NAME.timer and $NAME.service."
exit 0
fi
[ ${#PROVIDERS[@]} -eq 0 ] && PROVIDERS=("all")
for f in "$REPO/ai-chat-exporter" "$REPO/scheduling/run-sync.sh"; do
if [ ! -x "$f" ]; then
echo "error: $f is missing or not executable." >&2
exit 1
fi
done
mkdir -p "$UNIT_DIR"
# WorkingDirectory is the point of the whole unit: .env, cache/ and exports/ all
# resolve against it, so the scheduled run writes to the same archive an
# interactive run from this directory would.
{
echo "[Unit]"
echo "Description=AI chat archive sync"
echo "Documentation=file://$REPO/README.md"
echo "After=network-online.target"
echo "Wants=network-online.target"
echo
echo "[Service]"
echo "Type=oneshot"
echo "WorkingDirectory=$REPO"
echo "Environment=AI_CHAT_EXPORTER_QUIET_CWD=1"
echo "Environment=AICHAT_SYNC_UNIT=$NAME"
# run-sync.sh attempts every provider even after one fails, and pushes a
# FAILED notification for any run that died without sending its own.
echo "ExecStart=$REPO/scheduling/run-sync.sh ${PROVIDERS[*]}"
} > "$UNIT_DIR/$NAME.service"
# Persistent=true runs a missed schedule at the next boot — the machine being
# off at 09:00 should delay the archive, not skip it. RandomizedDelaySec keeps
# the web providers from being hit at exactly the same second every day.
cat > "$UNIT_DIR/$NAME.timer" <<EOF
[Unit]
Description=Run the AI chat archive sync daily
[Timer]
OnCalendar=*-*-* $TIME:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now "$NAME.timer"
echo "Installed $NAME.timer — daily at $TIME for: ${PROVIDERS[*]}"
echo
systemctl --user list-timers "$NAME.timer" --no-pager || true
cat <<EOF
Next steps:
• Timers only run while you have a session. To archive when logged out:
loginctl enable-linger $USER
• Run it once by hand to confirm:
systemctl --user start $NAME.service
• Read the log:
journalctl --user -u $NAME.service -n 50
• The terms-of-service notice must have been acknowledged interactively at
least once on this machine, or the unit exits 1 with an explanation.
EOF