refactor(compose): protect services and centralize routing
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -2,7 +2,6 @@ services:
|
||||
adguardhome:
|
||||
image: ${ADGUARD_IMAGE}
|
||||
container_name: adguardhome
|
||||
pull_policy: always
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
+1
-2
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
services:
|
||||
homepage:
|
||||
image: ${HOMEPAGE_IMAGE}
|
||||
pull_policy: always
|
||||
container_name: homepage
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -2,7 +2,6 @@ services:
|
||||
mail-relay:
|
||||
image: ${MAIL_RELAY_IMAGE}
|
||||
container_name: mail-relay
|
||||
pull_policy: always
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: ${TZ}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -34,7 +34,6 @@ services:
|
||||
nextcloud:
|
||||
image: nextcloud:33-apache
|
||||
container_name: nextcloud
|
||||
pull_policy: always
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- nextcloud-db
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
SABLIER_VERSION=1.8.0
|
||||
Executable
+87
@@ -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
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
docker system prune -af --filter "label!=prune.protect=true"
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -2,7 +2,6 @@ services:
|
||||
wg-easy:
|
||||
image: ${WG_EASY_IMAGE}
|
||||
container_name: wg-easy
|
||||
pull_policy: always
|
||||
restart: unless-stopped
|
||||
|
||||
cap_add:
|
||||
|
||||
Reference in New Issue
Block a user