diff --git a/README.md b/README.md index 238c748..1de5c12 100644 --- a/README.md +++ b/README.md @@ -737,6 +737,16 @@ All channel-mutating runs share a host-wide release lock (`pkgs.omarchy.org/.release.lock`), so overlapping timers and manual runs serialize instead of interleaving. +Check on all of it with `bin/repo timers` — schedule, each unit's last run and +whether it succeeded, what is queued, whether a release is running right now, +and any failed units. Like the other host commands it forwards over ssh, so +the build box's state is one command away from any machine: + +```bash +bin/repo timers # runs on the repository host when one is configured +bin/repo timers --local # inspect this machine instead +``` + State files are stored in `/root/.state/`: - `.sync-needed-edge` - `.sync-needed-rc` diff --git a/bin/repo b/bin/repo index c9d4b49..344e77e 100755 --- a/bin/repo +++ b/bin/repo @@ -23,7 +23,7 @@ source "$BUILD_ROOT/helpers/host-helpers.sh" # # Local-machine workflows (list, push, deploy, setup) never forward: push and # deploy exist precisely to move local builds TO the host. -FORWARDABLE=" release build sign promote update clean migrate advance bootstrap-rc remove sync " +FORWARDABLE=" release build sign promote update clean migrate advance bootstrap-rc remove sync timers " LOCAL_OVERRIDE=false STRIPPED_ARGS=() @@ -109,6 +109,7 @@ show_usage() { echo " push Upload local builds to the repository host and publish them there" echo " deploy Build locally, then push: one command from a build machine" echo " setup Install everything the repository host needs" + echo " timers Show release timer schedule, last runs, and queued builds" echo "" echo "Typical workflows:" echo " $0 release # Complete release workflow" @@ -202,6 +203,11 @@ setup) "$SCRIPT_DIR/setup" "$@" 2>&1 | tee "$LOG_FILE" exit ${PIPESTATUS[0]} ;; +timers) + # Status output, not a build step: show it, do not log it. + "$SCRIPT_DIR/timers" "$@" + exit $? + ;; -h | --help | help) show_usage ;; diff --git a/bin/timers b/bin/timers new file mode 100755 index 0000000..e302efc --- /dev/null +++ b/bin/timers @@ -0,0 +1,128 @@ +#!/bin/bash +# Show the release timers: schedule, last run and its result, what is queued, +# and what is running right now. +# +# Through bin/repo this forwards to the repository host, so the build box's +# state is one command away from any machine. + +set -e + +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") +source "$BUILD_ROOT/helpers/message-helpers.sh" +source "$BUILD_ROOT/helpers/paths.sh" + +STATE_DIR="${OMARCHY_STATE_DIR:-/root/.state}" + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + echo "Usage: $0" + echo "" + echo "Reports each omarchy release timer's schedule, last run and result," + echo "any queued builds, and whether a release is running now." + exit 0 +fi + +print_header "Omarchy Release Timers" + +if ! command -v systemctl >/dev/null 2>&1; then + print_warning "systemctl not found — timers live on the repository host" + echo "Run this against the host: bin/repo timers (with OMARCHY_REPO_HOST or .repo-host set)" + exit 0 +fi + +# Discover units from the repo so this never drifts from what setup installs. +UNITS=() +for unit in "$BUILD_ROOT"/systemd/*.timer; do + [[ -e "$unit" ]] || continue + UNITS+=("$(basename "$unit" .timer)") +done + +if [[ ${#UNITS[@]} -eq 0 ]]; then + print_warning "No timer units found in $BUILD_ROOT/systemd" + exit 0 +fi + +# --- schedule ---------------------------------------------------------------- + +print_info "Schedule" +if ! systemctl list-timers --all 'omarchy-*' --no-pager 2>/dev/null | sed '/^$/d;s/^/ /'; then + echo " (could not read timers)" +fi +echo "" + +# --- per-unit state ---------------------------------------------------------- + +print_info "Units" +for unit in "${UNITS[@]}"; do + # systemctl prints "not-found" AND exits nonzero, so a || fallback would + # append a second line and wreck the alignment. Take its output as-is. + enabled=$(systemctl is-enabled "$unit.timer" 2>/dev/null || true) + enabled=${enabled:-unknown} + service_state=$(systemctl is-active "$unit.service" 2>/dev/null || true) + result=$(systemctl show "$unit.service" -p Result --value 2>/dev/null || true) + exit_status=$(systemctl show "$unit.service" -p ExecMainStatus --value 2>/dev/null || true) + finished=$(systemctl show "$unit.service" -p ExecMainExitTimestamp --value 2>/dev/null || true) + + case "$enabled" in + enabled) mark="✓" ;; + not-found | unknown) mark="✗" ;; + *) mark="⚠" ;; + esac + + printf ' %s %-28s timer:%-14s' "$mark" "$unit" "$enabled" + + if [[ "$service_state" == "activating" || "$service_state" == "active" ]]; then + printf 'RUNNING NOW\n' + elif [[ -z "$finished" ]]; then + printf 'never run\n' + elif [[ "$result" == "success" && "$exit_status" == "0" ]]; then + printf 'last: ok (%s)\n' "$finished" + else + printf 'last: FAILED [%s/exit %s] (%s)\n' "${result:-unknown}" "${exit_status:-?}" "$finished" + fi +done +echo "" + +# --- queued work ------------------------------------------------------------- + +print_info "Queued builds (state files in $STATE_DIR)" +queued=false +for channel in edge rc stable; do + state_file="$STATE_DIR/.sync-needed-$channel" + if [[ -f "$state_file" ]]; then + queued=true + since=$(date -r "$state_file" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unknown") + printf ' • %-7s queued since %s\n' "$channel" "$since" + fi +done +[[ "$queued" == false ]] && echo " (nothing queued — all channels up to date)" +echo "" + +# --- release lock ------------------------------------------------------------ + +print_info "Release lock" +lock_file="$REPO_ROOT/.release.lock" +if [[ -s "$lock_file" ]]; then + holder=$(tail -1 "$lock_file" 2>/dev/null) + # A recorded holder whose process is gone is a leftover, not a live release. + pid=$(sed -n 's/^pid \([0-9]\+\).*/\1/p' <<<"$holder") + if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then + print_warning "HELD — $holder" + else + echo " free (last holder: $holder)" + fi +else + echo " free" +fi +echo "" + +# --- recent failures --------------------------------------------------------- + +failed=$(systemctl list-units --failed --no-legend 'omarchy-*' 2>/dev/null | sed 's/^/ /') +if [[ -n "$failed" ]]; then + print_error "Failed units" + echo "$failed" + echo "" + echo " Logs: journalctl -u -n 50" +fi + +print_info "Full logs: journalctl -u omarchy-auto-release- -n 50"