diff --git a/joplin_server_daily_diag.sh b/joplin_server_daily_diag.sh new file mode 100644 index 0000000..4f2a883 --- /dev/null +++ b/joplin_server_daily_diag.sh @@ -0,0 +1,349 @@ +cat /home/scripts/joplin_server_daily_diag.sh +#!/bin/bash + +######################################## +# Rapport quotidien serveur + Discord +# - Ping API Joplin +# - Espace disque global +# - Ressources CPU / RAM / top process +# - Événements majeurs des 24h (journalctl -p 0..3) +# - Espace disque par user Linux (/home) +# - Espace disque par user Joplin (PostgreSQL) +# - Envoi Discord (message + log en pièce jointe) +######################################## + +# === CONFIG DISCORD === +WEBHOOK="https://discord.com/api/webhooks/1234567890000000987654321/hsagsklzjkldhfgasouihfgdhfdousahFLDSAHFOUHFJNDAFOUADHFAOUSFHDOU" + +# Dossier de logs +LOG_DIR="/var/log/server-daily-diag" +mkdir -p "${LOG_DIR}" +TS="$(date '+%Y-%m-%d_%H-%M-%S')" +LOG_FILE="${LOG_DIR}/server_daily_diag_${TS}.log" + +HOSTNAME="$(hostname -f 2>/dev/null || hostname)" + +# === CONFIG PING JOPLIN === +JOPLIN_PING_URL="http://127.0.0.1:22300/api/ping" +JOPLIN_HOST_HEADER="joplin.server.me" +JOPLIN_ORIGIN_HEADER="https://joplin.server.me" +JOPLIN_X_FORWARDED_PROTO="https" + +# === CONFIG JOPLIN DB (PostgreSQL) === +ENABLE_JOPLIN_DB_STATS=1 + +JOPLIN_DB_HOST="127.0.0.1" +JOPLIN_DB_PORT="5432" +JOPLIN_DB_NAME="joplin" +JOPLIN_DB_USER="joplin" +JOPLIN_DB_PASSWORD="PASSWORDxxxXXX1233764" + +######################################## +# PRÉREQUIS +######################################## + +# jq est un prérequis systématique pour Discord +if ! command -v jq >/dev/null 2>&1; then + echo "ERREUR: jq n'est pas installé sur ce serveur." + echo "Installe-le avant d'utiliser ce script :" + echo " apt update -y && apt install -y jq" + exit 1 +fi + +######################################## +# Variables globales pour le résumé +######################################## + +API_STATUS="inconnu" +ROOT_USAGE="n/a" +LOAD_AVG="n/a" +MEM_LINE="n/a" +JOPLIN_USERS_SUMMARY="" + +######################################## +# Utils +######################################## + +log() { + # log dans le fichier avec timestamp + printf '[%s] %s\n' "$(date +'%Y-%m-%dT%H:%M:%S%z')" "$*" >> "${LOG_FILE}" +} + +human_bytes() { + local bytes="$1" + local units=(B KB MB GB TB PB) + local i=0 + + if ! [[ "$bytes" =~ ^[0-9]+$ ]]; then + echo "-" + return + fi + + while [ "$bytes" -ge 1024 ] && [ "$i" -lt $(( ${#units[@]} - 1 )) ]; do + bytes=$(( (bytes + 512) / 1024 )) + i=$((i + 1)) + done + + echo "${bytes}${units[$i]}" +} + +######################################## +# 1) Ping Joplin +######################################## + +check_joplin() { + log "===== Vérification Joplin Server (/api/ping) =====" + + local response http_code status message json_body + + response="$(curl -sS -i \ + -H "Host: ${JOPLIN_HOST_HEADER}" \ + -H "Origin: ${JOPLIN_ORIGIN_HEADER}" \ + -H "X-Forwarded-Proto: ${JOPLIN_X_FORWARDED_PROTO}" \ + "${JOPLIN_PING_URL}" || true)" + + http_code="$(printf '%s\n' "${response}" | awk 'NR==1 {print $2}')" + json_body="$(printf '%s\n' "${response}" | awk '/^\{/{print}')" + status="$(printf '%s\n' "${json_body}" | jq -r '.status // empty' 2>/dev/null)" + message="$(printf '%s\n' "${json_body}" | jq -r '.message // empty' 2>/dev/null)" + + if [ "${http_code}" = "200" ] && [ "${status}" = "ok" ]; then + API_STATUS="OK (${http_code}, ${message})" + log "Joplin Server OK (HTTP ${http_code}) - ${message}" + else + API_STATUS="KO (${http_code:-N/A})" + log "Joplin Server PROBLÈME (HTTP ${http_code:-N/A})" + log "Réponse brute :" + printf '%s\n' "${response}" | sed 's/^/ /' >> "${LOG_FILE}" + fi + + log "" +} + +######################################## +# 2) Espace disque global +######################################## + +disk_global() { + log "===== Espace disque global (df -hT) =====" + df -hT >> "${LOG_FILE}" 2>&1 + log "" + + ROOT_USAGE="$(df -h / 2>/dev/null | awk 'NR==2 {print $5 " used on " $6}')" + [ -z "${ROOT_USAGE}" ] && ROOT_USAGE="n/a" +} + +######################################## +# 3) Espace disque par utilisateur Linux (/home) +######################################## + +disk_per_linux_user() { + log "===== Espace disque par utilisateur Linux (/home) =====" + + if [ ! -d /home ]; then + log "/home n'existe pas, section ignorée." + log "" + return + fi + + for dir in /home/*; do + [ -d "${dir}" ] || continue + user="$(basename "${dir}")" + bytes="$(du -sb "${dir}" 2>/dev/null | awk '{print $1}')" + human="$(human_bytes "${bytes}")" + log "User Linux: ${user} -> ${human} (~ ${bytes:-0} B) [${dir}]" + done + + log "" +} + +######################################## +# 4) Ressources système +######################################## + +system_resources() { + log "===== Ressources système =====" + + log "-- Uptime et charge --" + uptime 2>/dev/null >> "${LOG_FILE}" 2>&1 || log "uptime indisponible" + log "" + + LOAD_AVG="$(awk '{print $1","$2","$3}' /proc/loadavg 2>/dev/null)" + [ -z "${LOAD_AVG}" ] && LOAD_AVG="n/a" + + log "-- Mémoire (free -h) --" + free -h 2>/dev/null >> "${LOG_FILE}" || log "free indisponible" + log "" + + MEM_LINE="$(free -h 2>/dev/null | awk '/Mem:/ {print $3 " / " $2 " used"}')" + [ -z "${MEM_LINE}" ] && MEM_LINE="n/a" + + log "-- Top CPU (top 5) --" + if command -v ps >/dev/null 2>&1; then + ps -eo pid,user,pcpu,pmem,comm --sort=-pcpu | head -n 6 >> "${LOG_FILE}" + else + log "ps indisponible." + fi + log "" + + log "-- Top RAM (top 5) --" + if command -v ps >/dev/null 2>&1; then + ps -eo pid,user,pcpu,pmem,comm --sort=-pmem | head -n 6 >> "${LOG_FILE}" + else + log "ps indisponible." + fi + log "" +} + +######################################## +# 5) Logs système (24h, priorité 0..3) +######################################## + +major_events_24h() { + log "===== Événements système majeurs (24h, journalctl -p 0..3) =====" + + if ! command -v journalctl >/dev/null 2>&1; then + log "journalctl non disponible, section ignorée." + log "" + return + fi + + journalctl -p 0..3 --since "24 hours ago" --no-pager 2>/dev/null | tail -n 300 >> "${LOG_FILE}" + log "" +} + +######################################## +# 6) Espace disque par utilisateur Joplin +######################################## + +joplin_per_user() { + log "===== Espace disque par utilisateur Joplin (total_item_size) =====" + + if [ "${ENABLE_JOPLIN_DB_STATS}" -ne 1 ]; then + log "Section désactivée (ENABLE_JOPLIN_DB_STATS != 1)." + log "" + return + fi + + if ! command -v psql >/dev/null 2>&1; then + log "psql introuvable, impossible de lire la base Joplin." + log "" + return + fi + + if [ -z "${JOPLIN_DB_NAME}" ] || [ -z "${JOPLIN_DB_USER}" ]; then + log "Config DB Joplin incomplète (nom ou user manquant)." + log "" + return + fi + + export PGPASSWORD="${JOPLIN_DB_PASSWORD}" + + local query + query="SELECT email, full_name, COALESCE(total_item_size,0) AS total_item_size + FROM users + ORDER BY total_item_size DESC;" + + local lines + lines="$( + psql \ + -h "${JOPLIN_DB_HOST}" \ + -p "${JOPLIN_DB_PORT}" \ + -U "${JOPLIN_DB_USER}" \ + -d "${JOPLIN_DB_NAME}" \ + -t -A -F '|' \ + -c "${query}" 2>&1 + )" + + if printf '%s\n' "${lines}" | grep -qi "ERROR"; then + log "Erreur lors de la requête PostgreSQL :" + printf '%s\n' "${lines}" | sed 's/^/ /' >> "${LOG_FILE}" + log "" + return + fi + + while IFS='|' read -r email full_name total_bytes; do + [ -z "${email}" ] && continue + human_size="$(human_bytes "${total_bytes}")" + + if [ -n "${full_name}" ]; then + line="Joplin user: ${email} (${full_name}) -> ${human_size} (~ ${total_bytes} B)" + else + line="Joplin user: ${email} -> ${human_size} (~ ${total_bytes} B)" + fi + + # Log complet + log "${line}" + # Résumé pour Discord + JOPLIN_USERS_SUMMARY+="${line}"$'\n' + + done <<< "${lines}" + + log "" +} + +######################################## +# 7) Envoi sur Discord +######################################## + +send_discord() { + log "===== Envoi du rapport sur Discord =====" + + # Si aucune donnée Joplin collectée + if [ -z "${JOPLIN_USERS_SUMMARY}" ]; then + JOPLIN_USERS_SUMMARY="(Aucune donnée Joplin ou stats désactivées)" + fi + + SUMMARY="🧾 Rapport Joplin serveur + +Host : ${HOSTNAME} +Date : $(date '+%Y-%m-%d %H:%M:%S') + +API Joplin : ${API_STATUS} +Disque racine : ${ROOT_USAGE} +Charge (1/5/15min): ${LOAD_AVG} +Mémoire : ${MEM_LINE} + +Users: +${JOPLIN_USERS_SUMMARY} + +Détails complets (logs, stats Joplin & users) dans le fichier joint. +" + + # Sécurité limite Discord (<2000 chars) + SUMMARY_TRIMMED="$(printf '%s\n' "${SUMMARY}" | cut -c1-1900)" + + # Encodage JSON safe via jq -Rs + JSON_PAYLOAD="$(printf '%s' "${SUMMARY_TRIMMED}" | jq -Rs '{content: .}')" + + curl -sS -X POST \ + -F "payload_json=${JSON_PAYLOAD}" \ + -F "file=@${LOG_FILE};type=text/plain" \ + "${WEBHOOK}" >/dev/null || { + echo "ERREUR: échec de l'envoi à Discord." >&2 + return 1 + } + + log "Rapport envoyé sur Discord (résumé + pièce jointe)." +} + + +######################################## +# MAIN +######################################## + +log "===== DIAGNOSTIC QUOTIDIEN SERVEUR JOPLIN =====" +log "Date : $(date '+%Y-%m-%d %H:%M:%S %z')" +log "" + +check_joplin +disk_global +disk_per_linux_user +system_resources +major_events_24h +joplin_per_user +send_discord + +log "===== FIN DIAGNOSTIC =====" + +exit 0