Compare commits
4 Commits
c7b660df9a
...
0116e4b905
| Author | SHA1 | Date | |
|---|---|---|---|
| 0116e4b905 | |||
| dc8a2df9d2 | |||
| 74a6e181ce | |||
| b1f35a5ef4 |
0
config/installer.conf
Normal file
0
config/installer.conf
Normal file
@ -82,6 +82,24 @@ verify_workdir() {
|
|||||||
echo "GRUB verification passed."
|
echo "GRUB verification passed."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create_iso() {
|
||||||
|
echo "Creating Tuxflotte ISO..."
|
||||||
|
|
||||||
|
local output_iso="$OUTPUT_DIR/tuxflotte-provisioning-0.1.iso"
|
||||||
|
|
||||||
|
rm -f "$output_iso"
|
||||||
|
|
||||||
|
xorriso \
|
||||||
|
-indev "$SOURCE_ISO" \
|
||||||
|
-outdev "$output_iso" \
|
||||||
|
-compliance no_emul_toc \
|
||||||
|
-map "$REPO_DIR/grub/EFI-BOOT-grub.cfg" /EFI/BOOT/grub.cfg \
|
||||||
|
-map "$REPO_DIR/grub/boot-grub2-grub.cfg" /boot/grub2/grub.cfg \
|
||||||
|
-boot_image any replay
|
||||||
|
|
||||||
|
echo "ISO created: $output_iso"
|
||||||
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
check_input
|
check_input
|
||||||
check_dependencies
|
check_dependencies
|
||||||
@ -89,6 +107,7 @@ main() {
|
|||||||
extract_iso
|
extract_iso
|
||||||
patch_grub
|
patch_grub
|
||||||
verify_workdir
|
verify_workdir
|
||||||
|
create_iso
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Build preparation complete."
|
echo "Build preparation complete."
|
||||||
|
|||||||
33
scripts/installer.sh
Executable file
33
scripts/installer.sh
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
INSTALLER_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
|
||||||
|
source "$SCRIPT_DIR/lib/logging.sh"
|
||||||
|
source "$SCRIPT_DIR/lib/errors.sh"
|
||||||
|
source "$SCRIPT_DIR/lib/utils.sh"
|
||||||
|
source "$SCRIPT_DIR/lib/checks.sh"
|
||||||
|
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
|
*) error_exit "Unbekannter Parameter: $arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
load_config "$INSTALLER_ROOT/config/installer.conf"
|
||||||
|
|
||||||
|
log_info "Tuxflotte Installer gestartet"
|
||||||
|
log_info "Installer Root: $INSTALLER_ROOT"
|
||||||
|
|
||||||
|
[[ "$DRY_RUN" == true ]] && log_warn "Dry-Run aktiv"
|
||||||
|
|
||||||
|
run_module "$SCRIPT_DIR/modules/00_preflight.sh" "always"
|
||||||
|
run_module "$SCRIPT_DIR/modules/10_hardware.sh" "always"
|
||||||
|
run_module "$SCRIPT_DIR/modules/20_storage.sh" "dry-run-safe"
|
||||||
|
run_module "$SCRIPT_DIR/modules/99_finish.sh" "always"
|
||||||
|
|
||||||
|
log_success "Tuxflotte Installer abgeschlossen"
|
||||||
17
scripts/lib/checks.sh
Normal file
17
scripts/lib/checks.sh
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
detect_uefi() {
|
||||||
|
if [[ -d /sys/firmware/efi ]]; then
|
||||||
|
echo "uefi"
|
||||||
|
else
|
||||||
|
echo "bios"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_network() {
|
||||||
|
ping -c 1 -W 2 1.1.1.1 >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
list_install_disks() {
|
||||||
|
lsblk -dpno NAME,SIZE,MODEL,TRAN,TYPE | awk '$5 == "disk" && $4 != "usb" {print}'
|
||||||
|
}
|
||||||
8
scripts/lib/errors.sh
Normal file
8
scripts/lib/errors.sh
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
error_exit() {
|
||||||
|
log_error "$1"
|
||||||
|
exit "${2:-1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
trap 'error_exit "Unerwarteter Fehler in Zeile $LINENO."' ERR
|
||||||
61
scripts/lib/hardware_collectors.sh
Normal file
61
scripts/lib/hardware_collectors.sh
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
get_cpu_model() {
|
||||||
|
awk -F: '
|
||||||
|
$1 ~ /^model name[[:space:]]*$/ {
|
||||||
|
value = $2
|
||||||
|
sub(/^[[:space:]]*/, "", value)
|
||||||
|
print value
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' /proc/cpuinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
get_cpu_count() {
|
||||||
|
getconf _NPROCESSORS_ONLN
|
||||||
|
}
|
||||||
|
|
||||||
|
get_memory_bytes() {
|
||||||
|
awk '
|
||||||
|
$1 == "MemTotal:" {
|
||||||
|
print $2 * 1024
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' /proc/meminfo
|
||||||
|
}
|
||||||
|
|
||||||
|
build_storage_devices_json() {
|
||||||
|
lsblk \
|
||||||
|
--bytes \
|
||||||
|
--json \
|
||||||
|
--nodeps \
|
||||||
|
--output NAME,TYPE,MODEL,SERIAL,SIZE,TRAN |
|
||||||
|
jq '
|
||||||
|
[
|
||||||
|
.blockdevices[]
|
||||||
|
| select(.type == "disk")
|
||||||
|
| {
|
||||||
|
name: .name,
|
||||||
|
model: (
|
||||||
|
if .model == null or .model == ""
|
||||||
|
then null
|
||||||
|
else (.model | gsub("^[[:space:]]+|[[:space:]]+$"; ""))
|
||||||
|
end
|
||||||
|
),
|
||||||
|
serial: (
|
||||||
|
if .serial == null or .serial == ""
|
||||||
|
then null
|
||||||
|
else (.serial | gsub("^[[:space:]]+|[[:space:]]+$"; ""))
|
||||||
|
end
|
||||||
|
),
|
||||||
|
size_bytes: .size,
|
||||||
|
transport: (
|
||||||
|
if .tran == null or .tran == ""
|
||||||
|
then null
|
||||||
|
else .tran
|
||||||
|
end
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
'
|
||||||
|
}
|
||||||
17
scripts/lib/logging.sh
Normal file
17
scripts/lib/logging.sh
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo "[INFO ] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo "[WARN ] $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo "[ERROR] $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo "[ OK ] $*"
|
||||||
|
}
|
||||||
40
scripts/lib/utils.sh
Normal file
40
scripts/lib/utils.sh
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
load_config() {
|
||||||
|
local config_file="$1"
|
||||||
|
|
||||||
|
if [[ -f "$config_file" ]]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$config_file"
|
||||||
|
log_info "Konfiguration geladen: $config_file"
|
||||||
|
else
|
||||||
|
log_warn "Keine Konfiguration gefunden: $config_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_module() {
|
||||||
|
local module="$1"
|
||||||
|
local mode="${2:-normal}"
|
||||||
|
|
||||||
|
[[ -f "$module" ]] || error_exit "Modul nicht gefunden: $module"
|
||||||
|
|
||||||
|
log_info "Starte Modul: $(basename "$module")"
|
||||||
|
|
||||||
|
if [[ "${DRY_RUN:-false}" == true && "$mode" != "always" ]]; then
|
||||||
|
log_warn "Dry-Run: Modul übersprungen: $module"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$module"
|
||||||
|
|
||||||
|
log_success "Modul abgeschlossen: $(basename "$module")"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_root() {
|
||||||
|
[[ "$EUID" -eq 0 ]] || error_exit "Installer muss als root ausgeführt werden."
|
||||||
|
}
|
||||||
|
|
||||||
|
command_exists() {
|
||||||
|
command -v "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
0
scripts/modules/00_preflight.sh
Executable file
0
scripts/modules/00_preflight.sh
Executable file
497
scripts/modules/05_network.sh
Executable file
497
scripts/modules/05_network.sh
Executable file
@ -0,0 +1,497 @@
|
|||||||
|
#!/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 "$@"
|
||||||
550
scripts/modules/10_hardware.sh
Executable file
550
scripts/modules/10_hardware.sh
Executable file
@ -0,0 +1,550 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Tuxflotte Installer
|
||||||
|
# Phase 2 – Hardware- und Geräteidentität
|
||||||
|
#
|
||||||
|
# Ermittelt:
|
||||||
|
# - DMI-/SMBIOS-Daten
|
||||||
|
# - System-UUID und Seriennummer
|
||||||
|
# - CPU-Architektur
|
||||||
|
# - physische Netzwerkinterfaces und MAC-Adressen
|
||||||
|
# - TPM-Verfügbarkeit
|
||||||
|
# - UEFI- und Secure-Boot-Status
|
||||||
|
#
|
||||||
|
# Ausgabe:
|
||||||
|
# /run/tuxflotte/hardware/hardware.json
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(
|
||||||
|
cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &&
|
||||||
|
pwd
|
||||||
|
)"
|
||||||
|
readonly SCRIPT_DIR
|
||||||
|
|
||||||
|
readonly COLLECTORS_FILE="${SCRIPT_DIR}/../lib/hardware_collectors.sh"
|
||||||
|
|
||||||
|
if [[ ! -r "${COLLECTORS_FILE}" ]]; then
|
||||||
|
printf '[%s] FEHLER: Collector-Library nicht gefunden: %s\n' \
|
||||||
|
"${0##*/}" \
|
||||||
|
"${COLLECTORS_FILE}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck source=../lib/hardware_collectors.sh
|
||||||
|
source "${COLLECTORS_FILE}"
|
||||||
|
|
||||||
|
readonly SCRIPT_NAME="${0##*/}"
|
||||||
|
|
||||||
|
readonly RUNTIME_DIR="/run/tuxflotte/hardware"
|
||||||
|
readonly HARDWARE_FILE="${RUNTIME_DIR}/hardware.json"
|
||||||
|
|
||||||
|
readonly SYS_DMI_DIR="/sys/class/dmi/id"
|
||||||
|
readonly SYS_NET_DIR="/sys/class/net"
|
||||||
|
readonly EFI_VARS_DIR="/sys/firmware/efi/efivars"
|
||||||
|
|
||||||
|
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 Hardwaremodul 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 -- "${HARDWARE_FILE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
read_trimmed_file() {
|
||||||
|
local file="$1"
|
||||||
|
local value
|
||||||
|
|
||||||
|
if [[ ! -r "${file}" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
value="$(tr -d '\000' <"${file}")"
|
||||||
|
|
||||||
|
value="$(
|
||||||
|
printf '%s' "${value}" |
|
||||||
|
sed \
|
||||||
|
-e 's/^[[:space:]]*//' \
|
||||||
|
-e 's/[[:space:]]*$//'
|
||||||
|
)"
|
||||||
|
|
||||||
|
printf '%s' "${value}"
|
||||||
|
}
|
||||||
|
|
||||||
|
read_dmi_value() {
|
||||||
|
local name="$1"
|
||||||
|
|
||||||
|
read_trimmed_file "${SYS_DMI_DIR}/${name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_uuid() {
|
||||||
|
local value="$1"
|
||||||
|
|
||||||
|
value="${value,,}"
|
||||||
|
|
||||||
|
case "${value}" in
|
||||||
|
""|\
|
||||||
|
"none"|\
|
||||||
|
"not specified"|\
|
||||||
|
"to be filled by o.e.m."|\
|
||||||
|
"00000000-0000-0000-0000-000000000000"|\
|
||||||
|
"ffffffff-ffff-ffff-ffff-ffffffffffff")
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printf '%s' "${value}"
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_serial() {
|
||||||
|
local value="$1"
|
||||||
|
local normalized
|
||||||
|
|
||||||
|
normalized="${value,,}"
|
||||||
|
|
||||||
|
case "${normalized}" in
|
||||||
|
""|\
|
||||||
|
"none"|\
|
||||||
|
"unknown"|\
|
||||||
|
"not specified"|\
|
||||||
|
"default string"|\
|
||||||
|
"system serial number"|\
|
||||||
|
"to be filled by o.e.m.")
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printf '%s' "${value}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_machine_id() {
|
||||||
|
local candidate
|
||||||
|
|
||||||
|
for candidate in \
|
||||||
|
/etc/machine-id \
|
||||||
|
/var/lib/dbus/machine-id
|
||||||
|
do
|
||||||
|
if [[ -r "${candidate}" ]]; then
|
||||||
|
read_trimmed_file "${candidate}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
get_architecture() {
|
||||||
|
uname -m
|
||||||
|
}
|
||||||
|
|
||||||
|
get_boot_mode() {
|
||||||
|
if [[ -d /sys/firmware/efi ]]; then
|
||||||
|
printf 'uefi'
|
||||||
|
else
|
||||||
|
printf 'bios'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_secure_boot_state() {
|
||||||
|
local secure_boot_file
|
||||||
|
local value
|
||||||
|
|
||||||
|
if [[ ! -d /sys/firmware/efi ]]; then
|
||||||
|
printf 'unsupported'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
secure_boot_file="$(
|
||||||
|
find "${EFI_VARS_DIR}" \
|
||||||
|
-maxdepth 1 \
|
||||||
|
-type f \
|
||||||
|
-name 'SecureBoot-*' \
|
||||||
|
-print \
|
||||||
|
-quit 2>/dev/null || true
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -z "${secure_boot_file}" || ! -r "${secure_boot_file}" ]]; then
|
||||||
|
printf 'unknown'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
value="$(
|
||||||
|
od \
|
||||||
|
--address-radix=n \
|
||||||
|
--format=u1 \
|
||||||
|
--skip-bytes=4 \
|
||||||
|
--read-bytes=1 \
|
||||||
|
"${secure_boot_file}" 2>/dev/null |
|
||||||
|
tr -d '[:space:]'
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "${value}" in
|
||||||
|
1)
|
||||||
|
printf 'enabled'
|
||||||
|
;;
|
||||||
|
0)
|
||||||
|
printf 'disabled'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'unknown'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
get_tpm_version() {
|
||||||
|
if [[ ! -e /dev/tpm0 && ! -e /dev/tpmrm0 ]]; then
|
||||||
|
printf 'none'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -r /sys/class/tpm/tpm0/tpm_version_major ]]; then
|
||||||
|
read_trimmed_file /sys/class/tpm/tpm0/tpm_version_major
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -r /sys/class/tpm/tpm0/device/description ]]; then
|
||||||
|
local description
|
||||||
|
|
||||||
|
description="$(
|
||||||
|
read_trimmed_file /sys/class/tpm/tpm0/device/description
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "${description}" in
|
||||||
|
*2.0*)
|
||||||
|
printf '2'
|
||||||
|
;;
|
||||||
|
*1.2*)
|
||||||
|
printf '1.2'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'unknown'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface_is_physical() {
|
||||||
|
local interface="$1"
|
||||||
|
|
||||||
|
[[ "${interface}" != "lo" ]] || return 1
|
||||||
|
[[ -e "${SYS_NET_DIR}/${interface}/device" ]] || return 1
|
||||||
|
[[ -r "${SYS_NET_DIR}/${interface}/address" ]] || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_interface_type() {
|
||||||
|
local interface="$1"
|
||||||
|
|
||||||
|
if [[ -d "${SYS_NET_DIR}/${interface}/wireless" ]]; then
|
||||||
|
printf 'wifi'
|
||||||
|
else
|
||||||
|
printf 'ethernet'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
build_network_interfaces_json() {
|
||||||
|
local interface
|
||||||
|
local mac
|
||||||
|
local type
|
||||||
|
local -a interfaces=()
|
||||||
|
|
||||||
|
for interface_path in "${SYS_NET_DIR}"/*; do
|
||||||
|
[[ -e "${interface_path}" ]] || continue
|
||||||
|
|
||||||
|
interface="${interface_path##*/}"
|
||||||
|
|
||||||
|
interface_is_physical "${interface}" || continue
|
||||||
|
|
||||||
|
mac="$(read_trimmed_file "${interface_path}/address")"
|
||||||
|
type="$(get_interface_type "${interface}")"
|
||||||
|
|
||||||
|
[[ -n "${mac}" ]] || continue
|
||||||
|
|
||||||
|
interfaces+=("$(
|
||||||
|
jq \
|
||||||
|
--null-input \
|
||||||
|
--arg name "${interface}" \
|
||||||
|
--arg type "${type}" \
|
||||||
|
--arg mac "${mac,,}" \
|
||||||
|
'{
|
||||||
|
name: $name,
|
||||||
|
type: $type,
|
||||||
|
mac: $mac
|
||||||
|
}'
|
||||||
|
)")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "${#interfaces[@]}" -eq 0 ]]; then
|
||||||
|
printf '[]'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "${interfaces[@]}" |
|
||||||
|
jq --slurp 'sort_by(.type, .name)'
|
||||||
|
}
|
||||||
|
|
||||||
|
build_device_fingerprint() {
|
||||||
|
local system_uuid="$1"
|
||||||
|
local system_serial="$2"
|
||||||
|
local board_serial="$3"
|
||||||
|
local interfaces_json="$4"
|
||||||
|
local identity_material
|
||||||
|
local mac_addresses
|
||||||
|
|
||||||
|
mac_addresses="$(
|
||||||
|
jq \
|
||||||
|
--raw-output \
|
||||||
|
'.[].mac // empty' \
|
||||||
|
<<<"${interfaces_json}" |
|
||||||
|
tr '[:upper:]' '[:lower:]' |
|
||||||
|
sort -u |
|
||||||
|
paste -sd ',' -
|
||||||
|
)"
|
||||||
|
|
||||||
|
identity_material="$(
|
||||||
|
printf 'system_uuid=%s\n' "${system_uuid,,}"
|
||||||
|
printf 'system_serial=%s\n' "${system_serial,,}"
|
||||||
|
printf 'board_serial=%s\n' "${board_serial,,}"
|
||||||
|
printf 'mac_addresses=%s\n' "${mac_addresses}"
|
||||||
|
)"
|
||||||
|
|
||||||
|
printf '%s' "${identity_material}" |
|
||||||
|
sha256sum |
|
||||||
|
awk '{ print $1 }'
|
||||||
|
}
|
||||||
|
|
||||||
|
build_hardware_json() {
|
||||||
|
local system_uuid
|
||||||
|
local system_serial
|
||||||
|
local machine_id
|
||||||
|
local manufacturer
|
||||||
|
local product_name
|
||||||
|
local product_version
|
||||||
|
local board_vendor
|
||||||
|
local board_name
|
||||||
|
local board_serial
|
||||||
|
local bios_vendor
|
||||||
|
local bios_version
|
||||||
|
local architecture
|
||||||
|
local boot_mode
|
||||||
|
local secure_boot
|
||||||
|
local tpm_version
|
||||||
|
local interfaces_json
|
||||||
|
local device_fingerprint
|
||||||
|
local cpu_model
|
||||||
|
local cpu_count
|
||||||
|
local memory_bytes
|
||||||
|
local storage_devices_json
|
||||||
|
|
||||||
|
system_uuid="$(normalize_uuid "$(read_dmi_value product_uuid)")"
|
||||||
|
system_serial="$(normalize_serial "$(read_dmi_value product_serial)")"
|
||||||
|
machine_id="$(get_machine_id)"
|
||||||
|
|
||||||
|
manufacturer="$(read_dmi_value sys_vendor)"
|
||||||
|
product_name="$(read_dmi_value product_name)"
|
||||||
|
product_version="$(read_dmi_value product_version)"
|
||||||
|
|
||||||
|
board_vendor="$(read_dmi_value board_vendor)"
|
||||||
|
board_name="$(read_dmi_value board_name)"
|
||||||
|
board_serial="$(normalize_serial "$(read_dmi_value board_serial)")"
|
||||||
|
|
||||||
|
bios_vendor="$(read_dmi_value bios_vendor)"
|
||||||
|
bios_version="$(read_dmi_value bios_version)"
|
||||||
|
|
||||||
|
architecture="$(get_architecture)"
|
||||||
|
boot_mode="$(get_boot_mode)"
|
||||||
|
secure_boot="$(get_secure_boot_state)"
|
||||||
|
tpm_version="$(get_tpm_version)"
|
||||||
|
cpu_model="$(get_cpu_model)"
|
||||||
|
cpu_count="$(get_cpu_count)"
|
||||||
|
memory_bytes="$(get_memory_bytes)"
|
||||||
|
|
||||||
|
interfaces_json="$(build_network_interfaces_json)"
|
||||||
|
storage_devices_json="$(build_storage_devices_json)"
|
||||||
|
device_fingerprint="$(
|
||||||
|
build_device_fingerprint \
|
||||||
|
"${system_uuid}" \
|
||||||
|
"${system_serial}" \
|
||||||
|
"${board_serial}" \
|
||||||
|
"${interfaces_json}"
|
||||||
|
)"
|
||||||
|
|
||||||
|
jq \
|
||||||
|
--null-input \
|
||||||
|
--arg schema_version "1" \
|
||||||
|
--arg device_fingerprint "${device_fingerprint}" \
|
||||||
|
--arg system_uuid "${system_uuid}" \
|
||||||
|
--arg system_serial "${system_serial}" \
|
||||||
|
--arg machine_id "${machine_id}" \
|
||||||
|
--arg manufacturer "${manufacturer}" \
|
||||||
|
--arg product_name "${product_name}" \
|
||||||
|
--arg product_version "${product_version}" \
|
||||||
|
--arg board_vendor "${board_vendor}" \
|
||||||
|
--arg board_name "${board_name}" \
|
||||||
|
--arg board_serial "${board_serial}" \
|
||||||
|
--arg bios_vendor "${bios_vendor}" \
|
||||||
|
--arg bios_version "${bios_version}" \
|
||||||
|
--arg architecture "${architecture}" \
|
||||||
|
--arg boot_mode "${boot_mode}" \
|
||||||
|
--arg secure_boot "${secure_boot}" \
|
||||||
|
--arg tpm_version "${tpm_version}" \
|
||||||
|
--arg cpu_model "${cpu_model}" \
|
||||||
|
--argjson cpu_count "${cpu_count}" \
|
||||||
|
--argjson memory_bytes "${memory_bytes}" \
|
||||||
|
--argjson network_interfaces "${interfaces_json}" \
|
||||||
|
--argjson storage_devices "${storage_devices_json}" \
|
||||||
|
'{
|
||||||
|
schema_version: ($schema_version | tonumber),
|
||||||
|
|
||||||
|
identity: {
|
||||||
|
device_fingerprint: $device_fingerprint,
|
||||||
|
system_uuid: (
|
||||||
|
if $system_uuid == "" then null else $system_uuid end
|
||||||
|
),
|
||||||
|
system_serial: (
|
||||||
|
if $system_serial == "" then null else $system_serial end
|
||||||
|
),
|
||||||
|
board_serial: (
|
||||||
|
if $board_serial == "" then null else $board_serial end
|
||||||
|
),
|
||||||
|
machine_id: (
|
||||||
|
if $machine_id == "" then null else $machine_id end
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
system: {
|
||||||
|
manufacturer: (
|
||||||
|
if $manufacturer == "" then null else $manufacturer end
|
||||||
|
),
|
||||||
|
product_name: (
|
||||||
|
if $product_name == "" then null else $product_name end
|
||||||
|
),
|
||||||
|
product_version: (
|
||||||
|
if $product_version == "" then null else $product_version end
|
||||||
|
),
|
||||||
|
architecture: $architecture,
|
||||||
|
cpu: {
|
||||||
|
model: (
|
||||||
|
if $cpu_model == "" then null else $cpu_model end
|
||||||
|
),
|
||||||
|
logical_count: $cpu_count
|
||||||
|
},
|
||||||
|
memory_bytes: $memory_bytes,
|
||||||
|
},
|
||||||
|
|
||||||
|
mainboard: {
|
||||||
|
vendor: (
|
||||||
|
if $board_vendor == "" then null else $board_vendor end
|
||||||
|
),
|
||||||
|
name: (
|
||||||
|
if $board_name == "" then null else $board_name end
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
firmware: {
|
||||||
|
bios_vendor: (
|
||||||
|
if $bios_vendor == "" then null else $bios_vendor end
|
||||||
|
),
|
||||||
|
bios_version: (
|
||||||
|
if $bios_version == "" then null else $bios_version end
|
||||||
|
),
|
||||||
|
boot_mode: $boot_mode,
|
||||||
|
secure_boot: $secure_boot
|
||||||
|
},
|
||||||
|
|
||||||
|
security: {
|
||||||
|
tpm_version: $tpm_version
|
||||||
|
},
|
||||||
|
|
||||||
|
network_interfaces: $network_interfaces,
|
||||||
|
storage_devices: $storage_devices
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_hardware_identity() {
|
||||||
|
local uuid
|
||||||
|
local serial
|
||||||
|
local board_serial
|
||||||
|
local mac_count
|
||||||
|
|
||||||
|
uuid="$(jq -r '.identity.system_uuid // empty' "${HARDWARE_FILE}")"
|
||||||
|
serial="$(jq -r '.identity.system_serial // empty' "${HARDWARE_FILE}")"
|
||||||
|
board_serial="$(jq -r '.identity.board_serial // empty' "${HARDWARE_FILE}")"
|
||||||
|
mac_count="$(jq '.network_interfaces | length' "${HARDWARE_FILE}")"
|
||||||
|
|
||||||
|
if [[ -z "${uuid}" &&
|
||||||
|
-z "${serial}" &&
|
||||||
|
-z "${board_serial}" &&
|
||||||
|
"${mac_count}" -eq 0 ]]; then
|
||||||
|
fatal "Es konnte kein stabiles Hardwaremerkmal ermittelt werden."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${uuid}" ]]; then
|
||||||
|
warn "Das Gerät stellt keine verwertbare System-UUID bereit."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${serial}" ]]; then
|
||||||
|
warn "Das Gerät stellt keine verwertbare Systemseriennummer bereit."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
require_root
|
||||||
|
require_command jq
|
||||||
|
require_command uname
|
||||||
|
require_command sed
|
||||||
|
require_command find
|
||||||
|
require_command od
|
||||||
|
require_command sha256sum
|
||||||
|
require_command sort
|
||||||
|
require_command paste
|
||||||
|
require_command tr
|
||||||
|
require_command awk
|
||||||
|
|
||||||
|
prepare_runtime_directory
|
||||||
|
|
||||||
|
log "Ermittle Hardware- und Geräteidentität."
|
||||||
|
|
||||||
|
umask 077
|
||||||
|
build_hardware_json >"${HARDWARE_FILE}"
|
||||||
|
|
||||||
|
chmod 0600 "${HARDWARE_FILE}"
|
||||||
|
|
||||||
|
jq --exit-status . "${HARDWARE_FILE}" >/dev/null ||
|
||||||
|
fatal "Die erzeugte Hardwaredatei enthält kein gültiges JSON."
|
||||||
|
|
||||||
|
validate_hardware_identity
|
||||||
|
|
||||||
|
log "Hardwareinformationen wurden unter ${HARDWARE_FILE} gespeichert."
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
139
scripts/modules/15_server_handshake.sh
Executable file
139
scripts/modules/15_server_handshake.sh
Executable file
@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
readonly SCRIPT_NAME="${0##*/}"
|
||||||
|
|
||||||
|
readonly NETWORK_STATE="/run/tuxflotte/network/state.env"
|
||||||
|
readonly HARDWARE_FILE="/run/tuxflotte/hardware/hardware.json"
|
||||||
|
|
||||||
|
readonly RUNTIME_DIR="/run/tuxflotte/server"
|
||||||
|
readonly ACTIVATION_FILE="${RUNTIME_DIR}/activation.json"
|
||||||
|
|
||||||
|
readonly RESPONSE_FILE="${RUNTIME_DIR}/response.json"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf '[%s] %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 Server-Handshake-Modul muss als root ausgeführt werden."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_runtime_directory() {
|
||||||
|
install -d \
|
||||||
|
--mode=0700 \
|
||||||
|
--owner=root \
|
||||||
|
--group=root \
|
||||||
|
"${RUNTIME_DIR}"
|
||||||
|
|
||||||
|
rm -f -- "${ACTIVATION_FILE}" "${RESPONSE_FILE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_inputs() {
|
||||||
|
[[ -r "${NETWORK_STATE}" ]] ||
|
||||||
|
fatal "Netzwerkstatus nicht gefunden: ${NETWORK_STATE}"
|
||||||
|
|
||||||
|
[[ -r "${HARDWARE_FILE}" ]] ||
|
||||||
|
fatal "Hardwareinformationen nicht gefunden: ${HARDWARE_FILE}"
|
||||||
|
|
||||||
|
jq --exit-status . "${HARDWARE_FILE}" >/dev/null ||
|
||||||
|
fatal "Hardwaredatei enthält kein gültiges JSON."
|
||||||
|
}
|
||||||
|
|
||||||
|
build_activation_request() {
|
||||||
|
local activation_code="$1"
|
||||||
|
local hostname
|
||||||
|
local machine_id
|
||||||
|
|
||||||
|
hostname="$(hostname)"
|
||||||
|
machine_id="$(
|
||||||
|
jq --raw-output \
|
||||||
|
'.identity.machine_id // empty' \
|
||||||
|
"${HARDWARE_FILE}"
|
||||||
|
)"
|
||||||
|
|
||||||
|
jq \
|
||||||
|
--null-input \
|
||||||
|
--arg activation_code "${activation_code}" \
|
||||||
|
--arg hostname "${hostname}" \
|
||||||
|
--arg machine_id "${machine_id}" \
|
||||||
|
--arg client_version "0.1.0" \
|
||||||
|
--slurpfile hardware "${HARDWARE_FILE}" \
|
||||||
|
'{
|
||||||
|
activation_code: $activation_code,
|
||||||
|
device_fingerprint: $hardware[0].identity.device_fingerprint,
|
||||||
|
hostname: $hostname,
|
||||||
|
machine_id: (
|
||||||
|
if $machine_id == ""
|
||||||
|
then null
|
||||||
|
else $machine_id
|
||||||
|
end
|
||||||
|
),
|
||||||
|
client_version: $client_version,
|
||||||
|
hardware: $hardware[0]
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
send_activation_request() {
|
||||||
|
local server_url
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "${NETWORK_STATE}"
|
||||||
|
|
||||||
|
server_url="${TUXFLOTTE_SERVER_URL%/health}"
|
||||||
|
|
||||||
|
curl \
|
||||||
|
--silent \
|
||||||
|
--show-error \
|
||||||
|
--fail \
|
||||||
|
--location \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data-binary "@${ACTIVATION_FILE}" \
|
||||||
|
--output "${RESPONSE_FILE}" \
|
||||||
|
"${server_url}/api/v1/activate" ||
|
||||||
|
fatal "Provisioning-Server konnte nicht erfolgreich kontaktiert werden."
|
||||||
|
|
||||||
|
chmod 0600 "${RESPONSE_FILE}"
|
||||||
|
|
||||||
|
jq --exit-status . "${RESPONSE_FILE}" >/dev/null ||
|
||||||
|
fatal "Serverantwort enthält kein gültiges JSON."
|
||||||
|
|
||||||
|
jq --exit-status '.success == true' "${RESPONSE_FILE}" >/dev/null ||
|
||||||
|
fatal "Provisioning-Server hat die Aktivierung abgelehnt."
|
||||||
|
|
||||||
|
log "Provisioning-Handshake erfolgreich abgeschlossen."
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
require_root
|
||||||
|
prepare_runtime_directory
|
||||||
|
validate_inputs
|
||||||
|
|
||||||
|
log "Eingabedaten für den Provisioning-Handshake sind gültig."
|
||||||
|
|
||||||
|
local activation_code
|
||||||
|
|
||||||
|
activation_code="${TUXFLOTTE_ACTIVATION_CODE:-LAB-2026-START}"
|
||||||
|
|
||||||
|
build_activation_request "${activation_code}" >"${ACTIVATION_FILE}"
|
||||||
|
|
||||||
|
chmod 0600 "${ACTIVATION_FILE}"
|
||||||
|
|
||||||
|
jq --exit-status . "${ACTIVATION_FILE}" >/dev/null ||
|
||||||
|
fatal "Aktivierungsrequest enthält kein gültiges JSON."
|
||||||
|
|
||||||
|
log "Aktivierungsrequest wurde unter ${ACTIVATION_FILE} gespeichert."
|
||||||
|
|
||||||
|
send_activation_request
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
14
scripts/modules/20_storage.sh
Executable file
14
scripts/modules/20_storage.sh
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
log_info "Datenträger werden erkannt..."
|
||||||
|
|
||||||
|
DISKS="$(list_install_disks || true)"
|
||||||
|
|
||||||
|
if [[ -z "$DISKS" ]]; then
|
||||||
|
error_exit "Keine geeigneten Datenträger erkannt."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$DISKS"
|
||||||
|
|
||||||
|
log_warn "Phase 1: Datenträger werden nur angezeigt, nicht verändert."
|
||||||
|
log_warn "Partitionierung ist noch deaktiviert."
|
||||||
0
scripts/modules/99_finish.sh
Executable file
0
scripts/modules/99_finish.sh
Executable file
Loading…
x
Reference in New Issue
Block a user