Skip to content

Instantly share code, notes, and snippets.

@dedworks
Last active July 2, 2026 20:03
Show Gist options
  • Select an option

  • Save dedworks/2c3def2ffde3dafd8494860b78b8b70b to your computer and use it in GitHub Desktop.

Select an option

Save dedworks/2c3def2ffde3dafd8494860b78b8b70b to your computer and use it in GitHub Desktop.
gluetun-mullvad-rotate: Automate randomly choosing a Mullvad VPN server based on SERVER_CITIES variable in docker_compose.yml
#!/bin/bash
# Depends: jq (https://stedolan.github.io/jq/)
default_compose="/docker/compose/gluetun/docker-compose.yml"
compose="${1:-$default_compose}"
relays_url="https://api.mullvad.net/www/relays/all/"
fail() { printf "%s: %s\n" "$0" "$*"; exit 1; }
read_var() {
declare -g $1="$(sed -n '/^\s*- '$1'=/{s///;p}' "$compose")"
[[ -z "${!1}" ]] && fail "Couldn't read '$1' value from '$compose'."
}
extract_hostnames() {
jq -r \
--arg cities "$SERVER_CITIES" \
--arg vpntype "$VPN_TYPE" \
'
. as $mullvad
| $cities | split(",")
| [ .[] | sub(" (?<state>[A-Z][A-Z]$)"; ", " + .state) ]
| . as $cities
| $mullvad | .[]
| select(.active==true)
| select(.city_name as $c | any($cities[]; . == $c))
| select(.type == $vpntype)
| .hostname
'
}
type jq &>/dev/null ||
fail "jq not installed or not in PATH (https://stedolan.github.io/jq/)"
read_var VPN_TYPE
read_var SERVER_CITIES
read_var SERVER_HOSTNAMES
servers=$(curl -s "$relays_url" | extract_hostnames)
servers=$(grep -v "^$SERVER_HOSTNAMES$" <<<$servers) # Don't pick current server
[[ -z "$servers" ]] &&
fail "No candidate servers returned for cities '$SERVER_CITIES'."
server="$(shuf -n1 <<< "$servers")"
printf "New server: %s\tOld server: %s\n" "$server" "$SERVER_HOSTNAMES"
sed -i~ "s/^\(\s*- SERVER_HOSTNAMES\)=.*/\1=$server/" "$compose" &&
rm -f "$compose~"
docker-compose --file "$compose" pull
docker-compose --file "$compose" up -d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment