498 lines
12 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Tuxflotte Installer
# Phase 2 Netzwerkinitialisierung
#
# Unterstützt zunächst:
# - Ethernet über DHCP
# - bereits aktive NetworkManager-Verbindungen
# - WPA2/WPA3 Personal
# - interaktive WLAN-Auswahl
# - Prüfung des Tuxflotte-Servers
# - geschützte Runtime-Ablage des aktiven Netzwerkprofils
set -Eeuo pipefail
readonly SCRIPT_NAME="${0##*/}"
readonly RUNTIME_DIR="/run/tuxflotte/network"
readonly STATE_FILE="${RUNTIME_DIR}/state.env"
readonly CONNECTION_EXPORT="${RUNTIME_DIR}/connection.nmconnection"
readonly SERVER_URL="${TUXFLOTTE_SERVER_URL:-https://anode.tuxflotte.de/health}"
readonly SERVER_TIMEOUT="${TUXFLOTTE_SERVER_TIMEOUT:-10}"
readonly NMCLI="${NMCLI:-nmcli}"
readonly CURL="${CURL:-curl}"
log() {
printf '[%s] %s\n' "${SCRIPT_NAME}" "$*" >&2
}
warn() {
printf '[%s] WARNUNG: %s\n' "${SCRIPT_NAME}" "$*" >&2
}
fatal() {
printf '[%s] FEHLER: %s\n' "${SCRIPT_NAME}" "$*" >&2
exit 1
}
require_root() {
if [[ "${EUID}" -ne 0 ]]; then
fatal "Das Netzwerkmodul muss als root ausgeführt werden."
fi
}
require_command() {
local command_name="$1"
command -v "${command_name}" >/dev/null 2>&1 ||
fatal "Benötigtes Programm nicht gefunden: ${command_name}"
}
prepare_runtime_directory() {
install -d \
--mode=0700 \
--owner=root \
--group=root \
"${RUNTIME_DIR}"
rm -f -- "${STATE_FILE}" "${CONNECTION_EXPORT}"
}
networkmanager_is_running() {
"${NMCLI}" -t -f RUNNING general 2>/dev/null |
grep -qx 'running'
}
start_networkmanager_if_possible() {
if networkmanager_is_running; then
return 0
fi
log "NetworkManager ist noch nicht aktiv."
if command -v systemctl >/dev/null 2>&1; then
log "Versuche NetworkManager zu starten."
systemctl start NetworkManager.service 2>/dev/null || true
fi
networkmanager_is_running ||
fatal "NetworkManager konnte nicht verwendet werden."
}
enable_networking() {
"${NMCLI}" networking on >/dev/null 2>&1 || true
"${NMCLI}" radio wifi on >/dev/null 2>&1 || true
}
device_has_ipv4() {
local device="$1"
"${NMCLI}" -g IP4.ADDRESS device show "${device}" 2>/dev/null |
grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/'
}
get_active_device() {
local device
local type
while IFS=: read -r device type _; do
case "${type}" in
ethernet|wifi)
;;
*)
continue
;;
esac
[[ -e "/sys/class/net/${device}/device" ]] || continue
if device_has_ipv4 "${device}"; then
printf '%s\n' "${device}"
return 0
fi
done < <(
"${NMCLI}" \
--terse \
--fields DEVICE,TYPE,STATE \
device status
)
return 1
}
get_active_connection_name() {
local device="$1"
"${NMCLI}" \
--get-values GENERAL.CONNECTION \
device show "${device}" 2>/dev/null |
head -n 1
}
get_device_type() {
local device="$1"
"${NMCLI}" \
--get-values GENERAL.TYPE \
device show "${device}" 2>/dev/null |
head -n 1
}
server_is_reachable() {
"${CURL}" \
--silent \
--show-error \
--fail \
--location \
--connect-timeout "${SERVER_TIMEOUT}" \
--max-time "${SERVER_TIMEOUT}" \
--output /dev/null \
"${SERVER_URL}"
}
check_existing_connection() {
local device
device="$(get_active_device || true)"
if [[ -z "${device}" ]]; then
return 1
fi
if ! device_has_ipv4 "${device}"; then
warn "Interface ${device} ist verbunden, besitzt aber keine IPv4-Adresse."
return 1
fi
log "Aktive Netzwerkverbindung über ${device} gefunden."
if server_is_reachable; then
log "Tuxflotte-Server ist erreichbar."
return 0
fi
warn "Netzwerk ist aktiv, aber der Tuxflotte-Server ist nicht erreichbar."
return 1
}
get_ethernet_devices() {
local device
while IFS=: read -r device type state; do
[[ "${type}" == "ethernet" ]] || continue
[[ "${state}" != "unavailable" ]] || continue
[[ -e "/sys/class/net/${device}/device" ]] || continue
printf '%s\n' "${device}"
done < <(
"${NMCLI}" \
--terse \
--fields DEVICE,TYPE,STATE \
device status
)
}
try_ethernet() {
local device
while IFS= read -r device; do
[[ -n "${device}" ]] || continue
log "Prüfe Ethernet-Interface ${device}."
"${NMCLI}" device connect "${device}" >/dev/null 2>&1 || true
if device_has_ipv4 "${device}"; then
log "Ethernet-Verbindung über ${device} hergestellt."
if server_is_reachable; then
log "Tuxflotte-Server ist über Ethernet erreichbar."
return 0
fi
warn "Ethernet besitzt eine IP-Adresse, aber der Server ist nicht erreichbar."
fi
done < <(get_ethernet_devices)
return 1
}
get_wifi_device() {
"${NMCLI}" \
--terse \
--fields DEVICE,TYPE,STATE \
device status |
awk -F: '$2 == "wifi" && $3 != "unavailable" { print $1; exit }'
}
scan_wifi_networks() {
local wifi_device="$1"
"${NMCLI}" device wifi rescan ifname "${wifi_device}" >/dev/null 2>&1 ||
true
sleep 2
"${NMCLI}" \
--terse \
--escape yes \
--fields SSID,SIGNAL,SECURITY \
device wifi list \
ifname "${wifi_device}" |
awk -F: '
$1 != "" && !seen[$1]++ {
printf "%s\t%s\t%s\n", $1, $2, $3
}
' |
sort -t $'\t' -k2,2nr
}
choose_wifi_ssid() {
local wifi_device="$1"
local -a networks=()
local entry
local choice
local index=1
while IFS= read -r entry; do
[[ -n "${entry}" ]] && networks+=("${entry}")
done < <(scan_wifi_networks "${wifi_device}")
if [[ "${#networks[@]}" -eq 0 ]]; then
warn "Keine sichtbaren WLAN-Netze gefunden."
read -r -p "Versteckte SSID manuell eingeben oder leer abbrechen: " WIFI_SSID
[[ -n "${WIFI_SSID}" ]]
return
fi
printf '\nVerfügbare WLAN-Netze:\n\n' >&2
for entry in "${networks[@]}"; do
IFS=$'\t' read -r ssid signal security <<<"${entry}"
printf ' %2d) %-32s Signal: %-3s Sicherheit: %s\n' \
"${index}" \
"${ssid}" \
"${signal}" \
"${security:-offen}" >&2
((index += 1))
done
printf '\n' >&2
read -r -p "WLAN auswählen [1-${#networks[@]}], m = manuell, q = abbrechen: " choice
case "${choice}" in
q|Q)
return 1
;;
m|M)
read -r -p "SSID: " WIFI_SSID
[[ -n "${WIFI_SSID}" ]]
;;
*)
if [[ ! "${choice}" =~ ^[0-9]+$ ]] ||
(( choice < 1 || choice > ${#networks[@]} )); then
warn "Ungültige Auswahl."
return 1
fi
IFS=$'\t' read -r WIFI_SSID _ _ <<<"${networks[choice - 1]}"
;;
esac
}
wifi_network_is_open() {
local wifi_device="$1"
local ssid="$2"
local security
security="$(
"${NMCLI}" \
--terse \
--escape no \
--fields SSID,SECURITY \
device wifi list \
ifname "${wifi_device}" |
awk -F: -v wanted="${ssid}" '
$1 == wanted {
print $2
exit
}
'
)"
[[ -z "${security}" || "${security}" == "--" ]]
}
connect_wifi() {
local wifi_device="$1"
choose_wifi_ssid "${wifi_device}" ||
return 1
log "Verbinde mit WLAN '${WIFI_SSID}'."
if wifi_network_is_open "${wifi_device}" "${WIFI_SSID}"; then
if ! "${NMCLI}" \
device wifi connect "${WIFI_SSID}" \
ifname "${wifi_device}" \
>/dev/null; then
warn "Verbindung mit dem offenen WLAN konnte nicht hergestellt werden."
return 1
fi
else
log "NetworkManager fragt die WLAN-Zugangsdaten geschützt ab."
if ! "${NMCLI}" \
--ask \
device wifi connect "${WIFI_SSID}" \
ifname "${wifi_device}" \
>/dev/null; then
warn "WLAN-Anmeldung ist fehlgeschlagen."
return 1
fi
fi
if ! device_has_ipv4 "${wifi_device}"; then
warn "WLAN-Verbindung besitzt keine IPv4-Adresse."
return 1
fi
log "WLAN-Verbindung wurde hergestellt."
if ! server_is_reachable; then
warn "WLAN ist verbunden, aber der Tuxflotte-Server ist nicht erreichbar."
return 1
fi
log "Tuxflotte-Server ist über WLAN erreichbar."
}
store_network_state() {
local device
local connection_name
local device_type
local connection_uuid=""
device="$(get_active_device)" ||
fatal "Kein aktives Provisionierungsinterface gefunden."
connection_name="$(
get_active_connection_name "${device}" || true
)"
device_type="$(get_device_type "${device}")"
if [[ -n "${connection_name}" && "${connection_name}" != "--" ]]; then
connection_uuid="$(
"${NMCLI}" \
--get-values connection.uuid \
connection show "${connection_name}" 2>/dev/null |
head -n 1
)"
else
connection_name=""
warn "Interface ${device} wird nicht durch ein aktives NetworkManager-Profil verwaltet."
fi
umask 077
{
printf 'TUXFLOTTE_NETWORK_DEVICE=%q\n' "${device}"
printf 'TUXFLOTTE_NETWORK_TYPE=%q\n' "${device_type}"
printf 'TUXFLOTTE_CONNECTION_NAME=%q\n' "${connection_name}"
printf 'TUXFLOTTE_CONNECTION_UUID=%q\n' "${connection_uuid}"
printf 'TUXFLOTTE_SERVER_URL=%q\n' "${SERVER_URL}"
} >"${STATE_FILE}"
chmod 0600 "${STATE_FILE}"
log "Netzwerkstatus wurde unter ${STATE_FILE} gespeichert."
}
export_connection_profile() {
local device
local connection_name
local source_file
device="$(get_active_device)" ||
fatal "Kein aktives Provisionierungsinterface gefunden."
connection_name="$(
get_active_connection_name "${device}" || true
)"
if [[ -z "${connection_name}" || "${connection_name}" == "--" ]]; then
warn "Für Interface ${device} existiert kein aktives NetworkManager-Profil."
return 0
fi
source_file="$(
"${NMCLI}" \
--get-values connection.filename \
connection show "${connection_name}" 2>/dev/null |
head -n 1
)"
if [[ -z "${source_file}" || ! -f "${source_file}" ]]; then
warn "NetworkManager-Profil konnte nicht exportiert werden."
return 0
fi
install \
--mode=0600 \
--owner=root \
--group=root \
"${source_file}" \
"${CONNECTION_EXPORT}"
log "Aktives Verbindungsprofil wurde geschützt vorgemerkt."
}
main() {
require_root
require_command "${NMCLI}"
require_command "${CURL}"
prepare_runtime_directory
start_networkmanager_if_possible
enable_networking
log "Prüfe vorhandene Netzwerkverbindungen."
if check_existing_connection; then
:
elif try_ethernet; then
:
else
local wifi_device
wifi_device="$(get_wifi_device || true)"
if [[ -z "${wifi_device}" ]]; then
fatal "Keine funktionierende Ethernet-Verbindung und keine WLAN-Hardware gefunden."
fi
log "Ethernet ist nicht verfügbar. WLAN-Initialisierung wird gestartet."
connect_wifi "${wifi_device}" ||
fatal "Es konnte keine Verbindung zum Tuxflotte-Server hergestellt werden."
fi
store_network_state
export_connection_profile
log "Netzwerkinitialisierung erfolgreich abgeschlossen."
}
main "$@"