|
#!/bin/sh |
|
set -o nounset |
|
set -o errexit |
|
|
|
# UDEV shell script synchronizing network routes with Hetzner network |
|
# properties. It uses the Metadata Server to fetch information of private |
|
# networks and add or remove routes according to the `udev` action and the |
|
# modified net interface. |
|
# |
|
# WARN: it requires https://github.com/mikefarah/yq to parse and extract data |
|
# from the Hetzner API. |
|
# |
|
# Example: |
|
# #/etc/udev/rules.d/70-hetzner-net-routes.rules |
|
# SUBSYSTEM=="net", KERNEL=="eth*", RUN+="/usr/lib/sync-hetzner-routes" |
|
# |
|
|
|
API_URL=http://169.254.169.254/hetzner/v1/metadata/private-networks |
|
INTERFACE_WAIT_LIMIT=10 |
|
|
|
info() { echo '[INFO] ' "$@"; } |
|
warn() { echo '[WARN] ' "$@" >&2; } |
|
fatal() { echo '[ERROR] ' "$@" >&2; exit 1; } |
|
|
|
fetch_metadata() { |
|
TMP_METADATA="$(mktemp)" |
|
curl -s "${API_URL}" > "${TMP_METADATA}" |
|
|
|
info "metadata downloaded to ${TMP_METADATA}" |
|
} |
|
|
|
fetch_interface_ip() { |
|
nretry=0 |
|
until [ ${nretry} -ge ${INTERFACE_WAIT_LIMIT} ]; do |
|
interface_ip="$(ip -4 -o address show "${INTERFACE}" | awk '{print $4}' | cut -d'/' -f1)" |
|
if [ -n "${interface_ip}" ]; then return; fi |
|
|
|
warn "interface ${INTERFACE} doesn't have an IP... wait until $$INTERFACE_WAIT_LIMIT will be reached" |
|
nretry=$((nretry+1)) |
|
sleep 1 |
|
done |
|
|
|
fatal "failed to find \`${INTERFACE}\` IP: $$INTERFACE_WAIT_LIMIT reached" |
|
} |
|
|
|
add_route() { |
|
fetch_interface_ip |
|
fetch_metadata |
|
|
|
network=$(yq eval '.[] | select(.ip=="'"${interface_ip}"'") | .network' "${TMP_METADATA}") |
|
gateway=$(yq eval '.[] | select(.ip=="'"${interface_ip}"'") | .gateway' "${TMP_METADATA}") |
|
|
|
if [ -z "${network}" ]; then fatal "no network found on Hetzner API for interface \`${INTERFACE}\` (ip: ${interface_ip})"; fi |
|
if [ -z "${gateway}" ]; then fatal "no gateway found on Hetzner API for interface \`${INTERFACE}\` (ip: ${interface_ip})"; fi |
|
|
|
ip route add "${network}" via "${gateway}" dev "${INTERFACE}" |
|
} |
|
|
|
remove_route() { |
|
ip_route="$(ip route show | grep -E "dev ${INTERFACE} $")" |
|
if [ -z "${ip_route}" ]; then fatal "no route found for interface \`${INTERFACE}\`"; fi |
|
|
|
ip route del "${ip_route}" |
|
} |
|
|
|
case "${ACTION}" in |
|
"add") add_route;; |
|
"remove") remove_route;; |
|
*) warn "action \`${ACTION}\` not handled... ignored" |
|
esac |