function wrongChoices(labels) {
  return labels.map((label) => ({
    label,
    correct: false,
    feedback:
      "Dieser Weg passt nicht zum beobachteten Signal oder verändert mehr als nötig."
  }));
}

function choice(id, objective, title, prompt, correct, wrong, details = {}) {
  const explain = details.explain ?? `„${correct}“ löst die Aufgabe gezielt.`;
  return {
    id,
    type: "choice",
    objective,
    title,
    subtitle: details.subtitle ?? "Wähle das passende Werkzeug.",
    briefing: details.briefing ?? prompt,
    output: details.output ?? [],
    prompt,
    choices: [
      {
        label: correct,
        correct: true,
        feedback: `Richtig: ${explain}`
      },
      ...wrongChoices(wrong)
    ],
    signal: details.signal ?? details.briefing ?? prompt,
    tool: details.tool ?? correct,
    decision: details.decision ?? explain,
    risk:
      details.risk ??
      "Nicht auf Verdacht eingreifen; erst das konkrete Signal auswerten.",
    hint: details.hint ?? `Gesucht ist hier: ${details.tool ?? correct}.`,
    xp: details.xp ?? 80
  };
}

function terminal(
  id,
  objective,
  title,
  prompt,
  acceptedAnswers,
  details = {}
) {
  const tool = details.tool ?? acceptedAnswers[0];
  return {
    id,
    type: "terminal",
    objective,
    title,
    subtitle: details.subtitle ?? "Formuliere den Diagnosebefehl.",
    briefing: details.briefing ?? prompt,
    output: details.output ?? [],
    prompt,
    acceptedAnswers,
    placeholder: details.placeholder ?? "Befehl eingeben …",
    signal: details.signal ?? details.briefing ?? prompt,
    tool,
    decision:
      details.decision ?? `Mit „${tool}“ wird genau die geforderte Information ermittelt.`,
    risk:
      details.risk ??
      "Die Aufgabe läuft nur in der Simulation; auf echten Systemen Ausgaben vor Änderungen prüfen.",
    hint: details.hint ?? `Der gesuchte Befehl beginnt mit „${tool.split(" ")[0]}“.`,
    xp: details.xp ?? 90
  };
}

function sequence(
  id,
  objective,
  title,
  prompt,
  tokens,
  answer,
  details = {}
) {
  return {
    id,
    type: "sequence",
    objective,
    title,
    subtitle: details.subtitle ?? "Setze die Bausteine sicher zusammen.",
    briefing: details.briefing ?? prompt,
    output: details.output ?? [],
    prompt,
    tokens,
    answer,
    signal: details.signal ?? details.briefing ?? prompt,
    tool: details.tool ?? answer.join(" "),
    decision:
      details.decision ??
      "Die Reihenfolge verbindet kleine, kontrollierbare Schritte zu einer gültigen Lösung.",
    risk:
      details.risk ??
      "Falsche Reihenfolge oder Umleitungen können ein anderes Ergebnis erzeugen.",
    hint:
      details.hint ??
      "Beginne mit der Datenquelle und füge Verarbeitungsschritte von links nach rechts an.",
    xp: details.xp ?? 110
  };
}

const chapterDefinitions = [
  {
    id: "boot",
    number: "01",
    exam: "101-500",
    topic: "Boot, Hardware & systemd",
    title: "Der Maschinenraum",
    module: "kernel",
    success: {
      headline: "Kernel und Bootkette stabil",
      summary:
        "Du kannst Hardware, Kernelmeldungen, Bootfehler und systemd-Dienste gezielt untersuchen.",
      skills: ["Hardware erkennen", "Bootlogs lesen", "Dienste kontrollieren"]
    },
    questions: [
      choice(
        "boot-journal",
        "101.3",
        "Fehler im aktuellen Boot",
        "Welcher Befehl zeigt nur Fehler des aktuellen Bootvorgangs?",
        "journalctl -b -p err",
        ["journalctl --vacuum-time=1d", "rm -rf /var/log/*"],
        {
          output: ["[FAILED] Failed to start Network Name Resolution."],
          explain: "-b begrenzt auf diesen Boot und -p err auf Fehlermeldungen.",
          hint: "Journal, aktueller Boot, Priorität error."
        }
      ),
      terminal(
        "boot-driver",
        "101.1",
        "Treiber am PCI-Gerät",
        "Zeige den vom Kernel verwendeten Treiber für PCI-Geräte.",
        ["lspci -k"],
        {
          output: ["03:00.0 RAID bus controller: LSI Logic MegaRAID"],
          decision: "lspci -k ergänzt die PCI-Liste um Kernel driver und modules."
        }
      ),
      choice(
        "boot-modules",
        "101.1",
        "Geladene Kernelmodule",
        "Welcher Befehl listet aktuell geladene Kernelmodule?",
        "lsmod",
        ["modprobe -r", "uname -a"],
        { explain: "lsmod liest die Liste der im Kernel geladenen Module." }
      ),
      terminal(
        "boot-modprobe",
        "101.1",
        "Modul kontrolliert laden",
        "Lade das Kernelmodul loop samt Abhängigkeiten.",
        ["modprobe loop", "sudo modprobe loop"],
        {
          signal: "Das Modul loop ist noch nicht geladen.",
          decision: "modprobe lädt das Modul und berücksichtigt Abhängigkeiten."
        }
      ),
      choice(
        "boot-service-state",
        "101.3",
        "Dienstzustand prüfen",
        "Wie prüfst du skriptfreundlich, ob ssh aktiv ist?",
        "systemctl is-active --quiet ssh",
        ["systemctl enable ssh", "systemctl daemon-reexec"],
        {
          explain: "is-active --quiet liefert den Zustand über den Exit-Code.",
          risk: "enable verändert den Autostart und beantwortet nicht den Laufzustand."
        }
      ),
      terminal(
        "boot-enable-now",
        "101.3",
        "Jetzt und künftig starten",
        "Aktiviere nginx für den Boot und starte den Dienst sofort.",
        ["systemctl enable --now nginx", "sudo systemctl enable --now nginx"],
        { tool: "systemctl enable --now nginx" }
      ),
      choice(
        "boot-target",
        "101.3",
        "Standard-Target",
        "Welcher Befehl zeigt das standardmäßig gestartete systemd-Target?",
        "systemctl get-default",
        ["systemctl isolate rescue.target", "runlevel --set"],
        { explain: "get-default liest den gesetzten Default-Target-Link." }
      ),
      terminal(
        "boot-kernel-version",
        "101.2",
        "Laufender Kernel",
        "Zeige ausschließlich die Version des aktuell laufenden Kernels.",
        ["uname -r"],
        { output: ["Nach einem Kernel-Upgrade soll der aktive Stand geprüft werden."] }
      ),
      choice(
        "boot-grub-config",
        "101.2",
        "GRUB-Konfiguration",
        "Welche Datei wird bei Debian normalerweise bearbeitet, bevor update-grub läuft?",
        "/etc/default/grub",
        ["/boot/grub/grub.cfg direkt", "/etc/fstab"],
        {
          explain: "/etc/default/grub ist die vorgesehene Quelle; grub.cfg wird generiert.",
          risk: "Die generierte grub.cfg nicht von Hand pflegen."
        }
      ),
      choice(
        "boot-dmesg",
        "101.1",
        "Kernelmeldungen",
        "Wo suchst du zuerst nach Meldungen zur gerade erkannten Hardware?",
        "dmesg",
        ["history -c", "apt clean"],
        { explain: "dmesg zeigt Meldungen aus dem Kernel-Ringpuffer." }
      )
    ]
  },
  {
    id: "security",
    number: "09",
    exam: "102-500",
    topic: "Benutzer, SSH & Sicherheit",
    title: "Das Schutzschild",
    module: "security",
    success: {
      headline: "Schutzschild aktiviert",
      summary:
        "Konten, Gruppen, SSH-Schlüssel und sudo sind jetzt sicher und nachvollziehbar eingerichtet.",
      skills: ["Konten verwalten", "SSH absichern", "Rechte begrenzen"]
    },
    questions: [
      terminal("sec-authorized", "110.1", "SSH-Schlüsselrechte", "Setze authorized_keys auf Eigentümer lesen/schreiben, sonst nichts.", ["chmod 600 ~/.ssh/authorized_keys", "chmod 600 $HOME/.ssh/authorized_keys"]),
      choice("sec-passwd-fields", "107.1", "Kontodaten lesen", "Welches Feld steht in /etc/passwd an Position 7?", "Login-Shell", ["Passwort-Hash", "Primäre Gruppenbeschreibung"], {
        explain: "Die sieben Felder enden mit der Login-Shell."
      }),
      choice("sec-shadow", "107.1", "Passwort-Hashes", "Wo liegen lokale Passwort-Hashes üblicherweise geschützt?", "/etc/shadow", ["/etc/passwd", "/etc/group"]),
      terminal("sec-add-group", "107.1", "Zusätzliche Gruppe", "Füge Benutzer timo ergänzend zur Gruppe kurs hinzu.", ["usermod -aG kurs timo", "sudo usermod -aG kurs timo"], {
        risk: "Ohne -a würden vorhandene ergänzende Gruppen ersetzt."
      }),
      choice("sec-chage", "107.1", "Passwortalter", "Welcher Befehl zeigt Ablauf- und Altersdaten des Kontos andre?", "chage -l andre", ["passwd -d andre", "userdel andre"]),
      terminal("sec-keygen", "110.1", "Ed25519-Schlüssel", "Erzeuge ein neues Ed25519-SSH-Schlüsselpaar.", ["ssh-keygen -t ed25519"]),
      choice("sec-agent", "110.1", "Schlüssel in Agent laden", "Welcher Befehl lädt ~/.ssh/id_ed25519 in den laufenden ssh-agent?", "ssh-add ~/.ssh/id_ed25519", ["ssh-copy-id ~/.ssh/id_ed25519", "chmod 777 ~/.ssh/id_ed25519"]),
      choice("sec-visudo", "110.2", "sudoers sicher bearbeiten", "Warum sollte /etc/sudoers mit visudo bearbeitet werden?", "Es prüft die Syntax vor dem Speichern", ["Es setzt automatisch NOPASSWD", "Es verschlüsselt die Datei"], {
        explain: "visudo sperrt die Datei und verhindert Syntaxfehler beim Aktivieren."
      }),
      terminal("sec-last", "110.2", "Anmeldungen prüfen", "Zeige die letzten erfolgreichen Anmeldungen.", ["last"]),
      choice("sec-root-ssh", "110.1", "Root-Login begrenzen", "Welche sshd_config-Zeile verbietet Root-Anmeldungen vollständig?", "PermitRootLogin no", ["PasswordAuthentication yes", "StrictModes no"], {
        risk: "Vor Neustart des sshd einen funktionierenden Admin-Zugang sicherstellen."
      })
    ]
  },
  {
    id: "operations",
    number: "10",
    exam: "101 + 102",
    topic: "Systembetrieb & Verify",
    title: "Der Kontrollraum",
    module: "online",
    success: {
      headline: "Kontrollrunde bestanden",
      summary:
        "Zeitplanung, Locale, Logs und die abschließende Systemprüfung greifen zusammen.",
      skills: ["Jobs planen", "Logs prüfen", "Änderungen verifizieren"]
    },
    questions: [
      choice("ops-verify", "Projekt-Review", "Vor dem Neustart", "Welche Kontrollrunde prüft Units, fstab und lauschende Dienste?", "systemctl --failed · mount -a · ss -lntup", ["reboot -f", "history -c && clear"], {
        explain: "Die drei Prüfungen decken zentrale Folgefehler vor dem Neustart auf."
      }),
      terminal("ops-failed", "108.2", "Fehlgeschlagene Units", "Liste alle fehlgeschlagenen systemd-Units.", ["systemctl --failed"]),
      choice("ops-cron", "107.2", "Täglich um 03:15", "Welche Cron-Zeitangabe bedeutet täglich um 03:15?", "15 3 * * *", ["3 15 * * *", "15 * 3 * *"], {
        explain: "Cron beginnt mit Minute, danach Stunde."
      }),
      terminal("ops-crontab", "107.2", "Eigene Cronjobs bearbeiten", "Öffne die Crontab des aktuellen Benutzers zur Bearbeitung.", ["crontab -e"]),
      choice("ops-at", "107.2", "Einmalige Aufgabe", "Welches Werkzeug plant einen einmaligen Befehl für 18:00?", "at 18:00", ["crontab -r 18:00", "nice 18:00"]),
      terminal("ops-timezone", "107.3", "Zeitzone setzen", "Setze die Systemzeitzone auf Europe/Berlin.", ["timedatectl set-timezone Europe/Berlin", "sudo timedatectl set-timezone Europe/Berlin"]),
      choice("ops-locale", "107.3", "Aktive Locale", "Welcher Befehl zeigt die aktuell wirksamen Locale-Variablen?", "locale", ["localectl --delete", "date --locale-only"]),
      terminal("ops-journal-unit", "108.2", "Dienstjournal", "Zeige die letzten 50 Journalzeilen der Unit ssh.", ["journalctl -u ssh -n 50", "journalctl -n 50 -u ssh"]),
      choice("ops-logrotate", "108.3", "Logs begrenzen", "Welches Werkzeug rotiert und komprimiert klassische Logdateien?", "logrotate", ["updatedb", "ldconfig"]),
      choice("ops-shutdown", "101.3", "Geordnet neu starten", "Welcher Befehl plant einen geordneten Neustart in zehn Minuten?", "shutdown -r +10", ["reboot -f +10", "kill -9 1"], {
        risk: "Erzwungene Neustarts können Schreibvorgänge unterbrechen."
      })
    ]
  },
  {
    id: "permissions",
    number: "06",
    exam: "101-500",
    topic: "Dateien, Rechte & Suche",
    title: "Die Rechte-Luftschleuse",
    module: "permissions",
    success: {
      headline: "Zugriffskontrolle steht",
      summary:
        "Du kannst Dateien finden, Metadaten lesen und Zugriffe mit Besitz und Modus präzise steuern.",
      skills: ["Dateien finden", "Rechte lesen", "Besitz kontrollieren"]
    },
    questions: [
      choice("perm-octal", "104.5", "Rechte in Oktal", "Welcher Modus entspricht rw-r-----?", "640", ["740", "604"], {
        explain: "rw- ist 6, r-- ist 4 und --- ist 0."
      }),
      terminal("perm-chmod", "104.5", "Privater Schlüssel", "Setze private.key auf Lesen und Schreiben nur für den Eigentümer.", ["chmod 600 private.key"]),
      choice("perm-sgid", "104.5", "Projektgruppe vererben", "Welcher Modus setzt SGID und rwx für Eigentümer und Gruppe?", "chmod 2770 /srv/projekt", ["chmod 1770 /srv/projekt", "chmod 4777 /srv/projekt"], {
        explain: "Das führende 2 setzt SGID am Verzeichnis."
      }),
      choice("perm-sticky", "104.5", "Gemeinsames Temporärverzeichnis", "Welches Spezialbit verhindert in einem offenen Verzeichnis fremdes Löschen?", "Sticky Bit", ["SUID", "SGID auf einer Datei"], {
        explain: "Das Sticky Bit erlaubt Löschen grundsätzlich nur durch Eigentümer."
      }),
      terminal("perm-owner", "104.5", "Eigentümer und Gruppe", "Setze für bericht.txt den Eigentümer andre und die Gruppe kurs.", ["chown andre:kurs bericht.txt", "sudo chown andre:kurs bericht.txt"]),
      choice("perm-umask", "104.5", "Standardrechte für Dateien", "Welche Dateirechte entstehen typischerweise aus Basis 666 und umask 027?", "640", ["750", "666"], {
        explain: "Von 666 werden Gruppen-Schreibrecht und alle Other-Rechte maskiert."
      }),
      choice("perm-hardlink", "104.6", "Gleicher Inode", "Welche Aussage über einen Hardlink ist richtig?", "Er verweist auf denselben Inode", ["Er speichert immer einen Zielpfad", "Er funktioniert über jedes Dateisystem hinweg"], {
        explain: "Hardlinks sind zusätzliche Namen für denselben Inode."
      }),
      terminal("perm-symlink", "104.6", "Symbolischen Link anlegen", "Erzeuge den Symlink aktuell.log auf app-2026.log.", ["ln -s app-2026.log aktuell.log"]),
      choice("perm-find-size", "104.7", "Große Dateien suchen", "Welcher Ausdruck findet unter /var Dateien größer als 100 MiB?", "find /var -type f -size +100M", ["find /var -type d -size 100", "locate --delete /var"], {
        explain: "-type f begrenzt auf Dateien, +100M auf mehr als 100 MiB."
      }),
      terminal("perm-stat", "104.6", "Metadaten vollständig lesen", "Zeige Modus, Inode und Zeitstempel von bericht.txt.", ["stat bericht.txt"])
    ]
  },
  {
    id: "shell",
    number: "07",
    exam: "102-500",
    topic: "Skripte & Automatisierung",
    title: "Der Automatik-Schacht",
    module: "shell",
    success: {
      headline: "Automatik läuft",
      summary:
        "Variablen, Tests, Schleifen und sichere Verzweigungen arbeiten jetzt als wiederholbare Abläufe.",
      skills: ["Bedingungen bauen", "Schleifen nutzen", "Skripte absichern"]
    },
    questions: [
      sequence(
        "shell-if",
        "105.2",
        "Sicher verzweigen",
        "Baue ein Skript, das den SSH-Zustand nur meldet.",
        ["fi", "echo \"SSH prüfen\"", "#!/bin/bash", "systemctl restart ssh", "else", "if systemctl is-active --quiet ssh; then", "echo \"SSH läuft\"", "done"],
        ["#!/bin/bash", "if systemctl is-active --quiet ssh; then", "echo \"SSH läuft\"", "else", "echo \"SSH prüfen\"", "fi"],
        { risk: "Ein reines Diagnoseskript darf den Dienst nicht heimlich neu starten." }
      ),
      choice("shell-exit", "105.1", "Letzten Exit-Code lesen", "Welche Expansion liefert den Exit-Code des unmittelbar vorherigen Befehls?", "$?", ["$!", "$#"], {
        explain: "$? enthält den letzten Rückgabewert."
      }),
      terminal("shell-export", "105.1", "Variable an Kindprozesse geben", "Exportiere MODE mit dem Wert test.", ["export MODE=test", "MODE=test; export MODE"]),
      choice("shell-quotes", "105.1", "Leerzeichen erhalten", "Welche Schreibweise übergibt den Variableninhalt als genau ein Argument?", "\"$datei\"", ["$datei", "'$datei'"], {
        explain: "Doppelte Anführungszeichen erhalten Leerzeichen und expandieren die Variable."
      }),
      terminal("shell-command-sub", "105.1", "Befehlsausgabe speichern", "Speichere die Ausgabe von hostname in der Variablen host.", ["host=$(hostname)", "host=`hostname`"]),
      choice("shell-and", "105.1", "Nur bei Erfolg weiter", "Welcher Operator startet B nur, wenn A erfolgreich war?", "A && B", ["A || B", "A ; B"], {
        explain: "&& wertet den zweiten Befehl nur bei Exit-Code 0 aus."
      }),
      terminal("shell-test-file", "105.2", "Reguläre Datei prüfen", "Formuliere einen Test, ob /etc/passwd eine reguläre Datei ist.", ["test -f /etc/passwd", "[ -f /etc/passwd ]", "[[ -f /etc/passwd ]]"]),
      choice("shell-args", "105.2", "Erstes Skriptargument", "Welche Variable enthält das erste Positionsargument?", "$1", ["$0", "$#"], {
        explain: "$0 ist der Skriptname, $1 das erste Argument."
      }),
      terminal("shell-loop", "105.2", "Über Argumente laufen", "Welche kurze Schleife iteriert in Bash über alle Positionsargumente?", ["for arg in \"$@\"; do echo \"$arg\"; done"]),
      choice("shell-path", "105.1", "Befehlssuche", "Welche Variable enthält die Verzeichnisse für die Befehlssuche?", "PATH", ["HOME", "PS1"], {
        explain: "PATH ist eine durch Doppelpunkte getrennte Suchliste."
      })
    ]
  },
  {
    id: "network",
    number: "08",
    exam: "102-500",
    topic: "Netzwerk & DNS",
    title: "Die Netzwerk-Antenne",
    module: "network",
    success: {
      headline: "Netzwerkverbindung hergestellt",
      summary:
        "Adressen, Routen, Sockets und DNS lassen sich getrennt prüfen und sauber eingrenzen.",
      skills: ["IP prüfen", "Routen lesen", "DNS diagnostizieren"]
    },
    questions: [
      terminal("net-address", "109.2", "Adressen der Interfaces", "Zeige alle Netzwerkinterfaces und ihre IP-Adressen.", ["ip addr", "ip address", "ip a"]),
      terminal("net-route", "109.2", "Routingtabelle", "Zeige die IPv4-Routingtabelle.", ["ip route", "ip -4 route"]),
      choice("net-default", "109.2", "Standardroute lesen", "Welche Zeile kennzeichnet das Standardgateway?", "default via 192.168.1.1", ["127.0.0.1/8 dev lo", "broadcast 255.255.255.255"], {
        explain: "Eine Standardroute beginnt in der ip-route-Ausgabe mit default via."
      }),
      terminal("net-listen", "109.1", "Lauschende TCP-Sockets", "Zeige lauschende TCP-Sockets numerisch samt Prozessen.", ["ss -lntp", "sudo ss -lntp"]),
      terminal("net-dns", "109.4", "Resolver gezielt fragen", "Frage DNS nach example.org.", ["dig example.org", "host example.org", "nslookup example.org"], {
        output: ["ping 1.1.1.1: erreichbar", "getent hosts example.org: keine Ausgabe"],
        risk: "Bei funktionierendem IP-Verkehr nicht vorschnell Route oder Firewall ändern."
      }),
      choice("net-resolv", "109.4", "Resolver-Konfiguration", "Welche Datei enthält klassisch nameserver-Einträge?", "/etc/resolv.conf", ["/etc/fstab", "/etc/shadow"]),
      choice("net-hosts-order", "109.4", "Lokale Namensauflösung", "Welche Datei ordnet lokal Namen direkt IP-Adressen zu?", "/etc/hosts", ["/etc/services", "/etc/protocols"]),
      terminal("net-ping", "109.3", "Vier ICMP-Tests", "Sende genau vier ICMP-Echoanfragen an 1.1.1.1.", ["ping -c 4 1.1.1.1", "ping -c4 1.1.1.1"]),
      choice("net-cidr", "109.2", "Präfixlänge verstehen", "Wie viele IPv4-Adressen enthält ein /24-Netz insgesamt?", "256", ["24", "254"], {
        explain: "32−24 = 8 Hostbits, also 2^8 = 256 Adressen; nutzbar meist 254."
      }),
      terminal("net-hostname", "109.1", "Statischen Hostnamen setzen", "Setze den Hostnamen mit hostnamectl auf kursnode.", ["hostnamectl set-hostname kursnode", "sudo hostnamectl set-hostname kursnode"])
    ]
  },
  {
    id: "packages",
    number: "02",
    exam: "101-500",
    topic: "Pakete & Bibliotheken",
    title: "Die Paket-Schleuse",
    module: "packages",
    success: {
      headline: "Paketversorgung online",
      summary:
        "Du beherrschst Paketquellen, Installationen, Abfragen und gemeinsam genutzte Bibliotheken.",
      skills: ["Pakete installieren", "Quellen pflegen", "Bibliotheken prüfen"]
    },
    questions: [
      terminal(
        "pkg-apt-update",
        "102.4",
        "Paketindex erneuern",
        "Aktualisiere die lokalen Metadaten der Debian-Paketquellen.",
        ["apt update", "sudo apt update", "apt-get update", "sudo apt-get update"]
      ),
      terminal(
        "pkg-local-deb",
        "102.4",
        "Lokales DEB mit Abhängigkeiten",
        "Installiere ./sensor.deb und lasse Abhängigkeiten auflösen.",
        ["apt install ./sensor.deb", "sudo apt install ./sensor.deb"],
        {
          output: ["Package: kernel-sensor", "Depends: libpulse0"],
          risk: "dpkg -i allein lädt fehlende Abhängigkeiten nicht nach."
        }
      ),
      choice(
        "pkg-owner",
        "102.4",
        "Datei gehört zu welchem Paket?",
        "Welcher Befehl ermittelt das installierte Paket zu /bin/ls?",
        "dpkg -S /bin/ls",
        ["dpkg -L /bin/ls", "apt clean /bin/ls"],
        { explain: "dpkg -S sucht installierte Pakete anhand eines Dateipfads." }
      ),
      terminal(
        "pkg-list-files",
        "102.4",
        "Paketinhalt anzeigen",
        "Liste alle installierten Dateien des Pakets coreutils.",
        ["dpkg -L coreutils"],
        { decision: "dpkg -L listet die Dateien eines bereits installierten Pakets." }
      ),
      choice(
        "pkg-cache-clean",
        "102.4",
        "APT-Cache leeren",
        "Welcher Befehl entfernt heruntergeladene Pakete aus dem APT-Cache?",
        "apt clean",
        ["apt update", "apt-mark hold"],
        {
          explain: "apt clean leert /var/cache/apt/archives.",
          risk: "Das entfernt keine installierten Pakete, aber Offline-Paketkopien."
        }
      ),
      choice(
        "pkg-rpm-owner",
        "102.5",
        "RPM-Dateibesitzer",
        "Wie ermittelst du unter RPM, welches Paket /usr/bin/ssh bereitstellt?",
        "rpm -qf /usr/bin/ssh",
        ["rpm -ql /usr/bin/ssh", "rpm -e /usr/bin/ssh"],
        { explain: "-qf fragt das Paket ab, dem eine installierte Datei gehört." }
      ),
      terminal(
        "pkg-rpm-files",
        "102.5",
        "RPM-Paketdateien",
        "Liste die installierten Dateien des RPM-Pakets bash.",
        ["rpm -ql bash"],
        { tool: "rpm -ql bash" }
      ),
      choice(
        "pkg-dnf-provides",
        "102.5",
        "Fehlenden Befehl finden",
        "Welcher DNF-Befehl sucht das Paket, das */semanage bereitstellt?",
        "dnf provides '*/semanage'",
        ["dnf remove semanage", "dnf clean all semanage"],
        { explain: "dnf provides sucht Paketinhalt nach einem Pfadmuster." }
      ),
      terminal(
        "pkg-ldd",
        "102.3",
        "Dynamische Abhängigkeiten",
        "Zeige die gemeinsam genutzten Bibliotheken von /bin/ls.",
        ["ldd /bin/ls"],
        { signal: "Ein Programm meldet beim Start eine fehlende Shared Library." }
      ),
      choice(
        "pkg-ldconfig",
        "102.3",
        "Bibliothekscache erneuern",
        "Welcher Befehl erneuert nach einer Bibliotheksinstallation den Linker-Cache?",
        "ldconfig",
        ["ldd --delete", "chmod 777 /usr/lib"],
        {
          explain: "ldconfig aktualisiert Links und /etc/ld.so.cache.",
          risk: "Bibliotheksrechte nicht pauschal öffnen."
        }
      )
    ]
  },
  {
    id: "pipeline",
    number: "03",
    exam: "101-500",
    topic: "Shell, Text & Streams",
    title: "Die Text-Raffinerie",
    module: "pipes",
    success: {
      headline: "Datenstrom sauber",
      summary:
        "Streams, Filter, Umleitungen und Archive lassen sich zu kontrollierten Pipelines verbinden.",
      skills: ["Text filtern", "Streams lenken", "Daten archivieren"]
    },
    questions: [
      sequence(
        "text-user-pipeline",
        "103.2 / 103.4",
        "Benutzerliste raffinieren",
        "Baue eine sortierte, eindeutige Liste der Benutzernamen.",
        ["uniq", "|", "cut -d: -f1 /etc/passwd", "sort", "|", ">", "grep bash"],
        ["cut -d: -f1 /etc/passwd", "|", "sort", "|", "uniq"],
        {
          output: ["root:x:0:0:root:/root:/bin/bash"],
          hint: "uniq braucht sortierte Eingabe."
        }
      ),
      terminal(
        "text-grep-root",
        "103.7",
        "Zeilenanfang finden",
        "Zeige aus /etc/passwd nur Zeilen, die mit root: beginnen.",
        ["grep '^root:' /etc/passwd", "grep \"^root:\" /etc/passwd"],
        { tool: "grep '^root:' /etc/passwd" }
      ),
      choice(
        "text-stderr",
        "103.4",
        "Fehlerstrom umleiten",
        "Welche Umleitung schreibt nur stderr nach errors.log?",
        "2> errors.log",
        ["> errors.log", "2>&1 > /dev/null"],
        { explain: "Dateideskriptor 2 ist stderr." }
      ),
      terminal(
        "text-tee",
        "103.4",
        "Sehen und speichern",
        "Zeige die Ausgabe von ip addr und speichere sie zugleich in netz.txt.",
        ["ip addr | tee netz.txt", "ip a | tee netz.txt"],
        { decision: "tee kopiert stdin sowohl auf stdout als auch in die Datei." }
      ),
      choice(
        "text-uniq-count",
        "103.2",
        "Dubletten zählen",
        "Welche Pipeline zählt gleiche, möglicherweise unsortierte Zeilen?",
        "sort namen.txt | uniq -c",
        ["uniq -c namen.txt | sort", "wc -c namen.txt"],
        { explain: "sort gruppiert gleiche Zeilen, damit uniq -c sie zählen kann." }
      ),
      terminal(
        "text-lines",
        "103.2",
        "Zeilen zählen",
        "Zähle die Zeilen in /etc/passwd.",
        ["wc -l /etc/passwd"],
        { tool: "wc -l /etc/passwd" }
      ),
      choice(
        "text-tail-follow",
        "103.2",
        "Log live verfolgen",
        "Welcher Befehl verfolgt neue Zeilen in app.log fortlaufend?",
        "tail -f app.log",
        ["head -f app.log", "cat --delete app.log"],
        { explain: "tail -f bleibt geöffnet und zeigt angehängte Zeilen." }
      ),
      terminal(
        "text-tr",
        "103.2",
        "Klein nach groß",
        "Wandle stdin mit tr von Klein- in Großbuchstaben um.",
        ["tr 'a-z' 'A-Z'", "tr a-z A-Z"],
        { tool: "tr 'a-z' 'A-Z'" }
      ),
      choice(
        "text-tar-xz",
        "103.3",
        "XZ-Archiv entpacken",
        "Welcher Befehl entpackt backup.tar.xz?",
        "tar -xJf backup.tar.xz",
        ["tar -czf backup.tar.xz", "gzip --test backup.tar.xz"],
        { explain: "x extrahiert, J wählt xz, f benennt das Archiv." }
      ),
      terminal(
        "text-find-exec",
        "103.8",
        "Gefundene Logs auflisten",
        "Finde unter /var/log reguläre Dateien mit Endung .log.",
        ["find /var/log -type f -name '*.log'", "find /var/log -type f -name \"*.log\""],
        { tool: "find /var/log -type f -name '*.log'" }
      )
    ]
  },
  {
    id: "processes",
    number: "04",
    exam: "101-500",
    topic: "Prozesse, Jobs & Signale",
    title: "Der Prozess-Reaktor",
    module: "processes",
    success: {
      headline: "Prozess-Reaktor im Takt",
      summary:
        "Du kannst Prozesse beobachten, priorisieren und mit der passenden Signalstufe kontrollieren.",
      skills: ["Prozesse lesen", "Jobs steuern", "Signale eskalieren"]
    },
    questions: [
      choice(
        "proc-term",
        "103.5",
        "Geordnet beenden",
        "Welche erste Eskalationsstufe beendet PID 4242 kontrolliert?",
        "kill -15 4242",
        ["kill -9 4242", "renice -20 4242"],
        {
          explain: "SIGTERM erlaubt dem Prozess aufzuräumen.",
          risk: "SIGKILL erst verwenden, wenn SIGTERM nach Prüfung nicht wirkt."
        }
      ),
      terminal(
        "proc-pgrep",
        "103.5",
        "PID nach Namen",
        "Ermittle die PIDs aller Prozesse namens sshd.",
        ["pgrep sshd", "pidof sshd"],
        { tool: "pgrep sshd" }
      ),
      choice(
        "proc-background",
        "103.5",
        "Job direkt im Hintergrund",
        "Welches Zeichen startet sleep 1000 direkt als Hintergrundjob?",
        "sleep 1000 &",
        ["sleep 1000 |", "sleep 1000 >"],
        { explain: "Ein abschließendes & gibt die Shell sofort wieder frei." }
      ),
      terminal(
        "proc-jobs",
        "103.5",
        "Shell-Jobs auflisten",
        "Liste die Jobs der aktuellen Shell inklusive PIDs.",
        ["jobs -l"],
        { tool: "jobs -l" }
      ),
      choice(
        "proc-foreground",
        "103.5",
        "Job zurückholen",
        "Wie holst du Job 2 in den Vordergrund?",
        "fg %2",
        ["bg %2", "kill %2"],
        { explain: "fg verbindet den Job wieder mit dem Terminal im Vordergrund." }
      ),
      terminal(
        "proc-nice-start",
        "103.6",
        "Schonend starten",
        "Starte backup.sh mit Nice-Wert 10.",
        ["nice -n 10 ./backup.sh", "nice -10 ./backup.sh"],
        { tool: "nice -n 10 ./backup.sh" }
      ),
      choice(
        "proc-renice",
        "103.6",
        "Laufenden Prozess schonen",
        "Welcher Befehl setzt PID 4242 auf Nice-Wert 15?",
        "renice 15 -p 4242",
        ["nice 15 -p 4242", "kill -15 4242"],
        { explain: "renice ändert die Priorität eines bereits laufenden Prozesses." }
      ),
      terminal(
        "proc-ps-columns",
        "103.5",
        "PID, Zustand und Kommando",
        "Zeige für PID 4242 die Spalten PID, STAT und CMD.",
        ["ps -o pid,stat,cmd -p 4242", "ps -p 4242 -o pid,stat,cmd"],
        { tool: "ps -o pid,stat,cmd -p 4242" }
      ),
      choice(
        "proc-zombie",
        "103.5",
        "Zombie erkennen",
        "Welcher STAT-Buchstabe kennzeichnet einen Zombieprozess?",
        "Z",
        ["S", "R"],
        { explain: "Z steht für zombie bzw. defunct." }
      ),
      choice(
        "proc-nohup",
        "103.5",
        "Logout überleben",
        "Wie startest du job.sh so, dass es einen Terminal-Logout überstehen kann?",
        "nohup ./job.sh &",
        ["./job.sh && exit", "fg ./job.sh"],
        { explain: "nohup ignoriert SIGHUP; & löst den Job vom Vordergrund." }
      )
    ]
  },
  {
    id: "storage",
    number: "05",
    exam: "101-500",
    topic: "Datenträger & Dateisysteme",
    title: "Der Speicher-Kern",
    module: "storage",
    success: {
      headline: "Speicher-Kern eingebunden",
      summary:
        "Blockgeräte, Dateisysteme, Mounts, Inodes und Swap sind jetzt sicher im Blick.",
      skills: ["Geräte erkennen", "Mounts prüfen", "Kapazität verstehen"]
    },
    questions: [
      terminal(
        "storage-lsblk",
        "104.1",
        "Blockgeräte überblicken",
        "Liste Blockgeräte samt Dateisystemen und Mountpunkten.",
        ["lsblk -f"],
        { tool: "lsblk -f" }
      ),
      choice(
        "storage-uuid",
        "104.1",
        "UUID ermitteln",
        "Welcher Befehl zeigt UUID und Dateisystemtyp von Partitionen?",
        "blkid",
        ["blockdev --delete", "df -h"],
        { explain: "blkid liest die Kennungen von Blockgeräten." }
      ),
      terminal(
        "storage-inodes",
        "104.2",
        "Platz trotz freier Gigabytes",
        "Prüfe die Inode-Belegung von /var.",
        ["df -i /var", "df --inodes /var"],
        {
          output: ["app: No space left on device", "$ df -h /var", "Use% 44%"],
          risk: "Nicht blind Dateien löschen; zuerst Blöcke und Inodes unterscheiden."
        }
      ),
      choice(
        "storage-fstab-test",
        "104.3",
        "fstab ohne Neustart prüfen",
        "Welcher Befehl testet noch nicht eingehängte fstab-Einträge?",
        "mount -a",
        ["reboot -f", "mkfs -a"],
        { explain: "mount -a verarbeitet die passenden fstab-Einträge sofort." }
      ),
      terminal(
        "storage-du",
        "104.2",
        "Verzeichnisgröße",
        "Zeige die zusammengefasste, menschenlesbare Größe von /var/log.",
        ["du -sh /var/log", "du -hs /var/log"],
        { tool: "du -sh /var/log" }
      ),
      choice(
        "storage-fsck",
        "104.2",
        "Dateisystemprüfung",
        "Wann sollte fsck auf einer normalen Datenpartition ausgeführt werden?",
        "Wenn sie ausgehängt ist",
        ["Während darauf geschrieben wird", "Nach jedem apt update"],
        {
          explain: "Eine Prüfung eines aktiv beschriebenen Dateisystems kann Schäden verursachen.",
          risk: "Vor fsck Mountzustand und Backup prüfen."
        }
      ),
      terminal(
        "storage-swap",
        "104.1",
        "Swap aktivieren",
        "Aktiviere die vorbereitete Swap-Datei /swapfile.",
        ["swapon /swapfile", "sudo swapon /swapfile"],
        { tool: "swapon /swapfile" }
      ),
      choice(
        "storage-fstab-id",
        "104.3",
        "Stabiler fstab-Bezug",
        "Welche Referenz bleibt bei geänderter Geräte-Reihenfolge am stabilsten?",
        "UUID=…",
        ["/dev/sdb1", "major:minor aus ps"],
        { explain: "UUIDs identifizieren das Dateisystem statt seine wechselnde Position." }
      ),
      terminal(
        "storage-mount",
        "104.3",
        "Temporär einhängen",
        "Hänge /dev/sdb1 unter /mnt/daten ein.",
        ["mount /dev/sdb1 /mnt/daten", "sudo mount /dev/sdb1 /mnt/daten"],
        { tool: "mount /dev/sdb1 /mnt/daten" }
      ),
      choice(
        "storage-systemd-mount",
        "104.3",
        "Mount-Unit benennen",
        "Wie heißt die systemd-Mount-Unit für /srv/daten?",
        "srv-daten.mount",
        ["srv/daten.service", "mount@srv-daten.target"],
        { explain: "Pfadtrenner werden zu Bindestrichen; die Unit endet auf .mount." }
      )
    ]
  }
];

