Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/BuildImage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ 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: "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:
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

FROM scratch

LABEL maintainer="username"
LABEL maintainer="Stark"

# copy local files
COPY root/ /
33 changes: 0 additions & 33 deletions Dockerfile.complex

This file was deleted.

114 changes: 97 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,105 @@
# 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 `<baseimagename>-<modname>`. 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 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)

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.

### 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
- DOCKER_MODS=linuxserver/mods:wireguard-mullvad
- 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:
- /opt/appdata/sonarr:/config

seerr:
image: ghcr.io/seerr-team/seerr:latest
restart: unless-stopped
user: "1000"
depends_on:
- wireguard_client
environment:
- TZ=Europe/London
volumes:
- /opt/appdata/seerr:/app/config
```

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

157 changes: 157 additions & 0 deletions root/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/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"

# 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"
[[ -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"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/init-mod-wireguard-mullvad-install/run
7 changes: 0 additions & 7 deletions root/etc/s6-overlay/s6-rc.d/svc-mod-imagename-modname/run

This file was deleted.

Loading