From 6865f3c9a4acfa07d8e950972e6c368c3de3bc60 Mon Sep 17 00:00:00 2001 From: Debian Date: Mon, 5 Jan 2026 10:08:04 +0000 Subject: [PATCH] Add systemd timer fallback when crontab unavailable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Try crontab first, fall back to systemd timer - If neither available, still deploy script but warn user - Update frontend to show scheduling method (cron/systemd/none) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- backend/services/monitors.py | 68 +++++++++++++++++--- frontend/src/components/DiscoveryResults.jsx | 14 +++- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/backend/services/monitors.py b/backend/services/monitors.py index f856010..4301c5b 100644 --- a/backend/services/monitors.py +++ b/backend/services/monitors.py @@ -298,22 +298,61 @@ class MonitorService: "error": f"Failed to make script executable: {error_detail}", } - # Add cronjob entry (remove existing entry first to avoid duplicates) + # Try to add cronjob entry (crontab may not be available on all systems) cron_cmd = f"(crontab -l 2>/dev/null | grep -v '{script_filename}'; echo '{cronjob_entry}') | crontab -" cron_result = ssh.execute(hostname, cron_cmd, username, port) - if not cron_result.success: - return { - "status": "failed", - "error": f"Failed to add cronjob: {cron_result.stderr}", - } + + scheduling_method = None + scheduling_info = None + + if cron_result.success: + scheduling_method = "crontab" + scheduling_info = cronjob_entry + else: + # Try systemd timer as fallback + timer_name = f"kuma-push-{push_metric}-{monitor_id}" + timer_content = f"""[Unit] +Description=Kuma Push Monitor - {push_metric} + +[Timer] +OnBootSec=1min +OnUnitActiveSec={interval_minutes}min +AccuracySec=1s + +[Install] +WantedBy=timers.target +""" + service_content = f"""[Unit] +Description=Kuma Push Monitor - {push_metric} + +[Service] +Type=oneshot +ExecStart={script_path} +""" + # Write timer and service files + timer_path = f"/etc/systemd/system/{timer_name}.timer" + service_path = f"/etc/systemd/system/{timer_name}.service" + + timer_cmd = f"sudo tee {timer_path} > /dev/null << 'KUMA_TIMER_EOF'\n{timer_content}KUMA_TIMER_EOF" + service_cmd = f"sudo tee {service_path} > /dev/null << 'KUMA_SERVICE_EOF'\n{service_content}KUMA_SERVICE_EOF" + + timer_result = ssh.execute(hostname, timer_cmd, username, port) + if timer_result.success: + service_result = ssh.execute(hostname, service_cmd, username, port) + if service_result.success: + # Enable and start the timer + enable_cmd = f"sudo systemctl daemon-reload && sudo systemctl enable --now {timer_name}.timer" + enable_result = ssh.execute(hostname, enable_cmd, username, port) + if enable_result.success: + scheduling_method = "systemd" + scheduling_info = f"{timer_name}.timer" # Run the script once immediately to verify it works run_result = ssh.execute(hostname, script_path, username, port, timeout=30) - return { + result = { "status": "deployed", "script_path": script_path, - "cronjob": cronjob_entry, "initial_run": { "success": run_result.success, "stdout": run_result.stdout, @@ -321,6 +360,19 @@ class MonitorService: }, } + if scheduling_method: + result["scheduling"] = { + "method": scheduling_method, + "info": scheduling_info, + } + else: + result["scheduling"] = { + "method": "none", + "warning": "Neither crontab nor systemd available. Script deployed but not scheduled.", + } + + return result + except Exception as e: logger.exception(f"Failed to deploy push script to {hostname}") return { diff --git a/frontend/src/components/DiscoveryResults.jsx b/frontend/src/components/DiscoveryResults.jsx index d979a19..c287fe6 100644 --- a/frontend/src/components/DiscoveryResults.jsx +++ b/frontend/src/components/DiscoveryResults.jsx @@ -409,9 +409,17 @@ export default function DiscoveryResults({ scanId, scan, analysis, devMode, onCo )} - {result.deployment.cronjob && ( -
- Cronjob: {result.deployment.cronjob} + {result.deployment.scheduling && ( +
+ {result.deployment.scheduling.method === 'crontab' && ( + Cron: {result.deployment.scheduling.info} + )} + {result.deployment.scheduling.method === 'systemd' && ( + Systemd timer: {result.deployment.scheduling.info} + )} + {result.deployment.scheduling.method === 'none' && ( + {result.deployment.scheduling.warning} + )}
)}