#!/usr/bin/env bash set -euo pipefail [[ "$#" -eq 1 && -f "$1" ]] || { echo "usage: apply-valheim-relay.sh STAGED_FRAGMENT" >&2 exit 2 } source_fragment="$1" config=/etc/sysconfig/nftables.conf fragment=/etc/nftables/valheim.nft include_line="include \"${fragment}\"" work="$(mktemp -d /var/tmp/valheim-relay.XXXXXX)" config_backup="${work}/nftables.conf.before" fragment_backup="${work}/valheim.nft.before" fragment_existed=false changed=false cleanup() { local status=$? if [[ "$status" -ne 0 && "$changed" = true ]]; then cp -f "$config_backup" "$config" || true if [[ "$fragment_existed" = true ]]; then cp -f "$fragment_backup" "$fragment" || true else rm -f "$fragment" || true fi echo "Valheim relay persistence rolled back: ${config} ${fragment}" >&2 fi if [[ "$status" -ne 0 ]]; then rm -rf "$work"; fi rm -f "$source_fragment" exit "$status" } trap cleanup EXIT [[ -f "$config" ]] || { echo "missing persistent nftables config: ${config}" >&2 exit 1 } [[ "$(grep -Fc "$include_line" "$config" || true)" -le 1 ]] || { echo "refusing duplicate Valheim nftables include in ${config}" >&2 exit 1 } [[ "$(grep -c '^add rule ' "$source_fragment")" -eq 4 ]] || { echo "invalid Valheim nftables fragment" >&2 exit 1 } grep -q '^add rule inet filter forward .*bugabinga-valheim-forward-ipv4"$' "$source_fragment" grep -q '^add rule inet filter forward .*bugabinga-valheim-forward-ipv6"$' "$source_fragment" grep -q '^add rule ip nat prerouting .*bugabinga-valheim-dnat-ipv4"$' "$source_fragment" grep -q '^add rule ip6 nat prerouting .*bugabinga-valheim-dnat-ipv6"$' "$source_fragment" cp --preserve=mode "$config" "$config_backup" if [[ -e "$fragment" ]]; then cp --preserve=mode "$fragment" "$fragment_backup" fragment_existed=true fi cp --preserve=mode "$config" "${work}/nftables.conf.candidate" if ! grep -Fqx "$include_line" "${work}/nftables.conf.candidate"; then printf '\n%s\n' "$include_line" >>"${work}/nftables.conf.candidate" fi cp "$source_fragment" "${work}/valheim.nft.candidate" chmod 0644 "${work}/valheim.nft.candidate" sed "s|$(printf '%s' "$include_line" | sed 's/[][\\/.^$*|]/\\&/g')|include \"${work}/valheim.nft.candidate\"|" "${work}/nftables.conf.candidate" >"${work}/nftables.conf.check" nft --check --file "${work}/nftables.conf.check" : >"${work}/live.batch" for chain in 'inet filter forward' 'ip nat prerouting' 'ip6 nat prerouting'; do read -r family table name <<<"$chain" nft -a list chain "$family" "$table" "$name" | awk -v family="$family" -v table="$table" -v chain="$name" ' /comment "bugabinga-valheim-(forward|dnat)-ipv[46]"/ { if ($(NF-1) != "handle" || $NF !~ /^[0-9]+$/) exit 1 print "delete rule " family " " table " " chain " handle " $NF } ' >>"${work}/live.batch" done cat "$source_fragment" >>"${work}/live.batch" nft --check --file "${work}/live.batch" install -d -m 0755 /etc/nftables changed=true install -m 0644 "${work}/valheim.nft.candidate" "$fragment" cp --preserve=mode "${work}/nftables.conf.candidate" "$config" nft --file "${work}/live.batch" changed=false printf '%s\n' "changed root paths: ${fragment} ${config}" printf 'Rollback evidence retained at %s\n' "$work" printf '%s\n' 'Valheim relay rules installed without nftables restart or ruleset flush.'