From 183c8a8e3dadeb9bd2679e2ae5184a78b8d1ca28 Mon Sep 17 00:00:00 2001 From: Buba Date: Sat, 27 Jun 2026 13:15:15 +0200 Subject: [PATCH 1/7] Update Crowdsec Bouncer Traefik Plugin --- docker/security/traefik/config/traefik.yml | 3 ++- renovate.json | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docker/security/traefik/config/traefik.yml b/docker/security/traefik/config/traefik.yml index f5181c3e..862858a0 100644 --- a/docker/security/traefik/config/traefik.yml +++ b/docker/security/traefik/config/traefik.yml @@ -98,4 +98,5 @@ experimental: # https://plugins.traefik.io/plugins/6335346ca4caa9ddeffda116/crowdsec-bouncer-traefik-plugin crowdsec-bouncer-traefik-plugin: moduleName: "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" - version: "v1.4.1" + # renovate: datasource=github-releases depName=maxlerebourg/crowdsec-bouncer-traefik-plugin + version: "v1.6.0" diff --git a/renovate.json b/renovate.json index faca6096..05db7931 100644 --- a/renovate.json +++ b/renovate.json @@ -54,6 +54,15 @@ "# renovate: datasource=(?[a-z-]+?) depName=(?[^\\s]+?)(?:\\s+extractVersion=(?[^\\s]+))?\\s+[a-z_]+:\\s*\"(?[^\"]+)\"" ], "extractVersionTemplate": "{{#if extractVersion}}{{{extractVersion}}}{{else}}^v?(?.+)${{/if}}" + }, + { + "description": "Update tool versions set via inline renovate comments in Traefik static config", + "customType": "regex", + "managerFilePatterns": ["/^docker/security/traefik/config/traefik\\.yml$/"], + "matchStrings": [ + "# renovate: datasource=(?[a-z-]+?) depName=(?[^\\s]+?)(?:\\s+extractVersion=(?[^\\s]+))?\\s+[a-z_]+:\\s*\"(?[^\"]+)\"" + ], + "extractVersionTemplate": "{{#if extractVersion}}{{{extractVersion}}}{{else}}^v?(?.+)${{/if}}" } ], "packageRules": [ @@ -171,7 +180,7 @@ "matchPackageNames": [ "lscr.io/linuxserver/qbittorrent" ], - "allowedVersions": "<5" + "allowedVersions": "<6" }, { "description": "Disable invalid version tags", From 8bcffdd820f7e5631f3a6101ddb274d44dfdcbe6 Mon Sep 17 00:00:00 2001 From: Buba Date: Sat, 27 Jun 2026 13:28:33 +0200 Subject: [PATCH 2/7] Rename MAIN_NODE variable to PRIMARY_NODE --- config-example/docker/.env | 8 ++++---- docker/Taskfile.apps.yaml | 2 +- docker/Taskfile.docker.yaml | 4 ++-- docker/automation/homeassistant/homeassistant.yaml | 2 +- docker/infra/adguardhome-sync/adguardhome-sync.yaml | 4 ++-- docker/media/video/jellyfin/jellyfin.yaml | 2 +- docker/security/traefik/traefik.yaml | 1 - docker/security/wg-easy/wg-easy.yaml | 2 +- docker/storage/syncthing/syncthing.yaml | 2 +- 9 files changed, 13 insertions(+), 14 deletions(-) diff --git a/config-example/docker/.env b/config-example/docker/.env index 48c1f9ae..bf142d98 100644 --- a/config-example/docker/.env +++ b/config-example/docker/.env @@ -13,11 +13,11 @@ MYDOMAIN_TLS_SANS=*.${MYDOMAIN},*.dev.${MYDOMAIN} ### Nodes - Also update in ansible/inventory/inventory.yaml -MAIN_NODE=nest -MAIN_NODE_IP=xxx.xxx.xxx.xxx +PRIMARY_NODE=nest +PRIMARY_NODE_IP=xxx.xxx.xxx.xxx -SECOND_NODE=hive -SECOND_NODE_IP=xxx.xxx.xxx.xxx +SECONDARY_NODE=hive +SECONDARY_NODE_IP=xxx.xxx.xxx.xxx ### Local accounts diff --git a/docker/Taskfile.apps.yaml b/docker/Taskfile.apps.yaml index 0ac81375..a3a65905 100644 --- a/docker/Taskfile.apps.yaml +++ b/docker/Taskfile.apps.yaml @@ -9,4 +9,4 @@ tasks: LOCAL_PORT: 11434 cmds: - echo "Forwarding remote port (${REMOTE_PORT}) to local Ollama port (${LOCAL_PORT}). Press Ctrl+C to to stop." - - ssh -N -o 'ExitOnForwardFailure yes' -R "*:${REMOTE_PORT}:localhost:${LOCAL_PORT}" ${ADMIN_USER}@${MAIN_NODE} + - ssh -N -o 'ExitOnForwardFailure yes' -R "*:${REMOTE_PORT}:localhost:${LOCAL_PORT}" ${ADMIN_USER}@${PRIMARY_NODE} diff --git a/docker/Taskfile.docker.yaml b/docker/Taskfile.docker.yaml index 4218c0e8..91309925 100644 --- a/docker/Taskfile.docker.yaml +++ b/docker/Taskfile.docker.yaml @@ -77,7 +77,7 @@ tasks: cmds: # Process .env files - | - SOURCE_HOSTDIR="${CONFIG_DIR}/${MAIN_NODE}" + SOURCE_HOSTDIR="${CONFIG_DIR}/${PRIMARY_NODE}" TARGET_HOSTDIR="${EXAMPLE_DIR}/myhost" mkdir -p "${TARGET_HOSTDIR}" echo "Processing: ${CONFIG_DIR}/.env -> ${EXAMPLE_DIR}/.env" @@ -89,5 +89,5 @@ tasks: python3 "$SCRIPT" "$env_file" "$target_filename" done # Process services.yaml, services-schema.yaml - - sed "s/${MAIN_NODE}/myhost/g" config/docker/${MAIN_NODE}/services.yaml > config-example/docker/myhost/services.yaml + - sed "s/${PRIMARY_NODE}/myhost/g" config/docker/${PRIMARY_NODE}/services.yaml > config-example/docker/myhost/services.yaml - cp config/docker/services-schema.yaml config-example/docker/services-schema.yaml diff --git a/docker/automation/homeassistant/homeassistant.yaml b/docker/automation/homeassistant/homeassistant.yaml index 54202271..f4e6a5b5 100644 --- a/docker/automation/homeassistant/homeassistant.yaml +++ b/docker/automation/homeassistant/homeassistant.yaml @@ -21,7 +21,7 @@ services: container_name: homeassistant restart: unless-stopped network_mode: host - hostname: ${MAIN_NODE} + hostname: ${PRIMARY_NODE} environment: PUID: ${PUID} PGID: ${PGID} diff --git a/docker/infra/adguardhome-sync/adguardhome-sync.yaml b/docker/infra/adguardhome-sync/adguardhome-sync.yaml index 80f74702..a7a60be6 100644 --- a/docker/infra/adguardhome-sync/adguardhome-sync.yaml +++ b/docker/infra/adguardhome-sync/adguardhome-sync.yaml @@ -20,10 +20,10 @@ services: environment: # Config reference: https://github.com/bakito/adguardhome-sync?tab=readme-ov-file#config-via-environment-variables CRON: "0 */2 * * *" - ORIGIN_URL: "http://${MAIN_NODE_IP}:3000" + ORIGIN_URL: "http://${PRIMARY_NODE_IP}:3000" ORIGIN_USERNAME: ${ADGUARDHOME_USERNAME} ORIGIN_PASSWORD: ${ADGUARDHOME_PASSWORD} - REPLICA1_URL: "http://${SECOND_NODE_IP}:3000" + REPLICA1_URL: "http://${SECONDARY_NODE_IP}:3000" REPLICA1_USERNAME: ${ADGUARDHOME_USERNAME} REPLICA1_PASSWORD: ${ADGUARDHOME_PASSWORD} API_PORT: 8080 diff --git a/docker/media/video/jellyfin/jellyfin.yaml b/docker/media/video/jellyfin/jellyfin.yaml index d71061e3..a8a762cc 100644 --- a/docker/media/video/jellyfin/jellyfin.yaml +++ b/docker/media/video/jellyfin/jellyfin.yaml @@ -17,7 +17,7 @@ services: PUID: ${PUID} PGID: ${PGID} TZ: ${TIMEZONE} - JELLYFIN_PublishedServerUrl: ${MAIN_NODE_IP} + JELLYFIN_PublishedServerUrl: https://jellyfin.${MYDOMAIN} volumes: - ${DOCKER_VOLUMES}/jellyfin:/config - ${STORAGE_MEDIA}:/media:ro diff --git a/docker/security/traefik/traefik.yaml b/docker/security/traefik/traefik.yaml index 94974049..5bf680e0 100644 --- a/docker/security/traefik/traefik.yaml +++ b/docker/security/traefik/traefik.yaml @@ -15,7 +15,6 @@ services: environment: TZ: ${TIMEZONE} MYDOMAIN: ${MYDOMAIN} - MAIN_NODE_IP: ${MAIN_NODE_IP} CLOUDFLARE_DNS_API_TOKEN: ${CLOUDFLARE_DNS_API_TOKEN} # Generate key within the crowdsec container: cscli bouncers add traefik-bouncer CROWDSEC_BOUNCER_API_KEY: ${CROWDSEC_BOUNCER_API_KEY} diff --git a/docker/security/wg-easy/wg-easy.yaml b/docker/security/wg-easy/wg-easy.yaml index 51564a3e..149f2fd3 100644 --- a/docker/security/wg-easy/wg-easy.yaml +++ b/docker/security/wg-easy/wg-easy.yaml @@ -24,7 +24,7 @@ services: WG_HOST: vpn.${MYDOMAIN} # The public hostname of your VPN server # PASSWORD: ${ADMIN_PASSWORD} # When set, requires a password when logging in to the Web UI -> Disabled, Authelia SSO is configured # WG_DEFAULT_DNS: 1.1.1.1,8.8.8.8 # DNS server clients will use - WG_DEFAULT_DNS: ${MAIN_NODE_IP} # DNS server clients will use + WG_DEFAULT_DNS: ${PRIMARY_NODE_IP} # DNS server clients will use WG_DEFAULT_ADDRESS: 10.8.0.x # Clients IP address range # WG_ALLOWED_IPS: "0.0.0.0/0,::/0" # Allowed IPs clients will use WG_ALLOWED_IPS: "192.168.0.0/16" # Allowed IPs clients will use diff --git a/docker/storage/syncthing/syncthing.yaml b/docker/storage/syncthing/syncthing.yaml index c2a8320a..53462283 100644 --- a/docker/storage/syncthing/syncthing.yaml +++ b/docker/storage/syncthing/syncthing.yaml @@ -12,7 +12,7 @@ services: container_name: syncthing restart: unless-stopped network_mode: host - hostname: ${MAIN_NODE} + hostname: ${PRIMARY_NODE} environment: PUID: ${PUID} PGID: ${PGID} From 7bb64d6ba41a39f9449fcf23bfa13fc3ab5c3712 Mon Sep 17 00:00:00 2001 From: Buba Date: Sat, 27 Jun 2026 13:45:47 +0200 Subject: [PATCH 3/7] Update container debug skill --- .claude/skills/debug-container-service/SKILL.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.claude/skills/debug-container-service/SKILL.md b/.claude/skills/debug-container-service/SKILL.md index c8d95fc3..d35914e4 100644 --- a/.claude/skills/debug-container-service/SKILL.md +++ b/.claude/skills/debug-container-service/SKILL.md @@ -73,6 +73,8 @@ scripts/labctl.py service logs category/service-name --timestamps **Key insight from logs:** Pay attention to the source location (e.g. `module.file:function:line`). An error in `process_chat` vs `images_endpoint` are different code paths that may require completely different fixes, even if the symptom looks the same. +**If docker logs return empty or near-empty output:** some services (notably Traefik) write all logs to files rather than stdout. Skip immediately to Step 3 — do not waste tool calls on alternative retrieval methods (e.g. querying an internal API). + If dependent services are suspected, check their logs too. --- @@ -171,6 +173,8 @@ Choose the least invasive fix, in this order: 2. **Mount a patched file via Docker volume** — add a bind mount in the service YAML pointing to a patched copy stored in the repo (e.g. `./patched_file.py:/app/path/file.py:ro`); document clearly why it exists 3. **Custom Docker image** — only if a volume mount is not feasible; significantly increases maintenance burden +**When adding a service to the `proxy` network:** remove any host port binding for the port Traefik proxies (e.g. `8080:8080`). Traefik reaches the container via Docker networking — the host binding is redundant and will conflict if another service uses the same port. Ports needed for direct external access (e.g. BitTorrent, UDP services) should remain. + **Always revert wrong fixes before applying the correct one.** Accumulating incorrect changes obscures the actual state and makes future debugging harder. After applying any fix, recreate the container to pick up changes: From 2a66509e4c140b14c2ce943ff85f5d01b448b54b Mon Sep 17 00:00:00 2001 From: Buba Date: Sat, 27 Jun 2026 15:47:31 +0200 Subject: [PATCH 4/7] Configure container services --- docker/ai/sillytavern/sillytavern.yaml | 2 +- .../homeassistant/homeassistant.yaml | 2 +- .../dashboard/homepage/config/services.yaml | 4 +-- docker/fileshare/qbittorrent/Dockerfile | 2 +- docker/fileshare/qbittorrent/qbittorrent.yaml | 26 +++++++++++++++---- .../config/dynamic/external-services.yml | 10 ------- 6 files changed, 26 insertions(+), 20 deletions(-) diff --git a/docker/ai/sillytavern/sillytavern.yaml b/docker/ai/sillytavern/sillytavern.yaml index 61f36146..44a925c6 100644 --- a/docker/ai/sillytavern/sillytavern.yaml +++ b/docker/ai/sillytavern/sillytavern.yaml @@ -14,7 +14,7 @@ services: initContainer: image: busybox container_name: sillytavern-init - command: grep -q '^\s*-\s*172\.18\.0\.1\/16' /home/node/app/config/config.yaml || sed -i '/whitelist:/a \ \ - 172.18.0.1\/16' /home/node/app/config/config.yaml + command: sh -c 'grep -q "172\.16\.0\.0/12" /home/node/app/config/config.yaml || sed -i "s|172\\.18\\.0\\.1/16|172.16.0.0/12|; /^whitelist:/a - 172.16.0.0/12" /home/node/app/config/config.yaml' volumes: - ${DOCKER_VOLUMES}/sillytavern/config:/home/node/app/config network_mode: none diff --git a/docker/automation/homeassistant/homeassistant.yaml b/docker/automation/homeassistant/homeassistant.yaml index f4e6a5b5..d298e444 100644 --- a/docker/automation/homeassistant/homeassistant.yaml +++ b/docker/automation/homeassistant/homeassistant.yaml @@ -6,7 +6,7 @@ # http: # use_x_forwarded_for: true # trusted_proxies: -# - 172.18.0.0/24 +# - 172.16.0.0/12 # All Docker bridge networks # ``` # # Links: diff --git a/docker/dashboard/homepage/config/services.yaml b/docker/dashboard/homepage/config/services.yaml index dfe596bf..a2bad358 100644 --- a/docker/dashboard/homepage/config/services.yaml +++ b/docker/dashboard/homepage/config/services.yaml @@ -4,9 +4,9 @@ - Connections: - Router Web UI: - href: https://router.{{HOMEPAGE_VAR_MYDOMAIN}}/cgi-bin/luci/ + href: https://router.{{HOMEPAGE_VAR_MYDOMAIN}}/ siteMonitor: https://router.{{HOMEPAGE_VAR_MYDOMAIN}}/ - description: OpenWRT LuCi + description: Mikrotik hEX Router icon: router.png - Proxmox Web UI: href: https://proxmox.{{HOMEPAGE_VAR_MYDOMAIN}}/ diff --git a/docker/fileshare/qbittorrent/Dockerfile b/docker/fileshare/qbittorrent/Dockerfile index d386dadb..2fd8f037 100644 --- a/docker/fileshare/qbittorrent/Dockerfile +++ b/docker/fileshare/qbittorrent/Dockerfile @@ -1,5 +1,5 @@ # kics-scan ignore-line - Missing User Instruction -FROM lscr.io/linuxserver/qbittorrent:4.6.7 +FROM lscr.io/linuxserver/qbittorrent:5.2.2 WORKDIR /usr RUN curl -LO https://github.com/WDaan/VueTorrent/releases/latest/download/vuetorrent.zip && \ diff --git a/docker/fileshare/qbittorrent/qbittorrent.yaml b/docker/fileshare/qbittorrent/qbittorrent.yaml index 91d830c3..8fdefb98 100644 --- a/docker/fileshare/qbittorrent/qbittorrent.yaml +++ b/docker/fileshare/qbittorrent/qbittorrent.yaml @@ -1,5 +1,5 @@ # The qBittorrent project aims to provide an open-source software alternative to µTorrent. qBittorrent is based on the Qt toolkit and libtorrent-rasterbar library. -# The default username/password is `admin` / `adminadmin`. +# The default username/password is printed to the terminal on container start. # # Recommended setup steps: # - Configure "Bypass authentication for clients in whitelisted IP subnets" (Settings -> Webui) - Authelia handles the authentication @@ -17,8 +17,21 @@ services: build: . container_name: qbittorrent restart: unless-stopped - # network_mode: host - # hostname: ${MAIN_NODE} + networks: + - proxy + # If running on a separate host (not the Traefik host), remove the networks section above, + # add "- 8080:8080" to the ports section below so the WebUI is reachable on the host, + # and add a router+service entry in docker/security/traefik/config/dynamic/external-services.yml: + # routers: + # qbittorrent: + # rule: 'Host(`qbittorrent.${MYDOMAIN}`)' + # service: qbittorrent + # middlewares: [localaccess@file] + # services: + # qbittorrent: + # loadBalancer: + # servers: + # - url: http://:8080 environment: PUID: ${PUID} PGID: ${PGID} @@ -28,9 +41,8 @@ services: - ${DOCKER_VOLUMES}/qbittorrent:/config - ${STORAGE_DOWNLOADS}/torrent-downloads:/downloads - ${STORAGE_MEDIA}:/media - # Not needed if 'network_mode: host' is set ports: - - 8080:8080 # Web UI + # 8080 not exposed on host - Traefik reaches it via proxy network - 6881:6881/tcp # Bittorrent incoming connections - 6881:6881/udp # Bittorrent incoming connections labels: @@ -43,3 +55,7 @@ services: homepage.href: https://qbittorrent.${MYDOMAIN}/ homepage.description: "BitTorrent client" com.centurylinklabs.watchtower.enable: false + +networks: + proxy: + external: true diff --git a/docker/security/traefik/config/dynamic/external-services.yml b/docker/security/traefik/config/dynamic/external-services.yml index ac03d6de..1ee0b6d7 100644 --- a/docker/security/traefik/config/dynamic/external-services.yml +++ b/docker/security/traefik/config/dynamic/external-services.yml @@ -16,11 +16,6 @@ http: service: proxmox middlewares: - localaccess@file - qbittorrent: - rule: 'Host(`qbittorrent.{{env "MYDOMAIN"}}`)' - service: qbittorrent - middlewares: - - localaccess@file services: router: @@ -33,8 +28,3 @@ http: serversTransport: insecureTransport servers: - url: https://192.168.1.50:8006 - qbittorrent: - loadBalancer: - serversTransport: insecureTransport - servers: - - url: http://nas:8080 From 3a006817437448fb909813e5f0750f9c61bf5891 Mon Sep 17 00:00:00 2001 From: Buba Date: Sat, 27 Jun 2026 21:59:30 +0200 Subject: [PATCH 5/7] Configure SMB shares --- ansible/inventory/group_vars/debian/vars.yaml | 2 +- ansible/playbooks/homelab.yaml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ansible/inventory/group_vars/debian/vars.yaml b/ansible/inventory/group_vars/debian/vars.yaml index 82e20492..59876cf0 100644 --- a/ansible/inventory/group_vars/debian/vars.yaml +++ b/ansible/inventory/group_vars/debian/vars.yaml @@ -109,7 +109,7 @@ debian_tools_brew_packages_kubernetes: - helm # https://helm.sh/docs/intro/install/ debian_samba_client_servers: - - hostname: 192.168.1.12 + - hostname: colony shares: - media - family-media diff --git a/ansible/playbooks/homelab.yaml b/ansible/playbooks/homelab.yaml index 2f583b0e..474b884d 100755 --- a/ansible/playbooks/homelab.yaml +++ b/ansible/playbooks/homelab.yaml @@ -26,7 +26,6 @@ - name: Setup Samba client hosts: - nest - - colony roles: - role: debian_samba_client From 08daeb8aa4867dd98e96e01a961140dcbe9d8cd5 Mon Sep 17 00:00:00 2001 From: Buba Date: Sat, 27 Jun 2026 22:02:38 +0200 Subject: [PATCH 6/7] Add AMD GPU support for Ollama, Jellyfin, and Frigate with new configuration files --- docker/ai/ollama/ollama-amdgpu.yaml | 14 +++++++ docker/ai/ollama/ollama.yaml | 10 +---- .../media/video/jellyfin/jellyfin-amdgpu.yaml | 12 ++++++ docker/media/video/jellyfin/jellyfin.yaml | 2 + docker/security/frigate/config/config.yml | 7 ++++ docker/security/frigate/frigate-amdgpu.yaml | 14 +++++++ docker/security/frigate/frigate.yaml | 3 +- scripts/labctl.py | 39 +++++++++++++++---- 8 files changed, 83 insertions(+), 18 deletions(-) create mode 100644 docker/ai/ollama/ollama-amdgpu.yaml create mode 100644 docker/media/video/jellyfin/jellyfin-amdgpu.yaml create mode 100644 docker/security/frigate/frigate-amdgpu.yaml diff --git a/docker/ai/ollama/ollama-amdgpu.yaml b/docker/ai/ollama/ollama-amdgpu.yaml new file mode 100644 index 00000000..57876110 --- /dev/null +++ b/docker/ai/ollama/ollama-amdgpu.yaml @@ -0,0 +1,14 @@ +# GPU override for Ollama — AMD ROCm hardware inference. +# Enable by setting GPU_COMPOSE_SUFFIX=amdgpu in config/docker//.env +# Switches to the :rocm image tag and passes both DRI and KFD devices. +--- +name: ollama +services: + ollama: + image: ollama/ollama:0.30.10-rocm + devices: + - /dev/dri/renderD128:/dev/dri/renderD128 + - /dev/kfd:/dev/kfd + group_add: + - "${GPU_RENDER_GID}" # render group — numeric GID required (name may not exist in container) + - "${GPU_VIDEO_GID}" # video group diff --git a/docker/ai/ollama/ollama.yaml b/docker/ai/ollama/ollama.yaml index b2c33e89..4ecdb6f1 100644 --- a/docker/ai/ollama/ollama.yaml +++ b/docker/ai/ollama/ollama.yaml @@ -7,15 +7,7 @@ name: ollama services: ollama: - # Enable for GPU support (see ollama documentation for CUDA driver setup) - # deploy: - # resources: - # reservations: - # devices: - # - driver: nvidia - # count: 1 - # capabilities: - # - gpu + # GPU acceleration: set GPU_COMPOSE_SUFFIX=amdgpu in host .env to load ollama-amdgpu.yaml (ROCm/AMD) image: ollama/ollama:0.30.10 container_name: ollama restart: unless-stopped diff --git a/docker/media/video/jellyfin/jellyfin-amdgpu.yaml b/docker/media/video/jellyfin/jellyfin-amdgpu.yaml new file mode 100644 index 00000000..cf3eaa0e --- /dev/null +++ b/docker/media/video/jellyfin/jellyfin-amdgpu.yaml @@ -0,0 +1,12 @@ +# GPU override for Jellyfin — AMD VAAPI hardware transcoding. +# Enable by setting GPU_COMPOSE_SUFFIX=amdgpu in config/docker//.env +# After deploying, configure VA-API in Jellyfin UI: Dashboard → Playback → Transcoding → VA-API +--- +name: jellyfin +services: + jellyfin: + devices: + - /dev/dri/renderD128:/dev/dri/renderD128 + group_add: + - "${GPU_RENDER_GID}" # render group — numeric GID required (name may not exist in container) + - "${GPU_VIDEO_GID}" # video group diff --git a/docker/media/video/jellyfin/jellyfin.yaml b/docker/media/video/jellyfin/jellyfin.yaml index a8a762cc..3679246e 100644 --- a/docker/media/video/jellyfin/jellyfin.yaml +++ b/docker/media/video/jellyfin/jellyfin.yaml @@ -10,6 +10,8 @@ name: jellyfin services: jellyfin: + # GPU acceleration: set GPU_COMPOSE_SUFFIX=amdgpu in host .env to load jellyfin-amdgpu.yaml (VAAPI/AMD). + # Then enable VA-API in Jellyfin UI: Dashboard → Playback → Transcoding. image: lscr.io/linuxserver/jellyfin:10.11.11 container_name: jellyfin restart: unless-stopped diff --git a/docker/security/frigate/config/config.yml b/docker/security/frigate/config/config.yml index 991acc4a..7ecdf573 100644 --- a/docker/security/frigate/config/config.yml +++ b/docker/security/frigate/config/config.yml @@ -12,6 +12,13 @@ tls: # auth: # enabled: false +# Docs: https://docs.frigate.video/configuration/hardware_acceleration_video +# Docs: https://docs.frigate.video/configuration/ffmpeg_presets +# AMD VAAPI hardware acceleration for video decode +# Enable GPU_COMPOSE_SUFFIX=amdgpu in host .env to load frigate-amdgpu.yaml (VAAPI/AMD). +ffmpeg: + hwaccel_args: preset-vaapi + # Docs: https://docs.frigate.video/configuration/cameras cameras: reolink_front: diff --git a/docker/security/frigate/frigate-amdgpu.yaml b/docker/security/frigate/frigate-amdgpu.yaml new file mode 100644 index 00000000..63e8b32e --- /dev/null +++ b/docker/security/frigate/frigate-amdgpu.yaml @@ -0,0 +1,14 @@ +# GPU override for Frigate — AMD VAAPI video decode for camera streams. +# Enable by setting GPU_COMPOSE_SUFFIX=amdgpu in config/docker//.env +# Also set hwaccel in config/config.yml: +# ffmpeg: +# hwaccel_args: preset-vaapi +--- +name: frigate +services: + frigate: + devices: + - /dev/dri/renderD128:/dev/dri/renderD128 + group_add: + - "${GPU_RENDER_GID}" # render group — numeric GID required (name may not exist in container) + - "${GPU_VIDEO_GID}" # video group diff --git a/docker/security/frigate/frigate.yaml b/docker/security/frigate/frigate.yaml index bdbd31b7..55df1010 100644 --- a/docker/security/frigate/frigate.yaml +++ b/docker/security/frigate/frigate.yaml @@ -10,8 +10,9 @@ # - Install: https://docs.frigate.video/frigate/installation#docker # - Config reference: https://docs.frigate.video/configuration/ # +# GPU acceleration: set GPU_COMPOSE_SUFFIX=amdgpu in host .env to load frigate-amdgpu.yaml (VAAPI/AMD). +# # TODO: Configure cameras and detection settings in config.yml -# TODO: Set up hardware acceleration (Coral TPU, Intel QuickSync, etc.) by adding device mappings # TODO: Adjust shm-size based on camera count and resolution (calculate: width × height × 1.5 × 20 + 270480 bytes + 40MB) # TODO: Consider integration with Home Assistant for enhanced automation # TODO: Configure motion detection zones to reduce false positives diff --git a/scripts/labctl.py b/scripts/labctl.py index 30008752..df1ab622 100755 --- a/scripts/labctl.py +++ b/scripts/labctl.py @@ -145,6 +145,19 @@ def get_env_file_args(host_config_dir: Path, service_name: str) -> list[str]: return args +def get_gpu_suffix(host_config_dir: Path) -> str | None: + """Read GPU_COMPOSE_SUFFIX from the host-specific .env file.""" + env_file = host_config_dir / ".env" + if not env_file.exists(): + return None + for line in env_file.read_text().splitlines(): + stripped = line.strip() + if stripped.startswith("GPU_COMPOSE_SUFFIX="): + value = stripped.split("=", 1)[1].strip().strip("\"'") + return value or None + return None + + def docker( cmd: list[str], env: dict[str, str] | None = None, @@ -162,23 +175,24 @@ def docker( def docker_pull( stack_dir: Path, service_name: str, - compose_file: Path, + compose_files: list[Path], env_file_args: list[str], quiet: bool = False, ) -> None: """Pull Docker images for a service, using build if the service has a build directive.""" logger.info(f">>> Pulling {stack_dir}/{service_name}") + file_args = [arg for f in compose_files for arg in ["-f", str(f)]] - if has_build_directive(compose_file): + if has_build_directive(compose_files[0]): # Bake: https://docs.docker.com/guides/compose-bake/ env = os.environ.copy() env["COMPOSE_BAKE"] = "true" - cmd = ["compose", "-f", compose_file, *env_file_args, "build", "--pull"] + cmd = ["compose", *file_args, *env_file_args, "build", "--pull"] if quiet: cmd.append("--quiet") docker(cmd, env=env) else: - cmd = ["compose", "-f", compose_file, *env_file_args, "pull"] + cmd = ["compose", *file_args, *env_file_args, "pull"] if quiet: cmd.append("--quiet") docker(cmd) @@ -219,16 +233,25 @@ def docker_command( if action in {"up", "recreate"}: create_service_networks(compose_file) + compose_files = [compose_file] + suffix = get_gpu_suffix(host_config_dir) + if suffix: + override = compose_file.parent / f"{service_name}-{suffix}.yaml" + if override.exists(): + logger.info(f" GPU override: {override.name}") + compose_files.append(override) + env_file_args = get_env_file_args(host_config_dir, service_name) - base_cmd = ["compose", "-f", compose_file, *env_file_args] + file_args = [arg for f in compose_files for arg in ["-f", str(f)]] + base_cmd = ["compose", *file_args, *env_file_args] match action: case "pull": - docker_pull(stack_dir, service_name, compose_file, env_file_args, options.quiet) + docker_pull(stack_dir, service_name, compose_files, env_file_args, options.quiet) case "up": if options.pull_before_start: - docker_pull(stack_dir, service_name, compose_file, env_file_args, options.quiet) + docker_pull(stack_dir, service_name, compose_files, env_file_args, options.quiet) logger.info(f">>> Starting {stack_dir}/{service_name}") docker([*base_cmd, "up", "--detach"]) @@ -242,7 +265,7 @@ def docker_command( case "recreate": if options.pull_before_start: - docker_pull(stack_dir, service_name, compose_file, env_file_args, options.quiet) + docker_pull(stack_dir, service_name, compose_files, env_file_args, options.quiet) logger.info(f">>> Recreating {stack_dir}/{service_name}") docker([*base_cmd, "up", "--detach", "--force-recreate"]) From af4f98351f82010b44304a0cbb1e6e7ffccc7178 Mon Sep 17 00:00:00 2001 From: Buba Date: Sun, 28 Jun 2026 20:39:41 +0200 Subject: [PATCH 7/7] Add task for installing crontab entry, fix backup --- Taskfile.yaml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Taskfile.yaml b/Taskfile.yaml index b399dd24..f0601062 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -139,7 +139,7 @@ tasks: desc: Create a compressed backup of the configuration directory cmds: - mkdir -p ../infra-config-backup - - cd config && tar -cjf "../infra-config-backup/infra-config-$(date +%Y-%m-%d_%H-%M-%S).tar.bz2" --exclude=.git . + - cd config && tar -cjf "../../infra-config-backup/infra-config-$(date +%Y-%m-%d_%H-%M-%S).tar.bz2" --exclude=.git . - echo "Recent backups:" - ls -lh ../infra-config-backup/infra-config-*.tar.bz2 | tail -n 5 # Remove old backups (they are still kept in the repository - see the push-config-repo task) @@ -171,8 +171,7 @@ tasks: git push fi - # Setup scheduling with command `crontab -e`, add this line: - # 0 1 * * * cd /home/buba/repos/infra && /home/linuxbrew/.linuxbrew/bin/task backup >> config/log/infra-backup.log 2>&1 + # Run `task setup-crontab` to install the cron schedule automatically. backup: desc: Create backup of the configuration directory and the Obsidian vault silent: true @@ -186,6 +185,15 @@ tasks: bash "config/sync-backup-to-remote.sh" fi + setup-crontab: + desc: Install or update the daily backup cron job + cmds: + - | + MARKER="# managed:infra-backup" + CRON_ENTRY="0 1 * * * cd \"$(pwd)\" && PATH=\"/home/linuxbrew/.linuxbrew/bin:$HOME/.local/bin:\$PATH\" task backup >> $HOME/infra-backup.log 2>&1 $MARKER" + (crontab -l 2>/dev/null | grep -vF "$MARKER"; echo "$CRON_ENTRY") | crontab - + echo "Crontab entry installed/updated." + get-offline-data: desc: Download data files for offline use (stored both local and on Proxmox) cmds: