diff --git a/agents/patchmon-agent.sh b/agents/patchmon-agent.sh index 2bbad61..6048652 100755 --- a/agents/patchmon-agent.sh +++ b/agents/patchmon-agent.sh @@ -1093,16 +1093,33 @@ update_crontab() { local response=$(curl -ks -H "X-API-ID: $API_ID" -H "X-API-KEY: $API_KEY" -X GET "$PATCHMON_SERVER/api/$API_VERSION/settings/update-interval") if [[ $? -eq 0 ]]; then local update_interval=$(echo "$response" | grep -o '"updateInterval":[0-9]*' | cut -d':' -f2) + # Fallback if not found + if [[ -z "$update_interval" ]]; then + update_interval=60 + fi + # Normalize interval: 5-59 valid, otherwise snap to hour presets + if [[ $update_interval -lt 5 ]]; then + update_interval=5 + elif [[ $update_interval -gt 1440 ]]; then + update_interval=1440 + fi if [[ -n "$update_interval" ]]; then # Generate the expected crontab entry local expected_crontab="" - if [[ $update_interval -eq 60 ]]; then - # Hourly updates starting at current minute - local current_minute=$(date +%M) - expected_crontab="$current_minute * * * * /usr/local/bin/patchmon-agent.sh update >/dev/null 2>&1" - else - # Custom interval updates + if [[ $update_interval -lt 60 ]]; then + # Every N minutes (5-59) expected_crontab="*/$update_interval * * * * /usr/local/bin/patchmon-agent.sh update >/dev/null 2>&1" + else + # Hour-based schedules + if [[ $update_interval -eq 60 ]]; then + # Hourly updates starting at current minute to spread load + local current_minute=$(date +%M) + expected_crontab="$current_minute * * * * /usr/local/bin/patchmon-agent.sh update >/dev/null 2>&1" + else + # For 120, 180, 360, 720, 1440 -> every H hours at minute 0 + local hours=$((update_interval / 60)) + expected_crontab="0 */$hours * * * /usr/local/bin/patchmon-agent.sh update >/dev/null 2>&1" + fi fi # Get current crontab (without patchmon entries) diff --git a/agents/patchmon_install.sh b/agents/patchmon_install.sh index 58779ac..d191847 100644 --- a/agents/patchmon_install.sh +++ b/agents/patchmon_install.sh @@ -206,23 +206,35 @@ fi setup_crontab() { local update_interval="$1" local patchmon_pattern="/usr/local/bin/patchmon-agent.sh update" - + + # Normalize interval: min 5, max 1440 + if [[ -z "$update_interval" ]]; then update_interval=60; fi + if [[ "$update_interval" -lt 5 ]]; then update_interval=5; fi + if [[ "$update_interval" -gt 1440 ]]; then update_interval=1440; fi + # Get current crontab, remove any existing patchmon entries local current_cron=$(crontab -l 2>/dev/null | grep -v "$patchmon_pattern" || true) - + # Determine new cron entry local new_entry - if [[ "$update_interval" -eq 60 ]]; then - # Hourly updates - use a random minute to spread load - local current_minute=$(date +%M) - new_entry="$current_minute * * * * $patchmon_pattern >/dev/null 2>&1" - info "📋 Configuring hourly updates at minute $current_minute" - else - # Custom interval updates + if [[ "$update_interval" -lt 60 ]]; then + # Every N minutes (5-59) new_entry="*/$update_interval * * * * $patchmon_pattern >/dev/null 2>&1" info "📋 Configuring updates every $update_interval minutes" + else + if [[ "$update_interval" -eq 60 ]]; then + # Hourly updates - use current minute to spread load + local current_minute=$(date +%M) + new_entry="$current_minute * * * * $patchmon_pattern >/dev/null 2>&1" + info "📋 Configuring hourly updates at minute $current_minute" + else + # For 120, 180, 360, 720, 1440 -> every H hours at minute 0 + local hours=$((update_interval / 60)) + new_entry="0 */$hours * * * $patchmon_pattern >/dev/null 2>&1" + info "📋 Configuring updates every $hours hour(s)" + fi fi - + # Combine existing cron (without patchmon entries) + new entry { if [[ -n "$current_cron" ]]; then @@ -230,7 +242,7 @@ setup_crontab() { fi echo "$new_entry" } | crontab - - + success "✅ Crontab configured successfully (duplicates removed)" } diff --git a/backend/src/routes/settingsRoutes.js b/backend/src/routes/settingsRoutes.js index cbb1fcb..e496726 100644 --- a/backend/src/routes/settingsRoutes.js +++ b/backend/src/routes/settingsRoutes.js @@ -105,6 +105,45 @@ async function triggerCrontabUpdates() { } } +// Helpers +function normalizeUpdateInterval(minutes) { + let m = parseInt(minutes, 10); + if (Number.isNaN(m)) return 60; + if (m < 5) m = 5; + if (m > 1440) m = 1440; + if (m < 60) { + // Clamp to 5-59, step 5 + const snapped = Math.round(m / 5) * 5; + return Math.min(59, Math.max(5, snapped)); + } + // Allowed hour-based presets + const allowed = [60, 120, 180, 360, 720, 1440]; + let nearest = allowed[0]; + let bestDiff = Math.abs(m - nearest); + for (const a of allowed) { + const d = Math.abs(m - a); + if (d < bestDiff) { + bestDiff = d; + nearest = a; + } + } + return nearest; +} + +function buildCronExpression(minutes) { + const m = normalizeUpdateInterval(minutes); + if (m < 60) { + return `*/${m} * * * *`; + } + if (m === 60) { + // Hourly at current minute is chosen by agent; default 0 here + return `0 * * * *`; + } + const hours = Math.floor(m / 60); + // Every N hours at minute 0 + return `0 */${hours} * * *`; +} + // Get current settings router.get("/", authenticateToken, requireManageSettings, async (_req, res) => { try { @@ -191,11 +230,13 @@ router.put( const oldUpdateInterval = currentSettings.update_interval; // Update settings using the service + const normalizedInterval = normalizeUpdateInterval(updateInterval || 60); + const updatedSettings = await updateSettings(currentSettings.id, { server_protocol: serverProtocol, server_host: serverHost, server_port: serverPort, - update_interval: updateInterval || 60, + update_interval: normalizedInterval, auto_update: autoUpdate || false, signup_enabled: signupEnabled || false, default_user_role: @@ -211,9 +252,9 @@ router.put( console.log("Settings updated successfully:", updatedSettings); // If update interval changed, trigger crontab updates on all hosts with auto-update enabled - if (oldUpdateInterval !== (updateInterval || 60)) { + if (oldUpdateInterval !== normalizedInterval) { console.log( - `Update interval changed from ${oldUpdateInterval} to ${updateInterval || 60} minutes. Triggering crontab updates...`, + `Update interval changed from ${oldUpdateInterval} to ${normalizedInterval} minutes. Triggering crontab updates...`, ); await triggerCrontabUpdates(); } @@ -262,9 +303,10 @@ router.get("/update-interval", async (req, res) => { } const settings = await getSettings(); + const interval = normalizeUpdateInterval(settings.update_interval || 60); res.json({ - updateInterval: settings.update_interval, - cronExpression: `*/${settings.update_interval} * * * *`, // Generate cron expression + updateInterval: interval, + cronExpression: buildCronExpression(interval), }); } catch (error) { console.error("Update interval fetch error:", error); diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx index 7de6207..f3d218f 100644 --- a/frontend/src/pages/Settings.jsx +++ b/frontend/src/pages/Settings.jsx @@ -272,9 +272,37 @@ const Settings = () => { } }; + // Normalize update interval to safe presets + const normalizeInterval = (minutes) => { + let m = parseInt(minutes, 10); + if (Number.isNaN(m)) return 60; + if (m < 5) m = 5; + if (m > 1440) m = 1440; + // If less than 60 minutes, keep within 5-59 and step of 5 + if (m < 60) { + return Math.min(59, Math.max(5, Math.round(m / 5) * 5)); + } + // 60 or more: only allow exact hour multiples (60, 120, 180, 360, 720, 1440) + const allowed = [60, 120, 180, 360, 720, 1440]; + // Snap to nearest allowed value + let nearest = allowed[0]; + let bestDiff = Math.abs(m - nearest); + for (const a of allowed) { + const d = Math.abs(m - a); + if (d < bestDiff) { + bestDiff = d; + nearest = a; + } + } + return nearest; + }; + const handleInputChange = (field, value) => { setFormData((prev) => { - const newData = { ...prev, [field]: value }; + const newData = { + ...prev, + [field]: field === "updateInterval" ? normalizeInterval(value) : value, + }; return newData; }); setIsDirty(true); @@ -563,21 +591,23 @@ const Settings = () => { {/* Quick presets */}