export const chapters = [...chapterDefinitions]
  .sort((left, right) => left.number.localeCompare(right.number))
  .map((chapter) => ({
    ...chapter,
    questions: chapter.questions.map((question, index) => ({
      ...question,
      chapterId: chapter.id,
      chapterTitle: chapter.title,
      module: chapter.module,
      exam: question.exam ?? chapter.exam,
      topic: chapter.topic,
      number: chapter.number,
      poolPosition: index + 1
    }))
  }));

export const allQuestions = chapters.flatMap((chapter) => chapter.questions);

export const questionsById = Object.fromEntries(
  allQuestions.map((question) => [question.id, question])
);

export const moduleLayout = [
  { id: "kernel", label: "KERNEL", x: 246, y: 38, width: 148, height: 62 },
  { id: "packages", label: "PAKETE", x: 64, y: 132, width: 142, height: 68 },
  { id: "pipes", label: "PIPELINE", x: 249, y: 132, width: 142, height: 68 },
  { id: "processes", label: "PROZESSE", x: 434, y: 132, width: 142, height: 68 },
  { id: "storage", label: "STORAGE", x: 64, y: 246, width: 142, height: 68 },
  { id: "permissions", label: "RECHTE", x: 249, y: 246, width: 142, height: 68 },
  { id: "shell", label: "SHELL", x: 434, y: 246, width: 142, height: 68 },
  { id: "network", label: "NETZWERK", x: 156, y: 356, width: 142, height: 58 },
  { id: "security", label: "SECURITY", x: 342, y: 356, width: 142, height: 58 }
];
