bridge.sh: die Erkennung herausgeloest und allein lauffaehig gemacht
Die Bruecken-Erkennung stand in network.sh, geschrieben fuer den stillgelegten Rettungssystem-Weg, und borgte sich vier Dinge von woanders: detect_primary_interface aus proxmox.sh, log, http_get und CLUPILOT_PROBE_URL aus clupilot-bootstrap.sh. Der Debian-Weg laedt die Bibliothek EINZELN auf den Host und faehrt sie dort — geborgte Helfer waeren dann nicht da. Also allein lauffaehig, mit log und http_get unter einem command-v-Schutz, damit der Bootstrap seine eigenen behaelt. Neu und aus dem laufenden Zustand abgeleitet: - interface_is_physical — bridge_ports auf einem Bond oder einer bestehenden Bridge ist falsch, und aus der Ferne nicht reparierbar. - address_is_dynamic — der Kernel markiert eine geleaste Adresse, das steht bei jedem Anbieter gleich da. Die Datei des Anbieters ist nur noch das Zweitsignal. Geprueft gegen eine echte sh mit aufgezeichneten ip-Ausgaben, plus die Zusicherung, dass es detect_primary_interface im Repo genau einmal gibt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>main
parent
1e570f17fc
commit
81df08aaa6
|
|
@ -965,6 +965,10 @@ main() {
|
|||
CLUPILOT_BOOTSTRAP_DIR="$(resolve_bootstrap_dir)"
|
||||
# shellcheck source=lib/report.sh
|
||||
. "${CLUPILOT_BOOTSTRAP_DIR}/lib/report.sh"
|
||||
# VOR proxmox.sh und network.sh: bridge.sh hält seit dem Debian-Weg die
|
||||
# Brücke samt `detect_primary_interface`, und beide greifen darauf zu.
|
||||
# shellcheck source=lib/bridge.sh
|
||||
. "${CLUPILOT_BOOTSTRAP_DIR}/lib/bridge.sh"
|
||||
# shellcheck source=lib/proxmox.sh
|
||||
. "${CLUPILOT_BOOTSTRAP_DIR}/lib/proxmox.sh"
|
||||
# shellcheck source=lib/network.sh
|
||||
|
|
|
|||
|
|
@ -0,0 +1,184 @@
|
|||
# shellcheck shell=sh
|
||||
#
|
||||
# Die Brücke über die primäre Netzkarte — Erkennen, Sichern, Zeitgeber, Bauen,
|
||||
# Nachsehen.
|
||||
#
|
||||
# ---------------------------------------------------------------------------
|
||||
# Warum das eine eigene Datei ist
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# Diese Zeilen standen in `network.sh`, geschrieben für den stillgelegten
|
||||
# Rettungssystem-Weg, und borgten sich vier Dinge von woanders:
|
||||
# `detect_primary_interface` aus `proxmox.sh`, `log`, `http_get` und
|
||||
# `CLUPILOT_PROBE_URL` aus `clupilot-bootstrap.sh`. Der Debian-Weg lädt sie
|
||||
# einzeln auf den Host und fährt sie dort — geborgte Helfer wären dann nicht da.
|
||||
#
|
||||
# Also allein lauffähig. `network.sh` behält seine WireGuard- und
|
||||
# nftables-Hälfte; diese Datei wird von beiden Wegen benutzt und existiert genau
|
||||
# einmal. Eine zweite Fassung wären zwei Installationen, die bei jeder
|
||||
# Proxmox-Version nachgezogen werden müssten — und die zweite fiele erst auf,
|
||||
# wenn jemand sie benutzt.
|
||||
#
|
||||
# ---------------------------------------------------------------------------
|
||||
# Warum aus dem LAUFENDEN Zustand abgeleitet wird
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# Nicht aus `/etc/network/interfaces`. Was läuft, ist bei jedem Anbieter gleich
|
||||
# strukturiert; wie es aufgeschrieben wurde, nicht. Das ist der Teil, der
|
||||
# Hetzner und netcup zugleich trägt.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Stellschrauben — überschreibbar, damit die Bibliothek ohne Host prüfbar ist
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
CLUPILOT_BRIDGE="${CLUPILOT_BRIDGE:-vmbr0}"
|
||||
CLUPILOT_WORK_DIR="${CLUPILOT_WORK_DIR:-/var/lib/clupilot/bridge}"
|
||||
CLUPILOT_INTERFACES_FILE="${CLUPILOT_INTERFACES_FILE:-/etc/network/interfaces}"
|
||||
CLUPILOT_INTERFACES_D="${CLUPILOT_INTERFACES_D:-/etc/network/interfaces.d}"
|
||||
CLUPILOT_SYS_NET="${CLUPILOT_SYS_NET:-/sys/class/net}"
|
||||
CLUPILOT_IP="${CLUPILOT_IP:-ip}"
|
||||
CLUPILOT_IFRELOAD="${CLUPILOT_IFRELOAD:-ifreload}"
|
||||
CLUPILOT_PROBE_URL="${CLUPILOT_PROBE_URL:-http://deb.debian.org/}"
|
||||
CLUPILOT_ROLLBACK_UNIT="${CLUPILOT_ROLLBACK_UNIT:-clupilot-network-rollback}"
|
||||
CLUPILOT_NET_BACKUP="${CLUPILOT_NET_BACKUP:-/var/lib/clupilot/interfaces.vor-der-bruecke}"
|
||||
CLUPILOT_WG_HUB_PUBKEY="${CLUPILOT_WG_HUB_PUBKEY:-}"
|
||||
CLUPILOT_WG_HANDSHAKE_MAX_AGE="${CLUPILOT_WG_HANDSHAKE_MAX_AGE:-180}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Die zwei Helfer, die diese Datei früher geborgt hat
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# Unter Definitionsschutz: `clupilot-bootstrap.sh` bringt eigene mit, und wer
|
||||
# zuerst da ist, behält recht. Sonst überschriebe das Laden dieser Bibliothek
|
||||
# die Protokollierung des Rettungssystem-Wegs.
|
||||
|
||||
if ! command -v log >/dev/null 2>&1; then
|
||||
log() {
|
||||
printf '%s %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$1"
|
||||
}
|
||||
fi
|
||||
|
||||
if ! command -v http_get >/dev/null 2>&1; then
|
||||
http_get() {
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL --retry 3 --retry-delay 2 --max-time 20 "$1"
|
||||
else
|
||||
wget -qO- --timeout=20 "$1"
|
||||
fi
|
||||
}
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Feststellen, was da ist
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Die Schnittstelle, über die die Vorgabe-Route geht. Nicht „die erste, die
|
||||
# nicht lo heißt": eine Maschine mit zwei Karten hat oft eine angeschlossene und
|
||||
# eine nicht.
|
||||
detect_primary_interface() {
|
||||
"$CLUPILOT_IP" -4 route show default 2>/dev/null \
|
||||
| awk '{ for (i = 1; i < NF; i++) if ($i == "dev") { print $(i+1); exit } }'
|
||||
}
|
||||
|
||||
# Ist das eine physische Karte?
|
||||
#
|
||||
# `bridge_ports` auf einem Bond oder einer bestehenden Bridge ist falsch — die
|
||||
# Brücke nähme sich ihren eigenen Unterbau als Port, und was dabei herauskommt,
|
||||
# ist von hier aus nicht mehr zu reparieren. VLAN-Geräte und Bonds haben keinen
|
||||
# `device`-Verweis; die beiden weiteren Prüfungen sind der Gürtel dazu.
|
||||
interface_is_physical() {
|
||||
_if="${1:-}"
|
||||
|
||||
[ -n "$_if" ] || return 1
|
||||
[ -e "${CLUPILOT_SYS_NET}/${_if}/device" ] || return 1
|
||||
[ ! -d "${CLUPILOT_SYS_NET}/${_if}/bridge" ] || return 1
|
||||
[ ! -d "${CLUPILOT_SYS_NET}/${_if}/bonding" ] || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Kam die Adresse per DHCP?
|
||||
#
|
||||
# Aus dem laufenden Zustand: der Kernel markiert eine geleaste Adresse mit
|
||||
# `dynamic`. Das steht bei jedem Anbieter gleich da — anders als die Datei, in
|
||||
# die er es geschrieben hat.
|
||||
address_is_dynamic() {
|
||||
"$CLUPILOT_IP" -4 -o addr show dev "$1" scope global 2>/dev/null \
|
||||
| grep -q '[[:space:]]dynamic[[:space:]]'
|
||||
}
|
||||
|
||||
# Wie der Anbieter das Netz aufzieht. Eine Brücke, die für den einen Fall
|
||||
# richtig ist, nimmt den anderen vom Netz.
|
||||
#
|
||||
# - `dhcp` — die Adresse kommt per DHCP (Cloud-Produkte).
|
||||
# - `routed` — geroutete Einzeladresse, Gateway AUSSERHALB des eigenen Subnetzes
|
||||
# (Hetzner-dediziert mit /32). Braucht eine pointopoint-Route,
|
||||
# sonst findet der Kernel das Gateway nicht.
|
||||
# - `subnet` — gewöhnliches Subnetz, Gateway darin.
|
||||
detect_network_style() {
|
||||
_iface="${1:-}"
|
||||
[ -n "$_iface" ] || _iface="$(detect_primary_interface)"
|
||||
|
||||
# Laufender Zustand zuerst. Die Datei des Anbieters ist nur das Zweitsignal
|
||||
# — sie sagt, was jemand aufgeschrieben hat, nicht was gilt.
|
||||
if address_is_dynamic "$_iface"; then
|
||||
printf 'dhcp'
|
||||
return 0
|
||||
fi
|
||||
|
||||
if grep -qsE "iface[[:space:]]+${_iface}[[:space:]]+inet[[:space:]]+dhcp" \
|
||||
"$CLUPILOT_INTERFACES_FILE" "${CLUPILOT_INTERFACES_D}"/* 2>/dev/null; then
|
||||
printf 'dhcp'
|
||||
return 0
|
||||
fi
|
||||
|
||||
_cidr="$("$CLUPILOT_IP" -4 -o addr show dev "$_iface" scope global 2>/dev/null | awk '{ print $4; exit }')"
|
||||
_gw="$("$CLUPILOT_IP" -4 route show default 2>/dev/null | awk '{ print $3; exit }')"
|
||||
_prefix="${_cidr##*/}"
|
||||
|
||||
# /32 heißt: das eigene Subnetz besteht aus der eigenen Adresse. Ein Gateway
|
||||
# darin kann es nicht geben.
|
||||
if [ "$_prefix" = '32' ]; then
|
||||
printf 'routed'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Liegt das Gateway im eigenen Subnetz? `ip route get` beantwortet das, ohne
|
||||
# dass dieses Skript Netzmasken rechnen muss — und rechnet dabei mit
|
||||
# derselben Logik, die der Kernel später anwendet.
|
||||
if [ -n "$_gw" ] && "$CLUPILOT_IP" -4 route get "$_gw" 2>/dev/null | grep -q "dev ${_iface}.*src"; then
|
||||
printf 'subnet'
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf 'routed'
|
||||
}
|
||||
|
||||
bridge_exists() {
|
||||
"$CLUPILOT_IP" link show "${1:-$CLUPILOT_BRIDGE}" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Trägt die Brücke wirklich den Verkehr, oder existiert sie nur?
|
||||
#
|
||||
# Eine `vmbr0` ohne Adresse und ohne Vorgaberoute ist eine Brücke im Sinne von
|
||||
# `ip link show` und sonst nichts. Der alte Schritt prüfte genau das und warf das
|
||||
# Ergebnis weg; sein Kommentar behauptete, er halte die Abwesenheit fest, und er
|
||||
# hielt nichts fest.
|
||||
bridge_carries_default_route() {
|
||||
_want="${1:-$CLUPILOT_BRIDGE}"
|
||||
_dev="$("$CLUPILOT_IP" -4 route show default 2>/dev/null \
|
||||
| awk '{ for (i = 1; i < NF; i++) if ($i == "dev") { print $(i+1); exit } }')"
|
||||
|
||||
[ "$_dev" = "$_want" ]
|
||||
}
|
||||
|
||||
bridge_has_address() {
|
||||
[ -n "$("$CLUPILOT_IP" -4 -o addr show dev "${1:-$CLUPILOT_BRIDGE}" scope global 2>/dev/null | awk '{ print $4; exit }')" ]
|
||||
}
|
||||
|
||||
# Die drei zusammen. „Da" reicht nicht — sie muss tragen.
|
||||
bridge_is_up() {
|
||||
bridge_exists "$CLUPILOT_BRIDGE" \
|
||||
&& bridge_carries_default_route "$CLUPILOT_BRIDGE" \
|
||||
&& bridge_has_address "$CLUPILOT_BRIDGE"
|
||||
}
|
||||
|
|
@ -37,70 +37,20 @@
|
|||
# Die Sperre steht deshalb in Abschnitt `registered` (Task 9), an derselben
|
||||
# Stelle wie früher: nach dem bewiesenen Handshake, nach Traefik.
|
||||
|
||||
CLUPILOT_ROLLBACK_UNIT='clupilot-network-rollback'
|
||||
CLUPILOT_NET_BACKUP='/var/lib/clupilot/interfaces.vor-der-bruecke'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Feststellen, was da ist
|
||||
# Feststellen, was da ist — steht jetzt in lib/bridge.sh
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
bridge_exists() {
|
||||
ip link show "${1:-vmbr0}" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Trägt die Brücke wirklich den Verkehr, oder existiert sie nur?
|
||||
#
|
||||
# Eine `vmbr0` ohne Adresse und ohne Vorgaberoute ist eine Brücke im Sinne von
|
||||
# `ip link show` und sonst nichts. Der alte Schritt prüfte genau das und warf das
|
||||
# Ergebnis weg; sein Kommentar behauptete, er halte die Abwesenheit fest, und er
|
||||
# hielt nichts fest.
|
||||
bridge_carries_default_route() {
|
||||
_dev="$(ip -4 route show default 2>/dev/null | awk '{ for (i = 1; i < NF; i++) if ($i == "dev") { print $(i+1); exit } }')"
|
||||
[ "$_dev" = "${1:-vmbr0}" ]
|
||||
}
|
||||
|
||||
bridge_has_address() {
|
||||
[ -n "$(ip -4 -o addr show dev "${1:-vmbr0}" scope global 2>/dev/null | awk '{ print $4; exit }')" ]
|
||||
}
|
||||
|
||||
# Wie der Anbieter das Netz aufzieht. Eine Brücke, die für den einen Fall
|
||||
# richtig ist, nimmt den anderen vom Netz.
|
||||
# `bridge_exists`, `bridge_carries_default_route`, `bridge_has_address`,
|
||||
# `detect_network_style`, `detect_primary_interface` und die beiden Variablen
|
||||
# `CLUPILOT_ROLLBACK_UNIT`/`CLUPILOT_NET_BACKUP` sind nach `lib/bridge.sh`
|
||||
# gezogen, samt der Helfer, die sie sich früher aus `clupilot-bootstrap.sh`
|
||||
# borgten.
|
||||
#
|
||||
# - `dhcp` — die Adresse kommt per DHCP (Cloud-Produkte).
|
||||
# - `routed` — geroutete Einzeladresse, Gateway AUSSERHALB des eigenen Subnetzes
|
||||
# (Hetzner-dediziert mit /32). Braucht eine pointopoint-Route,
|
||||
# sonst findet der Kernel das Gateway nicht.
|
||||
# - `subnet` — gewöhnliches Subnetz, Gateway darin.
|
||||
detect_network_style() {
|
||||
_iface="${1:-}"
|
||||
[ -n "$_iface" ] || _iface="$(detect_primary_interface)"
|
||||
|
||||
if grep -qsE "iface[[:space:]]+${_iface}[[:space:]]+inet[[:space:]]+dhcp" /etc/network/interfaces /etc/network/interfaces.d/* 2>/dev/null; then
|
||||
printf 'dhcp'
|
||||
return 0
|
||||
fi
|
||||
|
||||
_cidr="$(ip -4 -o addr show dev "$_iface" scope global 2>/dev/null | awk '{ print $4; exit }')"
|
||||
_gw="$(ip -4 route show default 2>/dev/null | awk '{ print $3; exit }')"
|
||||
_prefix="${_cidr##*/}"
|
||||
|
||||
# /32 heißt: das eigene Subnetz besteht aus der eigenen Adresse. Ein Gateway
|
||||
# darin kann es nicht geben.
|
||||
if [ "$_prefix" = '32' ]; then
|
||||
printf 'routed'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Liegt das Gateway im eigenen Subnetz? `ip route get` beantwortet das, ohne
|
||||
# dass dieses Skript Netzmasken rechnen muss — und rechnet dabei mit
|
||||
# derselben Logik, die der Kernel später anwendet.
|
||||
if [ -n "$_gw" ] && ip -4 route get "$_gw" 2>/dev/null | grep -q "dev ${_iface}.*src"; then
|
||||
printf 'subnet'
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf 'routed'
|
||||
}
|
||||
# Der Grund ist der Debian-Weg: dort lädt `EnsureNetworkBridge` die Bibliothek
|
||||
# EINZELN auf den Host und fährt sie, und geborgte Helfer wären dann nicht da.
|
||||
# `clupilot-bootstrap.sh` sourced `bridge.sh` vor dieser Datei, hier ändert sich
|
||||
# also nichts — außer, dass es die Brücke nur noch einmal im Repo gibt.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Die Selbstrücknahme
|
||||
|
|
|
|||
|
|
@ -126,12 +126,10 @@ detect_boot_mode() {
|
|||
fi
|
||||
}
|
||||
|
||||
# Die Schnittstelle, über die die Vorgabe-Route geht. Nicht „die erste, die
|
||||
# nicht lo heißt": eine Maschine mit zwei Karten hat oft eine angeschlossene und
|
||||
# eine nicht.
|
||||
detect_primary_interface() {
|
||||
ip -4 route show default 2>/dev/null | awk '{ for (i = 1; i < NF; i++) if ($i == "dev") { print $(i+1); exit } }'
|
||||
}
|
||||
# `detect_primary_interface` steht in lib/bridge.sh — eine Fassung, zwei
|
||||
# Benutzer. clupilot-bootstrap.sh lädt bridge.sh vor dieser Datei, und die
|
||||
# Funktion wird ohnehin erst zur Aufrufzeit aufgelöst, also ist die Reihenfolge
|
||||
# unkritisch.
|
||||
|
||||
# Setzt CLUPILOT_NET_CIDR, _GATEWAY, _DNS, _MAC aus dem, was das Rettungssystem
|
||||
# gerade benutzt.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,194 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Process;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| bridge.sh, gegen eine echte Shell gefahren
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Der Rest der Testsuite liest die Befehle dieses Schritts als Zeichenketten —
|
||||
| FakeRemoteShell führt keinen davon aus. Die Strophe, die diese Bibliothek
|
||||
| schreibt, ist aber genau das, was eine Maschine umbringt, wenn sie falsch
|
||||
| ist. Deshalb wird bridge.sh hier mit `sh` wirklich ausgeführt, gegen
|
||||
| aufgezeichnete Ausgaben von `ip` statt gegen einen Host.
|
||||
|
|
||||
| `CLUPILOT_IP` zeigt dabei auf ein Attrappen-Skript, `CLUPILOT_SYS_NET` auf
|
||||
| einen Baum aus Textdateien. Beides ist die einzige Berührung mit dem System,
|
||||
| die die Bibliothek hat.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Legt eine `ip`-Attrappe an, die auf feste Argumentmuster feste Ausgaben gibt.
|
||||
*
|
||||
* @param array<string, string> $answers Argumentmuster => Ausgabe
|
||||
*/
|
||||
function fakeIp(string $dir, array $answers): string
|
||||
{
|
||||
$cases = '';
|
||||
foreach ($answers as $pattern => $output) {
|
||||
$cases .= " '{$pattern}') cat <<'OUT'\n{$output}\nOUT\n ;;\n";
|
||||
}
|
||||
|
||||
$script = "#!/bin/sh\ncase \"\$*\" in\n{$cases} *) exit 1 ;;\nesac\n";
|
||||
file_put_contents("{$dir}/ip", $script);
|
||||
chmod("{$dir}/ip", 0o755);
|
||||
|
||||
return "{$dir}/ip";
|
||||
}
|
||||
|
||||
/**
|
||||
* Fährt eine Zeile Shell mit bridge.sh im Rücken und gibt stdout zurück.
|
||||
*
|
||||
* `$pre` läuft VOR dem Sourcen. Damit kann ein Test `log` oder `http_get` selbst
|
||||
* definieren — bridge.sh legt beide unter einem `command -v`-Schutz an, wer
|
||||
* zuerst da ist, behält recht. Das ist zugleich die Probe auf diesen Schutz und
|
||||
* spart dem Test eine Abhängigkeit von curl im Container.
|
||||
*/
|
||||
function runBridgeSh(string $body, array $env = [], string $pre = ''): string
|
||||
{
|
||||
$lib = base_path('deploy/bootstrap/lib/bridge.sh');
|
||||
$exports = '';
|
||||
foreach ($env as $key => $value) {
|
||||
$exports .= "export {$key}=".escapeshellarg($value)."\n";
|
||||
}
|
||||
|
||||
$result = Process::input("{$exports}{$pre}\n. '{$lib}'\n{$body}\n")->run('sh');
|
||||
|
||||
return trim($result->output());
|
||||
}
|
||||
|
||||
/** Ein /sys/class/net-Baum aus Textdateien. */
|
||||
function fakeSysNet(string $dir, string $iface, string $mac, bool $physical = true): string
|
||||
{
|
||||
$root = "{$dir}/sys";
|
||||
mkdir("{$root}/{$iface}", 0o755, true);
|
||||
file_put_contents("{$root}/{$iface}/address", $mac."\n");
|
||||
if ($physical) {
|
||||
mkdir("{$root}/{$iface}/device", 0o755, true);
|
||||
}
|
||||
|
||||
return $root;
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
$this->dir = sys_get_temp_dir().'/bridge-'.bin2hex(random_bytes(6));
|
||||
mkdir($this->dir, 0o755, true);
|
||||
});
|
||||
|
||||
it('nimmt die Karte, über die die Standardroute geht — nicht die erste beste', function () {
|
||||
// Eine Maschine mit zwei Karten hat oft eine angeschlossene und eine nicht.
|
||||
$ip = fakeIp($this->dir, [
|
||||
'-4 route show default' => 'default via 49.12.121.65 dev enp0s31f6 proto static',
|
||||
]);
|
||||
|
||||
expect(runBridgeSh('detect_primary_interface', ['CLUPILOT_IP' => $ip]))
|
||||
->toBe('enp0s31f6');
|
||||
});
|
||||
|
||||
it('erkennt eine geroutete Einzeladresse an der /32', function () {
|
||||
// Hetzner dediziert. Das eigene Subnetz besteht aus der eigenen Adresse —
|
||||
// ein Gateway darin kann es nicht geben, es braucht pointopoint.
|
||||
$ip = fakeIp($this->dir, [
|
||||
'-4 route show default' => 'default via 49.12.121.65 dev enp0s31f6',
|
||||
'-4 -o addr show dev enp0s31f6 scope global' => '2: enp0s31f6 inet 49.12.121.79/32 scope global enp0s31f6\ valid_lft forever preferred_lft forever',
|
||||
]);
|
||||
|
||||
expect(runBridgeSh('detect_network_style enp0s31f6', ['CLUPILOT_IP' => $ip]))
|
||||
->toBe('routed');
|
||||
});
|
||||
|
||||
it('erkennt DHCP am laufenden Zustand, nicht an der Datei des Anbieters', function () {
|
||||
// Der Kernel markiert eine geleaste Adresse als `dynamic`. Das steht bei
|
||||
// jedem Anbieter gleich da — anders als die Datei, in die er es geschrieben
|
||||
// hat.
|
||||
$ip = fakeIp($this->dir, [
|
||||
'-4 route show default' => 'default via 10.0.0.1 dev ens3',
|
||||
'-4 -o addr show dev ens3 scope global' => '2: ens3 inet 10.0.0.7/24 brd 10.0.0.255 scope global dynamic ens3\ valid_lft 3521sec preferred_lft 3521sec',
|
||||
]);
|
||||
|
||||
expect(runBridgeSh('detect_network_style ens3', [
|
||||
'CLUPILOT_IP' => $ip,
|
||||
// Absichtlich auf eine Datei zeigen, die es nicht gibt: die Antwort darf
|
||||
// nicht davon abhängen.
|
||||
'CLUPILOT_INTERFACES_FILE' => $this->dir.'/gibtesnicht',
|
||||
]))->toBe('dhcp');
|
||||
});
|
||||
|
||||
it('hält eine Bridge und einen Bond für keine physische Karte', function () {
|
||||
// bridge_ports auf einem Bond oder einer bestehenden Bridge ist falsch: die
|
||||
// Brücke nähme sich ihren eigenen Unterbau als Port.
|
||||
$sys = fakeSysNet($this->dir, 'bond0', 'aa:bb:cc:dd:ee:ff', physical: false);
|
||||
mkdir("{$sys}/bond0/bonding", 0o755, true);
|
||||
|
||||
$out = runBridgeSh(
|
||||
'if interface_is_physical bond0; then echo JA; else echo NEIN; fi',
|
||||
['CLUPILOT_SYS_NET' => $sys]
|
||||
);
|
||||
|
||||
expect($out)->toBe('NEIN');
|
||||
});
|
||||
|
||||
it('hält eine gewöhnliche Karte für eine physische', function () {
|
||||
$sys = fakeSysNet($this->dir, 'enp0s31f6', 'a8:a1:59:00:11:22');
|
||||
|
||||
$out = runBridgeSh(
|
||||
'if interface_is_physical enp0s31f6; then echo JA; else echo NEIN; fi',
|
||||
['CLUPILOT_SYS_NET' => $sys]
|
||||
);
|
||||
|
||||
expect($out)->toBe('JA');
|
||||
});
|
||||
|
||||
it('sagt nur dann „Brücke steht", wenn sie Route UND Adresse trägt', function () {
|
||||
// Eine vmbr0 ohne Adresse und ohne Standardroute ist eine Brücke im Sinne
|
||||
// von `ip link show` und sonst nichts. Der alte Schritt prüfte genau das
|
||||
// und warf das Ergebnis weg; sein Kommentar behauptete, er halte die
|
||||
// Abwesenheit fest, und er hielt nichts fest.
|
||||
$ip = fakeIp($this->dir, [
|
||||
'link show vmbr0' => '5: vmbr0: <BROADCAST,MULTICAST,UP> mtu 1500',
|
||||
'-4 route show default' => 'default via 49.12.121.65 dev enp0s31f6',
|
||||
'-4 -o addr show dev vmbr0 scope global' => '',
|
||||
]);
|
||||
|
||||
$out = runBridgeSh(
|
||||
'if bridge_is_up; then echo JA; else echo NEIN; fi',
|
||||
['CLUPILOT_IP' => $ip]
|
||||
);
|
||||
|
||||
expect($out)->toBe('NEIN');
|
||||
});
|
||||
|
||||
it('hält detect_primary_interface an genau einer Stelle im Repo', function () {
|
||||
// Zwei Fassungen wären zwei Installationen, die bei jeder Proxmox-Version
|
||||
// nachgezogen werden müssten — und die zweite fiele erst auf, wenn jemand
|
||||
// sie benutzt. Genau die Entscheidung, die den Rettungssystem-Weg
|
||||
// stillgelegt hat.
|
||||
//
|
||||
// array_merge, NICHT `+`: bei numerischen Schlüsseln behält der
|
||||
// Vereinigungsoperator die linke Seite und wirft die rechte still weg. Und
|
||||
// PHPs glob() kennt kein rekursives `**`, also zwei Muster.
|
||||
$files = array_merge(
|
||||
glob(base_path('deploy/bootstrap/lib/*.sh')) ?: [],
|
||||
glob(base_path('deploy/bootstrap/*.sh')) ?: [],
|
||||
);
|
||||
|
||||
$hits = [];
|
||||
foreach ($files as $file) {
|
||||
if (preg_match('/^detect_primary_interface\(\)/m', (string) file_get_contents($file))) {
|
||||
$hits[] = str_replace(base_path().'/', '', $file);
|
||||
}
|
||||
}
|
||||
|
||||
expect($hits)->toBe(['deploy/bootstrap/lib/bridge.sh']);
|
||||
});
|
||||
|
||||
it('lässt jede Bibliothek des Bootstraps von einer Shell parsen', function () {
|
||||
// network.sh und proxmox.sh werden beim Herauslösen beschnitten. Ein
|
||||
// verrutschter Schnitt fiele sonst erst auf dem Rettungssystem-Weg auf, und
|
||||
// der wird selten gefahren.
|
||||
foreach (glob(base_path('deploy/bootstrap/lib/*.sh')) ?: [] as $file) {
|
||||
expect(Process::run(['sh', '-n', $file])->successful())
|
||||
->toBeTrue('kein gültiges POSIX-sh: '.$file);
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue