_mint_image_boot_medium_disk() aus backend.sh nach scripts/lib/boot_medium.sh verschoben (tuxflotte_boot_medium_disk()) - build_storage_devices_json() (hardware_collectors.sh) braucht dieselbe Ausschluss-Logik jetzt ebenfalls. Nutzer-Feedback (01.09.2026, echter Hardware-Test): der Installations- USB-Stick selbst tauchte in der Kundenplattform unter Flotte -> Aktionen -> Hardware-Info -> Datenträger auf - fuer den Kunden irrefuehrend, da dieser Datentraeger nach der Installation gar nicht mehr existiert. Gleiche Ursache wie der parted-Bug (Commit fa54461): lsblk liefert das Boot-Medium auf echter USB-Stick-Hardware als normalen TYPE="disk" zurueck.
561 lines
14 KiB
Bash
Executable File
561 lines
14 KiB
Bash
Executable File
#!/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"
|
||
readonly BOOT_MEDIUM_FILE="${SCRIPT_DIR}/../lib/boot_medium.sh"
|
||
|
||
if [[ ! -r "${COLLECTORS_FILE}" ]]; then
|
||
printf '[%s] FEHLER: Collector-Library nicht gefunden: %s\n' \
|
||
"${0##*/}" \
|
||
"${COLLECTORS_FILE}" >&2
|
||
exit 1
|
||
fi
|
||
|
||
if [[ ! -r "${BOOT_MEDIUM_FILE}" ]]; then
|
||
printf '[%s] FEHLER: Boot-Medium-Library nicht gefunden: %s\n' \
|
||
"${0##*/}" \
|
||
"${BOOT_MEDIUM_FILE}" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# shellcheck source=../lib/hardware_collectors.sh
|
||
source "${COLLECTORS_FILE}"
|
||
# shellcheck source=../lib/boot_medium.sh
|
||
source "${BOOT_MEDIUM_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 "$@"
|