From 3db855ba2d08314b242c76bf3e668d136e0ebd77 Mon Sep 17 00:00:00 2001 From: Josh Stark <4211747+JoshStark@users.noreply.github.com> Date: Sun, 22 Feb 2026 09:34:53 +0000 Subject: [PATCH 1/9] Update maintainer label in Dockerfile Signed-off-by: Josh Stark <4211747+JoshStark@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index aa661704..4b7437f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM scratch -LABEL maintainer="username" +LABEL maintainer="Stark" # copy local files COPY root/ / From a16867844d40c8fa4709b2219c4f0bc04ef8bcbb Mon Sep 17 00:00:00 2001 From: Josh Stark Date: Sun, 22 Feb 2026 10:05:30 +0000 Subject: [PATCH 2/9] Added mullvad init script --- .github/workflows/BuildImage.yml | 4 +- Dockerfile.complex | 33 ---- .../run | 30 ---- .../init-mod-imagename-modname-add-package/up | 1 - .../init-mod-imagename-modname-install/run | 8 - .../init-mod-imagename-modname-install/up | 1 - .../dependencies.d/init-mods | 0 .../run} | 0 .../type | 0 .../init-mod-wireguard-mullvad-add-package/up | 1 + .../dependencies.d/init-mods-package-install} | 0 .../init-mod-wireguard-mullvad-install/run | 154 ++++++++++++++++++ .../type | 0 .../init-mod-wireguard-mullvad-install/up | 1 + .../init-mod-wireguard-mullvad-install} | 0 .../init-mod-wireguard-mullvad-add-package} | 0 .../s6-rc.d/svc-mod-imagename-modname/run | 7 - .../s6-rc.d/svc-mod-imagename-modname/type | 1 - ...=> init-mod-wireguard-mullvad-add-package} | 0 ...all => init-mod-wireguard-mullvad-install} | 0 ...name-modname => svc-mod-wireguard-mullvad} | 0 21 files changed, 158 insertions(+), 83 deletions(-) delete mode 100644 Dockerfile.complex delete mode 100755 root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/run delete mode 100644 root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/up delete mode 100755 root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-install/run delete mode 100644 root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-install/up rename root/etc/s6-overlay/s6-rc.d/{init-mod-imagename-modname-add-package => init-mod-wireguard-mullvad-add-package}/dependencies.d/init-mods (100%) rename root/etc/s6-overlay/s6-rc.d/{init-mod-imagename-modname-install/dependencies.d/init-mods-package-install => init-mod-wireguard-mullvad-add-package/run} (100%) mode change 100644 => 100755 rename root/etc/s6-overlay/s6-rc.d/{init-mod-imagename-modname-add-package => init-mod-wireguard-mullvad-add-package}/type (100%) create mode 100644 root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/up rename root/etc/s6-overlay/s6-rc.d/{init-mods-end/dependencies.d/init-mod-imagename-modname-install => init-mod-wireguard-mullvad-install/dependencies.d/init-mods-package-install} (100%) create mode 100755 root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/run rename root/etc/s6-overlay/s6-rc.d/{init-mod-imagename-modname-install => init-mod-wireguard-mullvad-install}/type (100%) create mode 100644 root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/up rename root/etc/s6-overlay/s6-rc.d/{init-mods-package-install/dependencies.d/init-mod-imagename-modname-add-package => init-mods-end/dependencies.d/init-mod-wireguard-mullvad-install} (100%) rename root/etc/s6-overlay/s6-rc.d/{svc-mod-imagename-modname/dependencies.d/init-services => init-mods-package-install/dependencies.d/init-mod-wireguard-mullvad-add-package} (100%) delete mode 100755 root/etc/s6-overlay/s6-rc.d/svc-mod-imagename-modname/run delete mode 100644 root/etc/s6-overlay/s6-rc.d/svc-mod-imagename-modname/type rename root/etc/s6-overlay/s6-rc.d/user/contents.d/{init-mod-imagename-modname-add-package => init-mod-wireguard-mullvad-add-package} (100%) rename root/etc/s6-overlay/s6-rc.d/user/contents.d/{init-mod-imagename-modname-install => init-mod-wireguard-mullvad-install} (100%) rename root/etc/s6-overlay/s6-rc.d/user/contents.d/{svc-mod-imagename-modname => svc-mod-wireguard-mullvad} (100%) diff --git a/.github/workflows/BuildImage.yml b/.github/workflows/BuildImage.yml index 4715e563..bd8fab40 100644 --- a/.github/workflows/BuildImage.yml +++ b/.github/workflows/BuildImage.yml @@ -12,8 +12,8 @@ on: env: GITHUB_REPO: "linuxserver/docker-mods" #don't modify ENDPOINT: "linuxserver/mods" #don't modify - BASEIMAGE: "replace_baseimage" #replace - MODNAME: "replace_modname" #replace + BASEIMAGE: "scratch" #replace + MODNAME: "wireguard-mullvad" #replace MOD_VERSION: ${{ inputs.mod_version }} #don't modify MULTI_ARCH: "true" #set to false if not needed diff --git a/Dockerfile.complex b/Dockerfile.complex deleted file mode 100644 index 3ed07b0d..00000000 --- a/Dockerfile.complex +++ /dev/null @@ -1,33 +0,0 @@ -# syntax=docker/dockerfile:1 - -## Buildstage ## -FROM ghcr.io/linuxserver/baseimage-alpine:3.20 AS buildstage - -RUN \ - echo "**** install packages ****" && \ - apk add --no-cache \ - curl && \ - echo "**** grab rclone ****" && \ - mkdir -p /root-layer && \ - if [ $(uname -m) = "x86_64" ]; then \ - echo "Downloading x86_64 tarball" && \ - curl -o \ - /root-layer/rclone.deb -L \ - "https://downloads.rclone.org/v1.47.0/rclone-v1.47.0-linux-amd64.deb"; \ - elif [ $(uname -m) = "aarch64" ]; then \ - echo "Downloading aarch64 tarball" && \ - curl -o \ - /root-layer/rclone.deb -L \ - "https://downloads.rclone.org/v1.47.0/rclone-v1.47.0-linux-arm64.deb"; \ - fi && \ - -# copy local files -COPY root/ /root-layer/ - -## Single layer deployed image ## -FROM scratch - -LABEL maintainer="username" - -# Add files from buildstage -COPY --from=buildstage /root-layer/ / diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/run b/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/run deleted file mode 100755 index 063b5701..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/run +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/with-contenv bash - -# This is the init file used for adding os or pip packages to install lists. -# It takes advantage of the built-in init-mods-package-install init script that comes with the baseimages. -# If using this, we need to make sure we set this init as a dependency of init-mods-package-install so this one runs first - -if ! command -v apprise; then - echo "**** Adding apprise and its deps to package install lists ****" - echo "apprise" >> /mod-pip-packages-to-install.list - ## Ubuntu - if [ -f /usr/bin/apt ]; then - echo "\ - python3 \ - python3-pip \ - runc" >> /mod-repo-packages-to-install.list - fi - # Alpine - if [ -f /sbin/apk ]; then - echo "\ - cargo \ - libffi-dev \ - openssl-dev \ - python3 \ - python3-dev \ - python3 \ - py3-pip" >> /mod-repo-packages-to-install.list - fi -else - echo "**** apprise already installed, skipping ****" -fi diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/up b/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/up deleted file mode 100644 index 6414139f..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/run \ No newline at end of file diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-install/run b/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-install/run deleted file mode 100755 index 59a4b77f..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-install/run +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/with-contenv bash - -# This is an install script that is designed to run after init-mods-package-install -# so it can take advantage of packages installed -# init-mods-end depends on this script so that later init and services wait until this script exits - -echo "**** Setting up apprise ****" -apprise blah blah diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-install/up b/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-install/up deleted file mode 100644 index 03d298d2..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-install/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-install/run \ No newline at end of file diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/dependencies.d/init-mods b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/dependencies.d/init-mods similarity index 100% rename from root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/dependencies.d/init-mods rename to root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/dependencies.d/init-mods diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-install/dependencies.d/init-mods-package-install b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/run old mode 100644 new mode 100755 similarity index 100% rename from root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-install/dependencies.d/init-mods-package-install rename to root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/run diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/type b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/type similarity index 100% rename from root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/type rename to root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/type diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/up b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/up new file mode 100644 index 00000000..564b80bc --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/run \ No newline at end of file diff --git a/root/etc/s6-overlay/s6-rc.d/init-mods-end/dependencies.d/init-mod-imagename-modname-install b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/dependencies.d/init-mods-package-install similarity index 100% rename from root/etc/s6-overlay/s6-rc.d/init-mods-end/dependencies.d/init-mod-imagename-modname-install rename to root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/dependencies.d/init-mods-package-install diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/run b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/run new file mode 100755 index 00000000..82fdb85c --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/run @@ -0,0 +1,154 @@ +#!/usr/bin/with-contenv bash + +fail() { + + echo "[mullvad_setup] Error: $1" >&2 + exit 1 +} + +skip() { + + echo "[mullvad_setup] $1" + exit 0 +} + +config_file_base="/opt/mullvad" +target_link="/config/wg_confs/wg0.conf" + +# Verify required environment variables are present. This script cannot run without them. +[[ -z "${MULLVAD_ACCOUNT}" ]] && fail "MULLVAD_ACCOUNT environment variable is not set" +[[ -z "${MULLVAD_PRIVATE_KEY}" ]] && fail "MULLVAD_PRIVATE_KEY environment variable is not set" +[[ -z "${MULLVAD_LOCATION}" ]] && fail "MULLVAD_LOCATION environment variable is not set" + +# Set up the configuration output directory. Create it if it doesn't exist. +[[ -d "${config_file_base}" ]] || mkdir -p "${config_file_base}" + +echo "[mullvad_setup] Cleaning config base..." +rm -f "${config_file_base}/"* + +echo "[mullvad_setup] Removing old symlink if it exists..." +rm -f "$target_link" + +# MULLVAD_LOCATION is a dynamic-value variable. +# It may may contain a value fo either: +# - a region (e.g. 'gb') +# - a city (e.g. 'gb-lon') +# - or specific nodes (e.g. 'gb-lon-wg-001,gb-lon-wg-002'). +if [[ "$MULLVAD_LOCATION" =~ ^[a-z]{2}$ ]]; then + + echo "[mullvad_setup] Location filter set to region '$MULLVAD_LOCATION'." + jq_filter=".countries[] | select(.code == \"$MULLVAD_LOCATION\") | .cities[] | .relays[]" + +elif [[ "$MULLVAD_LOCATION" =~ ^[a-z]{2}-[a-z]{3}$ ]]; then + + echo "[mullvad_setup] Location filter set to city '$MULLVAD_LOCATION'." + country="${MULLVAD_LOCATION%%-*}" + city="${MULLVAD_LOCATION##*-}" + + jq_filter=".countries[] | select(.code == \"$country\") | .cities[] | select(.code == \"$city\") | .relays[]" + +elif [[ "$MULLVAD_LOCATION" =~ ^([a-z]{2}-[a-z]{3}-wg-[0-9]{3},?)+$ ]]; then + + echo "[mullvad_setup] Location filter set to nodes '$MULLVAD_LOCATION'." + jq_list=$(echo "$MULLVAD_LOCATION" | sed 's/,/", "/g' | sed 's/^/"/; s/$/"/') + jq_filter=".countries[] | .cities[] | .relays[] | select(.hostname | IN($jq_list))" + +else + fail "Invalid MULLVAD_LOCATION format. Expected formats: 'gb', 'gb-lon', or 'gb-lon-wg-001,nl-ams-wg-001'." +fi + +# The client's tunnel address needs to be obtained. +# A call to the Mullvad API is made using the account number and the public key derived from the private key. +# The API returns a comma-delimited string with the (ipv4) assigned tunnel address as the first value. +echo "[mullvad_setup] Fetching tunnel address from Mullvad API..." +wg_pubkey="$(wg pubkey <<<"$MULLVAD_PRIVATE_KEY")" +tunnel_address_response="$(curl -sSL https://api.mullvad.net/wg -d account="$MULLVAD_ACCOUNT" --data-urlencode pubkey="$wg_pubkey")" || fail "Could not talk to Mullvad API." +tunnel_address="${tunnel_address_response%%,*}" + +# Now the specified node needs to be selected. +# The API is called again to get the list of all available nodes, which is then filtered down to matching nodes. +# A random node is selected from the filtered list. If only one node matches, that one is selected. +echo "[mullvad_setup] Fetching relay information from Mullvad API..." +relay_response="$(curl -LsS https://api.mullvad.net/public/relays/wireguard/v1/)" || fail "Unable to connect to Mullvad API." +relay_fields=$(echo "$relay_response" | jq -r "$jq_filter | [.hostname, .public_key, .ipv4_addr_in] | @tsv" | sort -R | head) || fail "Failed to parse Mullvad API response. Check your MULLVAD_LOCATION value." + +[[ -z "$relay_fields" ]] && fail "No relays found matching the specified location filter '$MULLVAD_LOCATION'." + +# Optionally, if the user defines a network which needs routing access to services running through wireguared, +# a PostUp and PreDown hook is created to add and remove the necessary routing rules when the tunnel is brought up and down. +if [[ -n "$LAN_NETWORKS" || -n "${ALLOW_ATTACHED_NETWORKS}" ]]; then + + ip_route_add="" + ip_route_delete="" + chain_route_add="" + chain_route_delete="" + + if [[ -n "$LAN_NETWORKS" ]]; then + + IFS=',' read -ra lan_network_array <<< "${LAN_NETWORKS}" + DROUTE=$(ip route | grep default | awk '{print $3}'); + + for lan_network_item in $(echo "$LAN_NETWORKS" | tr "," " "); do + + echo "[mullvad_setup] Adding iptables rule for LAN network $lan_network_item" + + lan_network_item=$(echo "${lan_network_item}" | sed -e 's~^[ \t]*~~;s~[ \t]*$~~') + + ip_route_add+="ip route add ${lan_network_item} via ${DROUTE}; " + ip_route_delete+="ip route delete ${lan_network_item}; " + chain_route_delete+="iptables -D OUTPUT -d ${lan_network_item} -j ACCEPT; " + + if [[ "${lan_network_item}" = "${lan_network_array[0]}" ]]; then + chain_route_add+="iptables -I OUTPUT -d ${lan_network_item} -j ACCEPT; " + else + chain_route_add+="iptables -A OUTPUT -d ${lan_network_item} -j ACCEPT; " + fi + done + + fi + + # If set, the iptables rules will be amended to allow inbound traffic from services on the same attached network(s). + # This allows other containers on the same stack or global network to access services running through the WireGuard tunnel. + if [[ "${ALLOW_ATTACHED_NETWORKS:-}" == "true" ]]; then + + attached_networks=$(ip route | grep -e "link" | awk '{print $1}'); + for attached_network in $attached_networks; do + + echo "[mullvad_setup] Adding iptables rule for attached docker network $attached_network" + chain_route_add+="iptables -A OUTPUT -d $attached_network -j ACCEPT; " + chain_route_delete+="iptables -D OUTPUT -d $attached_network -j ACCEPT; " + + done + fi + + post_up_hook="${ip_route_add}${chain_route_add}iptables -A OUTPUT ! -o %i -m mark ! --mark \$(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT;" + pre_down_hook="${ip_route_delete}iptables -D OUTPUT ! -o %i -m mark ! --mark \$(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT; $chain_route_delete" +fi + +IFS=$'\t' read -r WG_HOST WG_PUBKEY WG_IPADDR <<< "$relay_fields" +echo "[mullvad_setup] Generating config for chosen node $WG_HOST..." + +# The selected node is used to generate the WireGuard configuration file. +configuration_file="$config_file_base/$WG_HOST.conf" +umask 077 +rm -f "$configuration_file.tmp" +cat > "$configuration_file.tmp" <<-_EOF +[Interface] +PrivateKey = $MULLVAD_PRIVATE_KEY +Address = $tunnel_address +DNS = ${MULLVAD_DNS:-"10.64.0.1"} +PostUp = $post_up_hook +PreDown = $pre_down_hook + +[Peer] +PublicKey = $WG_PUBKEY +Endpoint = $WG_IPADDR:51820 +AllowedIPs = 0.0.0.0/0 +_EOF + +mv "$configuration_file.tmp" "$configuration_file" + +# Finally, a symlink is created to the generated configuration file. +# Use of a link allows the container owner to check which node was selected. +echo "[mullvad_setup] Symlink created $target_link -> $configuration_file" +ln -sf "$configuration_file" "$target_link" \ No newline at end of file diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-install/type b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/type similarity index 100% rename from root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-install/type rename to root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/type diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/up b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/up new file mode 100644 index 00000000..8cb8e3ce --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/run \ No newline at end of file diff --git a/root/etc/s6-overlay/s6-rc.d/init-mods-package-install/dependencies.d/init-mod-imagename-modname-add-package b/root/etc/s6-overlay/s6-rc.d/init-mods-end/dependencies.d/init-mod-wireguard-mullvad-install similarity index 100% rename from root/etc/s6-overlay/s6-rc.d/init-mods-package-install/dependencies.d/init-mod-imagename-modname-add-package rename to root/etc/s6-overlay/s6-rc.d/init-mods-end/dependencies.d/init-mod-wireguard-mullvad-install diff --git a/root/etc/s6-overlay/s6-rc.d/svc-mod-imagename-modname/dependencies.d/init-services b/root/etc/s6-overlay/s6-rc.d/init-mods-package-install/dependencies.d/init-mod-wireguard-mullvad-add-package similarity index 100% rename from root/etc/s6-overlay/s6-rc.d/svc-mod-imagename-modname/dependencies.d/init-services rename to root/etc/s6-overlay/s6-rc.d/init-mods-package-install/dependencies.d/init-mod-wireguard-mullvad-add-package diff --git a/root/etc/s6-overlay/s6-rc.d/svc-mod-imagename-modname/run b/root/etc/s6-overlay/s6-rc.d/svc-mod-imagename-modname/run deleted file mode 100755 index 02ffe39a..00000000 --- a/root/etc/s6-overlay/s6-rc.d/svc-mod-imagename-modname/run +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/with-contenv bash - -# This is an example service that would run for the mod -# It depends on init-services, the baseimage hook for start of all longrun services - -exec \ - s6-setuidgid abc run my app diff --git a/root/etc/s6-overlay/s6-rc.d/svc-mod-imagename-modname/type b/root/etc/s6-overlay/s6-rc.d/svc-mod-imagename-modname/type deleted file mode 100644 index 1780f9f4..00000000 --- a/root/etc/s6-overlay/s6-rc.d/svc-mod-imagename-modname/type +++ /dev/null @@ -1 +0,0 @@ -longrun \ No newline at end of file diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-imagename-modname-add-package b/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-wireguard-mullvad-add-package similarity index 100% rename from root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-imagename-modname-add-package rename to root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-wireguard-mullvad-add-package diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-imagename-modname-install b/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-wireguard-mullvad-install similarity index 100% rename from root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-imagename-modname-install rename to root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-wireguard-mullvad-install diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-mod-imagename-modname b/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-mod-wireguard-mullvad similarity index 100% rename from root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-mod-imagename-modname rename to root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-mod-wireguard-mullvad From c9a94608deed59ba867206b14bfb1dbae5aaa22c Mon Sep 17 00:00:00 2001 From: Josh Stark Date: Sun, 22 Feb 2026 10:25:29 +0000 Subject: [PATCH 3/9] Updated README --- README.md | 50 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 17006683..a808fc69 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,41 @@ -# Rsync - Docker mod for openssh-server +# Mullvad - Docker mod for Wireguard -This mod adds rsync to openssh-server, to be installed/updated during container start. +This mod adds a script which runs on service startup to communicate with the Mullvad APIs. This obtains the relevant config for a chosen Wireguard node which the container can tunnel through when running in CLIENT mode. -In openssh-server docker arguments, set an environment variable `DOCKER_MODS=linuxserver/mods:openssh-server-rsync` +## Parameters -If adding multiple mods, enter them in an array separated by `|`, such as `DOCKER_MODS=linuxserver/mods:openssh-server-rsync|linuxserver/mods:openssh-server-mod2` +### `-e MULLVAD_ACCOUNT` (required) -# Mod creation instructions +Your Mullvad account number. This is used to make an API call to obtain your tunnel IP address. -* Fork the repo, create a new branch based on the branch `template`. -* Edit the `Dockerfile` for the mod. `Dockerfile.complex` is only an example and included for reference; it should be deleted when done. -* Inspect the `root` folder contents. Edit, add and remove as necessary. -* After all init scripts and services are created, run `find ./ -path "./.git" -prune -o \( -name "run" -o -name "finish" -o -name "check" \) -not -perm -u=x,g=x,o=x -print -exec chmod +x {} +` to fix permissions. -* Edit this readme with pertinent info, delete these instructions. -* Finally edit the `.github/workflows/BuildImage.yml`. Customize the vars for `BASEIMAGE` and `MODNAME`. Set the versioning logic and `MULTI_ARCH` if needed. -* Ask the team to create a new branch named `-`. Baseimage should be the name of the image the mod will be applied to. The new branch will be based on the `template` branch. -* Submit PR against the branch created by the team. +### `-e MULLVAD_PRIVATE_KEY` (required) +The private key of a device on your Mullvad account. You will need to [create a device](https://mullvad.net/en/account/devices) under your account, then use the generated private key for this variable's value. If you have an existing device, you will need to get the private key out of a previously generated config file. -## Tips and tricks +### `-e MULLVAD_LOCATION` (required) -* Some images have helpers built in, these images are currently: - * [Openvscode-server](https://github.com/linuxserver/docker-openvscode-server/pull/10/files) - * [Code-server](https://github.com/linuxserver/docker-code-server/pull/95) +Your spefied location you wish to tunnel through. This variable supports three different formats which effect which node you tunnel through: + +| Type | Example | Result | +| :-- | :-- | :-- | +| Region | gb | A node will be randomly picked from all locations within Great Britain | +| City | gb-lon | A node will be randomly picked from one of the locations in London | +| Node(s) | gb-lon-wg-001,gb-lon-wg-002 | Allows for a specific node to be selected, or from a pool of hand-picked nodes. This option is not region or city locked, so you may pick nodes from any global location | + +**Note**: The API this script uses does not distinguish between owned or rentded nodes. If that is something you care about, you may need to look at the [Mullvad server list](https://mullvad.net/en/servers) and pick some nodes you wish to tunnel through. + +### `-e MULLVAD_DNS` (default: 10.64.0.1) + +An optional variable which lets you override the default DNS used for tunnelled connections. If not set, this default's to Mullvad's DNS. + +### `-e LAN_NETWORKS` + +If you run web services through a Wireguard container (via `network_mode: service`) you will likely lose access to their web UIs due to the container's default routing rules. Use this variable to inform the container to apply a rule which allows inbound traffic from one or more LAN networks. + +E.g. `-e LAN_NETWORKS=192.168.0.0/24,10.20.0.0/16`. + +Only use this if you require access to a service's web UI. + +### `-e ALLOW_ATTACHED_NETWORKS` (default: false) + +If you have a service running within the same stack as Wireguard but not routed through it, you can't be default contact another service routed through the Wireguard container. When this parameter is set to `true`, the script will apply a rule which allows inbound traffic from services on any networks which have been attached to the Wireguard container. \ No newline at end of file From a15e4cea6004f30e3f96dcc70c4169504b646471 Mon Sep 17 00:00:00 2001 From: Josh Stark Date: Sun, 22 Feb 2026 10:41:11 +0000 Subject: [PATCH 4/9] Updated README --- README.md | 69 ++++++++++++++++++- .../init-mod-wireguard-mullvad-install/run | 3 + 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a808fc69..8577c14a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Your spefied location you wish to tunnel through. This variable supports three d | City | gb-lon | A node will be randomly picked from one of the locations in London | | Node(s) | gb-lon-wg-001,gb-lon-wg-002 | Allows for a specific node to be selected, or from a pool of hand-picked nodes. This option is not region or city locked, so you may pick nodes from any global location | -**Note**: The API this script uses does not distinguish between owned or rentded nodes. If that is something you care about, you may need to look at the [Mullvad server list](https://mullvad.net/en/servers) and pick some nodes you wish to tunnel through. +**Note**: The API this script uses does not distinguish between owned or rented nodes. If that is something you care about, you may need to look at the [Mullvad server list](https://mullvad.net/en/servers) and pick some nodes you wish to tunnel through. ### `-e MULLVAD_DNS` (default: 10.64.0.1) @@ -38,4 +38,69 @@ Only use this if you require access to a service's web UI. ### `-e ALLOW_ATTACHED_NETWORKS` (default: false) -If you have a service running within the same stack as Wireguard but not routed through it, you can't be default contact another service routed through the Wireguard container. When this parameter is set to `true`, the script will apply a rule which allows inbound traffic from services on any networks which have been attached to the Wireguard container. \ No newline at end of file +If you have a service running within the same stack as Wireguard but not routed through it, you can't be default contact another service routed through the Wireguard container. When this parameter is set to `true`, the script will apply a rule which allows inbound traffic from services on any networks which have been attached to the Wireguard container. + +### Example `compose.yml` + +A basic example showing a wireguard container in client mode using this mod, with Sonarr routed through it and Seerr which is on the same shared stack network but not routed through wireguard. The outcome of this is that Sonarr will have its WAN requests routed through Wireguard, but Seerr will not. Seerr is able to communicate with Sonarr via `http://wireguard_client:8989` because it is on the same default stack network and `ALLOW_ATTACHED_NETWORKS=true`. Users on the LAN network `192.168.0.0/24` may also access Sonarr's web UI via the docker host's IP. + +```yaml +services: + + wireguard_client: + image: lscr.io/linuxserver/wireguard + cap_add: + - NET_ADMIN + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/London + - MULLVAD_PRIVATE_KEY=$MULLVAD_PRIVATE_KEY + - MULLVAD_ACCOUNT=$MULLVAD_ACCOUNT + - MULLVAD_LOCATION=gb-lon + - LAN_NETWORKS=192.168.0.0/24 + - ALLOW_ATTACHED_NETWORKS=true + volumes: + - /opt/appdata/wireguard/config:/config + - /lib/modules:/lib/modules + ports: + - "8989:8989" + healthcheck: + test: | + bash -c 'curl -fs https://am.i.mullvad.net/connected | grep -q "You are connected to Mullvad" || exit 1' + interval: 1m + timeout: 10s + retries: 3 + start_period: 10s + sysctls: + - net.ipv4.conf.all.src_valid_mark=1 + - net.ipv6.conf.all.disable_ipv6=1 + - net.ipv6.conf.default.disable_ipv6=1 + restart: unless-stopped + + sonarr: + image: lscr.io/linuxserver/sonarr:latest + network_mode: service:wireguard_client + depends_on: + - wireguard_client + restart: unless-stopped + environment: + PUID: '1000' + PGID: '1004' + TZ: Europe/London + volumes: + - /dev/rtc:/dev/rtc:ro + - /opt/appdata/sonarr:/config + + seerr: + image: ghcr.io/seerr-team/seerr:latest + restart: unless-stopped + init: true + user: "1000" + depends_on: + - wireguard_client + environment: + - TZ=Europe/London + volumes: + - /opt/appdata/seerr:/app/config +``` diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/run b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/run index 82fdb85c..615eb7cd 100755 --- a/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/run +++ b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/run @@ -15,6 +15,9 @@ skip() { config_file_base="/opt/mullvad" target_link="/config/wg_confs/wg0.conf" +# Ensure the service is not running in SERVER mode. +[[ -n "${PEERS}" ]] && skip "PEERS environment variable is set. This setup is intended for CLIENT mode only. Skipping Mullvad configuration." + # Verify required environment variables are present. This script cannot run without them. [[ -z "${MULLVAD_ACCOUNT}" ]] && fail "MULLVAD_ACCOUNT environment variable is not set" [[ -z "${MULLVAD_PRIVATE_KEY}" ]] && fail "MULLVAD_PRIVATE_KEY environment variable is not set" From e7c6265eb748dd3f9916ee9dde03ec6813f0339f Mon Sep 17 00:00:00 2001 From: Josh Stark Date: Sun, 22 Feb 2026 10:45:15 +0000 Subject: [PATCH 5/9] Updated README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8577c14a..91394bb4 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ services: - PUID=1000 - PGID=1000 - TZ=Europe/London + - DOCKER_MODS=linuxserver/mods:wireguard-mullvad - MULLVAD_PRIVATE_KEY=$MULLVAD_PRIVATE_KEY - MULLVAD_ACCOUNT=$MULLVAD_ACCOUNT - MULLVAD_LOCATION=gb-lon From 85f368e62fa4d777875dfd449239dfd54e4b787b Mon Sep 17 00:00:00 2001 From: Josh Stark Date: Sun, 22 Feb 2026 13:27:56 +0000 Subject: [PATCH 6/9] Simplified mod structure --- .../s6-rc.d/init-mod-wireguard-mullvad-add-package/run | 0 .../s6-rc.d/init-mod-wireguard-mullvad-add-package/type | 1 - .../s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/up | 1 - .../dependencies.d/init-mods | 0 .../dependencies.d/init-mods-package-install | 0 .../dependencies.d/init-mod-wireguard-mullvad-add-package | 0 .../user/contents.d/init-mod-wireguard-mullvad-add-package | 0 .../s6-overlay/s6-rc.d/user/contents.d/svc-mod-wireguard-mullvad | 0 8 files changed, 2 deletions(-) delete mode 100755 root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/run delete mode 100644 root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/type delete mode 100644 root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/up rename root/etc/s6-overlay/s6-rc.d/{init-mod-wireguard-mullvad-add-package => init-mod-wireguard-mullvad-install}/dependencies.d/init-mods (100%) delete mode 100644 root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/dependencies.d/init-mods-package-install delete mode 100644 root/etc/s6-overlay/s6-rc.d/init-mods-package-install/dependencies.d/init-mod-wireguard-mullvad-add-package delete mode 100644 root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-wireguard-mullvad-add-package delete mode 100644 root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-mod-wireguard-mullvad diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/run b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/run deleted file mode 100755 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/type b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/type deleted file mode 100644 index 3d92b15f..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/type +++ /dev/null @@ -1 +0,0 @@ -oneshot \ No newline at end of file diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/up b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/up deleted file mode 100644 index 564b80bc..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/run \ No newline at end of file diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/dependencies.d/init-mods b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/dependencies.d/init-mods similarity index 100% rename from root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-add-package/dependencies.d/init-mods rename to root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/dependencies.d/init-mods diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/dependencies.d/init-mods-package-install b/root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/dependencies.d/init-mods-package-install deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-mods-package-install/dependencies.d/init-mod-wireguard-mullvad-add-package b/root/etc/s6-overlay/s6-rc.d/init-mods-package-install/dependencies.d/init-mod-wireguard-mullvad-add-package deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-wireguard-mullvad-add-package b/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-wireguard-mullvad-add-package deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-mod-wireguard-mullvad b/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-mod-wireguard-mullvad deleted file mode 100644 index e69de29b..00000000 From db789f6f066c3967a01756b8db7f9db7c282c855 Mon Sep 17 00:00:00 2001 From: Josh Stark Date: Sun, 22 Feb 2026 13:37:06 +0000 Subject: [PATCH 7/9] Fix BuildImage baseimage value --- .github/workflows/BuildImage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/BuildImage.yml b/.github/workflows/BuildImage.yml index bd8fab40..0040395f 100644 --- a/.github/workflows/BuildImage.yml +++ b/.github/workflows/BuildImage.yml @@ -12,8 +12,8 @@ on: env: GITHUB_REPO: "linuxserver/docker-mods" #don't modify ENDPOINT: "linuxserver/mods" #don't modify - BASEIMAGE: "scratch" #replace - MODNAME: "wireguard-mullvad" #replace + BASEIMAGE: "wireguard" #replace + MODNAME: "mullvad" #replace MOD_VERSION: ${{ inputs.mod_version }} #don't modify MULTI_ARCH: "true" #set to false if not needed From b0240f190eb0acd3054b9a24f3d88e8b445c92bc Mon Sep 17 00:00:00 2001 From: Josh Stark Date: Sun, 22 Feb 2026 13:53:04 +0000 Subject: [PATCH 8/9] Fix example in README --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 91394bb4..5bbc88b7 100644 --- a/README.md +++ b/README.md @@ -90,13 +90,11 @@ services: PGID: '1004' TZ: Europe/London volumes: - - /dev/rtc:/dev/rtc:ro - /opt/appdata/sonarr:/config seerr: image: ghcr.io/seerr-team/seerr:latest restart: unless-stopped - init: true user: "1000" depends_on: - wireguard_client From 3b8442779a1dd5da05c44625f612331398f13858 Mon Sep 17 00:00:00 2001 From: Josh Stark Date: Sun, 22 Feb 2026 14:18:59 +0000 Subject: [PATCH 9/9] Set MULTI_ARCH to false --- .github/workflows/BuildImage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/BuildImage.yml b/.github/workflows/BuildImage.yml index 0040395f..ce5cf6eb 100644 --- a/.github/workflows/BuildImage.yml +++ b/.github/workflows/BuildImage.yml @@ -15,7 +15,7 @@ env: BASEIMAGE: "wireguard" #replace MODNAME: "mullvad" #replace MOD_VERSION: ${{ inputs.mod_version }} #don't modify - MULTI_ARCH: "true" #set to false if not needed + MULTI_ARCH: "false" #set to false if not needed jobs: set-vars: