Version 2.8.44 / Lizenzserver 2.8.22: Client-Konfiguration am richtigen Ort, Fusszeile bleibt stehen
This commit is contained in:
1 parent
f59ba42e55
commit
29b62714cb
5 files changed
+115
-25
No files matched your search
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "tesm"
|
||||
version = "2.8.43"
|
||||
version = "2.8.44"
|
||||
description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
|
||||
@@ -11,7 +11,7 @@ einen Dienst starten.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import shlex
|
||||
from typing import Any
|
||||
|
||||
from flask import Blueprint, abort, flash, redirect, render_template, request, url_for
|
||||
@@ -684,16 +684,12 @@ def push_client_config():
|
||||
flash("Es wurde kein Gerät ausgewählt.", "warning")
|
||||
return redirect(url_for("dhcp.overview"))
|
||||
|
||||
# Ueber base64 statt ueber ein Here-Dokument: der Inhalt enthaelt
|
||||
# Anfuehrungszeichen, Rauten und Zeilenumbrueche, und er geht durch SSH,
|
||||
# eine Shell und moeglicherweise sudo. Was kodiert reist, kommt an.
|
||||
inhalt = base64.b64encode(konfig["inhalt"].encode("utf-8")).decode("ascii")
|
||||
verzeichnis = str(konfig["pfad"]).rsplit("/", 1)[0]
|
||||
befehl = (
|
||||
f"{{sudo}} mkdir -p {verzeichnis} && "
|
||||
f"echo {inhalt} | base64 -d | {{sudo}} tee {konfig['pfad']} >/dev/null && "
|
||||
f"{{sudo}} sh -c '{konfig['nachher']}' || true"
|
||||
)
|
||||
# Ein Skript, ein Aufruf. Der frühere Weg schrieb auf einen festen Pfad --
|
||||
# bei networkd auf einen, den kein Dienst liest -- und überschrieb bei
|
||||
# dhcpcd/dhclient die Datei des Clients. Das Skript sucht sich das Ziel
|
||||
# selbst und ersetzt nur seinen eigenen Block; es geht als **Argument**
|
||||
# durch, damit stdin für das sudo-Passwort frei bleibt.
|
||||
befehl = "{sudo} sh -c " + shlex.quote(konfig["skript"])
|
||||
|
||||
gestartet = 0
|
||||
for device_id in device_ids:
|
||||
|
||||
@@ -746,7 +746,11 @@ IMMER_ANGEFRAGT = frozenset({1, 3, 6, 12, 15, 28, 42, 51, 54, 58, 59, 119, 121})
|
||||
#: * **dhclient** -- Debian 11 bis 13 und Ubuntu 22.04, dort wo isc-dhcp-client
|
||||
#: noch installiert ist.
|
||||
CLIENT_STILE: tuple[tuple[str, str, str], ...] = (
|
||||
("networkd", "systemd-networkd", "/etc/systemd/network/10-tesm.network.d/50-tesm.conf"),
|
||||
# Der Zusatz gehoert zu der .network-Datei, die fuer die Schnittstelle
|
||||
# wirklich gilt -- bei netplan heisst die 10-netplan-<if>.network und liegt
|
||||
# unter /run. Ein Verzeichnis "10-tesm.network.d" liest niemand: es gibt
|
||||
# keine 10-tesm.network. Den Namen ermittelt das Skript auf dem Client.
|
||||
("networkd", "systemd-networkd", "/etc/systemd/network/<aktive>.network.d/50-tesm.conf"),
|
||||
("dhcpcd", "dhcpcd", "/etc/dhcpcd.conf"),
|
||||
("dhclient", "dhclient (isc-dhcp-client)", "/etc/dhcp/dhclient.conf"),
|
||||
)
|
||||
@@ -789,6 +793,40 @@ def gesendete_optionen(conn: sqlite3.Connection) -> list[dict[str, Any]]:
|
||||
)
|
||||
|
||||
|
||||
#: Traegt den Zusatz dort ein, wo networkd ihn auch liest.
|
||||
NETWORKD_SKRIPT = """set -e
|
||||
schnittstelle=$(ip -o -4 route show default 2>/dev/null | awk '{print $5; exit}')
|
||||
[ -n "$schnittstelle" ] || { echo "Keine Standardroute -- Schnittstelle unbekannt." >&2; exit 1; }
|
||||
datei=$(COLUMNS=400 SYSTEMD_COLORS=0 networkctl status "$schnittstelle" 2>/dev/null | sed -n 's/^ *Network File: *//p' | head -n1)
|
||||
case "${datei:-}" in
|
||||
""|n/a|*not*) datei=$(grep -ls "Name=$schnittstelle" /run/systemd/network/*.network /etc/systemd/network/*.network 2>/dev/null | head -n1) ;;
|
||||
esac
|
||||
[ -n "${datei:-}" ] || { echo "Keine .network-Datei fuer $schnittstelle gefunden." >&2; exit 1; }
|
||||
ziel="/etc/systemd/network/$(basename "$datei").d/50-tesm.conf"
|
||||
mkdir -p "$(dirname "$ziel")"
|
||||
printf %s '@FRACHT@' | base64 -d > "$ziel"
|
||||
chmod 0644 "$ziel"
|
||||
echo "geschrieben: $ziel"
|
||||
networkctl reload
|
||||
networkctl reconfigure "$schnittstelle"
|
||||
echo "Lease: /run/systemd/netif/leases/$(cat /sys/class/net/"$schnittstelle"/ifindex)"
|
||||
"""
|
||||
|
||||
#: Ersetzt den eigenen Block in einer Datei, die dem Client gehoert.
|
||||
BLOCK_SKRIPT = """set -e
|
||||
ziel=@ZIEL@
|
||||
mkdir -p "$(dirname "$ziel")"
|
||||
[ -f "$ziel" ] || : > "$ziel"
|
||||
zwischen=$(mktemp)
|
||||
sed '/^# >>> TESM/,/^# <<< TESM/d' "$ziel" > "$zwischen"
|
||||
printf %s '@FRACHT@' | base64 -d >> "$zwischen"
|
||||
cat "$zwischen" > "$ziel"
|
||||
rm -f "$zwischen"
|
||||
echo "ergaenzt: $ziel"
|
||||
@DIENST@
|
||||
"""
|
||||
|
||||
|
||||
def client_konfiguration(conn: sqlite3.Connection, stil: str) -> dict[str, Any]:
|
||||
"""Was ein Linux-Client braucht, damit die Optionen bei ihm ankommen.
|
||||
|
||||
@@ -798,8 +836,10 @@ def client_konfiguration(conn: sqlite3.Connection, stil: str) -> dict[str, Any]:
|
||||
ausdruecklich anfordern. Ohne diese Zeilen sendet der Server brav und der
|
||||
Client wirft es weg, ohne dass irgendwo etwas danebengeht.
|
||||
|
||||
Rueckgabe: ``{"pfad", "inhalt", "nachher", "optionen"}`` -- Datei, Inhalt,
|
||||
der Befehl zum Uebernehmen und die Optionen, um die es geht.
|
||||
Rueckgabe: ``{"pfad", "inhalt", "nachher", "skript", "optionen"}``.
|
||||
``skript`` ist die Fassung zum Ausfuehren -- sie sucht sich das Ziel auf
|
||||
dem Client selbst und traegt den Inhalt ein, ohne Vorhandenes zu
|
||||
verlieren.
|
||||
"""
|
||||
optionen = [
|
||||
eintrag
|
||||
@@ -814,20 +854,22 @@ def client_konfiguration(conn: sqlite3.Connection, stil: str) -> dict[str, Any]:
|
||||
"# Von TESM erzeugt. Fordert die Optionen an, die dieser Server sendet --",
|
||||
"# ohne diese Zeilen verwirft der Client sie stillschweigend.",
|
||||
"#",
|
||||
"# Der Dateiname muss zur .network-Datei der Schnittstelle passen:",
|
||||
"# netplan legt sie unter /run/systemd/network/10-netplan-<if>.network ab,",
|
||||
"# der Zusatz gehoert also nach",
|
||||
"# Diese Datei ist ein Zusatz zu der .network-Datei, die fuer die",
|
||||
"# Schnittstelle gilt. Wie sie heisst, sagt 'networkctl status <if>';",
|
||||
"# bei netplan ist es /run/systemd/network/10-netplan-<if>.network, der",
|
||||
"# Zusatz gehoert dann nach",
|
||||
"# /etc/systemd/network/10-netplan-<if>.network.d/50-tesm.conf.",
|
||||
"#",
|
||||
"# Die Werte stehen danach in /run/systemd/netif/leases/<ifindex>",
|
||||
"# als OPTION_<code>=...",
|
||||
"# als OPTION_<code>= -- hexkodiert, weil systemd fremde Optionen nicht",
|
||||
"# deutet.",
|
||||
"[DHCPv4]",
|
||||
]
|
||||
zeilen.append("RequestOptions=" + " ".join(str(code) for code in codes))
|
||||
nachher = "networkctl reload && networkctl reconfigure <schnittstelle>"
|
||||
nachher = "networkctl reload && networkctl reconfigure <Schnittstelle>"
|
||||
elif stil == "dhcpcd":
|
||||
zeilen = [
|
||||
"# Von TESM erzeugt -- an das Ende von /etc/dhcpcd.conf.",
|
||||
"# >>> TESM -- nicht von Hand aendern, wird beim naechsten Mal ersetzt",
|
||||
"# 'define' macht die Option bekannt, 'option' fordert sie an.",
|
||||
"# Danach steht der Wert in der Lease (/var/lib/dhcpcd/<if>.lease)",
|
||||
"# und laesst sich mit 'dhcpcd -U <if>' als Variable auslesen.",
|
||||
@@ -837,10 +879,11 @@ def client_konfiguration(conn: sqlite3.Connection, stil: str) -> dict[str, Any]:
|
||||
zeilen.append(f"define {eintrag['code']} {typ} {eintrag['name']}")
|
||||
for eintrag in optionen:
|
||||
zeilen.append(f"option {eintrag['name']}")
|
||||
zeilen.append("# <<< TESM")
|
||||
nachher = "systemctl restart dhcpcd"
|
||||
else:
|
||||
zeilen = [
|
||||
"# Von TESM erzeugt -- an das Ende von /etc/dhcp/dhclient.conf.",
|
||||
"# >>> TESM -- nicht von Hand aendern, wird beim naechsten Mal ersetzt",
|
||||
"# Die Werte stehen danach in /var/lib/dhcp/dhclient.leases,",
|
||||
"# und in Hooks unter $new_<name>.",
|
||||
]
|
||||
@@ -851,16 +894,57 @@ def client_konfiguration(conn: sqlite3.Connection, stil: str) -> dict[str, Any]:
|
||||
zeilen.append(
|
||||
"also request " + ", ".join(str(eintrag["name"]) for eintrag in optionen) + ";"
|
||||
)
|
||||
zeilen.append("# <<< TESM")
|
||||
nachher = "systemctl restart networking || dhclient -r && dhclient"
|
||||
|
||||
inhalt = chr(10).join(zeilen) + chr(10)
|
||||
return {
|
||||
"pfad": pfad,
|
||||
"inhalt": chr(10).join(zeilen) + chr(10),
|
||||
"inhalt": inhalt,
|
||||
"nachher": nachher,
|
||||
"skript": _uebernahme_skript(stil, inhalt),
|
||||
"optionen": optionen,
|
||||
}
|
||||
|
||||
|
||||
def _uebernahme_skript(stil: str, inhalt: str) -> str:
|
||||
"""Das Skript, das die Konfiguration auf dem Client eintraegt.
|
||||
|
||||
Zwei Dinge macht es anders als ein blosses ``tee``, und beide sind mit
|
||||
Schaden bezahlt worden:
|
||||
|
||||
* Bei ``networkd`` **sucht es das Ziel auf dem Client**. Der Name des
|
||||
Zusatzverzeichnisses muss zur aktiven .network-Datei passen; ein fester
|
||||
Pfad legt eine Datei an, die kein Dienst je liest -- sie stand da, und
|
||||
die Option fehlte trotzdem in der Lease.
|
||||
* Bei ``dhcpcd`` und ``dhclient`` gehoeren die Zeilen in eine Datei, die
|
||||
dem Client gehoert. Sie zu ueberschreiben haette seine uebrige
|
||||
Konfiguration geloescht. Stattdessen wird nur der eigene Block zwischen
|
||||
den Marken ersetzt -- beliebig oft wiederholbar.
|
||||
|
||||
Der Inhalt reist base64-kodiert und als **Argument**, nicht ueber stdin:
|
||||
dort steht bei einem Client mit Passwort das Kennwort fuer ``sudo``.
|
||||
"""
|
||||
import base64
|
||||
|
||||
fracht = base64.b64encode(inhalt.encode("utf-8")).decode("ascii")
|
||||
|
||||
if stil == "networkd":
|
||||
return NETWORKD_SKRIPT.replace("@FRACHT@", fracht)
|
||||
|
||||
ziel = "/etc/dhcpcd.conf" if stil == "dhcpcd" else "/etc/dhcp/dhclient.conf"
|
||||
dienst = (
|
||||
"systemctl restart dhcpcd"
|
||||
if stil == "dhcpcd"
|
||||
else "systemctl restart networking 2>/dev/null || { dhclient -r || true; dhclient; }"
|
||||
)
|
||||
return (
|
||||
BLOCK_SKRIPT.replace("@ZIEL@", ziel)
|
||||
.replace("@FRACHT@", fracht)
|
||||
.replace("@DIENST@", dienst)
|
||||
)
|
||||
|
||||
|
||||
def service_state(sysops: SysOps) -> dict[str, str]:
|
||||
state = sysops.service_state(SERVICE_UNIT)
|
||||
import shutil
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "tesm-core"
|
||||
version = "2.8.21"
|
||||
version = "2.8.22"
|
||||
description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
|
||||
@@ -657,9 +657,19 @@
|
||||
gap: var(--sp-5);
|
||||
}
|
||||
|
||||
/* Bleibt stehen wie die Kopfzeile: Fassung und angemeldeter Benutzer sind
|
||||
Angaben, nach denen man mitten in einer langen Seite fragt -- und bis
|
||||
hierher zu scrollen, nur um zu sehen, welche Fassung laeuft, ist eine
|
||||
Zumutung. Derselbe milchige Untergrund wie oben, damit Inhalt, der
|
||||
darunter durchlaeuft, nicht mitliest. */
|
||||
.page-footer {
|
||||
padding: var(--sp-4) var(--sp-5);
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: var(--z-sticky);
|
||||
padding: var(--sp-3) var(--sp-5);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
background: color-mix(in srgb, var(--canvas) 82%, transparent);
|
||||
backdrop-filter: blur(12px) saturate(1.4);
|
||||
color: var(--text-faint);
|
||||
font-size: var(--fs-xs);
|
||||
display: flex;
|
||||
|
||||
Reference in new issue
Block a user