mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-10 20:07:14 +01:00
The periodic diet-guard-sync.timer paid a network round-trip every ~15 min to approximate the two moments the shared log actually changes on this device. Sync now fires on those events instead: * before a lock -- already present in _cli_gate._should_lock, which pulls only when a lock is otherwise due, so a phone-logged meal clears the lock and a not-due tick never touches the network; * after a meal is logged here -- new _sync_events.publish_after_log, wired into all three write paths (_cli_log, _cli_gate, _mcp). This is what stops the PHONE nagging for a slot already logged on the PC, the mirror image of the rule that keeps the phone's own tick unconditional. The gate's publish sits in cmd_gate AFTER MealGate.run() returns, never in the unlock handler: that runs on the Tk thread with the window still up, so a hanging network call would keep the user locked in behind a successful log. It also covers the "Synced from another device" unlock, which hooking log_meal would miss. Accepted trade: a PC that is on but neither logging nor locking no longer pulls, so an inbound phone edit lands at the next log or lock rather than within 15 min. Bounded by slot spacing -- an unlogged day raises a lock. install.sh now disables and removes the installed timer (guarded, since a fresh machine has no such unit and `disable` would abort under `set -euo pipefail`); the .service stays for manual one-off ticks. Every test that logs a meal would otherwise reach the network through the new call sites, so conftest patches publish_after_log at each of the three call sites -- not on _sync_events, so test_sync_events still exercises the real helper. Suite stays at ~8s, which is the actual leak detector. Two files had to be split to commit at all, since file-length passes filenames and both were already over the 250-line cap: _cli.py 466 -> 207, + _cli_args.py, _cli_init.py, _cli_log.py _mcp.py 370 -> 194, + _mcp_server.py (singleton + annotations), _mcp_read.py Their conftest/test patch targets moved in the same commit; a stale target redirects nothing and fails open. Verified: 701 python tests at 100% branch coverage, 555 flutter tests, `python -m diet_guard sync` -> 402 entries/exit 0, `gate --check` -> exit 0, all five MCP tools still register, live ~/.local/share/diet_guard byte-identical. Over-cap files: 44 -> 42. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RA9ynjqXPnexRQnR5oAuCC
102 lines
5.1 KiB
Bash
Executable File
102 lines
5.1 KiB
Bash
Executable File
#!/bin/bash
|
|
# ============================================================================
|
|
# Diet Guard installer: log-to-unlock gate with a freely-editable budget.
|
|
#
|
|
# Usage: bash install.sh
|
|
#
|
|
# What it does:
|
|
# 1. Ensures system deps (setxkbmap for VT-disable, requests for OFF lookups)
|
|
# 2. pip-installs this package + gatelock into system Python's user
|
|
# site-packages (the systemd service runs /usr/bin/python directly, not
|
|
# a venv, so the package must live where that interpreter can find it —
|
|
# see CLAUDE.md's "Production dependency installation" section)
|
|
# 3. Installs + enables the systemd user timer that fires the gate every ~30m
|
|
# 4. Installs + enables the systemd user timer that syncs the log every ~15m
|
|
# (the sync itself stays unconfigured -- and a no-op -- until you create
|
|
# a sync token; see the reminder this step prints)
|
|
# 5. Sets your initial daily budget from biometrics (only if not already set;
|
|
# it stays freely editable afterward from the gate or the phone app)
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
# Split declare/assign so the command-substitution exit code is not masked (SC2155).
|
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
|
readonly SCRIPT_DIR
|
|
readonly REPO_DIR="$SCRIPT_DIR"
|
|
readonly SERVICE_SRC="$SCRIPT_DIR/diet-guard-gate.service"
|
|
readonly TIMER_SRC="$SCRIPT_DIR/diet-guard-gate.timer"
|
|
readonly SYNC_SERVICE_SRC="$SCRIPT_DIR/diet-guard-sync.service"
|
|
readonly SYSTEMD_USER_DIR="$HOME/.config/systemd/user"
|
|
readonly DATA_DIR="$HOME/.local/share/diet_guard"
|
|
readonly BUDGET_FILE="$DATA_DIR/.budget"
|
|
readonly SYNC_TOKEN_FILE="$HOME/.config/diet_guard/sync_token"
|
|
|
|
echo "=== Diet Guard Installer ==="
|
|
|
|
# 1. System dependencies ------------------------------------------------------
|
|
echo "[1/5] Checking system dependencies..."
|
|
if ! command -v setxkbmap &>/dev/null; then
|
|
echo " Installing xorg-setxkbmap (gate disables VT switching while locked)..."
|
|
sudo pacman -S --noconfirm xorg-setxkbmap
|
|
else
|
|
echo " setxkbmap present"
|
|
fi
|
|
|
|
# 2. Install this package + its dependencies into system Python -------------
|
|
echo "[2/5] Installing diet_guard + dependencies for /usr/bin/python..."
|
|
/usr/bin/python3 -m pip install --user --break-system-packages -e "$REPO_DIR"
|
|
echo " Installed. Verifying import..."
|
|
/usr/bin/python3 -c "import diet_guard; import gatelock" \
|
|
&& echo " diet_guard and gatelock import cleanly from the system interpreter."
|
|
|
|
# 3. systemd user timer + service (gate) -------------------------------------
|
|
echo "[3/5] Installing the gate's systemd user timer + service..."
|
|
mkdir -p "$SYSTEMD_USER_DIR"
|
|
cp "$SERVICE_SRC" "$SYSTEMD_USER_DIR/diet-guard-gate.service"
|
|
cp "$TIMER_SRC" "$SYSTEMD_USER_DIR/diet-guard-gate.timer"
|
|
systemctl --user daemon-reload
|
|
systemctl --user enable --now diet-guard-gate.timer
|
|
echo " Timer enabled and started (fires the gate every ~30 min)."
|
|
|
|
# 4. Retire the old periodic sync timer ---------------------------------------
|
|
# Sync is event-driven now (before a lock, after a meal is logged -- see
|
|
# diet_guard/_sync_events.py), so the ~15 min timer is gone. Removing the unit
|
|
# from the repo does NOT stop an already-installed copy: without this block it
|
|
# would keep firing forever from $SYSTEMD_USER_DIR with no unit file in git to
|
|
# explain it. `|| true` because a fresh machine has no such unit and `disable`
|
|
# exits non-zero there, which would abort the install under `set -euo pipefail`.
|
|
echo "[4/5] Removing the retired periodic sync timer (sync is event-driven)..."
|
|
systemctl --user disable --now diet-guard-sync.timer 2>/dev/null || true
|
|
rm -f "$SYSTEMD_USER_DIR/diet-guard-sync.timer"
|
|
# The *service* stays: it is how a manual one-off tick is run
|
|
# (`systemctl --user start diet-guard-sync`). Only the timer is retired.
|
|
cp "$SYNC_SERVICE_SRC" "$SYSTEMD_USER_DIR/diet-guard-sync.service"
|
|
systemctl --user daemon-reload
|
|
echo " Periodic sync timer removed; 'python -m diet_guard sync' still works."
|
|
if [[ -e "$SYNC_TOKEN_FILE" ]]; then
|
|
echo " Sync token already present at $SYNC_TOKEN_FILE."
|
|
else
|
|
echo " No sync token yet at $SYNC_TOKEN_FILE -- sync will no-op (and log a"
|
|
echo " failure) after every meal you log, until you create a GitHub PAT"
|
|
echo " scoped to the diet-guard-sync repo's contents and save it there,"
|
|
echo " mode 600: chmod 600 \"$SYNC_TOKEN_FILE\""
|
|
fi
|
|
|
|
# 5. Set the initial daily budget ---------------------------------------------
|
|
echo "[5/5] Setting your initial daily budget..."
|
|
if [[ -e "$BUDGET_FILE" ]]; then
|
|
echo " Budget already set at $BUDGET_FILE - skipping init."
|
|
echo " Edit it any time from the gate's calendar tab or the phone app."
|
|
else
|
|
echo " Enter your biometrics (used once then discarded to compute a"
|
|
echo " starting value; you can change the number itself any time from"
|
|
echo " the gate's calendar tab or the phone app):"
|
|
python -m diet_guard init
|
|
fi
|
|
|
|
echo "=== Installation complete ==="
|
|
echo "The gate checks every ~30 min (08:00-22:00) and locks until you log a meal"
|
|
echo "once you have gone 5h without logging."
|
|
echo "Test the lock now (safe, closeable): python -m diet_guard gate --demo"
|