#!/usr/bin/env bash # # bt-netconfig - apply Ethernet or Wi-Fi configuration via NetworkManager. # # Usage: # bt-netconfig dhcp # bt-netconfig static
[gateway] [dns_csv] # bt-netconfig dhcp wifi [password] # bt-netconfig static
[gateway] [dns_csv] wifi [password] # # 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 [args...] [wifi [password]]" IFACE="$1" MODE="$2" shift 2 PROFILE="buttontask-$IFACE" BOOT="${BUTTONTASK_NET_BOOT:-0}" ACTIVATE_TIMEOUT="${BUTTONTASK_NET_ACTIVATE_TIMEOUT:-30}" NET_TYPE="ethernet" SSID="" WIFI_PASS="" [[ "$IFACE" =~ ^[A-Za-z0-9_.:-]{1,32}$ ]] || err "invalid interface name" command -v nmcli >/dev/null 2>&1 || err "nmcli not found" # Fast path: disconnect / disable interface without reconfiguring. if [ "$MODE" = "disconnect" ]; then nmcli device disconnect "$IFACE" 2>/dev/null || true # Turn off autoconnect on our profile so it does not come back immediately. if nmcli -t -f NAME connection show | grep -Fxq "$PROFILE"; then nmcli connection modify "$PROFILE" connection.autoconnect no 2>/dev/null || true nmcli connection down "$PROFILE" 2>/dev/null || true fi # Also disable autoconnect on other profiles bound to this iface. while IFS= read -r name; do [ -z "$name" ] && continue bound="$(nmcli -g connection.interface-name connection show "$name" 2>/dev/null || true)" if [ "$bound" = "$IFACE" ] || [ "$name" = "$PROFILE" ]; then nmcli connection modify "$name" connection.autoconnect no 2>/dev/null || true fi done < <(nmcli -t -f NAME connection show 2>/dev/null || true) echo "disconnected $IFACE (autoconnect off)" exit 0 fi [ "$#" -ge 0 ] || true # Parse remaining args: optional static fields, then optional "wifi ssid [password]" ADDR="" PREFIX="" GW="" DNS="" ARGS=("$@") i=0 n=${#ARGS[@]} while [ "$i" -lt "$n" ]; do if [ "${ARGS[$i]}" = "wifi" ]; then NET_TYPE="wifi" i=$((i + 1)) [ "$i" -lt "$n" ] || err "wifi requires " SSID="${ARGS[$i]}" i=$((i + 1)) if [ "$i" -lt "$n" ]; then WIFI_PASS="${ARGS[$i]}" i=$((i + 1)) fi break fi case "$MODE" in static) if [ -z "$ADDR" ]; then ADDR="${ARGS[$i]}" elif [ -z "$PREFIX" ]; then PREFIX="${ARGS[$i]}" elif [ -z "$GW" ]; then GW="${ARGS[$i]}" elif [ -z "$DNS" ]; then DNS="${ARGS[$i]}" else err "unexpected argument: ${ARGS[$i]}" fi ;; dhcp) err "unexpected argument for dhcp: ${ARGS[$i]}" ;; *) err "mode must be dhcp, static or disconnect" ;; esac i=$((i + 1)) done if [ "$NET_TYPE" = "wifi" ]; then [ -n "$SSID" ] || err "wifi requires " fi # Ensure NM manages this interface. nmcli device set "$IFACE" managed yes 2>/dev/null || true # Create dedicated profile of the right type. ensure_profile() { if nmcli -t -f NAME connection show | grep -Fxq "$PROFILE"; then local typ typ="$(nmcli -g connection.type connection show "$PROFILE" 2>/dev/null || true)" if [ "$NET_TYPE" = "wifi" ] && [ "$typ" != "802-11-wireless" ]; then nmcli connection delete "$PROFILE" >/dev/null 2>&1 || true elif [ "$NET_TYPE" = "ethernet" ] && [ "$typ" != "802-3-ethernet" ]; then nmcli connection delete "$PROFILE" >/dev/null 2>&1 || true fi fi if ! nmcli -t -f NAME connection show | grep -Fxq "$PROFILE"; then if [ "$NET_TYPE" = "wifi" ]; then nmcli connection add type wifi ifname "$IFACE" con-name "$PROFILE" \ ssid "$SSID" connection.autoconnect yes >/dev/null else nmcli connection add type ethernet ifname "$IFACE" con-name "$PROFILE" \ connection.autoconnect yes >/dev/null fi fi } ensure_profile CON="$PROFILE" # Disable autoconnect on other profiles for this interface (same media type). disable_other_profiles() { local iface="$1" con="$2" want_type="$3" 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" = "$want_type" ] || 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) } if [ "$NET_TYPE" = "wifi" ]; then disable_other_profiles "$IFACE" "$CON" "802-11-wireless" nmcli connection modify "$CON" \ connection.interface-name "$IFACE" \ connection.autoconnect yes \ connection.autoconnect-priority 100 \ wifi.ssid "$SSID" if [ -n "$WIFI_PASS" ]; then nmcli connection modify "$CON" \ wifi-sec.key-mgmt wpa-psk \ wifi-sec.psk "$WIFI_PASS" else # Open network nmcli connection modify "$CON" \ wifi-sec.key-mgmt none 2>/dev/null || true nmcli connection modify "$CON" remove wifi-sec.psk 2>/dev/null || true fi else disable_other_profiles "$IFACE" "$CON" "802-3-ethernet" nmcli connection modify "$CON" \ connection.interface-name "$IFACE" \ connection.autoconnect yes \ connection.autoconnect-priority 100 fi 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) [ -n "$ADDR" ] && [ -n "$PREFIX" ] || err "static requires
" [[ "$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 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 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 type=$NET_TYPE)${SSID:+ ssid=$SSID}${APPLIED:+ ip=$APPLIED}"