mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-11-06 15:03:26 +00:00
Merge pull request #98 from 9technologygroup/dev
Fixed Crontab timing Expression
This commit is contained in:
@@ -84,7 +84,7 @@ apt install curl -y
|
|||||||
|
|
||||||
#### Script
|
#### Script
|
||||||
```bash
|
```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 : #####
|
#### Minimum specs for building : #####
|
||||||
|
|||||||
@@ -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")
|
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
|
if [[ $? -eq 0 ]]; then
|
||||||
local update_interval=$(echo "$response" | grep -o '"updateInterval":[0-9]*' | cut -d':' -f2)
|
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
|
if [[ -n "$update_interval" ]]; then
|
||||||
# Generate the expected crontab entry
|
# Generate the expected crontab entry
|
||||||
local expected_crontab=""
|
local expected_crontab=""
|
||||||
if [[ $update_interval -eq 60 ]]; then
|
if [[ $update_interval -lt 60 ]]; then
|
||||||
# Hourly updates starting at current minute
|
# Every N minutes (5-59)
|
||||||
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"
|
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
|
fi
|
||||||
|
|
||||||
# Get current crontab (without patchmon entries)
|
# Get current crontab (without patchmon entries)
|
||||||
|
|||||||
@@ -206,23 +206,35 @@ fi
|
|||||||
setup_crontab() {
|
setup_crontab() {
|
||||||
local update_interval="$1"
|
local update_interval="$1"
|
||||||
local patchmon_pattern="/usr/local/bin/patchmon-agent.sh update"
|
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
|
# Get current crontab, remove any existing patchmon entries
|
||||||
local current_cron=$(crontab -l 2>/dev/null | grep -v "$patchmon_pattern" || true)
|
local current_cron=$(crontab -l 2>/dev/null | grep -v "$patchmon_pattern" || true)
|
||||||
|
|
||||||
# Determine new cron entry
|
# Determine new cron entry
|
||||||
local new_entry
|
local new_entry
|
||||||
if [[ "$update_interval" -eq 60 ]]; then
|
if [[ "$update_interval" -lt 60 ]]; then
|
||||||
# Hourly updates - use a random minute to spread load
|
# Every N minutes (5-59)
|
||||||
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"
|
new_entry="*/$update_interval * * * * $patchmon_pattern >/dev/null 2>&1"
|
||||||
info "📋 Configuring updates every $update_interval minutes"
|
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
|
fi
|
||||||
|
|
||||||
# Combine existing cron (without patchmon entries) + new entry
|
# Combine existing cron (without patchmon entries) + new entry
|
||||||
{
|
{
|
||||||
if [[ -n "$current_cron" ]]; then
|
if [[ -n "$current_cron" ]]; then
|
||||||
@@ -230,7 +242,7 @@ setup_crontab() {
|
|||||||
fi
|
fi
|
||||||
echo "$new_entry"
|
echo "$new_entry"
|
||||||
} | crontab -
|
} | crontab -
|
||||||
|
|
||||||
success "✅ Crontab configured successfully (duplicates removed)"
|
success "✅ Crontab configured successfully (duplicates removed)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// Get current settings
|
||||||
router.get("/", authenticateToken, requireManageSettings, async (_req, res) => {
|
router.get("/", authenticateToken, requireManageSettings, async (_req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -191,11 +230,13 @@ router.put(
|
|||||||
const oldUpdateInterval = currentSettings.update_interval;
|
const oldUpdateInterval = currentSettings.update_interval;
|
||||||
|
|
||||||
// Update settings using the service
|
// Update settings using the service
|
||||||
|
const normalizedInterval = normalizeUpdateInterval(updateInterval || 60);
|
||||||
|
|
||||||
const updatedSettings = await updateSettings(currentSettings.id, {
|
const updatedSettings = await updateSettings(currentSettings.id, {
|
||||||
server_protocol: serverProtocol,
|
server_protocol: serverProtocol,
|
||||||
server_host: serverHost,
|
server_host: serverHost,
|
||||||
server_port: serverPort,
|
server_port: serverPort,
|
||||||
update_interval: updateInterval || 60,
|
update_interval: normalizedInterval,
|
||||||
auto_update: autoUpdate || false,
|
auto_update: autoUpdate || false,
|
||||||
signup_enabled: signupEnabled || false,
|
signup_enabled: signupEnabled || false,
|
||||||
default_user_role:
|
default_user_role:
|
||||||
@@ -211,9 +252,9 @@ router.put(
|
|||||||
console.log("Settings updated successfully:", updatedSettings);
|
console.log("Settings updated successfully:", updatedSettings);
|
||||||
|
|
||||||
// If update interval changed, trigger crontab updates on all hosts with auto-update enabled
|
// If update interval changed, trigger crontab updates on all hosts with auto-update enabled
|
||||||
if (oldUpdateInterval !== (updateInterval || 60)) {
|
if (oldUpdateInterval !== normalizedInterval) {
|
||||||
console.log(
|
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();
|
await triggerCrontabUpdates();
|
||||||
}
|
}
|
||||||
@@ -262,9 +303,10 @@ router.get("/update-interval", async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const settings = await getSettings();
|
const settings = await getSettings();
|
||||||
|
const interval = normalizeUpdateInterval(settings.update_interval || 60);
|
||||||
res.json({
|
res.json({
|
||||||
updateInterval: settings.update_interval,
|
updateInterval: interval,
|
||||||
cronExpression: `*/${settings.update_interval} * * * *`, // Generate cron expression
|
cronExpression: buildCronExpression(interval),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Update interval fetch error:", error);
|
console.error("Update interval fetch error:", error);
|
||||||
|
|||||||
@@ -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) => {
|
const handleInputChange = (field, value) => {
|
||||||
setFormData((prev) => {
|
setFormData((prev) => {
|
||||||
const newData = { ...prev, [field]: value };
|
const newData = {
|
||||||
|
...prev,
|
||||||
|
[field]: field === "updateInterval" ? normalizeInterval(value) : value,
|
||||||
|
};
|
||||||
return newData;
|
return newData;
|
||||||
});
|
});
|
||||||
setIsDirty(true);
|
setIsDirty(true);
|
||||||
@@ -563,21 +591,23 @@ const Settings = () => {
|
|||||||
|
|
||||||
{/* Quick presets */}
|
{/* Quick presets */}
|
||||||
<div className="mt-3 flex flex-wrap items-center gap-2">
|
<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(
|
||||||
<button
|
(m) => (
|
||||||
key={m}
|
<button
|
||||||
type="button"
|
key={m}
|
||||||
onClick={() => handleInputChange("updateInterval", m)}
|
type="button"
|
||||||
className={`px-3 py-1.5 rounded-full text-xs font-medium border ${
|
onClick={() => handleInputChange("updateInterval", m)}
|
||||||
formData.updateInterval === m
|
className={`px-3 py-1.5 rounded-full text-xs font-medium border ${
|
||||||
? "bg-primary-600 text-white border-primary-600"
|
formData.updateInterval === m
|
||||||
: "bg-white dark:bg-secondary-700 text-secondary-700 dark:text-secondary-200 border-secondary-300 dark:border-secondary-600 hover:bg-secondary-50 dark:hover:bg-secondary-600"
|
? "bg-primary-600 text-white border-primary-600"
|
||||||
}`}
|
: "bg-white dark:bg-secondary-700 text-secondary-700 dark:text-secondary-200 border-secondary-300 dark:border-secondary-600 hover:bg-secondary-50 dark:hover:bg-secondary-600"
|
||||||
aria-label={`Set ${m} minutes`}
|
}`}
|
||||||
>
|
aria-label={`Set ${m} minutes`}
|
||||||
{m % 60 === 0 ? `${m / 60}h` : `${m}m`}
|
>
|
||||||
</button>
|
{m % 60 === 0 ? `${m / 60}h` : `${m}m`}
|
||||||
))}
|
</button>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Range slider */}
|
{/* Range slider */}
|
||||||
@@ -588,12 +618,13 @@ const Settings = () => {
|
|||||||
max="1440"
|
max="1440"
|
||||||
step="5"
|
step="5"
|
||||||
value={formData.updateInterval}
|
value={formData.updateInterval}
|
||||||
onChange={(e) =>
|
onChange={(e) => {
|
||||||
|
const raw = parseInt(e.target.value, 10);
|
||||||
handleInputChange(
|
handleInputChange(
|
||||||
"updateInterval",
|
"updateInterval",
|
||||||
parseInt(e.target.value, 10),
|
normalizeInterval(raw),
|
||||||
)
|
);
|
||||||
}
|
}}
|
||||||
className="w-full accent-primary-600"
|
className="w-full accent-primary-600"
|
||||||
aria-label="Update interval slider"
|
aria-label="Update interval slider"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user