Luigit
repositories / bugabinga.net

bugabinga.net

personal infrastructure for bugabinga!

owned by admin

scripts/remote/apply-wireguard-mtu.sh

Raw
#!/usr/bin/env bash
set -euo pipefail
umask 077

[[ "$#" -eq 1 || ("$#" -eq 2 && "$2" = --check) ]] || {
  echo 'usage: apply-wireguard-mtu.sh MTU [--check]' >&2
  exit 2
}
mtu="$1"
[[ "$mtu" =~ ^[0-9]+$ && "$mtu" -ge 1280 && "$mtu" -le 1420 ]] || {
  echo 'invalid WireGuard MTU' >&2
  exit 2
}
check_only=false
[[ "${2:-}" = --check ]] && check_only=true
interface=wg0
config=/etc/wireguard/wg0.conf

fail() {
  printf '%s\n' "$*" >&2
  exit 1
}

if [[ "${WG_AUTO_MTU_TEST:-}" = 1 ]]; then
  config="${WG_AUTO_MTU_CONFIG:-$config}"
else
  [[ "$config" = /etc/wireguard/wg0.conf ]] || fail 'invalid WireGuard config path'
fi
[[ -f "$config" && ! -L "$config" ]] || fail 'invalid WireGuard config path'

validate_config() {
  awk '
    function clean(line) { sub(/[[:space:]]*[#;].*$/, "", line); return line }
    /^[[:space:]]*\[/ {
      section = tolower($0)
      in_interface = (section ~ /^[[:space:]]*\[interface\][[:space:]]*$/)
      if (in_interface) interfaces++
      next
    }
    in_interface {
      line = clean($0)
      lower = tolower(line)
      sub(/[[:space:]]+$/, "", lower)
      if (lower ~ /^[[:space:]]*saveconfig[[:space:]]*=/) {
        sub(/^[[:space:]]*saveconfig[[:space:]]*=[[:space:]]*/, "", lower)
        if (lower == "true") save_config = 1
      }
      if (lower ~ /^[[:space:]]*mtu[[:space:]]*=/) {
        mtus++
        sub(/^[[:space:]]*mtu[[:space:]]*=[[:space:]]*/, "", lower)
        line = lower
        if (line !~ /^[0-9]+[[:space:]]*$/) bad_mtu = 1
      }
    }
    END { exit !(interfaces == 1 && !save_config && mtus <= 1 && !bad_mtu) }
  ' "$config" || fail 'WireGuard config has invalid Interface, SaveConfig, or MTU directives'
}

link_info() {
  ip -o link show dev "$interface" || fail 'WireGuard interface is absent'
}

parse_link() {
  local link="$1"
  ifindex="$(awk -F: '{gsub(/^[[:space:]]+/, "", $1); print $1}' <<<"$link")"
  live_mtu="$(awk '{for (i = 1; i <= NF; i++) if ($i == "mtu") { print $(i + 1); exit }}' <<<"$link")"
  [[ "$ifindex" =~ ^[0-9]+$ && "$live_mtu" =~ ^[0-9]+$ && "$link" == *'<'*'UP'*'>'* ]] || fail 'WireGuard interface is down or malformed'
}

render_config() {
  awk -v mtu="$mtu" '
    tolower($0) ~ /^[[:space:]]*\[interface\][[:space:]]*$/ {
      print
      in_interface = 1
      next
    }
    /^[[:space:]]*\[/ {
      if (in_interface && !replaced) print "MTU = " mtu
      in_interface = 0
      print
      next
    }
    in_interface && $0 ~ /^[[:space:]]*[Mm][Tt][Uu][[:space:]]*=/ {
      print "MTU = " mtu
      replaced = 1
      next
    }
    { print }
    END { if (in_interface && !replaced) print "MTU = " mtu }
  ' "$config"
}

validate_config
link_before="$(link_info)"
parse_link "$link_before"
ifindex_before="$ifindex"
old_mtu="$live_mtu"

if [[ "$check_only" = true ]]; then
  printf '%s\n' "$mtu"
  exit 0
fi

config_dir="$(dirname "$config")"
stage="$(mktemp "$config_dir/.wg0.conf.mtu.XXXXXX")"
cp -a -- "$config" "$stage"
render_config >"$stage.render"
cat "$stage.render" >"$stage"
rm -f -- "$stage.render"

if cmp -s "$config" "$stage" && [[ "$old_mtu" = "$mtu" ]]; then
  rm -f -- "$stage"
  printf '%s\n' "$mtu"
  exit 0
fi

backup_dir="$(mktemp -d /var/tmp/WG-auto-mtu.XXXXXX)"
chmod 0700 "$backup_dir"
backup="$backup_dir/wg0.conf.before"
config_changed=false
mtu_change_attempted=false
cleanup() {
  local status="$1"
  if [[ "$status" -ne 0 ]]; then
    if [[ "$mtu_change_attempted" = true ]]; then ip link set dev "$interface" mtu "$old_mtu" || true; fi
    if [[ "$config_changed" = true ]]; then cp -a -- "$backup" "$config" || true; fi
    printf 'Rollback evidence retained at %s\n' "$backup_dir" >&2
  fi
  rm -f -- "$stage"
  exit "$status"
}
trap 'status=$?; cleanup "$status"' EXIT

cp -a -- "$config" "$backup"
chmod 0600 "$backup"
if ! cmp -s "$config" "$stage"; then
  mv -f -- "$stage" "$config"
  config_changed=true
fi
if [[ "$old_mtu" != "$mtu" ]]; then
  mtu_change_attempted=true
  ip link set dev "$interface" mtu "$mtu"
fi

link_after="$(link_info)"
parse_link "$link_after"
[[ "$ifindex" = "$ifindex_before" && "$live_mtu" = "$mtu" ]] || fail 'WireGuard interface identity, state, or MTU changed unexpectedly'
printf '%s %s\n' "$mtu" "$backup_dir"