Last active
February 10, 2026 17:23
-
-
Save klutchell/c766da0a59e11218a782d0f873f5d1bd to your computer and use it in GitHub Desktop.
Generate NetworkManager .nmconnection keyfile for Hetzner dedicated servers (queries Robot API, outputs to stdout)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Generate a NetworkManager .nmconnection keyfile for a Hetzner dedicated server. | |
| # Queries the Hetzner Robot API for network details and writes keyfile to stdout. | |
| # | |
| # Usage: ROBOT_USER=xxx ROBOT_PASS=xxx ./hetzner-nmconnection.sh <server-number> | |
| # | |
| # Dependencies: curl, jq | |
| # Output: .nmconnection keyfile on stdout; progress/errors on stderr | |
| set -euo pipefail | |
| die() { echo "error: $*" >&2; exit 1; } | |
| log() { echo ":: $*" >&2; } | |
| # -- deps -- | |
| for cmd in curl jq; do | |
| command -v "$cmd" >/dev/null 2>&1 || die "$cmd not found (apt-get install -y $cmd)" | |
| done | |
| # -- inputs -- | |
| [[ ${1:-} ]] || die "usage: $0 <server-number>" | |
| SERVER_NUMBER="$1" | |
| : "${ROBOT_USER:?set ROBOT_USER}" | |
| : "${ROBOT_PASS:?set ROBOT_PASS}" | |
| ROBOT_API="https://robot-ws.your-server.de" | |
| robot_get() { | |
| local url="${ROBOT_API}$1" | |
| log "GET $url" | |
| curl -sf -u "${ROBOT_USER}:${ROBOT_PASS}" "$url" | |
| } | |
| # -- fetch server info -- | |
| server_json=$(robot_get "/server/${SERVER_NUMBER}") | |
| server_ip=$(jq -r '.server.server_ip' <<<"$server_json") | |
| server_mac=$(jq -r '.server.server_mac // empty' <<<"$server_json") | |
| [[ $server_ip != "null" ]] || die "no server_ip in response" | |
| # fall back to MAC of the default route interface on this host | |
| if [[ -z $server_mac ]]; then | |
| default_iface=$(ip route show default 2>/dev/null | awk '{print $5; exit}') | |
| if [[ -n ${default_iface:-} ]]; then | |
| server_mac=$(ip link show "$default_iface" 2>/dev/null | awk '/ether/ {print $2; exit}') | |
| log "mac from local interface ${default_iface}=${server_mac}" | |
| fi | |
| fi | |
| log "server_ip=$server_ip mac=${server_mac:-none}" | |
| # extract first ipv6 subnet (may not exist) | |
| ipv6_subnet=$(jq -r '.server.subnet[]? | select(.ip | contains(":")) | .ip' <<<"$server_json" | head -1) | |
| ipv6_mask=$(jq -r '.server.subnet[]? | select(.ip | contains(":")) | .mask' <<<"$server_json" | head -1) | |
| if [[ -n $ipv6_subnet ]]; then | |
| # API returns network address like 2a01:4f8:xxx::/64 — strip trailing :: | |
| ipv6_host="${ipv6_subnet%%::*}::1" | |
| log "ipv6=${ipv6_host}/${ipv6_mask}" | |
| else | |
| log "no ipv6 subnet found" | |
| fi | |
| # -- fetch ipv4 details -- | |
| ip_json=$(robot_get "/ip/${server_ip}") | |
| ipv4_addr=$(jq -r '.ip.ip' <<<"$ip_json") | |
| ipv4_gw=$(jq -r '.ip.gateway' <<<"$ip_json") | |
| ipv4_mask=$(jq -r '.ip.mask' <<<"$ip_json") | |
| log "ipv4=${ipv4_addr}/${ipv4_mask} gw=${ipv4_gw}" | |
| # -- generate keyfile -- | |
| log "generating keyfile" | |
| cat <<EOF | |
| [connection] | |
| id=static | |
| type=ethernet | |
| autoconnect-priority=1 | |
| [ipv4] | |
| method=manual | |
| address1=${ipv4_addr}/${ipv4_mask},${ipv4_gw} | |
| dns=1.1.1.1;1.0.0.1; | |
| EOF | |
| if [[ -n ${ipv6_subnet:-} ]]; then | |
| cat <<EOF | |
| [ipv6] | |
| method=manual | |
| address1=${ipv6_host}/${ipv6_mask},fe80::1 | |
| dns=2606:4700:4700::1111;2606:4700:4700::1001; | |
| EOF | |
| else | |
| cat <<EOF | |
| [ipv6] | |
| method=disabled | |
| EOF | |
| fi | |
| if [[ -n ${server_mac:-} ]]; then | |
| cat <<EOF | |
| [ethernet] | |
| mac-address=${server_mac} | |
| EOF | |
| else | |
| cat <<EOF | |
| [ethernet] | |
| EOF | |
| fi | |
| log "done" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment