Track per-schedule last run time, only stamp on successful backup
Replace global last-run (based on log file mtime) with per-schedule LAST_RUN field in the schedule config. New scheduled-run CLI command runs backup and stamps LAST_RUN only on success. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
39
bin/gniza
39
bin/gniza
@@ -146,7 +146,7 @@ fi
|
||||
|
||||
# Only create log files for operations that produce meaningful output
|
||||
case "${SUBCOMMAND:-}" in
|
||||
backup|restore|retention) init_logging ;;
|
||||
backup|restore|retention|scheduled-run) init_logging ;;
|
||||
esac
|
||||
|
||||
# ── Parse subcommand flags helper ────────────────────────────
|
||||
@@ -499,6 +499,43 @@ run_cli() {
|
||||
fi
|
||||
;;
|
||||
|
||||
scheduled-run)
|
||||
local sched_name="" target="" remote=""
|
||||
sched_name=$(_parse_flag "--schedule" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true
|
||||
target=$(_parse_flag "--target" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true
|
||||
remote=$(_parse_flag "--remote" "${SUBCMD_ARGS[@]+"${SUBCMD_ARGS[@]}"}") || true
|
||||
[[ -z "$sched_name" ]] && die "scheduled-run requires --schedule=NAME"
|
||||
|
||||
trap release_all_target_locks EXIT
|
||||
|
||||
local rc=0
|
||||
if [[ -n "$target" && "$target" == *,* ]]; then
|
||||
local IFS=','
|
||||
local names
|
||||
read -ra names <<< "$target"
|
||||
for t in "${names[@]}"; do
|
||||
t="${t#"${t%%[![:space:]]*}"}"
|
||||
t="${t%"${t##*[![:space:]]}"}"
|
||||
[[ -z "$t" ]] && continue
|
||||
backup_target "$t" "$remote" || rc=$?
|
||||
done
|
||||
elif [[ -n "$target" ]]; then
|
||||
backup_target "$target" "$remote" || rc=$?
|
||||
else
|
||||
backup_all_targets "$remote" || rc=$?
|
||||
fi
|
||||
|
||||
# Stamp LAST_RUN on success
|
||||
if (( rc == 0 )); then
|
||||
local conf="$CONFIG_DIR/schedules.d/${sched_name}.conf"
|
||||
if [[ -f "$conf" ]]; then
|
||||
sed -i '/^LAST_RUN=/d' "$conf"
|
||||
echo "LAST_RUN=\"$(date '+%Y-%m-%d %H:%M')\"" >> "$conf"
|
||||
fi
|
||||
fi
|
||||
exit "$rc"
|
||||
;;
|
||||
|
||||
version)
|
||||
echo "gniza v${GNIZA4LINUX_VERSION}"
|
||||
;;
|
||||
|
||||
@@ -185,7 +185,7 @@ build_cron_line() {
|
||||
extra_flags+=" --target=$SCHEDULE_TARGETS"
|
||||
fi
|
||||
|
||||
echo "$cron_expr PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\" $bin_path backup${extra_flags} >>\"${LOG_DIR}/cron.log\" 2>&1"
|
||||
echo "${cron_expr} PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\" ${bin_path} scheduled-run --schedule=${name}${extra_flags} >>\"${LOG_DIR}/cron.log\" 2>&1"
|
||||
}
|
||||
|
||||
# ── Crontab Management ────────────────────────────────────────
|
||||
|
||||
@@ -7,7 +7,7 @@ from tui.widgets.header import GnizaHeader as Header # noqa: F811
|
||||
from textual.containers import Vertical, Horizontal
|
||||
from textual import work
|
||||
|
||||
from tui.config import list_conf_dir, parse_conf, update_conf_key, CONFIG_DIR, LOG_DIR
|
||||
from tui.config import list_conf_dir, parse_conf, update_conf_key, CONFIG_DIR
|
||||
from tui.models import Schedule
|
||||
from tui.backend import run_cli
|
||||
from tui.widgets import ConfirmDialog, OperationLog, DocsPanel
|
||||
@@ -40,28 +40,15 @@ class ScheduleScreen(Screen):
|
||||
table = self.query_one("#sched-table", DataTable)
|
||||
table.clear(columns=True)
|
||||
table.add_columns("Name", "Active", "Type", "Time", "Last Run", "Next Run", "Sources", "Destinations")
|
||||
last_run = self._get_last_run()
|
||||
schedules = list_conf_dir("schedules.d")
|
||||
for name in schedules:
|
||||
data = parse_conf(CONFIG_DIR / "schedules.d" / f"{name}.conf")
|
||||
s = Schedule.from_conf(name, data)
|
||||
active = "✅" if s.active == "yes" else "❌"
|
||||
last_run = data.get("LAST_RUN", "never") or "never"
|
||||
next_run = self._calc_next_run(s) if s.active == "yes" else "inactive"
|
||||
table.add_row(name, active, s.schedule, s.time, last_run, next_run, s.targets or "all", s.remotes or "all", key=name)
|
||||
|
||||
def _get_last_run(self) -> str:
|
||||
"""Get the timestamp of the most recent backup log."""
|
||||
from pathlib import Path
|
||||
log_dir = Path(str(LOG_DIR))
|
||||
if not log_dir.is_dir():
|
||||
return "never"
|
||||
logs = sorted(log_dir.glob("gniza-*.log"), key=lambda p: p.stat().st_mtime, reverse=True)
|
||||
if not logs:
|
||||
return "never"
|
||||
mtime = logs[0].stat().st_mtime
|
||||
dt = datetime.fromtimestamp(mtime)
|
||||
return dt.strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
def _calc_next_run(self, s: Schedule) -> str:
|
||||
"""Calculate the next run time from schedule config."""
|
||||
now = datetime.now()
|
||||
|
||||
Reference in New Issue
Block a user