235 lines
8.2 KiB
Bash
Executable File
235 lines
8.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# bt-netconfig - apply Ethernet or Wi-Fi configuration via NetworkManager.
|
|
#
|
|
# Usage:
|
|
# bt-netconfig <iface> dhcp
|
|
# bt-netconfig <iface> static <address> <prefix> [gateway] [dns_csv]
|
|
# bt-netconfig <iface> dhcp wifi <ssid> [password]
|
|
# bt-netconfig <iface> static <address> <prefix> [gateway] [dns_csv] wifi <ssid> [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 <iface> <dhcp|static|disconnect> [args...] [wifi <ssid> [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>"
|
|
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 <ssid>"
|
|
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 <address> <prefix>"
|
|
[[ "$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}"
|