Merge pull request #98 from 9technologygroup/dev

Fixed Crontab timing Expression
This commit is contained in:
9 Technology Group LTD
2025-09-30 09:38:37 +01:00
committed by GitHub
5 changed files with 145 additions and 43 deletions

View File

@@ -84,7 +84,7 @@ apt install curl -y
#### Script
```bash
curl -fsSL -o setup.sh https://raw.githubusercontent.com/9technologygroup/patchmon.net/main/setup.sh && chmod +x setup.sh && bash setup.sh
curl -fsSL -o setup.sh https://raw.githubusercontent.com/9technologygroup/patchmon.net/refs/heads/main/setup.sh && chmod +x setup.sh && bash setup.sh
```
#### Minimum specs for building : #####

View File

@@ -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 -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
# 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
# Custom interval updates
expected_crontab="*/$update_interval * * * * /usr/local/bin/patchmon-agent.sh update >/dev/null 2>&1"
# 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)

View File

@@ -207,20 +207,32 @@ 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" -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 a random minute to spread load
# 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
# Custom interval updates
new_entry="*/$update_interval * * * * $patchmon_pattern >/dev/null 2>&1"
info "📋 Configuring updates every $update_interval minutes"
# 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

View File

@@ -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);

View File

@@ -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,7 +591,8 @@ const Settings = () => {
{/* Quick presets */}
<div className="mt-3 flex flex-wrap items-center gap-2">
{[15, 30, 60, 120, 360, 720, 1440].map((m) => (
{[5, 10, 15, 30, 45, 60, 120, 180, 360, 720, 1440].map(
(m) => (
<button
key={m}
type="button"
@@ -577,7 +606,8 @@ const Settings = () => {
>
{m % 60 === 0 ? `${m / 60}h` : `${m}m`}
</button>
))}
),
)}
</div>
{/* Range slider */}
@@ -588,12 +618,13 @@ const Settings = () => {
max="1440"
step="5"
value={formData.updateInterval}
onChange={(e) =>
onChange={(e) => {
const raw = parseInt(e.target.value, 10);
handleInputChange(
"updateInterval",
parseInt(e.target.value, 10),
)
}
normalizeInterval(raw),
);
}}
className="w-full accent-primary-600"
aria-label="Update interval slider"
/>