88 lines
3.3 KiB
Bash
Executable File
88 lines
3.3 KiB
Bash
Executable File
#!/bin/bash
|
|
# Recorre contenedores con restart-policy always/unless-stopped que estén
|
|
# caídos (Exited) o atascados (Restarting) y los recrea vía su propio
|
|
# proyecto docker compose, para curar referencias de red obsoletas tras
|
|
# un reinicio del daemon Docker (ej. update automático de docker-ce).
|
|
#
|
|
# Idempotente: si nada está roto, no hace nada. Pensado para correr
|
|
# periódicamente vía systemd timer (docker-selfheal.timer).
|
|
|
|
set -uo pipefail
|
|
|
|
LOG_FILE="/var/log/docker-selfheal.log"
|
|
TELEGRAM_ENV="/home/felidae/f2b-telegram-bot/.env"
|
|
SABLIER_CONFIG="/opt/traefik/dynamic/sablier.yml"
|
|
|
|
# Contenedores que Sablier apaga a propósito (scale-to-zero) quedan Exited
|
|
# con restart-policy unless-stopped igual que un crash real -- no hay forma
|
|
# de distinguirlos por estado/policy. Se excluyen por nombre, leyendo los
|
|
# `names:` de cada middleware sablier-* en el dynamic config de Traefik.
|
|
declare -A SABLIER_MANAGED=()
|
|
if [ -r "$SABLIER_CONFIG" ]; then
|
|
while IFS= read -r line; do
|
|
line="${line#*names:}"
|
|
line="${line//\"/}"
|
|
IFS=',' read -ra names <<< "$line"
|
|
for n in "${names[@]}"; do
|
|
n="$(echo "$n" | xargs)"
|
|
[ -n "$n" ] && SABLIER_MANAGED["$n"]=1
|
|
done
|
|
done < <(grep -E '^\s*names:' "$SABLIER_CONFIG")
|
|
fi
|
|
|
|
log() {
|
|
echo "$(date -u +'%Y-%m-%dT%H:%M:%SZ') $*" | tee -a "$LOG_FILE"
|
|
}
|
|
|
|
notify() {
|
|
local msg="$1"
|
|
[ -f "$TELEGRAM_ENV" ] || return 0
|
|
local token chat_id
|
|
token=$(grep -E '^BOT_TOKEN=' "$TELEGRAM_ENV" | cut -d= -f2- | tr -d '"')
|
|
chat_id=$(grep -E '^PRIMARY_CHAT_ID=' "$TELEGRAM_ENV" | cut -d= -f2- | tr -d '"')
|
|
[ -n "$token" ] && [ -n "$chat_id" ] || return 0
|
|
curl -s -m 10 -X POST "https://api.telegram.org/bot${token}/sendMessage" \
|
|
-d "chat_id=${chat_id}" \
|
|
-d "text=${msg}" >/dev/null 2>&1
|
|
}
|
|
|
|
fixed_any=0
|
|
|
|
# Contenedores parados o atascados que deberían estar corriendo
|
|
mapfile -t broken < <(docker ps -a --filter "status=exited" --filter "status=restarting" --filter "status=created" --format '{{.ID}}')
|
|
|
|
for cid in "${broken[@]}"; do
|
|
policy=$(docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' "$cid" 2>/dev/null)
|
|
name=$(docker inspect -f '{{.Name}}' "$cid" 2>/dev/null | sed 's#^/##')
|
|
|
|
case "$policy" in
|
|
always|unless-stopped) ;;
|
|
*) continue ;;
|
|
esac
|
|
|
|
if [ -n "${SABLIER_MANAGED[$name]:-}" ]; then
|
|
continue
|
|
fi
|
|
|
|
workdir=$(docker inspect -f '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}' "$cid" 2>/dev/null)
|
|
project=$(docker inspect -f '{{ index .Config.Labels "com.docker.compose.project" }}' "$cid" 2>/dev/null)
|
|
service=$(docker inspect -f '{{ index .Config.Labels "com.docker.compose.service" }}' "$cid" 2>/dev/null)
|
|
|
|
if [ -n "$workdir" ] && [ -d "$workdir" ]; then
|
|
log "Recreando '$name' (proyecto '$project', servicio '$service') vía compose en $workdir"
|
|
out=$(cd "$workdir" && docker compose up -d --force-recreate "$service" 2>&1)
|
|
log "$out"
|
|
fixed_any=1
|
|
notify "🔧 docker-selfheal: '$name' estaba caído (policy=$policy), recreado vía compose en ${project}."
|
|
else
|
|
log "Recreando '$name' vía docker start (sin metadata de compose)"
|
|
docker start "$cid" >/dev/null 2>&1
|
|
fixed_any=1
|
|
notify "🔧 docker-selfheal: '$name' estaba caído (policy=$policy), levantado con docker start."
|
|
fi
|
|
done
|
|
|
|
if [ "$fixed_any" -eq 0 ]; then
|
|
log "Nada que arreglar."
|
|
fi
|