#!/usr/bin/env bash # # bt-netconfig - apply Ethernet configuration via NetworkManager. # # Usage: # bt-netconfig dhcp # bt-netconfig static
[gateway] [dns_csv] # # Intended to be invoked through sudo by the (unprivileged) web configurator. # Do NOT use ip/ifconfig directly — NetworkManager will undo manual changes. # # Environment (optional): # BUTTONTASK_NET_BOOT=1 boot path: succeed even if activation times out # BUTTONTASK_NET_ACTIVATE_TIMEOUT=N seconds to wait for nmcli connection up (default 30) set -euo pipefail err() { echo "bt-netconfig: $*" >&2; exit 1; } [ "$#" -ge 2 ] || err "usage: bt-netconfig ..." IFACE="$1" MODE="$2" PROFILE="buttontask-$IFACE" BOOT="${BUTTONTASK_NET_BOOT:-0}" ACTIVATE_TIMEOUT="${BUTTONTASK_NET_ACTIVATE_TIMEOUT:-30}" [[ "$IFACE" =~ ^[A-Za-z0-9_.:-]{1,32}$ ]] || err "invalid interface name" command -v nmcli >/dev/null 2>&1 || err "nmcli not found" # Ensure NM manages this interface (manual ip addr fights with NM). nmcli device set "$IFACE" managed yes 2>/dev/null || true # Use a dedicated profile per interface so we never fight "Wired connection 1". if ! nmcli -t -f NAME connection show | grep -Fxq "$PROFILE"; then nmcli connection add type ethernet ifname "$IFACE" con-name "$PROFILE" \ connection.autoconnect yes >/dev/null fi CON="$PROFILE" # Disable autoconnect on other ethernet profiles for this interface. disable_other_profiles() { local iface="$1" con="$2" name bound typ while IFS= read -r name; do [ -z "$name" ] && continue [ "$name" = "$con" ] && continue typ="$(nmcli -g connection.type connection show "$name" 2>/dev/null || true)" [ "$typ" = "802-3-ethernet" ] || continue bound="$(nmcli -g connection.interface-name connection show "$name" 2>/dev/null || true)" if [ -n "$bound" ] && [ "$bound" != "--" ] && [ "$bound" != "$iface" ]; then continue fi nmcli connection modify "$name" connection.autoconnect no 2>/dev/null || true done < <(nmcli -t -f NAME connection show 2>/dev/null || true) } disable_other_profiles "$IFACE" "$CON" nmcli connection modify "$CON" \ connection.interface-name "$IFACE" \ connection.autoconnect yes \ connection.autoconnect-priority 100 case "$MODE" in dhcp) nmcli connection modify "$CON" \ ipv4.method auto \ ipv4.addresses "" \ ipv4.gateway "" \ ipv4.dns "" \ ipv4.ignore-auto-dns no \ ipv4.never-default no ;; static) [ "$#" -ge 4 ] || err "static requires
" ADDR="$3" PREFIX="$4" GW="${5:-}" DNS="${6:-}" [[ "$PREFIX" =~ ^[0-9]{1,2}$ ]] && [ "$PREFIX" -ge 1 ] && [ "$PREFIX" -le 32 ] \ || err "invalid prefix" if [ -n "$GW" ]; then nmcli connection modify "$CON" \ ipv4.method manual \ ipv4.addresses "${ADDR}/${PREFIX}" \ ipv4.gateway "$GW" \ ipv4.dns "$DNS" \ ipv4.ignore-auto-dns yes \ ipv4.never-default no else nmcli connection modify "$CON" \ ipv4.method manual \ ipv4.addresses "${ADDR}/${PREFIX}" \ ipv4.gateway "" \ ipv4.dns "$DNS" \ ipv4.ignore-auto-dns yes \ ipv4.never-default yes fi ;; *) err "mode must be dhcp or static" ;; esac # Drop the active profile so the new settings apply cleanly. nmcli device disconnect "$IFACE" 2>/dev/null || true nmcli connection down "$CON" 2>/dev/null || true activate_connection() { local con="$1" iface="$2" if command -v timeout >/dev/null 2>&1; then timeout "$ACTIVATE_TIMEOUT" nmcli connection up "$con" ifname "$iface" 2>/dev/null \ && return 0 timeout "$ACTIVATE_TIMEOUT" nmcli connection up "$con" >/dev/null 2>&1 \ && return 0 return 1 fi nmcli connection up "$con" ifname "$iface" 2>/dev/null \ || nmcli connection up "$con" >/dev/null } if ! activate_connection "$CON" "$IFACE"; then if [ "$BOOT" = "1" ]; then echo "bt-netconfig: activation timed out (boot); profile saved, NM will retry" >&2 exit 0 fi err "connection activation failed or timed out after ${ACTIVATE_TIMEOUT}s" fi # Brief settle, then report what NM actually applied. sleep 1 APPLIED="$(nmcli -t -f IP4.ADDRESS device show "$IFACE" 2>/dev/null \ | sed 's/^IP4.ADDRESS://' | head -n1 | tr '\n' ' ')" echo "applied $MODE on $IFACE ($CON)${APPLIED:+ ip=$APPLIED}"