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
|
# Only create log files for operations that produce meaningful output
|
||||||
case "${SUBCOMMAND:-}" in
|
case "${SUBCOMMAND:-}" in
|
||||||
backup|restore|retention) init_logging ;;
|
backup|restore|retention|scheduled-run) init_logging ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# ── Parse subcommand flags helper ────────────────────────────
|
# ── Parse subcommand flags helper ────────────────────────────
|
||||||
@@ -499,6 +499,43 @@ run_cli() {
|
|||||||
fi
|
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)
|
version)
|
||||||
echo "gniza v${GNIZA4LINUX_VERSION}"
|
echo "gniza v${GNIZA4LINUX_VERSION}"
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ build_cron_line() {
|
|||||||
extra_flags+=" --target=$SCHEDULE_TARGETS"
|
extra_flags+=" --target=$SCHEDULE_TARGETS"
|
||||||
fi
|
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 ────────────────────────────────────────
|
# ── Crontab Management ────────────────────────────────────────
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from tui.widgets.header import GnizaHeader as Header # noqa: F811
|
|||||||
from textual.containers import Vertical, Horizontal
|
from textual.containers import Vertical, Horizontal
|
||||||
from textual import work
|
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.models import Schedule
|
||||||
from tui.backend import run_cli
|
from tui.backend import run_cli
|
||||||
from tui.widgets import ConfirmDialog, OperationLog, DocsPanel
|
from tui.widgets import ConfirmDialog, OperationLog, DocsPanel
|
||||||
@@ -40,28 +40,15 @@ class ScheduleScreen(Screen):
|
|||||||
table = self.query_one("#sched-table", DataTable)
|
table = self.query_one("#sched-table", DataTable)
|
||||||
table.clear(columns=True)
|
table.clear(columns=True)
|
||||||
table.add_columns("Name", "Active", "Type", "Time", "Last Run", "Next Run", "Sources", "Destinations")
|
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")
|
schedules = list_conf_dir("schedules.d")
|
||||||
for name in schedules:
|
for name in schedules:
|
||||||
data = parse_conf(CONFIG_DIR / "schedules.d" / f"{name}.conf")
|
data = parse_conf(CONFIG_DIR / "schedules.d" / f"{name}.conf")
|
||||||
s = Schedule.from_conf(name, data)
|
s = Schedule.from_conf(name, data)
|
||||||
active = "✅" if s.active == "yes" else "❌"
|
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"
|
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)
|
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:
|
def _calc_next_run(self, s: Schedule) -> str:
|
||||||
"""Calculate the next run time from schedule config."""
|
"""Calculate the next run time from schedule config."""
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|||||||
Reference in New Issue
Block a user