aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md102
-rw-r--r--flake.lock69
-rw-r--r--flake.nix26
-rw-r--r--keys.nix39
-rw-r--r--keys/vg1
-rw-r--r--machines.nix225
-rw-r--r--tharos.nix133
7 files changed, 595 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d60f3dc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,102 @@
+# IT-Infrastruktur für den Stadtteilbeirat Heimfeld
+
+Dieses Git-Repository enthält den Quellcode für Konfiguration und Verwaltung von Rechnern, welche Dienste unter der DNS-Domain `heimfeld.hamburg` bereitstellen.
+
+## Vorbereitung
+
+Um mit dem Code zu arbeiten, zunächst Nix installieren:
+
+- Debian/Ubuntu
+
+ ```bash
+ apt install --yes curl git jq nix
+ ```
+
+- Arch
+
+ ```bash
+ pacman --sync --refresh --noconfirm curl git jq nix
+ ```
+
+Anschließend Flakes aktivieren:
+
+```bash
+echo extra-experimental-features = nix-command flakes >> /etc/nix/nix.conf
+```
+
+Beim ersten Aufruf von `nix run` oder `nix flake check` werden Abhängigkeiten geladen oder gebaut, was eine Weile dauern kann.
+Spätere Aufrufe sind viel schneller, da nur Änderungen verarbeitet werden müssen.
+
+## Abläufe
+
+### Tests durchführen
+
+```bash
+nix flake check
+```
+
+### Konfiguration lokal ausprobieren
+
+```bash
+nix run .#vm-tharos
+```
+
+Dieser Befehl startet eine virtuelle Maschine mit der exakten Konfiguration des Produktionssystems.
+
+Anschließend kann man sich über SSH mit dem auf der Maschine eingerichteten Nutzer und dem entsprechenden SSH-Key verbinden:
+
+```bash
+ssh localhost -p 10022 -o ForwardAgent=yes -i ~/.ssh/tharos
+```
+
+Der SSH-Port ist entsprechend der VM-Konfiguration versetzt um Konflikte mit möglicherweise bestehenden Diensten auf dem Host zu vermeiten.
+`ForwardAgent=yes` ist erforderlich, um Befehle mit `sudo` auszuführen, wobei die Authentifizierung über direkt SSH statt einem Passwort erfolgt.
+
+### Eine neue Maschine aufsetzen
+
+**ACHTUNG: Alle Daten auf dem Zielsystem werden dabei gelöscht!**
+
+```bash
+nix run .#infect-tharos
+```
+
+Die NixOS-Installation erfolgt mit [`nixos-anywhere`](https://nix-community.github.io/nixos-anywhere/).
+
+Angenommen, in `~/.ssh/config` ist Folgendes eingetragen:
+
+```
+Host tharos
+ ForwardAgent yes
+ HostName 81.169.239.254
+ IdentityFile /home/user/.ssh/tharos
+```
+
+Dann kann man sich mit dem Produktionssystem über SSH verbinden:
+
+```bash
+ssh tharos
+```
+
+### Geänderte Konfiguration anwenden
+
+```bash
+nix run .#deploy-tharos -- switch
+```
+
+## Architektur
+
+Die Systeme laufen auf NixOS und werden mit Nix verwaltet.
+
+Dokumentation zu beiden ist zu finden unter <https://nix.dev>.
+Die wesentlichen Aspekte sind dort die [Nix-Sprache](https://nix.dev/tutorials/nix-language) und das [Modulsystem](https://nix.dev/tutorials/module-system/).
+
+Der Code hier ist modular organisiert mit Flake Parts, was es einfacher macht isolierte Änderungen vorzunehmen und die Lesbarkeit verbessern hilft.
+Flake Parts sind dokumentiert unter <https://flake.parts>.
+
+Zusätzlich wird mit `flakes.machines` eine eigene Abstraktion verwendet um Systeme und entsprechende Hilfswerkzeuge zu konfigurieren.
+Dies ist momentan nur im Quellcode dokumentiert in [`machines.nix`](./machines.nix).
+
+Nutzernamen von Administratoren und ihre SSH-Keys sind unter [`keys`](./keys) organisiert.
+
+Zur Zeit ist genau eine Maschine in Betrieb, die in [`tharos.nix`](./tharos.nix) spezifiziert ist.
+Der Server läuft bei [STRATO](https://www.strato.de) unter Kundennummer 73292174, Auftragsnummer 7709638.
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..3ee4e26
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,69 @@
+{
+ "nodes": {
+ "disko": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1758287904,
+ "narHash": "sha256-IGmaEf3Do8o5Cwp1kXBN1wQmZwQN3NLfq5t4nHtVtcU=",
+ "owner": "nix-community",
+ "repo": "disko",
+ "rev": "67ff9807dd148e704baadbd4fd783b54282ca627",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "disko",
+ "type": "github"
+ }
+ },
+ "flake-parts": {
+ "inputs": {
+ "nixpkgs-lib": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1759362264,
+ "narHash": "sha256-wfG0S7pltlYyZTM+qqlhJ7GMw2fTF4mLKCIVhLii/4M=",
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "rev": "758cf7296bee11f1706a574c77d072b8a7baa881",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "flake-parts",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1759580034,
+ "narHash": "sha256-YWo57PL7mGZU7D4WeKFMiW4ex/O6ZolUS6UNBHTZfkI=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "3bcc93c5f7a4b30335d31f21e2f1281cba68c318",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixos-25.05",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "disko": "disko",
+ "flake-parts": "flake-parts",
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..5c9b8d0
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,26 @@
+{
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
+
+ flake-parts = {
+ url = "github:hercules-ci/flake-parts";
+ inputs.nixpkgs-lib.follows = "nixpkgs";
+ };
+ disko = {
+ url = "github:nix-community/disko";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+ outputs =
+ inputs@{ flake-parts, ... }:
+ flake-parts.lib.mkFlake { inherit inputs; } (
+ { self, lib, ... }:
+ {
+ imports = lib.fileset.toList (
+ # Alle Nix-Dateien in diesem Projekt sind Flake-Parts-Module
+ lib.fileset.fileFilter (file: file.hasExt "nix" && file.name != "flake.nix") ./.
+ );
+ systems = [ "x86_64-linux" ];
+ }
+ );
+}
diff --git a/keys.nix b/keys.nix
new file mode 100644
index 0000000..0076788
--- /dev/null
+++ b/keys.nix
@@ -0,0 +1,39 @@
+{ lib, ... }:
+let
+ inherit (lib) mkOption types;
+in
+{
+ options.flake.keys = mkOption {
+ description = ''
+ Dateisystempfade zu öffentlichen SSH-Schlüsseln für alle Administratoren
+
+ Kann benutzt werden um entsprechende Systemnutzer in `users.users` automatisch zu erstellen.
+ '';
+ type = with types; attrsOf (listOf path);
+ default =
+ let
+ /*
+ Dateinamen mit öffentlichen SSH-Schlüsseln aus einem Verzeichnis mit Nutzernamen auslesen.
+ Format der Verzeichniseinträrge muss eines der Folgenden sein:
+ - Datei mit genau einem Eintrag für den jeweiligen Nutzer
+ - Verzeichnis mit Dateien die jeweils einen Eintrag enthalten
+ */
+ get-key-files =
+ dir:
+ let
+ key-files =
+ username: type:
+ with builtins;
+ if type == "regular" then
+ [ "${dir}/${username}" ]
+ else
+ map (keyfile: "${dir}/${username}/${keyfile}") (
+ attrValues (lib.filterAttrs (_: keytype: keytype == "regular") subdir)
+ );
+ in
+ with builtins;
+ lib.mapAttrs key-files (builtins.readDir dir);
+ in
+ get-key-files ./keys;
+ };
+}
diff --git a/keys/vg b/keys/vg
new file mode 100644
index 0000000..a8d7cfc
--- /dev/null
+++ b/keys/vg
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINXpKx7ckbOaYr/3F51AKJJVJ7u/8A/f6sFbc9W8GUUr
diff --git a/machines.nix b/machines.nix
new file mode 100644
index 0000000..8f29ef0
--- /dev/null
+++ b/machines.nix
@@ -0,0 +1,225 @@
+{
+ self,
+ config,
+ lib,
+ ...
+}:
+let
+ inherit (lib) mkOption types;
+in
+{
+ options.flake.machines = mkOption {
+ description = ''
+ Hilfswerkzeuge um Rechner mit NixOS neu aufzusetzen und zu verwalten
+
+ `networking.hostName` wird standardmäßig auf den Attributnamen der Maschine gesetzt.
+ '';
+ type =
+ with types;
+ attrsOf (
+ submodule (
+ machine@{ name, options, ... }:
+ {
+ options = {
+ bootstrap-target = mkOption {
+ description = ''
+ SSH-Verbindung fürs Aufsetzen der Maschine
+
+ Die jeweilige Maschine wird einen vorhandenen Authentifikationsmechanismus haben, z.b. SSH-Key oder Passwort, der hierfür beim Aufruf einmalig manuell genutzt wird.
+ '';
+ type = types.str;
+ example = "root@example.org";
+ };
+
+ deploy-target = mkOption {
+ description = ''
+ SSH-Verbindung für reguläres Verwaltung der Maschine
+
+ Die Authentifizierung erfolg entsprechend der Maschinenkonfiguration, und Admins sollten die Verbindungsdaten bei sich entsprechend einrichten, z.B. über `~/.ssh/config`.
+ '';
+ type = types.str;
+ example = "user@example.org";
+ default = machine.config.bootstrap-target;
+ };
+
+ nixos = mkOption {
+ description = ''
+ NixOS Konfigurationsmodul für die Maschine
+
+ Dokumentation: <https://search.nixos.org/options>
+ '';
+ type =
+ with types;
+ deferredModuleWith {
+ staticModules = [
+ {
+ _class = "nixos";
+ networking.hostName = lib.mkDefault machine.name;
+ }
+ ];
+ };
+ };
+
+ vm = mkOption {
+ description = ''
+ Konfigurationsmodul zur virtuellen Maschine (VM) für lokale Tests
+
+ Abgesehen von den hier festgelegten Einstellungen und Datenbankinhalten enspricht die virtuelle Maschine genau dem Produktionssystem.
+ Dies erlaubt recht zuverlässige manuelle Prüfung der Konfiguration im Vorfeld von Änderungen am Produktionssystem.
+
+ Maschinenspezifische VM-Einstellungen werden mit den hier vorgegebenen Standardwerten zusammengefügt.
+ Bei Bedarf müssen die Standards explizit mit `lib.mkForce` überschrieben werden.
+ '';
+ type = types.deferredModuleWith {
+ staticModules = [ options.vm.default ];
+ };
+ default =
+ {
+ config,
+ options,
+ modulesPath,
+ lib,
+ ...
+ }:
+ {
+ _class = "nixos";
+
+ imports = [
+ "${modulesPath}/virtualisation/qemu-vm.nix"
+ ];
+
+ options = {
+ virtualisation.portOffset = mkOption {
+ description = ''
+ An den VM-Host exponierte Portnummern von Diensten innerhalb der VM um diesen Wert versetzen
+
+ Dies dient dazu, Konflikte mit möglicherweise bestehenden Diensten auf dem Host zu vermeiden.
+
+ Beispiel: SSH auf Port 22 in der VM wird beim Host exponiert auf Port ${
+ toString (22 + options.virtualisation.portOffset.default)
+ }.
+ '';
+ type = types.ints.positive;
+ default = 10000;
+ };
+ };
+
+ config = {
+ virtualisation.forwardPorts = map (port: {
+ from = "host";
+ guest.port = port;
+ host.port = port + config.virtualisation.portOffset;
+ proto = "tcp";
+ }) config.networking.firewall.allowedTCPPorts;
+
+ services.getty.autologinUser = lib.mkDefault "root";
+ };
+ };
+ };
+
+ eval = mkOption {
+ readOnly = true;
+ internal = true;
+ default = lib.nixosSystem {
+ modules = [ machine.config.nixos ];
+ };
+ };
+
+ vm-eval = mkOption {
+ readOnly = true;
+ internal = true;
+ default = lib.nixosSystem {
+ modules = [
+ machine.config.nixos
+ machine.config.vm
+ ];
+ };
+ };
+ };
+ }
+ )
+ );
+ };
+
+ config.flake.nixosConfigurations = lib.mapAttrs (name: machine: machine.eval) self.machines;
+
+ config.perSystem =
+ { pkgs, ... }:
+ {
+ /*
+ Projektweite Tests, ausführen mit:
+
+ nix flake check
+ */
+ checks = lib.concatMapAttrs (name: machine: {
+ /*
+ Konsistenzprüfung der Partitionierung.
+
+ Erfolgreicher Test bedeutet nicht unbedingt, dass die Konfiguration auch in der Produktionsumgebung funktioniert.
+ Beispielsweise muss die Konfiguration dazu passen, ob das Produktionssystem mit BIOS oder UEFI bootet.
+ */
+ "${name}-installTest" = machine.eval.config.system.build.installTest;
+ }) self.machines;
+
+ # Werkzeuge zur Verwaltung von Maschinen
+ packages = lib.concatMapAttrs (name: machine: {
+ "infect-${name}" = pkgs.writeShellApplication {
+ name = "infect";
+ runtimeInputs = with pkgs; [ nixos-anywhere ];
+ /*
+ ACHTUNG: Vor dem Aufsetzen erst Informationen zur Hardware abfragen und in die Konfiguration einbetten!
+
+ nix run .#machines.infect-<machine> -- --no-reboot --generate-hardware-config nixos-hardware-config <datei>
+ */
+ text = ''
+ nixos-anywhere --store-paths \
+ ${machine.eval.config.system.build.diskoScript} \
+ ${machine.eval.config.system.build.toplevel} \
+ ${machine.bootstrap-target} "$@"
+ '';
+ };
+
+ "deploy-${name}" = pkgs.writeShellApplication {
+ name = "deploy";
+ text =
+ let
+ system = machine.eval.config.system.build.toplevel;
+ in
+ ''
+ ${
+ "" # XXX: Keine Signaturen. Das richtig einzurichten ist für den Moment zu umständlich.
+ }nix copy --to ssh-ng://${machine.deploy-target} --no-check-sigs ${system}
+
+ # shellcheck disable=SC2087
+ ${
+ ""
+ /*
+ ACHTUNG: Admins sollten sicherstellen, dass für diesen Host in `~/.ssh/config` folgendes enthalten ist:
+
+ ForwardAgent: yes
+
+ Außerdem muss der dort konfigurierte Nutzer dem Nutzer auf der jeweiligen Maschine enstprechen, und der SSH-Key zum SSH-Agent hinzugefügt sein.
+ Es gibt keine Passwörter und auch keine Möglichkeit eins einzugeben.
+ */
+ }ssh ${machine.deploy-target} << EOF
+ sudo nix-env -p /nix/var/nix/profiles/system --set ${system}
+ sudo ${system}/bin/switch-to-configuration "$@"
+ EOF
+ '';
+ };
+
+ "vm-${name}" = pkgs.writeShellApplication {
+ name = "vm";
+ text = ''
+ ${
+ "" # Festplattenabbild im flüchtigen Speicher erstellen, das ist deutlich schneller
+ }cd "$(mktemp -d)"
+ ${
+ "" # Immer am Ende aufräumen
+ }trap 'rm -f nixos.qcow2' EXIT
+ ${lib.getExe machine.vm-eval.config.system.build.vm} "$@"
+ '';
+ };
+ }) self.machines;
+ };
+}
diff --git a/tharos.nix b/tharos.nix
new file mode 100644
index 0000000..6d96754
--- /dev/null
+++ b/tharos.nix
@@ -0,0 +1,133 @@
+{
+ self,
+ inputs,
+ lib,
+ ...
+}:
+{
+ flake.machines.tharos = {
+ bootstrap-target = "root@${self.machines.tharos.deploy-target}";
+ # Administratoren verbinden sich mit ihrem selbst festgelegten Nutzernamen
+ deploy-target = "81.169.239.254";
+ nixos =
+ {
+ config,
+ pkgs,
+ modulesPath,
+ ...
+ }:
+
+ {
+ imports = [
+ inputs.disko.nixosModules.default
+ "${modulesPath}/profiles/qemu-guest.nix"
+ ];
+
+ nixpkgs.hostPlatform = "x86_64-linux";
+ system.stateVersion = "25.05";
+
+ services.cloud-init = {
+ enable = true;
+ network.enable = true;
+ };
+ # `cloud-init` übernimmt Netzwerkeinstellungen
+ networking.useDHCP = false;
+
+ # Kein Login für Nutzer die nicht explizit deklariert sind
+ users.mutableUsers = false;
+ users.users = lib.mapAttrs (username: keyFiles: {
+ isNormalUser = true;
+ openssh.authorizedKeys.keyFiles = keyFiles;
+ # ANMERKUNG: Der Einfachheit halber sind bis auf Weiteres alle Nutzer mit SSH-Zugang auch Administratoren
+ extraGroups = [ "wheel" ];
+ }) self.keys;
+
+ /*
+ `sudo` über SSH ohne Passworteingabe
+ ANMERKUNG: Nutzer sollten in ihrem ` ~/.ssh/config` für die Maschine einstellen:
+
+ ForwardAgent: yes
+ */
+ security.pam.sshAgentAuth.enable = true;
+ security.pam.services.sudo.sshAgentAuth = true;
+
+ # Nur Administratoren können den angemeldeten Benutzer wechseln
+ security.pam.services.su.requireWheel = true;
+
+ networking.firewall.allowPing = true;
+ services.openssh = {
+ enable = true;
+ settings = {
+ PasswordAuthentication = false;
+ PermitRootLogin = "prohibit-password";
+ };
+ };
+
+ nix = {
+ settings.trusted-users = [
+ "root"
+ "@wheel"
+ ];
+ settings.experimental-features = [
+ "nix-command"
+ "flakes"
+ ];
+ };
+
+ disko.devices.disk.main = {
+ device = "/dev/vda";
+ type = "disk";
+ content = {
+ type = "gpt";
+ partitions = {
+ # Die KVM läuft auf SeaBIOS, daher muss es hier eine MBR-Partition sein
+ boot = {
+ size = "1M";
+ type = "EF02";
+ };
+ root = {
+ size = "100%";
+ content = {
+ type = "filesystem";
+ format = "ext4";
+ mountpoint = "/";
+ };
+ };
+ };
+ };
+ };
+
+ /*
+ ANMERKUNG: Erhalten durch:
+
+ nix run .#machines.infect-tharos -- --no-reboot --generate-hardware-config nixos-hardware-config <datei>
+ */
+ boot.initrd.availableKernelModules = [
+ "ata_piix"
+ "uhci_hcd"
+ "virtio_pci"
+ "virtio_blk"
+ ];
+ boot.kernelModules = [ "kvm-amd" ];
+ };
+
+ vm =
+ {
+ config,
+ lib,
+ pkgs,
+ ...
+ }:
+ {
+ virtualisation = {
+ memorySize = 4096;
+ diskSize = 4096;
+ cores = 2;
+ graphics = false;
+ };
+
+ services.cloud-init.enable = lib.mkForce false;
+ networking.useDHCP = lib.mkForce true;
+ };
+ };
+}