From 933324bd745f5cef7b43a87c46f7d6e785eed656 Mon Sep 17 00:00:00 2001 From: Adrien Poupa Date: Sun, 14 May 2023 00:19:04 -0400 Subject: [PATCH] feat: replace Heimdall by Homepage --- .env.example | 15 ++++++- .gitignore | 4 +- README.md | 78 +++++++++++++++++++++++---------- adguardhome/docker-compose.yml | 10 +++++ docker-compose.yml | 75 ++++++++++++++++++++++++++----- {heimdall => homepage}/.gitkeep | 0 homepage/tpl/bookmarks.yaml | 8 ++++ homepage/tpl/docker.yaml | 6 +++ homepage/tpl/settings.yaml | 20 +++++++++ homepage/tpl/widgets.yaml | 27 ++++++++++++ sabnzbd/docker-compose.yml | 8 +++- update-config.sh | 3 ++ 12 files changed, 216 insertions(+), 38 deletions(-) rename {heimdall => homepage}/.gitkeep (100%) create mode 100644 homepage/tpl/bookmarks.yaml create mode 100644 homepage/tpl/docker.yaml create mode 100644 homepage/tpl/settings.yaml create mode 100644 homepage/tpl/widgets.yaml diff --git a/.env.example b/.env.example index e947268..ff5f0fc 100644 --- a/.env.example +++ b/.env.example @@ -11,10 +11,23 @@ PIA_PASS= PIA_LOCAL_NETWORK="192.168.0.0/16" HOSTNAME=localhost ADGUARD_HOSTNAME= +ADGUARD_USERNAME= +ADGUARD_PASSWORD= DNS_CHALLENGE=true DNS_CHALLENGE_PROVIDER=cloudflare -LETS_ENCRYPT_CA_SERVER=https://acme-v02.api.letsencrypt.org/directory +LETS_ENCRYPT_CA_SERVER="https://acme-v02.api.letsencrypt.org/directory" LETS_ENCRYPT_EMAIL= CLOUDFLARE_EMAIL= CLOUDFLARE_DNS_API_TOKEN= CLOUDFLARE_ZONE_API_TOKEN= +SONARR_API_KEY= +RADARR_API_KEY= +PROWLARR_API_KEY= +JELLYFIN_API_KEY= +HOMEPAGE_VAR_TITLE="Docker-Compose NAS" +HOMEPAGE_VAR_SEARCH_PROVIDER=google +HOMEPAGE_VAR_HEADER_STYLE=boxed +HOMEPAGE_VAR_WEATHER_CITY= +HOMEPAGE_VAR_WEATHER_LAT= +HOMEPAGE_VAR_WEATHER_LONG= +HOMEPAGE_VAR_WEATHER_UNIT=metric \ No newline at end of file diff --git a/.gitignore b/.gitignore index da30505..b3d93cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ .env .idea docker-compose.override.yml -/heimdall -!/heimdall/.gitkeep +/homepage/logs +/homepage/*.yaml /sonarr !/sonarr/.gitkeep /radarr diff --git a/README.md b/README.md index a5538e2..227382d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ SSL certificates and remote access through Tailscale are supported. Requirements: Any Docker-capable recent Linux box with Docker Engine and Docker Compose V2. I am running it in Ubuntu Server 22.04; I also tested this setup on a [Synology DS220+ with DSM 7.1](#synology-quirks). +![Docker-Compose NAS Homepage](https://github.com/AdrienPoupa/docker-compose-nas/assets/15086425/3492a9f6-3779-49a5-b052-4193844f16f0) + ## Table of Content @@ -23,6 +25,7 @@ I am running it in Ubuntu Server 22.04; I also tested this setup on a [Synology * [Prowlarr](#prowlarr) * [qBittorrent](#qbittorrent) * [Jellyfin](#jellyfin) + * [Homepage](#homepage) * [Traefik and SSL Certificates](#traefik-and-ssl-certificates) * [Accessing from the outside with Tailscale](#accessing-from-the-outside-with-tailscale) * [Optional Services](#optional-services) @@ -55,7 +58,7 @@ I am running it in Ubuntu Server 22.04; I also tested this setup on a [Synology | [PIA WireGuard VPN](https://github.com/thrnz/docker-wireguard-pia) | Encapsulate qBittorrent traffic in [PIA](https://www.privateinternetaccess.com/) using [WireGuard](https://www.wireguard.com/) with port forwarding. | [thrnz/docker-wireguard-pia](https://hub.docker.com/r/thrnz/docker-wireguard-pia) | | | [qBittorrent](https://www.qbittorrent.org) | Bittorrent client with a complete web UI
Uses VPN network
Using Libtorrent 1.x | [linuxserver/qbittorrent:libtorrentv1](https://hub.docker.com/r/linuxserver/qbittorrent) | /qbittorrent | | [Jellyfin](https://jellyfin.org) | Media server designed to organize, manage, and share digital media files to networked devices | [linuxserver/jellyfin](https://hub.docker.com/r/linuxserver/jellyfin) | /jellyfin | -| [Heimdall](https://heimdall.site) | Application dashboard | [linuxserver/heimdall](https://hub.docker.com/r/linuxserver/heimdall) | / | +| [Homepage](https://gethomepage.dev) | Application dashboard | [benphelps/homepage](https://github.com/benphelps/homepage/pkgs/container/homepage) | / | | [Traefik](https://traefik.io) | Reverse proxy | [traefik](https://hub.docker.com/_/traefik) | | | [Watchtower](https://containrrr.dev/watchtower/) | Automated Docker images update | [containrrr/watchtower](https://hub.docker.com/r/containrrr/watchtower) | | | [SABnzbd](https://sabnzbd.org/) | Optional - Free and easy binary newsreader | [linuxserver/sabnzbd](https://hub.docker.com/r/linuxserver/sabnzbd) | /sabnzbd | @@ -71,32 +74,47 @@ see [Optional Services](#optional-services) for more information. `cp .env.example .env`, edit to your needs then `sudo docker compose up -d`. -For the first time, run `./update-config.sh` to update the applications base URLs. +For the first time, run `./update-config.sh` to update the applications base URLs and set the API keys in `.env`. + +If you want to show Jellyfin information in the homepage, create it in Jellyfin settings and fill `JELLYFIN_API_KEY`. ## Environment Variables -| Variable | Description | Default | -|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------| -| `COMPOSE_FILE` | Docker compose files to load | `docker-compose.yml` | -| `COMPOSE_PATH_SEPARATOR` | Path separator between compose files to load | `:` | -| `USER_ID` | ID of the user to use in Docker containers | `1000` | -| `GROUP_ID` | ID of the user group to use in Docker containers | `1000` | -| `TIMEZONE` | TimeZone used by the container. | `America/New_York` | -| `DATA_ROOT` | Host location of the data files | `/mnt/data` | -| `DOWNLOAD_ROOT` | Host download location for qBittorrent, should be a subfolder of `DATA_ROOT` | `/mnt/data/torrents` | -| `PIA_LOCATION` | Servers to use for PIA | `ca` (Montreal, Canada) | -| `PIA_USER` | PIA username | | -| `PIA_PASS` | PIA password | | -| `PIA_LOCAL_NETWORK` | PIA local network | `192.168.0.0/16` | -| `HOSTNAME` | Hostname of the NAS, could be a local IP or a domain name | `localhost` | -| `ADGUARD_HOSTNAME` | AdGuard Home hostname used, if enabled | | -| `DNS_CHALLENGE` | Enable/Disable DNS01 challenge, set to `false` to disable. | `true` | -| `DNS_CHALLENGE_PROVIDER` | Provider for DNS01 challenge, [see list here](https://doc.traefik.io/traefik/https/acme/#providers). | `cloudflare` | -| `LETS_ENCRYPT_CA_SERVER` | Let's Encrypt CA Server used to generate certificates, set to production by default.
Set to `https://acme-staging-v02.api.letsencrypt.org/directory` to test your changes with the staging server. | `https://acme-v02.api.letsencrypt.org/directory` | -| `LETS_ENCRYPT_EMAIL` | E-mail address used to send expiration notifications | | -| `CLOUDFLARE_EMAIL` | CloudFlare Account email | | -| `CLOUDFLARE_DNS_API_TOKEN` | API token with `DNS:Edit` permission | | -| `CLOUDFLARE_ZONE_API_TOKEN` | API token with `Zone:Read` permission | | +| Variable | Description | Default | +|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------| +| `COMPOSE_FILE` | Docker compose files to load | `docker-compose.yml` | +| `COMPOSE_PATH_SEPARATOR` | Path separator between compose files to load | `:` | +| `USER_ID` | ID of the user to use in Docker containers | `1000` | +| `GROUP_ID` | ID of the user group to use in Docker containers | `1000` | +| `TIMEZONE` | TimeZone used by the container. | `America/New_York` | +| `DATA_ROOT` | Host location of the data files | `/mnt/data` | +| `DOWNLOAD_ROOT` | Host download location for qBittorrent, should be a subfolder of `DATA_ROOT` | `/mnt/data/torrents` | +| `PIA_LOCATION` | Servers to use for PIA | `ca` (Montreal, Canada) | +| `PIA_USER` | PIA username | | +| `PIA_PASS` | PIA password | | +| `PIA_LOCAL_NETWORK` | PIA local network | `192.168.0.0/16` | +| `HOSTNAME` | Hostname of the NAS, could be a local IP or a domain name | `localhost` | +| `ADGUARD_HOSTNAME` | Optional - AdGuard Home hostname used, if enabled | | +| `ADGUARD_USERNAME` | Optional - AdGuard Home username to show details in the homepage, if enabled | | +| `ADGUARD_PASSWORD` | Optional - AdGuard Home password to show details in the homepage, if enabled | | +| `DNS_CHALLENGE` | Enable/Disable DNS01 challenge, set to `false` to disable. | `true` | +| `DNS_CHALLENGE_PROVIDER` | Provider for DNS01 challenge, [see list here](https://doc.traefik.io/traefik/https/acme/#providers). | `cloudflare` | +| `LETS_ENCRYPT_CA_SERVER` | Let's Encrypt CA Server used to generate certificates, set to production by default.
Set to `https://acme-staging-v02.api.letsencrypt.org/directory` to test your changes with the staging server. | `https://acme-v02.api.letsencrypt.org/directory` | +| `LETS_ENCRYPT_EMAIL` | E-mail address used to send expiration notifications | | +| `CLOUDFLARE_EMAIL` | CloudFlare Account email | | +| `CLOUDFLARE_DNS_API_TOKEN` | API token with `DNS:Edit` permission | | +| `CLOUDFLARE_ZONE_API_TOKEN` | API token with `Zone:Read` permission | | +| `SONARR_API_KEY` | Sonarr API key to show information in the homepage | | +| `RADARR_API_KEY` | Radarr API key to show information in the homepage | | +| `PROWLARR_API_KEY` | Prowlarr API key to show information in the homepage | | +| `JELLYFIN_API_KEY` | Jellyfin API key to show information in the homepage | | +| `HOMEPAGE_VAR_TITLE` | Title of the homepage | `Docker-Compose NAS` | +| `HOMEPAGE_VAR_SEARCH_PROVIDER` | Homepage search provider, [see list here](https://gethomepage.dev/en/widgets/search/) | `google` | +| `HOMEPAGE_VAR_HEADER_STYLE` | Homepage header style, [see list here](https://gethomepage.dev/en/configs/settings/#header-style) | `boxed` | +| `HOMEPAGE_VAR_WEATHER_CITY` | Homepage weather city name | | +| `HOMEPAGE_VAR_WEATHER_LAT` | Homepage weather city latitude | | +| `HOMEPAGE_VAR_WEATHER_LONG` | Homepage weather city longitude | | +| `HOMEPAGE_VAR_WEATHER_UNIT` | Homepage weather unit, either `metric` or `imperial` | `metric` | ## PIA WireGuard VPN @@ -112,6 +130,9 @@ For PIA + WireGuard, fill `.env` and fill it with your PIA credentials. The location of the server it will connect to is set by `LOC=ca`, defaulting to Montreal - Canada. +You need to fill the credentials in the `PIA_*` environment variable, +otherwise the VPN container will exit and qBittorrent will not start. + ## Sonarr & Radarr ### File Structure @@ -178,6 +199,15 @@ devices: Generally, running Docker on Linux you will want to use VA-API, but the exact mount paths may differ depending on your hardware. +## Homepage + +The homepage comes with sensible defaults; some settings can ben controlled via environment variables in `.env`. + +If you to customize further, you can modify the files in `/homepage/*.yaml` according to the [documentation](https://gethomepage.dev). +Due to how the Docker socket is configured for the Docker integration, files must be edited as root. + +The files in `/homepage/tpl/*.yaml` only serve as a base to set up the homepage configuration on first run. + ## Traefik and SSL Certificates While you can use the private IP to access your NAS, how cool would it be for it to be accessible through a subdomain diff --git a/adguardhome/docker-compose.yml b/adguardhome/docker-compose.yml index 2168ac8..6a7573e 100644 --- a/adguardhome/docker-compose.yml +++ b/adguardhome/docker-compose.yml @@ -38,6 +38,16 @@ services: - "traefik.http.routers.adguardhome.rule=(Host(`${ADGUARD_HOSTNAME}`))" - "traefik.http.routers.adguardhome.tls=true" - "traefik.http.routers.adguardhome.tls.certresolver=myresolver" + - homepage.group=Utilities + - homepage.name=Adguard + - homepage.icon=adguard-home.png + - homepage.href=https://${ADGUARD_HOSTNAME} + - homepage.description=DNS Adblocker + - homepage.weight=0 + - homepage.widget.type=adguard + - homepage.widget.url=https://${ADGUARD_HOSTNAME} + - homepage.widget.username=${ADGUARD_USERNAME} + - homepage.widget.password=${ADGUARD_PASSWORD} traefik-certs-dumper: image: ldez/traefik-certs-dumper diff --git a/docker-compose.yml b/docker-compose.yml index 7770e20..1188dd5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -45,6 +45,15 @@ services: - traefik.http.routers.sonarr.tls=true - traefik.http.routers.sonarr.tls.certresolver=myresolver - traefik.http.services.sonarr.loadbalancer.server.port=8989 + - homepage.group=Media + - homepage.name=Sonarr + - homepage.icon=sonarr.png + - homepage.href=/sonarr + - homepage.description=Series management + - homepage.weight=0 + - homepage.widget.type=sonarr + - homepage.widget.url=http://sonarr:8989/sonarr + - homepage.widget.key=${SONARR_API_KEY} radarr: image: lscr.io/linuxserver/radarr container_name: radarr @@ -62,6 +71,15 @@ services: - traefik.http.routers.radarr.tls=true - traefik.http.routers.radarr.tls.certresolver=myresolver - traefik.http.services.radarr.loadbalancer.server.port=7878 + - homepage.group=Media + - homepage.name=Radarr + - homepage.icon=radarr.png + - homepage.href=/radarr + - homepage.description=Movies management + - homepage.weight=1 + - homepage.widget.type=radarr + - homepage.widget.url=http://radarr:7878/radarr + - homepage.widget.key=${RADARR_API_KEY} prowlarr: image: lscr.io/linuxserver/prowlarr:latest container_name: prowlarr @@ -78,6 +96,15 @@ services: - traefik.http.routers.prowlarr.tls=true - traefik.http.routers.prowlarr.tls.certresolver=myresolver - traefik.http.services.prowlarr.loadbalancer.server.port=9696 + - homepage.group=Download + - homepage.name=Prowlarr + - homepage.icon=prowlarr.png + - homepage.href=/prowlarr + - homepage.description=Indexers management + - homepage.weight=4 + - homepage.widget.type=prowlarr + - homepage.widget.url=http://prowlarr:9696/prowlarr + - homepage.widget.key=${PROWLARR_API_KEY} qbittorrent: image: lscr.io/linuxserver/qbittorrent:libtorrentv1 container_name: qbittorrent @@ -108,6 +135,16 @@ services: - traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.replacement=$$1/ - traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.permanent=false #- com.centurylinklabs.watchtower.depends-on=/vpn + - homepage.group=Download + - homepage.name=qBittorrent + - homepage.icon=qbittorrent.png + - homepage.href=/qbittorrent + - homepage.description=Bittorrent client + - homepage.weight=5 + - homepage.widget.type=qbittorrent + - homepage.widget.url=http://vpn:8080 + - homepage.widget.username=admin + - homepage.widget.password=adminadmin vpn: image: thrnz/docker-wireguard-pia container_name: vpn @@ -164,21 +201,39 @@ services: - traefik.http.routers.jellyfin.tls=true - traefik.http.routers.jellyfin.tls.certresolver=myresolver - traefik.http.services.jellyfin.loadbalancer.server.port=8096 - heimdall: - image: lscr.io/linuxserver/heimdall - container_name: heimdall + - homepage.group=Media + - homepage.name=Jellyfin + - homepage.icon=jellyfin.png + - homepage.href=/jellyfin + - homepage.description=Media server + - homepage.weight=3 + - homepage.widget.type=jellyfin + - homepage.widget.url=http://jellyfin:8096/jellyfin + - homepage.widget.key=${JELLYFIN_API_KEY} + homepage: + image: ghcr.io/benphelps/homepage:latest + container_name: homepage environment: - - PUID=${USER_ID} - - PGID=${GROUP_ID} + - HOMEPAGE_VAR_TITLE=${HOMEPAGE_VAR_TITLE} + - HOMEPAGE_VAR_SEARCH_PROVIDER=${HOMEPAGE_VAR_SEARCH_PROVIDER} + - HOMEPAGE_VAR_HEADER_STYLE=${HOMEPAGE_VAR_HEADER_STYLE} + - HOMEPAGE_VAR_WEATHER_CITY=${HOMEPAGE_VAR_WEATHER_CITY} + - HOMEPAGE_VAR_WEATHER_LAT=${HOMEPAGE_VAR_WEATHER_LAT} + - HOMEPAGE_VAR_WEATHER_LONG=${HOMEPAGE_VAR_WEATHER_LONG} + - HOMEPAGE_VAR_WEATHER_TIME=${TIMEZONE} + - HOMEPAGE_VAR_WEATHER_UNIT=${HOMEPAGE_VAR_WEATHER_UNIT} volumes: - - ./heimdall:/config + - ./homepage:/app/config + - /var/run/docker.sock:/var/run/docker.sock:ro + - ${DATA_ROOT}:/data restart: always + command: [sh, -c, "cp -n /app/config/tpl/*.yaml /app/config && node server.js"] labels: - traefik.enable=true - - traefik.http.routers.heimdall.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/`)) - - traefik.http.routers.heimdall.tls=true - - traefik.http.routers.heimdall.tls.certresolver=myresolver - - traefik.http.services.heimdall.loadbalancer.server.port=80 + - traefik.http.routers.homepage.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/`)) + - traefik.http.routers.homepage.tls=true + - traefik.http.routers.homepage.tls.certresolver=myresolver + - traefik.http.services.homepage.loadbalancer.server.port=3000 watchtower: image: containrrr/watchtower container_name: watchtower diff --git a/heimdall/.gitkeep b/homepage/.gitkeep similarity index 100% rename from heimdall/.gitkeep rename to homepage/.gitkeep diff --git a/homepage/tpl/bookmarks.yaml b/homepage/tpl/bookmarks.yaml new file mode 100644 index 0000000..8edd78b --- /dev/null +++ b/homepage/tpl/bookmarks.yaml @@ -0,0 +1,8 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/en/configs/bookmarks + +- Credits: + - Docker-Compose-NAS: + - abbr: NAS + href: https://github.com/AdrienPoupa/docker-compose-nas diff --git a/homepage/tpl/docker.yaml b/homepage/tpl/docker.yaml new file mode 100644 index 0000000..25f33ba --- /dev/null +++ b/homepage/tpl/docker.yaml @@ -0,0 +1,6 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/en/configs/docker/ + +my-docker: + socket: /var/run/docker.sock diff --git a/homepage/tpl/settings.yaml b/homepage/tpl/settings.yaml new file mode 100644 index 0000000..e2b1cf4 --- /dev/null +++ b/homepage/tpl/settings.yaml @@ -0,0 +1,20 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/en/configs/settings + +title: {{HOMEPAGE_VAR_TITLE}} + +headerStyle: {{HOMEPAGE_VAR_HEADER_STYLE}} + +layout: + Media: + style: row + columns: 3 + Download: + style: row + columns: 2 + +quicklaunch: + searchDescriptions: true + hideInternetSearch: true + hideVisitURL: true diff --git a/homepage/tpl/widgets.yaml b/homepage/tpl/widgets.yaml new file mode 100644 index 0000000..23b3e7f --- /dev/null +++ b/homepage/tpl/widgets.yaml @@ -0,0 +1,27 @@ +--- +# For configuration options and examples, please see: +# https://gethomepage.dev/en/configs/widgets + +- resources: + cpu: true + memory: true + disk: + - / + - /data + +- search: + provider: {{HOMEPAGE_VAR_SEARCH_PROVIDER}} + target: _blank + +- openmeteo: + label: {{HOMEPAGE_VAR_WEATHER_CITY}} + latitude: {{HOMEPAGE_VAR_WEATHER_LAT}} + longitude: {{HOMEPAGE_VAR_WEATHER_LONG}} + timezone: {{HOMEPAGE_VAR_WEATHER_TIME}} + units: {{HOMEPAGE_VAR_WEATHER_UNIT}} + cache: 5 # Time in minutes to cache API responses, to stay within limits + +- datetime: + text_size: md + format: + timeStyle: short diff --git a/sabnzbd/docker-compose.yml b/sabnzbd/docker-compose.yml index 1a5a359..ec9d52b 100644 --- a/sabnzbd/docker-compose.yml +++ b/sabnzbd/docker-compose.yml @@ -17,4 +17,10 @@ services: - traefik.http.routers.sabnzbd.rule=(Host(`${HOSTNAME}`) && PathPrefix(`/sabnzbd`) || PathPrefix(`/sabnzbd`)) - traefik.http.routers.sabnzbd.tls=true - traefik.http.routers.sabnzbd.tls.certresolver=myresolver - - traefik.http.services.sabnzbd.loadbalancer.server.port=8080 \ No newline at end of file + - traefik.http.services.sabnzbd.loadbalancer.server.port=8080 + - homepage.group=Media + - homepage.name=Sabnzbd + - homepage.icon=sabnzbd.png + - homepage.href=/sabnzbd + - homepage.description=Usenet + - homepage.weight=6 diff --git a/update-config.sh b/update-config.sh index cb575ef..5fae86e 100755 --- a/update-config.sh +++ b/update-config.sh @@ -8,6 +8,7 @@ do sleep 5 done sed -i.bak "s/<\/UrlBase>/\/radarr<\/UrlBase>/" ./radarr/config.xml && rm ./radarr/config.xml.bak +sed -i.bak 's/^RADARR_API_KEY=.*/RADARR_API_KEY='"$(sed -n 's/.*\(.*\)<\/ApiKey>.*/\1/p' ./radarr/config.xml)"'/' .env && rm .env.bak echo "Updating Sonarr configuration..." until [ -f ./sonarr/config.xml ] @@ -15,6 +16,7 @@ do sleep 5 done sed -i.bak "s/<\/UrlBase>/\/sonarr<\/UrlBase>/" ./sonarr/config.xml && rm ./sonarr/config.xml.bak +sed -i.bak 's/^SONARR_API_KEY=.*/SONARR_API_KEY='"$(sed -n 's/.*\(.*\)<\/ApiKey>.*/\1/p' ./sonarr/config.xml)"'/' .env && rm .env.bak echo "Updating Prowlarr configuration..." until [ -f ./prowlarr/config.xml ] @@ -22,6 +24,7 @@ do sleep 5 done sed -i.bak "s/<\/UrlBase>/\/prowlarr<\/UrlBase>/" ./prowlarr/config.xml && rm ./prowlarr/config.xml.bak +sed -i.bak 's/^PROWLARR_API_KEY=.*/PROWLARR_API_KEY='"$(sed -n 's/.*\(.*\)<\/ApiKey>.*/\1/p' ./prowlarr/config.xml)"'/' .env && rm .env.bak echo "Updating Jellyfin configuration..." until [ -f ./jellyfin/network.xml ]