|
|
|
|
@@ -1,11 +1,24 @@
|
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
|
|
|
|
|
# Copyright (c) 2021-2026 tteck
|
|
|
|
|
# Author: tteck (tteckster)
|
|
|
|
|
# License: MIT
|
|
|
|
|
# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
|
|
|
|
# Copyright (c) 2021-2026 community-scripts ORG
|
|
|
|
|
# Author: MickLesk (CanbiZ)
|
|
|
|
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
|
|
|
|
#
|
|
|
|
|
# This script manages a local cron job for automatic LXC container OS updates.
|
|
|
|
|
# The update script is downloaded once, displayed for review, and installed
|
|
|
|
|
# locally. Cron runs the local copy — no remote code execution at runtime.
|
|
|
|
|
#
|
|
|
|
|
# bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/cron-update-lxcs.sh)"
|
|
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
REPO_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
|
|
|
|
|
SCRIPT_URL="${REPO_URL}/tools/pve/update-lxcs-cron.sh"
|
|
|
|
|
LOCAL_SCRIPT="/usr/local/bin/update-lxcs.sh"
|
|
|
|
|
CONF_FILE="/etc/update-lxcs.conf"
|
|
|
|
|
LOG_FILE="/var/log/update-lxcs-cron.log"
|
|
|
|
|
CRON_ENTRY="0 0 * * 0 ${LOCAL_SCRIPT} >>${LOG_FILE} 2>&1"
|
|
|
|
|
|
|
|
|
|
clear
|
|
|
|
|
cat <<"EOF"
|
|
|
|
|
______ __ __ __ __ __ _ ________
|
|
|
|
|
@@ -16,41 +29,322 @@ cat <<"EOF"
|
|
|
|
|
/_/
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
add() {
|
|
|
|
|
info() { echo -e "\n \e[36m[Info]\e[0m $1"; }
|
|
|
|
|
ok() { echo -e " \e[32m[OK]\e[0m $1"; }
|
|
|
|
|
err() { echo -e " \e[31m[Error]\e[0m $1" >&2; }
|
|
|
|
|
|
|
|
|
|
confirm() {
|
|
|
|
|
local prompt="${1:-Proceed?}"
|
|
|
|
|
while true; do
|
|
|
|
|
read -p "This script will add a crontab schedule that updates all LXCs every Sunday at midnight. Proceed(y/n)?" yn
|
|
|
|
|
read -rp " ${prompt} (y/n): " yn
|
|
|
|
|
case $yn in
|
|
|
|
|
[Yy]*) break ;;
|
|
|
|
|
[Nn]*) exit ;;
|
|
|
|
|
*) echo "Please answer yes or no." ;;
|
|
|
|
|
[Yy]*) return 0 ;;
|
|
|
|
|
[Nn]*) return 1 ;;
|
|
|
|
|
*) echo " Please answer yes or no." ;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
sh -c '(crontab -l -u root 2>/dev/null; echo "0 0 * * 0 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/update-lxcs-cron.sh)\" >>/var/log/update-lxcs-cron.log 2>/dev/null") | crontab -u root -'
|
|
|
|
|
clear
|
|
|
|
|
echo -e "\n To view Cron Update LXCs logs: cat /var/log/update-lxcs-cron.log"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
download_script() {
|
|
|
|
|
local tmp
|
|
|
|
|
tmp=$(mktemp)
|
|
|
|
|
if ! curl -fsSL -o "$tmp" "$SCRIPT_URL"; then
|
|
|
|
|
err "Failed to download script from:\n ${SCRIPT_URL}"
|
|
|
|
|
rm -f "$tmp"
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
echo "$tmp"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
review_script() {
|
|
|
|
|
local file="$1"
|
|
|
|
|
local hash
|
|
|
|
|
hash=$(sha256sum "$file" | awk '{print $1}')
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e " \e[1;33m─── Script Content ───────────────────────────────────────────\e[0m"
|
|
|
|
|
cat "$file"
|
|
|
|
|
echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m"
|
|
|
|
|
echo -e " \e[36mSHA256:\e[0m ${hash}"
|
|
|
|
|
echo -e " \e[36mSource:\e[0m ${SCRIPT_URL}"
|
|
|
|
|
echo ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
remove_legacy_cron() {
|
|
|
|
|
if crontab -l -u root 2>/dev/null | grep -q "update-lxcs-cron.sh"; then
|
|
|
|
|
(crontab -l -u root 2>/dev/null | grep -v "update-lxcs-cron.sh") | crontab -u root -
|
|
|
|
|
ok "Removed legacy curl-based cron entry"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
add() {
|
|
|
|
|
info "Downloading update script..."
|
|
|
|
|
local tmp
|
|
|
|
|
tmp=$(download_script) || exit 1
|
|
|
|
|
|
|
|
|
|
local hash
|
|
|
|
|
hash=$(sha256sum "$tmp" | awk '{print $1}')
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e " \e[1;33m─── Installation Summary ─────────────────────────────────────\e[0m"
|
|
|
|
|
echo -e " \e[36mSource:\e[0m ${SCRIPT_URL}"
|
|
|
|
|
echo -e " \e[36mSHA256:\e[0m ${hash}"
|
|
|
|
|
echo -e " \e[36mInstall to:\e[0m ${LOCAL_SCRIPT}"
|
|
|
|
|
echo -e " \e[36mConfig:\e[0m ${CONF_FILE}"
|
|
|
|
|
echo -e " \e[36mLog file:\e[0m ${LOG_FILE}"
|
|
|
|
|
echo -e " \e[36mCron schedule:\e[0m Every Sunday at midnight (0 0 * * 0)"
|
|
|
|
|
echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
if confirm "Review script content before installing?"; then
|
|
|
|
|
review_script "$tmp"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if ! confirm "Install this script and activate cron schedule?"; then
|
|
|
|
|
rm -f "$tmp"
|
|
|
|
|
echo " Aborted."
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
remove_legacy_cron
|
|
|
|
|
|
|
|
|
|
install -m 0755 "$tmp" "$LOCAL_SCRIPT"
|
|
|
|
|
rm -f "$tmp"
|
|
|
|
|
ok "Installed script to ${LOCAL_SCRIPT}"
|
|
|
|
|
|
|
|
|
|
if [[ ! -f "$CONF_FILE" ]]; then
|
|
|
|
|
cat >"$CONF_FILE" <<'CONF'
|
|
|
|
|
# Configuration for automatic LXC container OS updates.
|
|
|
|
|
# Add container IDs to exclude from updates (comma-separated):
|
|
|
|
|
# EXCLUDE=100,101,102
|
|
|
|
|
EXCLUDE=
|
|
|
|
|
CONF
|
|
|
|
|
ok "Created config ${CONF_FILE}"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
(
|
|
|
|
|
crontab -l -u root 2>/dev/null | grep -v "${LOCAL_SCRIPT}"
|
|
|
|
|
echo "${CRON_ENTRY}"
|
|
|
|
|
) | crontab -u root -
|
|
|
|
|
ok "Added cron schedule: Every Sunday at midnight"
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e " \e[36mLocal script:\e[0m ${LOCAL_SCRIPT}"
|
|
|
|
|
echo -e " \e[36mConfig:\e[0m ${CONF_FILE}"
|
|
|
|
|
echo -e " \e[36mLog file:\e[0m ${LOG_FILE}"
|
|
|
|
|
echo ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
remove() {
|
|
|
|
|
(crontab -l | grep -v "update-lxcs-cron.sh") | crontab -
|
|
|
|
|
rm -rf /var/log/update-lxcs-cron.log
|
|
|
|
|
echo "Removed Crontab Schedule from Proxmox VE"
|
|
|
|
|
if crontab -l -u root 2>/dev/null | grep -q "${LOCAL_SCRIPT}"; then
|
|
|
|
|
(crontab -l -u root 2>/dev/null | grep -v "${LOCAL_SCRIPT}") | crontab -u root -
|
|
|
|
|
ok "Removed cron schedule"
|
|
|
|
|
fi
|
|
|
|
|
remove_legacy_cron
|
|
|
|
|
[[ -f "$LOCAL_SCRIPT" ]] && rm -f "$LOCAL_SCRIPT" && ok "Removed ${LOCAL_SCRIPT}"
|
|
|
|
|
[[ -f "$LOG_FILE" ]] && rm -f "$LOG_FILE" && ok "Removed ${LOG_FILE}"
|
|
|
|
|
echo -e "\n Cron Update LXCs has been fully removed."
|
|
|
|
|
echo -e " \e[90mNote: ${CONF_FILE} was kept (remove manually if desired).\e[0m"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OPTIONS=(Add "Add Crontab Schedule"
|
|
|
|
|
Remove "Remove Crontab Schedule")
|
|
|
|
|
update_script() {
|
|
|
|
|
if [[ ! -f "$LOCAL_SCRIPT" ]]; then
|
|
|
|
|
err "No local script found at ${LOCAL_SCRIPT}. Use 'Add' first."
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Cron Update LXCs" --menu "Select an option:" 10 58 2 \
|
|
|
|
|
"${OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
|
|
|
|
info "Downloading latest version..."
|
|
|
|
|
local tmp
|
|
|
|
|
tmp=$(download_script) || exit 1
|
|
|
|
|
|
|
|
|
|
if command -v diff &>/dev/null; then
|
|
|
|
|
local changes
|
|
|
|
|
changes=$(diff --color=auto "$LOCAL_SCRIPT" "$tmp" 2>/dev/null || true)
|
|
|
|
|
if [[ -z "$changes" ]]; then
|
|
|
|
|
ok "Script is already up-to-date (no changes)."
|
|
|
|
|
rm -f "$tmp"
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e " \e[1;33m─── Changes ──────────────────────────────────────────────────\e[0m"
|
|
|
|
|
echo "$changes"
|
|
|
|
|
echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m"
|
|
|
|
|
else
|
|
|
|
|
review_script "$tmp"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local new_hash old_hash
|
|
|
|
|
new_hash=$(sha256sum "$tmp" | awk '{print $1}')
|
|
|
|
|
old_hash=$(sha256sum "$LOCAL_SCRIPT" | awk '{print $1}')
|
|
|
|
|
echo -e " \e[36mCurrent SHA256:\e[0m ${old_hash}"
|
|
|
|
|
echo -e " \e[36mNew SHA256:\e[0m ${new_hash}"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
if ! confirm "Apply update?"; then
|
|
|
|
|
rm -f "$tmp"
|
|
|
|
|
echo " Aborted."
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
install -m 0755 "$tmp" "$LOCAL_SCRIPT"
|
|
|
|
|
rm -f "$tmp"
|
|
|
|
|
ok "Updated ${LOCAL_SCRIPT}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
view_script() {
|
|
|
|
|
if [[ ! -f "$LOCAL_SCRIPT" ]]; then
|
|
|
|
|
err "No local script found at ${LOCAL_SCRIPT}. Use 'Add' first."
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local view_choice
|
|
|
|
|
view_choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "View Script" --menu "What do you want to view?" 12 60 3 \
|
|
|
|
|
"Worker" "Installed update script (${LOCAL_SCRIPT##*/})" \
|
|
|
|
|
"Cron" "Cron schedule & configuration" \
|
|
|
|
|
"Both" "Show everything" \
|
|
|
|
|
3>&1 1>&2 2>&3) || return 0
|
|
|
|
|
|
|
|
|
|
case "$view_choice" in
|
|
|
|
|
"Worker") view_worker_script ;;
|
|
|
|
|
"Cron") view_cron_config ;;
|
|
|
|
|
"Both") view_cron_config && echo "" && view_worker_script ;;
|
|
|
|
|
esac
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
view_worker_script() {
|
|
|
|
|
local hash
|
|
|
|
|
hash=$(sha256sum "$LOCAL_SCRIPT" | awk '{print $1}')
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e " \e[1;33m─── ${LOCAL_SCRIPT} ───\e[0m"
|
|
|
|
|
cat "$LOCAL_SCRIPT"
|
|
|
|
|
echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m"
|
|
|
|
|
echo -e " \e[36mSHA256:\e[0m ${hash}"
|
|
|
|
|
echo -e " \e[36mInstalled:\e[0m $(stat -c '%y' "$LOCAL_SCRIPT" 2>/dev/null | cut -d. -f1)"
|
|
|
|
|
echo ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
view_cron_config() {
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e " \e[1;33m─── Cron Configuration ───────────────────────────────────────\e[0m"
|
|
|
|
|
if crontab -l -u root 2>/dev/null | grep -q "${LOCAL_SCRIPT}"; then
|
|
|
|
|
local entry
|
|
|
|
|
entry=$(crontab -l -u root 2>/dev/null | grep "${LOCAL_SCRIPT}")
|
|
|
|
|
echo -e " \e[36mCron entry:\e[0m ${entry}"
|
|
|
|
|
local schedule
|
|
|
|
|
schedule=$(echo "$entry" | awk '{print $1,$2,$3,$4,$5}')
|
|
|
|
|
echo -e " \e[36mSchedule:\e[0m ${schedule} ($(cron_to_human "$schedule"))"
|
|
|
|
|
else
|
|
|
|
|
echo -e " \e[31mCron:\e[0m Not configured"
|
|
|
|
|
fi
|
|
|
|
|
if [[ -f "$CONF_FILE" ]]; then
|
|
|
|
|
echo -e " \e[36mConfig file:\e[0m ${CONF_FILE}"
|
|
|
|
|
local excludes
|
|
|
|
|
excludes=$(grep -oP '^\s*EXCLUDE\s*=\s*\K.*' "$CONF_FILE" 2>/dev/null || true)
|
|
|
|
|
echo -e " \e[36mExcluded:\e[0m ${excludes:-(none)}"
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e " \e[90m--- ${CONF_FILE} ---\e[0m"
|
|
|
|
|
cat "$CONF_FILE"
|
|
|
|
|
else
|
|
|
|
|
echo -e " \e[36mConfig file:\e[0m (not created yet)"
|
|
|
|
|
fi
|
|
|
|
|
if [[ -f "$LOG_FILE" ]]; then
|
|
|
|
|
local log_size
|
|
|
|
|
log_size=$(du -h "$LOG_FILE" | awk '{print $1}')
|
|
|
|
|
echo -e " \e[36mLog file:\e[0m ${LOG_FILE} (${log_size})"
|
|
|
|
|
fi
|
|
|
|
|
echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m"
|
|
|
|
|
echo ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cron_to_human() {
|
|
|
|
|
local schedule="$1"
|
|
|
|
|
case "$schedule" in
|
|
|
|
|
"0 0 * * 0") echo "Every Sunday at midnight" ;;
|
|
|
|
|
"0 0 * * *") echo "Daily at midnight" ;;
|
|
|
|
|
"0 * * * *") echo "Every hour" ;;
|
|
|
|
|
*) echo "Custom schedule" ;;
|
|
|
|
|
esac
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
show_status() {
|
|
|
|
|
echo ""
|
|
|
|
|
if [[ -f "$LOCAL_SCRIPT" ]]; then
|
|
|
|
|
local hash
|
|
|
|
|
hash=$(sha256sum "$LOCAL_SCRIPT" | awk '{print $1}')
|
|
|
|
|
ok "Script installed: ${LOCAL_SCRIPT}"
|
|
|
|
|
echo -e " \e[36mSHA256:\e[0m ${hash}"
|
|
|
|
|
echo -e " \e[36mInstalled:\e[0m $(stat -c '%y' "$LOCAL_SCRIPT" 2>/dev/null | cut -d. -f1)"
|
|
|
|
|
else
|
|
|
|
|
err "Script not installed"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if crontab -l -u root 2>/dev/null | grep -q "${LOCAL_SCRIPT}"; then
|
|
|
|
|
local schedule
|
|
|
|
|
schedule=$(crontab -l -u root 2>/dev/null | grep "${LOCAL_SCRIPT}" | awk '{print $1,$2,$3,$4,$5}')
|
|
|
|
|
ok "Cron active: ${schedule}"
|
|
|
|
|
else
|
|
|
|
|
err "Cron not configured"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ -f "$CONF_FILE" ]]; then
|
|
|
|
|
local excludes
|
|
|
|
|
excludes=$(grep -oP '^\s*EXCLUDE\s*=\s*\K.*' "$CONF_FILE" 2>/dev/null || echo "(none)")
|
|
|
|
|
echo -e " \e[36mExcluded:\e[0m ${excludes:-"(none)"}"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ -f "$LOG_FILE" ]]; then
|
|
|
|
|
local log_size last_run
|
|
|
|
|
log_size=$(du -h "$LOG_FILE" | awk '{print $1}')
|
|
|
|
|
last_run=$(grep -oP '^\s+\K\w.*' "$LOG_FILE" | tail -1)
|
|
|
|
|
echo -e " \e[36mLog file:\e[0m ${LOG_FILE} (${log_size})"
|
|
|
|
|
[[ -n "${last_run:-}" ]] && echo -e " \e[36mLast run:\e[0m ${last_run}"
|
|
|
|
|
else
|
|
|
|
|
echo -e " \e[36mLog file:\e[0m (no runs yet)"
|
|
|
|
|
fi
|
|
|
|
|
echo ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
run_now() {
|
|
|
|
|
if [[ ! -f "$LOCAL_SCRIPT" ]]; then
|
|
|
|
|
err "No local script found at ${LOCAL_SCRIPT}. Use 'Add' first."
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
info "Running update script now..."
|
|
|
|
|
bash "$LOCAL_SCRIPT" | tee -a "$LOG_FILE"
|
|
|
|
|
ok "Run completed. Log appended to ${LOG_FILE}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rotate_log() {
|
|
|
|
|
if [[ ! -f "$LOG_FILE" ]]; then
|
|
|
|
|
info "No log file to rotate."
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
local log_size
|
|
|
|
|
log_size=$(stat -c '%s' "$LOG_FILE" 2>/dev/null || echo 0)
|
|
|
|
|
local log_size_h
|
|
|
|
|
log_size_h=$(du -h "$LOG_FILE" | awk '{print $1}')
|
|
|
|
|
if confirm "Rotate log file? (current size: ${log_size_h})"; then
|
|
|
|
|
mv "$LOG_FILE" "${LOG_FILE}.old"
|
|
|
|
|
ok "Rotated: ${LOG_FILE} → ${LOG_FILE}.old"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OPTIONS=(
|
|
|
|
|
Add "Download, review & install cron schedule"
|
|
|
|
|
Remove "Remove cron schedule & local script"
|
|
|
|
|
Update "Update local script from repository"
|
|
|
|
|
Status "Show installation status & last run"
|
|
|
|
|
Run "Run update script now (manual trigger)"
|
|
|
|
|
View "View cron config & installed script"
|
|
|
|
|
Rotate "Rotate log file"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Cron Update LXCs" --menu "Select an option:" 16 68 7 \
|
|
|
|
|
"${OPTIONS[@]}" 3>&1 1>&2 2>&3) || exit 0
|
|
|
|
|
|
|
|
|
|
case $CHOICE in
|
|
|
|
|
"Add")
|
|
|
|
|
add
|
|
|
|
|
;;
|
|
|
|
|
"Remove")
|
|
|
|
|
remove
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
echo "Exiting..."
|
|
|
|
|
exit 0
|
|
|
|
|
;;
|
|
|
|
|
"Add") add ;;
|
|
|
|
|
"Remove") remove ;;
|
|
|
|
|
"Update") update_script ;;
|
|
|
|
|
"Status") show_status ;;
|
|
|
|
|
"Run") run_now ;;
|
|
|
|
|
"View") view_script ;;
|
|
|
|
|
"Rotate") rotate_log ;;
|
|
|
|
|
esac
|
|
|
|
|
|