diff --git a/Traefik/docker-compose.yml b/Traefik/docker-compose.yml index aa19863..d63f2d0 100644 --- a/Traefik/docker-compose.yml +++ b/Traefik/docker-compose.yml @@ -48,6 +48,10 @@ services: - "--experimental.plugins.crowdsec-bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" - "--experimental.plugins.crowdsec-bouncer.version=v1.6.0" + # Plugin Sablier (scale-to-zero on demand) + - "--experimental.plugins.sablier.modulename=github.com/acouvreur/sablier" + - "--experimental.plugins.sablier.version=v1.8.0" + # ACME / Let's Encrypt (resolver "letsencrypt") - "--certificatesresolvers.letsencrypt.acme.email=${TRAEFIK_ACME_EMAIL}" - "--certificatesresolvers.letsencrypt.acme.storage=${TRAEFIK_ACME_STORAGE}" @@ -63,6 +67,7 @@ services: - ${TRAEFIK_DYNAMIC_DIR}:/dynamic:Z - ${TRAEFIK_LETSENCRYPT_DIR}:/letsencrypt:Z - ${TRAEFIK_LOG_DIR}:/var/log/traefik:Z + - /opt/traefik/plugins-storage:/plugins-storage:Z networks: - proxy diff --git a/adguard/docker-compose.yml b/adguard/docker-compose.yml index 55eb30a..8c6d6a1 100644 --- a/adguard/docker-compose.yml +++ b/adguard/docker-compose.yml @@ -2,7 +2,6 @@ services: adguardhome: image: ${ADGUARD_IMAGE} container_name: adguardhome - pull_policy: always restart: unless-stopped volumes: diff --git a/authentik/docker-compose.yml b/authentik/docker-compose.yml index 6cb72b0..6a01f02 100644 --- a/authentik/docker-compose.yml +++ b/authentik/docker-compose.yml @@ -25,7 +25,6 @@ services: ths-authentik-server: image: ${AUTHENTIK_IMAGE} container_name: ths-authentik-server - pull_policy: always restart: unless-stopped command: ["server"] environment: diff --git a/beszel/docker-compose.yml b/beszel/docker-compose.yml index e09807a..87c71b6 100644 --- a/beszel/docker-compose.yml +++ b/beszel/docker-compose.yml @@ -1,7 +1,6 @@ services: beszel: image: ${BESZEL_IMAGE} - pull_policy: always container_name: beszel restart: unless-stopped environment: @@ -31,7 +30,6 @@ services: beszel-agent: image: ${BESZEL_AGENT_IMAGE} - pull_policy: always container_name: beszel-agent restart: unless-stopped network_mode: host diff --git a/bookstack/docker-compose.yml b/bookstack/docker-compose.yml index c9810db..e6ce863 100644 --- a/bookstack/docker-compose.yml +++ b/bookstack/docker-compose.yml @@ -3,6 +3,8 @@ services: image: ${BOOKSTACK_DB_IMAGE} container_name: bookstack-db restart: unless-stopped + labels: + prune.protect: "true" environment: MYSQL_ROOT_PASSWORD: ${BOOKSTACK_DB_ROOT_PASSWORD} MYSQL_DATABASE: ${BOOKSTACK_DB_NAME} @@ -45,14 +47,9 @@ services: - proxy - mail_internal labels: + prune.protect: "true" traefik.enable: "true" traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}" - traefik.http.routers.bookstack.rule: "Host(`${BOOKSTACK_DOMAIN}`)" - traefik.http.routers.bookstack.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}" - traefik.http.routers.bookstack.tls: "true" - traefik.http.routers.bookstack.tls.certresolver: "${TRAEFIK_CERTRESOLVER}" - traefik.http.routers.bookstack.middlewares: "crowdsec-bouncer@file,${TRAEFIK_AUTH_MIDDLEWARE}" - traefik.http.services.bookstack.loadbalancer.server.port: "80" homepage.group: "Productividad" homepage.name: "BookStack" homepage.icon: "bookstack.png" diff --git a/crowdsec/docker-compose.yml b/crowdsec/docker-compose.yml index 15d021f..7e926b5 100644 --- a/crowdsec/docker-compose.yml +++ b/crowdsec/docker-compose.yml @@ -1,7 +1,6 @@ services: crowdsec: image: ${CROWDSEC_IMAGE} - pull_policy: always container_name: crowdsec restart: unless-stopped security_opt: @@ -59,7 +58,7 @@ services: retries: 5 crowdsec-prometheus: - image: prom/prometheus:latest + image: prom/prometheus@sha256:e4254400b85610324913f0dc4acf92603d9984e7519414c5a12811aa6146acc3 container_name: crowdsec-prometheus restart: unless-stopped command: diff --git a/crowdsec/setup.sh b/crowdsec/setup.sh index c20542d..d677aa3 100755 --- a/crowdsec/setup.sh +++ b/crowdsec/setup.sh @@ -16,8 +16,7 @@ echo "=== CrowdSec Setup ===" # 1. Cargar .env if [ ! -f "${ENV_FILE}" ]; then echo "ERROR: No se encontro ${ENV_FILE}" - echo "Copia stack.env a .env y editalo:" - echo " cp ${SCRIPT_DIR}/stack.env ${ENV_FILE}" + echo "Crea ${ENV_FILE} con las variables reales y editalo:" echo " nano ${ENV_FILE}" exit 1 fi diff --git a/dozzle/docker-compose.yml b/dozzle/docker-compose.yml index 16859fb..0d3d41e 100644 --- a/dozzle/docker-compose.yml +++ b/dozzle/docker-compose.yml @@ -1,7 +1,6 @@ services: dozzle: image: ${DOZZLE_IMAGE} - pull_policy: always container_name: dozzle restart: unless-stopped environment: @@ -15,14 +14,9 @@ services: networks: - proxy labels: + prune.protect: "true" traefik.enable: "true" traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}" - traefik.http.routers.dozzle.rule: "Host(`${DOZZLE_DOMAIN}`)" - traefik.http.routers.dozzle.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}" - traefik.http.routers.dozzle.tls: "true" - traefik.http.routers.dozzle.tls.certresolver: "${TRAEFIK_CERTRESOLVER}" - traefik.http.routers.dozzle.middlewares: "crowdsec-bouncer@file,${TRAEFIK_AUTH_MIDDLEWARE}" - traefik.http.services.dozzle.loadbalancer.server.port: "8080" homepage.group: "Observabilidad" homepage.name: "Dozzle" homepage.icon: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/dozzle.svg" diff --git a/gitea/docker-compose.yml b/gitea/docker-compose.yml index f3ca333..314e0ce 100644 --- a/gitea/docker-compose.yml +++ b/gitea/docker-compose.yml @@ -3,6 +3,8 @@ services: image: ${GITEA_POSTGRES_IMAGE} container_name: gitea-postgres restart: unless-stopped + labels: + prune.protect: "true" environment: POSTGRES_DB: ${GITEA_DB_NAME} POSTGRES_USER: ${GITEA_DB_USER} @@ -15,7 +17,6 @@ services: gitea: image: ${GITEA_IMAGE} - pull_policy: always container_name: gitea restart: unless-stopped depends_on: @@ -79,19 +80,16 @@ services: ports: - "${GITEA_SSH_PORT}:${GITEA_SSH_PORT}" labels: + prune.protect: "true" traefik.enable: "true" traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}" - traefik.http.routers.gitea.rule: "Host(`${GITEA_DOMAIN}`)" - traefik.http.routers.gitea.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}" - traefik.http.routers.gitea.tls: "true" - traefik.http.routers.gitea.tls.certresolver: "${TRAEFIK_CERTRESOLVER}" - traefik.http.routers.gitea.middlewares: "crowdsec-bouncer@file" - traefik.http.services.gitea.loadbalancer.server.port: "3000" gitea-runner: image: ${GITEA_RUNNER_IMAGE} container_name: gitea-act-runner restart: unless-stopped + labels: + prune.protect: "true" depends_on: - gitea environment: @@ -107,6 +105,8 @@ services: networks: gitea: + labels: + prune.protect: "true" driver: bridge mail_internal: external: true diff --git a/guacamole/docker-compose.yml b/guacamole/docker-compose.yml index 0bcd022..1d66b13 100644 --- a/guacamole/docker-compose.yml +++ b/guacamole/docker-compose.yml @@ -1,11 +1,18 @@ services: guacd: image: ${GUACD_IMAGE} - pull_policy: always container_name: guacd restart: unless-stopped + labels: + prune.protect: "true" extra_hosts: - - "host.docker.internal:host-gateway" + - "host.docker.internal:10.0.4.1" + healthcheck: + test: ["CMD-SHELL", "nc -z 127.0.0.1 4822 || exit 1"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 5s networks: - guacamole @@ -13,6 +20,8 @@ services: image: ${GUACAMOLE_POSTGRES_IMAGE} container_name: guacamole-postgres restart: unless-stopped + labels: + prune.protect: "true" environment: POSTGRES_DB: ${GUACAMOLE_DB_NAME} POSTGRES_USER: ${GUACAMOLE_DB_USER} @@ -26,7 +35,6 @@ services: guacamole: image: ${GUACAMOLE_IMAGE} - pull_policy: always container_name: guacamole restart: unless-stopped depends_on: @@ -41,19 +49,12 @@ services: POSTGRESQL_USERNAME: ${GUACAMOLE_DB_USER} POSTGRESQL_PASSWORD: ${GUACAMOLE_DB_PASSWORD} extra_hosts: - - "host.docker.internal:host-gateway" + - "host.docker.internal:10.0.4.1" networks: - guacamole - proxy labels: - traefik.enable: "true" - traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}" - traefik.http.routers.guacamole.rule: "Host(`${GUACAMOLE_DOMAIN}`)" - traefik.http.routers.guacamole.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}" - traefik.http.routers.guacamole.tls: "true" - traefik.http.routers.guacamole.tls.certresolver: "${TRAEFIK_CERTRESOLVER}" - traefik.http.routers.guacamole.middlewares: "crowdsec-bouncer@file,${TRAEFIK_AUTH_MIDDLEWARE}" - traefik.http.services.guacamole.loadbalancer.server.port: "8080" + prune.protect: "true" homepage.group: "Infraestructura" homepage.name: "Remote Desktop" homepage.icon: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/apache-guacamole.svg" diff --git a/homepage/docker-compose.yml b/homepage/docker-compose.yml index 3f8adae..74857a7 100644 --- a/homepage/docker-compose.yml +++ b/homepage/docker-compose.yml @@ -1,7 +1,6 @@ services: homepage: image: ${HOMEPAGE_IMAGE} - pull_policy: always container_name: homepage restart: unless-stopped environment: diff --git a/karakeep/docker-compose.yml b/karakeep/docker-compose.yml index e577dca..d28a59d 100644 --- a/karakeep/docker-compose.yml +++ b/karakeep/docker-compose.yml @@ -1,7 +1,6 @@ services: karakeep: image: ghcr.io/karakeep-app/karakeep:${KARAKEEP_VERSION} - pull_policy: always container_name: karakeep restart: unless-stopped environment: @@ -42,17 +41,16 @@ services: - karakeep_internal - proxy labels: + prune.protect: "true" traefik.enable: "true" traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}" - traefik.http.routers.karakeep.rule: "Host(`${KARAKEEP_DOMAIN}`)" - traefik.http.routers.karakeep.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}" - traefik.http.routers.karakeep.tls.certresolver: "${TRAEFIK_CERTRESOLVER}" - traefik.http.services.karakeep.loadbalancer.server.port: "3000" karakeep-chrome: image: gcr.io/zenika-hub/alpine-chrome:124 container_name: karakeep-chrome restart: unless-stopped + labels: + prune.protect: "true" command: - --no-sandbox - --disable-gpu @@ -67,6 +65,8 @@ services: image: getmeili/meilisearch:v1.37.0 container_name: karakeep-meilisearch restart: unless-stopped + labels: + prune.protect: "true" environment: MEILI_MASTER_KEY: ${MEILI_MASTER_KEY} MEILI_NO_ANALYTICS: "true" diff --git a/mail-relay/docker-compose.yml b/mail-relay/docker-compose.yml index 54a3334..99a42d1 100644 --- a/mail-relay/docker-compose.yml +++ b/mail-relay/docker-compose.yml @@ -2,7 +2,6 @@ services: mail-relay: image: ${MAIL_RELAY_IMAGE} container_name: mail-relay - pull_policy: always restart: unless-stopped environment: TZ: ${TZ} diff --git a/media-server/docker-compose.yml b/media-server/docker-compose.yml index abe1d0f..fc04659 100644 --- a/media-server/docker-compose.yml +++ b/media-server/docker-compose.yml @@ -6,7 +6,7 @@ networks: services: flaresolverr: - image: ghcr.io/flaresolverr/flaresolverr:latest + image: ghcr.io/flaresolverr/flaresolverr@sha256:b91dbd42a716ec5334a3e348ef835759f96d4082f29d6f2ab4542353d3a08aef container_name: flaresolverr environment: - LOG_LEVEL=${LOG_LEVEL:-info} @@ -19,9 +19,8 @@ services: shm_size: "1gb" prowlarr: - image: lscr.io/linuxserver/prowlarr:latest + image: lscr.io/linuxserver/prowlarr@sha256:df25cfc91278e78abdf33623f7ee341b3b476118af68a2434cd8f8df49e6f986 container_name: prowlarr - pull_policy: always environment: - PUID=0 - PGID=0 @@ -36,9 +35,8 @@ services: - traefik.http.services.prowlarr.loadbalancer.server.port=9696 jackett: - image: lscr.io/linuxserver/jackett:latest + image: lscr.io/linuxserver/jackett@sha256:520d1a04f7aba6c71eb607eea1ca806eb4e2e7f0861ee79624dc716c423e8573 container_name: jackett - pull_policy: always environment: - PUID=0 - PGID=0 @@ -53,9 +51,8 @@ services: - traefik.http.services.jackett.loadbalancer.server.port=9117 sonarr: - image: lscr.io/linuxserver/sonarr:latest + image: lscr.io/linuxserver/sonarr@sha256:9272af4955c92cccc39bc75dcad2f0758bc0ab9353dc61a8efaf35501d9ad60d container_name: sonarr - pull_policy: always environment: - PUID=0 - PGID=0 @@ -72,9 +69,8 @@ services: - traefik.http.services.sonarr.loadbalancer.server.port=8989 radarr: - image: lscr.io/linuxserver/radarr:latest + image: lscr.io/linuxserver/radarr@sha256:3d851f850263ac1cbb5b2c78b20123a224547d819c2f4ba5a0a8cacadbf97038 container_name: radarr - pull_policy: always environment: - PUID=0 - PGID=0 @@ -91,9 +87,8 @@ services: - traefik.http.services.radarr.loadbalancer.server.port=7878 jellyseerr: - image: fallenbagel/jellyseerr:latest + image: fallenbagel/jellyseerr@sha256:499fbe958fa4a0c93172a231c14898986556bb28bd5cd569a0f41dbf887b04d7 container_name: jellyseerr - pull_policy: always environment: - LOG_LEVEL=debug - TZ=${TZ:-Europe/Madrid} @@ -108,9 +103,8 @@ services: # Opcional: Jellyfin en VPS (sin GPU) jellyfin: - image: lscr.io/linuxserver/jellyfin:latest + image: lscr.io/linuxserver/jellyfin@sha256:b46e291fb2a532eef9e2e1b7d3b927ab39558f0c58964a63f21013071f1ae278 container_name: jellyfin-vps - pull_policy: always environment: - PUID=0 - PGID=0 diff --git a/nextcloud/docker-compose.yml b/nextcloud/docker-compose.yml index 93f9021..4fdc5dc 100644 --- a/nextcloud/docker-compose.yml +++ b/nextcloud/docker-compose.yml @@ -34,7 +34,6 @@ services: nextcloud: image: nextcloud:33-apache container_name: nextcloud - pull_policy: always restart: unless-stopped depends_on: - nextcloud-db diff --git a/opengist/docker-compose.yml b/opengist/docker-compose.yml index 0885e95..3d4a467 100644 --- a/opengist/docker-compose.yml +++ b/opengist/docker-compose.yml @@ -1,12 +1,12 @@ services: opengist: image: ${OPENGIST_IMAGE} - pull_policy: always container_name: opengist restart: unless-stopped environment: TZ: ${TZ} OG_OPENGIST_HOME: /opengist + OG_EXTERNAL_URL: "https://${OPENGIST_DOMAIN}" OG_OIDC_PROVIDER_NAME: ${OPENGIST_OIDC_PROVIDER_NAME} OG_OIDC_CLIENT_KEY: ${OPENGIST_OIDC_CLIENT_ID} OG_OIDC_SECRET: ${OPENGIST_OIDC_CLIENT_SECRET} @@ -16,15 +16,9 @@ services: networks: - proxy labels: + prune.protect: "true" traefik.enable: "true" traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}" - traefik.http.routers.opengist.rule: "Host(`${OPENGIST_DOMAIN}`)" - traefik.http.routers.opengist.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}" - traefik.http.routers.opengist.tls: "true" - traefik.http.routers.opengist.tls.certresolver: "${TRAEFIK_CERTRESOLVER}" - traefik.http.routers.opengist.middlewares: "crowdsec-bouncer@file" - traefik.http.routers.opengist.service: "opengist-svc" - traefik.http.services.opengist-svc.loadbalancer.server.port: "6157" homepage.group: "Automatizacion y desarrollo" homepage.name: "OpenGist" diff --git a/privatebin/docker-compose.yml b/privatebin/docker-compose.yml index 45b43ec..d9a39b1 100644 --- a/privatebin/docker-compose.yml +++ b/privatebin/docker-compose.yml @@ -11,28 +11,14 @@ services: networks: - proxy labels: + prune.protect: "true" traefik.enable: "true" traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}" # Router publico — permite abrir enlaces compartidos sin login. - traefik.http.routers.pbin-public.rule: "Host(`${PRIVATEBIN_DOMAIN}`) && (Method(`GET`) || Method(`HEAD`))" - traefik.http.routers.pbin-public.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}" - traefik.http.routers.pbin-public.tls: "true" - traefik.http.routers.pbin-public.tls.certresolver: "${TRAEFIK_CERTRESOLVER}" - traefik.http.routers.pbin-public.middlewares: "crowdsec-bouncer@file" - traefik.http.routers.pbin-public.service: "pbin-svc" - traefik.http.routers.pbin-public.priority: "20" # Router protegido — crear/borrar requiere login. - traefik.http.routers.pbin.rule: "Host(`${PRIVATEBIN_DOMAIN}`)" - traefik.http.routers.pbin.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}" - traefik.http.routers.pbin.tls: "true" - traefik.http.routers.pbin.tls.certresolver: "${TRAEFIK_CERTRESOLVER}" - traefik.http.routers.pbin.middlewares: "crowdsec-bouncer@file,${TRAEFIK_AUTH_MIDDLEWARE}" - traefik.http.routers.pbin.service: "pbin-svc" - traefik.http.routers.pbin.priority: "10" - traefik.http.services.pbin-svc.loadbalancer.server.port: "8080" homepage.group: "Automatizacion y desarrollo" homepage.name: "PrivateBin" diff --git a/sablier/docker-compose.yml b/sablier/docker-compose.yml new file mode 100644 index 0000000..32d7751 --- /dev/null +++ b/sablier/docker-compose.yml @@ -0,0 +1,16 @@ +services: + sablier: + image: acouvreur/sablier:${SABLIER_VERSION} + container_name: sablier + restart: unless-stopped + command: + - start + - --provider.name=docker + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro,Z + networks: + - proxy + +networks: + proxy: + external: true diff --git a/sablier/stack.env b/sablier/stack.env new file mode 100644 index 0000000..cea9e32 --- /dev/null +++ b/sablier/stack.env @@ -0,0 +1 @@ +SABLIER_VERSION=1.8.0 diff --git a/scripts/docker-selfheal.sh b/scripts/docker-selfheal.sh new file mode 100755 index 0000000..175d770 --- /dev/null +++ b/scripts/docker-selfheal.sh @@ -0,0 +1,87 @@ +#!/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 diff --git a/scripts/prune-safe.sh b/scripts/prune-safe.sh new file mode 100755 index 0000000..1acf112 --- /dev/null +++ b/scripts/prune-safe.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +set -eu + +docker system prune -af --filter "label!=prune.protect=true" diff --git a/trilium/docker-compose.yml b/trilium/docker-compose.yml index f70ab9a..6d14143 100644 --- a/trilium/docker-compose.yml +++ b/trilium/docker-compose.yml @@ -2,7 +2,6 @@ services: trilium: image: ${TRILIUM_IMAGE} container_name: trilium - pull_policy: always restart: unless-stopped hostname: ${TRILIUM_HOSTNAME} @@ -19,19 +18,9 @@ services: - proxy labels: + prune.protect: "true" traefik.enable: "true" traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}" - traefik.http.routers.trilium.rule: "Host(`${TRILIUM_DOMAIN_1}`) || Host(`${TRILIUM_DOMAIN_2}`)" - traefik.http.routers.trilium.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}" - traefik.http.routers.trilium.middlewares: "trilium-sec" - traefik.http.routers.trilium.tls: "true" - traefik.http.routers.trilium.tls.certresolver: "${TRAEFIK_CERTRESOLVER}" - traefik.http.services.trilium.loadbalancer.server.port: "${TRILIUM_HTTP_PORT}" - traefik.http.middlewares.trilium-sec.headers.stsSeconds: "31536000" - traefik.http.middlewares.trilium-sec.headers.stsIncludeSubdomains: "true" - traefik.http.middlewares.trilium-sec.headers.stsPreload: "true" - traefik.http.middlewares.trilium-sec.headers.contentTypeNosniff: "true" - traefik.http.middlewares.trilium-sec.headers.frameDeny: "true" networks: proxy: diff --git a/vikunja/docker-compose.yml b/vikunja/docker-compose.yml index 885ca6b..3e69ae8 100644 --- a/vikunja/docker-compose.yml +++ b/vikunja/docker-compose.yml @@ -3,6 +3,8 @@ services: image: postgres:17-alpine container_name: vikunja-db restart: unless-stopped + labels: + prune.protect: "true" environment: TZ: ${TZ} POSTGRES_DB: ${VIKUNJA_DB_NAME} @@ -20,8 +22,7 @@ services: - vikunja_internal vikunja: - image: vikunja/vikunja:latest - pull_policy: always + image: vikunja/vikunja@sha256:f6b80393c1998cd5cd0dc38d24762c59ab4c10000a6f1032ef5b554e262cab93 container_name: vikunja restart: unless-stopped user: "1000:1000" @@ -55,13 +56,9 @@ services: - mail_internal - proxy labels: + prune.protect: "true" traefik.enable: "true" traefik.docker.network: "proxy" - traefik.http.routers.vikunja.rule: "Host(`${VIKUNJA_DOMAIN}`)" - traefik.http.routers.vikunja.entrypoints: "websecure" - traefik.http.routers.vikunja.tls: "true" - traefik.http.routers.vikunja.tls.certresolver: "${TRAEFIK_CERTRESOLVER}" - traefik.http.services.vikunja.loadbalancer.server.port: "3456" networks: vikunja_internal: diff --git a/wireguard/docker-compose.yml b/wireguard/docker-compose.yml index c6dd552..9cf796f 100644 --- a/wireguard/docker-compose.yml +++ b/wireguard/docker-compose.yml @@ -2,7 +2,6 @@ services: wg-easy: image: ${WG_EASY_IMAGE} container_name: wg-easy - pull_policy: always restart: unless-stopped cap_add: