utils/sickdays/install.sh
Krzysztof kuhy Rudnicki bd64b27109 sickdays 0.3.0: desktop sick-day picker and embeddable KeepPanel
`sickdays gui` opens a Tk window modelled on the phone's Sick days screen:
a Monday-first month calendar (cat-2 fill, a dot for kept lockers, past
days faded and inert), a five-switch panel for the clicked day, and the
"Marked sick" list with "Kept: ..." lines. A free day is refused with the
phone's wording. Each write starts sync_quietly() on a non-daemon thread.
The window class is "sickdays" and Escape closes it.

sickdays.gui.KeepPanel is the panel on its own, a plain tk.Frame with no
root, mainloop, grab or wm_* call, so screen-locker can embed it under its
lock-screen grab. `import sickdays` and the rest of the CLI never import
tkinter: the CLI loads the gui module by name only for `gui`, and a
subprocess test checks this.

The tests drive real Tk on a withdrawn root, and CI now runs them under
xvfb-run, as gatelock's CI does. Adds a sickdays.desktop launcher, which
install.sh installs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAhbNszs5JKdtnD4uBi3xp
2026-10-17 13:47:26 +02:00

134 lines
4.7 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# Install the sickdays CLI and its sync timer for the current user.
#
# sickdays gets its OWN venv, like freedays: the pool is shared by every gate,
# so the command that manages it must not depend on whichever app venv
# happened to be active. freedays goes into the same venv because adding a
# sick day checks the free-day pool first (a date is never in both).
# Idempotent: rerun it after any change.
# ============================================================================
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
UTILS_DIR="$(dirname "$REPO_DIR")"
readonly SCRIPT_NAME REPO_DIR UTILS_DIR
readonly STATE_DIR="${HOME}/.local/share/sickdays"
readonly VENV_DIR="${STATE_DIR}/venv"
readonly BIN_DIR="${HOME}/.local/bin"
readonly UNIT_DIR="${HOME}/.config/systemd/user"
readonly APPS_DIR="${HOME}/.local/share/applications"
readonly SYSTEM_PYTHON="/usr/bin/python3"
log() { printf 'install: %s\n' "$1" >&2; }
fail() { printf 'install: FAILED -- %s\n' "$1" >&2; exit 1; }
usage() {
echo "Usage: $SCRIPT_NAME [--no-timer]"
echo " --no-timer install the CLI only; do not enable the sync timer"
exit 0
}
INSTALL_TIMER=1
build_venv() {
log "building the sickdays venv at $VENV_DIR"
mkdir -p "$STATE_DIR"
if [[ ! -x "$VENV_DIR/bin/python" ]]; then
"$SYSTEM_PYTHON" -m venv "$VENV_DIR" || fail "could not create the venv"
fi
# Siblings install from the working tree, not from a tag: this script runs
# from a checkout, and installing the released tag here would silently
# test something other than what is on disk.
"$VENV_DIR/bin/pip" install --quiet --upgrade pip || fail "pip self-upgrade"
"$VENV_DIR/bin/pip" install --quiet "$UTILS_DIR/crdt-sync" \
|| fail "the crdt-sync sibling package"
"$VENV_DIR/bin/pip" install --quiet --no-deps "$UTILS_DIR/freedays" \
|| fail "the freedays sibling package"
"$VENV_DIR/bin/pip" install --quiet --no-deps -e "$REPO_DIR" \
|| fail "sickdays itself"
}
link_cli() {
log "putting the sickdays command on PATH at $BIN_DIR/sickdays"
mkdir -p "$BIN_DIR"
ln -sf "$VENV_DIR/bin/sickdays" "$BIN_DIR/sickdays"
}
install_launcher() {
# Exec is the bare command, so the launcher follows the $BIN_DIR symlink.
log "installing the 'Sick days' launcher into $APPS_DIR"
mkdir -p "$APPS_DIR"
install -m 644 "$REPO_DIR/sickdays.desktop" "$APPS_DIR/"
if command -v update-desktop-database >/dev/null; then
update-desktop-database "$APPS_DIR" || log "NOTE: update-desktop-database failed"
fi
}
install_timer() {
log "installing the sync timer"
mkdir -p "$UNIT_DIR"
install -m 644 "$REPO_DIR/systemd/sickdays-sync.service" "$UNIT_DIR/"
install -m 644 "$REPO_DIR/systemd/sickdays-sync.timer" "$UNIT_DIR/"
systemctl --user daemon-reload || fail "systemctl daemon-reload"
systemctl --user enable --now sickdays-sync.timer || fail "enabling the timer"
}
verify() {
# Two environments, either of which can be broken on its own: the CLI's
# venv, and the system python the gate apps import the library from.
log "verifying"
"$BIN_DIR/sickdays" list >/dev/null || fail "the sickdays CLI does not run"
"$VENV_DIR/bin/python" -c "import freedays" \
|| fail "freedays is missing from the venv; adding a sick day would refuse"
"$VENV_DIR/bin/python" -c "import tkinter" \
|| fail "the venv's python has no tkinter; 'sickdays gui' would not open"
"$SYSTEM_PYTHON" -c "import sickdays" 2>/dev/null || {
log "NOTE: the system python cannot import sickdays yet."
log " The gate apps need it there; their own install.sh does that."
}
if [[ "$INSTALL_TIMER" -eq 1 ]]; then
systemctl --user is-enabled sickdays-sync.timer >/dev/null \
|| fail "the sync timer is not enabled"
fi
}
main() {
build_venv
link_cli
install_launcher
if [[ "$INSTALL_TIMER" -eq 1 ]]; then
install_timer
fi
verify
echo "============================================================"
"$BIN_DIR/sickdays" list
echo "============================================================"
log "done. 'sickdays add' marks today sick, everywhere."
if [[ ":$PATH:" != *":$BIN_DIR:"* ]]; then
log "NOTE: $BIN_DIR is not on your PATH; add it to use 'sickdays' bare."
fi
}
while [[ $# -gt 0 ]]; do
case $1 in
--no-timer)
INSTALL_TIMER=0
shift
;;
-h|--help)
usage
;;
*)
echo "Unknown option: $1" >&2
exit 1
;;
esac
done
main "$@"