121 lines
4.4 KiB
Bash
Executable File
121 lines
4.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Vérifie les conteneurs Compose, leur healthcheck, leur redémarrage et leurs routes Traefik.
|
||
# Ce contrôle est volontairement non intrusif : Docker et manus-apps.service assurent les redémarrages.
|
||
set -Eeuo pipefail
|
||
|
||
readonly STATE_DIR="/var/lib/manus-apps-health"
|
||
readonly REPORT_FILE="${STATE_DIR}/latest.txt"
|
||
readonly LOCK_FILE="/run/manus-apps-health.lock"
|
||
readonly MODE="${1:---check-only}"
|
||
|
||
mkdir -p "$STATE_DIR"
|
||
exec 9>"$LOCK_FILE"
|
||
flock -n 9 || exit 0
|
||
|
||
declare -A SEEN_HTTP_HOSTS=()
|
||
declare -a REPORT_LINES=()
|
||
HAS_FAILURE=false
|
||
|
||
log_line() {
|
||
local level="$1"
|
||
local message="$2"
|
||
local line="[$(date -Is)] [$level] $message"
|
||
REPORT_LINES+=("$line")
|
||
echo "$line"
|
||
}
|
||
|
||
mark_failure() {
|
||
HAS_FAILURE=true
|
||
log_line "KO" "$1"
|
||
}
|
||
|
||
is_healthy_http_status() {
|
||
# Toute réponse HTTP (< 500) confirme que Traefik et le service restent joignables.
|
||
# Les 401/404 sont légitimes pour les tableaux protégés ou API sans page racine.
|
||
[[ "$1" =~ ^[1-4][0-9][0-9]$ ]]
|
||
}
|
||
|
||
check_http_route() {
|
||
local container_id="$1"
|
||
local project="$2"
|
||
local labels host_rule host status
|
||
|
||
labels="$(docker inspect -f '{{range $key, $value := .Config.Labels}}{{$key}}={{$value}}{{"\n"}}{{end}}' "$container_id")"
|
||
host_rule="$(printf '%s\n' "$labels" | grep -oE 'Host\(`[^`]+`\)' | head -n 1 || true)"
|
||
[[ -n "$host_rule" ]] || return 0
|
||
|
||
host="${host_rule#Host(\`}"
|
||
host="${host%\`)}"
|
||
[[ -n "$host" ]] || return 0
|
||
[[ -z "${SEEN_HTTP_HOSTS[$host]:-}" ]] || return 0
|
||
SEEN_HTTP_HOSTS["$host"]=1
|
||
|
||
status="$(curl --silent --show-error --location --max-redirs 3 --connect-timeout 5 --max-time 8 \
|
||
--output /dev/null --write-out '%{http_code}' "https://${host}" 2>/dev/null || true)"
|
||
if is_healthy_http_status "$status"; then
|
||
log_line "OK" "${project}: HTTPS ${host} → ${status}"
|
||
else
|
||
# Un échec HTTPS peut dépendre de Traefik, DNS ou d’une redirection applicative.
|
||
# Il déclenche une alerte, jamais un redémarrage Compose automatique.
|
||
mark_failure "${project}: HTTPS ${host} → ${status:-erreur réseau}"
|
||
fi
|
||
}
|
||
|
||
check_containers() {
|
||
local container_id name project workdir config_files status health restart_policy exit_code
|
||
mapfile -t container_ids < <(docker ps -aq --filter label=com.docker.compose.project)
|
||
|
||
if [[ "${#container_ids[@]}" -eq 0 ]]; then
|
||
mark_failure "Aucun conteneur Docker Compose détecté"
|
||
return
|
||
fi
|
||
|
||
for container_id in "${container_ids[@]}"; do
|
||
name="$(docker inspect -f '{{.Name}}' "$container_id" | sed 's#^/##')"
|
||
project="$(docker inspect -f '{{index .Config.Labels "com.docker.compose.project"}}' "$container_id")"
|
||
workdir="$(docker inspect -f '{{index .Config.Labels "com.docker.compose.project.working_dir"}}' "$container_id")"
|
||
config_files="$(docker inspect -f '{{index .Config.Labels "com.docker.compose.project.config_files"}}' "$container_id")"
|
||
status="$(docker inspect -f '{{.State.Status}}' "$container_id")"
|
||
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container_id")"
|
||
restart_policy="$(docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' "$container_id")"
|
||
exit_code="$(docker inspect -f '{{.State.ExitCode}}' "$container_id")"
|
||
|
||
if [[ "$status" != "running" ]]; then
|
||
if [[ "$status" == "exited" && "$exit_code" == "0" ]]; then
|
||
log_line "INFO" "${project}/${name}: tâche ponctuelle terminée"
|
||
continue
|
||
fi
|
||
mark_failure "${project}/${name}: état Docker ${status}"
|
||
continue
|
||
fi
|
||
if [[ "$health" == "unhealthy" ]]; then
|
||
mark_failure "${project}/${name}: healthcheck unhealthy"
|
||
continue
|
||
fi
|
||
if [[ "$restart_policy" != "unless-stopped" && "$restart_policy" != "always" ]]; then
|
||
mark_failure "${project}/${name}: politique de redémarrage ${restart_policy:-none}"
|
||
continue
|
||
fi
|
||
|
||
log_line "OK" "${project}/${name}: running, health=${health}, restart=${restart_policy}"
|
||
check_http_route "$container_id" "$project"
|
||
done
|
||
}
|
||
|
||
check_containers
|
||
|
||
{
|
||
echo "Supervision Santinova / Itinova"
|
||
echo "Généré : $(date -Is)"
|
||
echo "Statut : $([[ "$HAS_FAILURE" == true ]] && echo KO || echo OK)"
|
||
printf '%s\n' "${REPORT_LINES[@]}"
|
||
} > "${REPORT_FILE}.tmp"
|
||
mv "${REPORT_FILE}.tmp" "$REPORT_FILE"
|
||
|
||
if [[ "$HAS_FAILURE" == true ]]; then
|
||
logger -p daemon.err -t manus-apps-health "Anomalie détectée : consulter ${REPORT_FILE}"
|
||
exit 1
|
||
fi
|
||
|
||
logger -p daemon.info -t manus-apps-health "Toutes les applications contrôlées sont opérationnelles"
|