--- /dev/null
+#!/bin/bash
+set -e
+umask 077
+
+function fail()
+{
+ printf "error: $@" >&2
+ echo >&2
+ exit 1
+}
+
+any_warnings=0
+
+function warn()
+{
+ any_warnings=1
+ printf "warning: $@" >&2
+ echo >&2
+}
+
+function install_deps()
+{
+ if ! which wg >/dev/null; then
+ echo "wireguard not found, installing:"
+ sudo apt update
+ sudo apt install wireguard
+ fi
+}
+
+function check_cidr_addr()
+{
+ local addr="$1"
+ [[ "$addr" =~ ^(0|[1-9][0-9]*)'.'(0|[1-9][0-9]*)'.'(0|[1-9][0-9]*)'.'(0|[1-9][0-9]*)'/'(0|[1-9][0-9]*)$ ]] \
+ || fail "can't parse IPv4 CIDR address: %q" "$addr"
+ ((${BASH_REMATCH[1]} <= 255 &&
+ ${BASH_REMATCH[2]} <= 255 &&
+ ${BASH_REMATCH[3]} <= 255 &&
+ ${BASH_REMATCH[4]} <= 255 &&
+ ${BASH_REMATCH[5]} <= 32)) \
+ || fail "invalid IPv4 CIDR address: %s" "$addr"
+}
+
+function init_server()
+{
+ local server_iface_name="$1" listen_port="$2" server_iface_addr="$3"
+ check_cidr_addr "$server_iface_addr"
+ set -o noclobber
+ local private_key="$(wg genkey)"
+ local config_contents="[Interface]
+PrivateKey = $private_key
+ListenPort = $listen_port
+Address = $server_iface_addr
+SaveConfig = true
+"
+ echo "$config_contents" > "/etc/wireguard/$server_iface_name.conf"
+ echo "Created config for $server_iface_name"
+ echo "You need to allow UDP port $listen_port through your firewall"
+}
+
+function add_client()
+{
+ local server_iface_name="$1" server_public_addr="$2" client_config="$3" client_iface_addr="$4"
+ check_cidr_addr "$client_iface_addr"
+ if [[ " $(wg show interfaces) " =~ " $server_iface_name " ]]; then
+ fail "You need to shutdown interface %s first:\nwg-quick down %s\nor:\nsystemctl stop wg-quick@%s" \
+ "$server_iface_name" "$server_iface_name" "$server_iface_name"
+ fi
+ local server_iface_conf="/etc/wireguard/$server_iface_name.conf"
+ local lines line key eq_value value section="" server_private_key=""
+ local server_iface_addr="" server_listen_port=""
+ mapfile -t lines < "$server_iface_conf"
+ for line in "${lines[@]}"; do
+ line="${line%%#*}" # remove comments
+ [[ "$line" =~ ^([^=]*)('='(.*))?$ ]] || fail "regex failed -- not supposed to happen"
+ key="${BASH_REMATCH[1]}"
+ eq_value="${BASH_REMATCH[2]}"
+ value="${BASH_REMATCH[3]}"
+ key="${key#"${key%%[![:space:]]*}"}" # remove leading whitespace
+ key="${key%"${key##*[![:space:]]}"}" # remove trailing whitespace
+ value="${value#"${value%%[![:space:]]*}"}" # remove leading whitespace
+ value="${value%"${value##*[![:space:]]}"}" # remove trailing whitespace
+ [[ "$key" == "" && "$eq_value" == "" ]] && continue
+ if [[ "$key" =~ ^'['(.+)']'$ && "$eq_value" == "" ]]; then
+ section="${BASH_REMATCH[1]}"
+ case "$section" in
+ 'Interface'|'Peer')
+ ;;
+ *)
+ warn "unknown config section %s" "$key"
+ ;;
+ esac
+ elif [[ "$section" == "Interface" ]]; then
+ case "$key" in
+ 'PrivateKey')
+ [[ "$value" == "" ]] && fail "empty [Interface] PrivateKey value"
+ server_private_key="$value"
+ ;;
+ 'Address')
+ [[ "$value" == "" ]] && fail "empty [Interface] Address value"
+ [[ "$server_iface_addr" != ""
+ || "$value" =~ [[:space:],] ]] && \
+ fail "multiple [Interface] Address values not supported"
+ server_iface_addr="$value"
+ check_cidr_addr "$server_iface_addr"
+ ;;
+ 'ListenPort')
+ [[ "$value" =~ ^[1-9][0-9]* ]] || \
+ fail "invalid [Interface] ListenPort value: %s" "$value"
+ server_listen_port="$value"
+ ;;
+ 'SaveConfig'|'FwMark'|'DNS'|'MTU')
+ ;;
+ 'Table'|'PreUp'|'PreDown'|'PostUp'|'PostDown')
+ ;;
+ *)
+ warn "unknown config key [Interface] %s" "$key"
+ ;;
+ esac
+ elif [[ "$section" == "Peer" ]]; then
+ case "$key" in
+ 'AllowedIPs'|'PublicKey'|'PresharedKey'|'Endpoint'|'PersistentKeepalive')
+ ;;
+ *)
+ warn "unknown config key [Peer] %s" "$key"
+ ;;
+ esac
+ fi
+ done
+ [[ "$server_iface_addr" != "" ]] || fail "missing [Interface] Address config key"
+ [[ "$server_private_key" != "" ]] || fail "missing [Interface] PrivateKey config key"
+ [[ "$server_listen_port" != "" ]] || fail "missing [Interface] ListenPort config key"
+ if ((any_warnings)); then
+ echo -n "Warnings generated, do you want to continue? [y/N]: " >&2
+ local cont
+ read -r cont
+ if [[ "$cont" != "y" ]]; then
+ exit 1
+ fi
+ fi
+ local client_private_key="$(wg genkey)"
+ local server_public_key="$(wg pubkey <<<"$server_private_key")"
+ local client_public_key="$(wg pubkey <<<"$client_private_key")"
+ local preshared_key="$(wg genpsk)"
+ local client_config_contents="[Interface]
+PrivateKey = $client_private_key
+Address = $client_iface_addr
+SaveConfig = true
+
+[Peer]
+PublicKey = $server_public_key
+PresharedKey = $preshared_key
+AllowedIPs = $server_iface_addr
+Endpoint = $server_public_addr:$server_listen_port
+PersistentKeepalive = 25
+"
+ set -o noclobber
+ echo "$client_config_contents" > "$client_config"
+ set +o noclobber
+ local server_config_new_peer="
+[Peer]
+PublicKey = $client_public_key
+PresharedKey = $preshared_key
+AllowedIPs = $client_iface_addr
+PersistentKeepalive = 25
+"
+ echo "$server_config_new_peer" >> "$server_iface_conf"
+ cat <<EOF
+Client added.
+Move $client_config to /etc/wireguard/<client-interface>.conf on the client,
+making sure that it is owned by root and has mode 600. Make sure it is NOT
+left lying around since it contains the private key for the client, as well
+as the preshared key.
+
+Once you did that, run on the server:
+wg-quick up $server_iface_name
+or:
+systemctl start wg-quick@$server_iface_name
+and run on the client:
+wg-quick up <client-interface>
+or:
+systemctl start wg-quick@<client-interface>
+EOF
+}
+
+case "$1" in
+ init-server)
+ install_deps
+ init_server "${@:2}"
+ ;;
+ add-client)
+ install_deps
+ add_client "${@:2}"
+ ;;
+ *)
+ cat >&2 <<EOF
+Usage: $0 init-server <server-iface-name> <listen-port> <server-iface-addr>
+ or: $0 add-client <server-iface-name> <server-public-addr> <client-config.conf> <client-iface-addr>
+
+init-server: create a new wireguard config for the server, writes to
+ '/etc/wireguard/<server-iface-name>.conf'
+
+add-client: add a client to the wireguard config for the server at
+ '/etc/wireguard/<server-iface-name>.conf'
+ Writes the generated client config to <client-config.conf>.
+ The client will connect to the server through
+ public IP or DNS address <server-public-addr>.
+EOF
+ exit 1
+ ;;
+esac