compose.yaml

# Portal de actualizaciones de los nodos: Semaphore UI en el master-1, FUERA de k3s
# (si el clúster falla, el portal que lo arregla tiene que seguir funcionando).
# Se gestiona con scripts/portal.sh; no lanzar compose a mano.
name: portal-nodos

services:
  semaphore:
    build: .
    image: portal-semaphore:2.19.16
    restart: unless-stopped
    # Red del host: los nodos solo aceptan la clave del usuario `ansible` desde
    # 192.0.2.112. La web escucha SOLO en la IP de Tailscale del master-1.
    network_mode: host
    read_only: true
    cap_drop: [ALL]
    security_opt: [no-new-privileges:true]
    tmpfs:
      - /tmp:exec,mode=1777
    env_file:
      - /root/.config/semaphore/semaphore.env   # secretos (0600), lo crea portal.sh
    environment:
      SEMAPHORE_DB_DIALECT: sqlite
      SEMAPHORE_INTERFACE: ${PORTAL_IP:-203.0.113.121}
      SEMAPHORE_PORT: ${PORTAL_PUERTO:-3010}
      SEMAPHORE_TMP_PATH: /tmp/semaphore
      SEMAPHORE_TOTP_ENABLED: "true"
      SEMAPHORE_TOTP_ISSUER: Portal de nodos
      SEMAPHORE_SCHEDULE_TIMEZONE: Europe/Madrid
      SEMAPHORE_MAX_PARALLEL_TASKS: "1"            # nunca dos actualizaciones a la vez
      SEMAPHORE_NON_ADMIN_CAN_CREATE_PROJECT: "false"
      SEMAPHORE_FORWARDED_ENV_VARS: '["TZ"]'
      TZ: Europe/Madrid
    volumes:
      - datos:/var/lib/semaphore
      - config:/etc/semaphore
      - /opt/portal-nodos/repo:/portal/repo:ro                   # copia de main (portal.sh repo)
      - /root/.config/semaphore/claves:/claves:ro                # clave SSH, known_hosts, kubeconfig

volumes:
  datos:
  config: