From e1f89b29b322b52a205d241fbf9ca2acf2a7b4c3 Mon Sep 17 00:00:00 2001 From: Ssyleric <47066760+Ssyleric@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:39:05 +0100 Subject: [PATCH] Create swap_cleaner.sh --- swap_cleaner.sh | 86 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 swap_cleaner.sh diff --git a/swap_cleaner.sh b/swap_cleaner.sh new file mode 100644 index 0000000..c311198 --- /dev/null +++ b/swap_cleaner.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# swap_cleaner.sh — purge le swap si > 90% pendant 2h et envoie une notif Discord +# Exécuter en root (PVE), sans sudo. + +set -euo pipefail + +# --- Config ------------------------------------------------------------------- +WEBHOOK="https://discord.com/api/webhooks/1237668309856686090/xqN9G2ZtCkNKuiivng2kEMXLUY7Zx3_RK2w1Y4TCgVgPcc4M5ZYN7QStwHzyp7dEIqrb" +THRESHOLD=${THRESHOLD:-90} # % de swap utilisé (override possible via env) +CHECK_EVERY_MIN=${CHECK_EVERY_MIN:-10} # fréquence du cron en minutes +DURATION_MIN=${DURATION_MIN:-120} # durée continue requise au-dessus du seuil +STATE_FILE="/var/tmp/swap_usage_count.txt" +LOG="/var/log/swap_cleaner.log" +HOST="$(hostname)" + +# --- Pré-requis --------------------------------------------------------------- +if ! command -v jq >/dev/null 2>&1; then + apt update -y && apt install -y jq +fi + +mkdir -p "$(dirname "$STATE_FILE")" +touch "$STATE_FILE" "$LOG" + +# --- Fonctions ---------------------------------------------------------------- +limit_checks() { + python3 - "$CHECK_EVERY_MIN" "$DURATION_MIN" <<'PY' +import sys, math +every = int(sys.argv[1]); duration = int(sys.argv[2]) +print(max(1, math.ceil(duration / every))) +PY +} + +discord_notify() { + # $1 = message (multi-ligne), $2 = fichier joint (optionnel) + local msg="$1" file="${2:-}" + # marge stricte < 2000 caractères + if [ "${#msg}" -gt 1900 ]; then msg="${msg:0:1900}\n…(truncated)"; fi + printf "%s" "$msg" | jq -Rs '{content: .}' > /tmp/payload.json + if [ -n "${file}" ] && [ -f "${file}" ]; then + curl -sS -f \ + -F "payload_json=@/tmp/payload.json;type=application/json" \ + -F "file=@${file};type=text/plain" \ + "$WEBHOOK" >/dev/null || true + else + curl -sS -f -H "Content-Type: application/json" \ + -d @/tmp/payload.json "$WEBHOOK" >/dev/null || true + fi + rm -f /tmp/payload.json +} + +swap_usage_percent() { + local total used + read -r total used _ < <(free -m | awk '/^Swap:/ {print $2, $3, $4}') + if [ "${total:-0}" -gt 0 ]; then echo $(( used * 100 / total )); else echo 0; fi +} + +log() { echo "$(date '+%F %T') $*" | tee -a "$LOG" >/dev/null; } + +# --- Single instance (évite chevauchements) ----------------------------------- +exec 9>/var/tmp/.swap_cleaner.lock +flock -n 9 || exit 0 + +# --- Logic -------------------------------------------------------------------- +COUNT="$(cat "$STATE_FILE" 2>/dev/null || echo 0)" +USAGE="$(swap_usage_percent)" +LIMIT="$(limit_checks)" + +if [ "$USAGE" -ge "$THRESHOLD" ]; then + COUNT=$((COUNT + 1)); echo "$COUNT" > "$STATE_FILE" + log "[INFO] Swap ${USAGE}% (>=${THRESHOLD}%). Compteur ${COUNT}/${LIMIT}." + if [ "$COUNT" -ge "$LIMIT" ]; then + log "[ACTION] ${DURATION_MIN} min >= ${THRESHOLD}% — swapoff -a && swapon -a" + BEFORE="$(free -h)" + if swapoff -a && swapon -a; then + AFTER="$(free -h)"; echo "0" > "$STATE_FILE" + MSG="🧹 **Swap cleaner (PVE: ${HOST})**\nSeuil: ${THRESHOLD}% maintenu ${DURATION_MIN} min.\nAction: \`swapoff -a && swapon -a\`\n\nAvant:\n${BEFORE}\n\nAprès:\n${AFTER}\n\nLog: ${LOG}" + discord_notify "$MSG" "$LOG" + else + log "[ERROR] Échec swapoff/swapon." + discord_notify "⚠️ **Swap cleaner (PVE: ${HOST})**\nÉchec de \`swapoff -a && swapon -a\` (swap=${USAGE}%)." "$LOG" + fi + fi +else + if [ "$COUNT" -ne 0 ]; then log "[INFO] Swap à ${USAGE}%, reset compteur."; fi + echo "0" > "$STATE_FILE" +fi