commit d2ab7241e4b01645456d4efc0fb187c8c698d99d Author: Joshua Falkenberg Date: Thu Sep 10 13:31:28 2026 +0200 inital config diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb7a6ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Nix build results +result +result-* + +# direnv +.direnv +.envrc + +# Secrets / private keys +*.key +*.pem +*.agekey +AGE-SECRET-KEY-* +*.secret +.sops.yaml + +# Local/private hardware information +*.local + +# macOS +.DS_Store + +# Editors +.vscode/ +.idea/ +.zed/ diff --git a/.sops.yaml.template b/.sops.yaml.template new file mode 100644 index 0000000..42ee97c --- /dev/null +++ b/.sops.yaml.template @@ -0,0 +1,14 @@ +keys: + - &yubikey_14321697 age1yubikey1q26trpwnpym6t9m5qlcfm00kddy3tzex7u8r6ck7sqkq39wxc5wwwafuq89 + - &yubikey_32464919 age1yubikey1qgvu222dwg2gtvc4mq2txk79t4ep5gk9qwgfh520lc3m4kyxjmtgzexyljy + - &recovery_bitwarden age1r993gvlratqk5h5seuzrd8fjv356csnrvtdt5ugcfdu3cw66hdls9gtuwd + - &host AGE_HOST_D + +creation_rules: + - path_regex: secrets/.*\.yaml$ + key_groups: + - age: + - *yubikey_14321697 + - *yubikey_32464919 + - *recovery_bitwarden + - *host diff --git a/README.md b/README.md new file mode 100644 index 0000000..b63a9eb --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# NixOS Config + +TODO... + + +## Neuinstallation + +### X. Nach der Installation + +#### X.X. Bootstrap-Skript ausführen + +Nach der Installation ist es notwendig das Bootstrap-Skript auszuführen. +Dafür muss zunächst der Key ausgewählt werden (Yubikey oder Bitwarden): + +```bash +sudo env SOPS_AGE_KEY_FILE=/home/user/nixos-config/age/yubikey-14321697.txt /home/user/nixos-config/scripts/bootstrap-host.sh +``` +```bash +sudo env SOPS_AGE_KEY_FILE=/home/user/nixos-config/age/yubikey-32464919.txt /home/user/nixos-config/scripts/bootstrap-host.sh +``` +Für den Recovery-Key aus Bitwarden, muss dieser zunächst exportiert werden und als .txt Datei gespeichert werden. +```bash +sudo env SOPS_AGE_KEY_FILE=/path/to/recovery-keyfile.txt /home/user/nixos-config/scripts/bootstrap-host.sh +``` diff --git a/age/yubikey-14321697.txt b/age/yubikey-14321697.txt new file mode 100644 index 0000000..a970414 --- /dev/null +++ b/age/yubikey-14321697.txt @@ -0,0 +1,9 @@ +# Serial: 14321697, Slot: 1 +# Name: age identity 34bb9100 +# Created: Thu, 10 Sep 2026 06:31:58 +0000 +# PIN policy: Once (A PIN is required once per session, if set) +# Touch policy: Always (A physical touch is required for every decryption) +# Recipient: age1yubikey1q26trpwnpym6t9m5qlcfm00kddy3tzex7u8r6ck7sqkq39wxc5wwwafuq89 +AGE-PLUGIN-YUBIKEY-1YXYD5QYZXJAEZQQF8YHJ5 + + diff --git a/age/yubikey-32464919.txt b/age/yubikey-32464919.txt new file mode 100644 index 0000000..d9db93e --- /dev/null +++ b/age/yubikey-32464919.txt @@ -0,0 +1,9 @@ +# Serial: 32464919, Slot: 1 +# Name: age identity 1b6d4f63 +# Created: Thu, 10 Sep 2026 06:46:10 +0000 +# PIN policy: Once (A PIN is required once per session, if set) +# Touch policy: Always (A physical touch is required for every decryption) +# Recipient: age1yubikey1qgvu222dwg2gtvc4mq2txk79t4ep5gk9qwgfh520lc3m4kyxjmtgzexyljy +AGE-PLUGIN-YUBIKEY-1ZASW7QVZRDK57CCKHC0YW + + diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..fb6b866 --- /dev/null +++ b/flake.nix @@ -0,0 +1,49 @@ +{ + description = "Declarative NixOS desktop configuration"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + impermanence.url = "github:nix-community/impermanence"; + + sops-nix.url = "github:Mic92/sops-nix"; + + disko.url = "github:nix-community/disko"; + }; + + outputs = + { + nixpkgs, + home-manager, + impermanence, + sops-nix, + disko, + ... + }: + { + nixosConfigurations.desktop = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + + modules = [ + disko.nixosModules.disko + impermanence.nixosModules.impermanence + sops-nix.nixosModules.sops + home-manager.nixosModules.home-manager + + ./hosts/desktop + ./modules/base.nix + ./modules/boot.nix + ./modules/ephemeral-root.nix + ./modules/impermanence.nix + ./modules/persistence.nix + ./modules/secrets.nix + ./modules/desktop.nix + ]; + }; + }; +} diff --git a/home/user.nix b/home/user.nix new file mode 100644 index 0000000..0d63cc3 --- /dev/null +++ b/home/user.nix @@ -0,0 +1,16 @@ +{ pkgs, ... }: + +{ + home.stateVersion = "25.11"; + + programs.home-manager.enable = true; + + programs.git = { + enable = true; + }; + + home.packages = with pkgs; [ + age + sops + ]; +} diff --git a/hosts/desktop/default.nix b/hosts/desktop/default.nix new file mode 100644 index 0000000..4c30d20 --- /dev/null +++ b/hosts/desktop/default.nix @@ -0,0 +1,30 @@ +{ ... }: + +{ + imports = [ + ./disko.nix + ]; + + networking.hostName = "desktop"; + + users.users.user = { + isNormalUser = true; + home = "/home/user"; + + extraGroups = [ + "wheel" + "networkmanager" + ]; + }; + + security.sudo.wheelNeedsPassword = false; + + home-manager = { + useGlobalPkgs = true; + useUserPackages = true; + + users.user = import ../../home/user.nix; + }; + + system.stateVersion = "25.11"; +} diff --git a/hosts/desktop/disko.nix b/hosts/desktop/disko.nix new file mode 100644 index 0000000..ffe25c0 --- /dev/null +++ b/hosts/desktop/disko.nix @@ -0,0 +1,60 @@ +{ + disko.devices.disk.main = { + type = "disk"; + device = "/dev/disk/by-id/ata-Vi550_S3_4935445284600580"; + + content = { + type = "gpt"; + + partitions = { + ESP = { + size = "1G"; + type = "EF00"; + + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "umask=0077" ]; + }; + }; + + luks = { + size = "100%"; + label="luks"; + + content = { + type = "luks"; + name = "cryptroot"; + + settings = { + allowDiscards = true; + }; + + content = { + type = "btrfs"; + + subvolumes = { + "/@nix" = { + mountpoint = "/nix"; + mountOptions = [ + "compress=zstd" + "noatime" + ]; + }; + + "/@persist" = { + mountpoint = "/persist"; + mountOptions = [ + "compress=zstd" + "noatime" + ]; + }; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/modules/base.nix b/modules/base.nix new file mode 100644 index 0000000..f474f36 --- /dev/null +++ b/modules/base.nix @@ -0,0 +1,29 @@ +{ pkgs, ... }: + +{ + nix.settings.experimental-features = [ + "nix-command" + "flakes" + ]; + + nixpkgs.config.allowUnfree = true; + + networking.networkmanager.enable = true; + + time.timeZone = "Europe/Berlin"; + + i18n.defaultLocale = "de_DE.UTF-8"; + + console.keyMap = "de"; + + environment.systemPackages = with pkgs; [ + git + vim + curl + wget + htop + tree + ]; + + programs.git.enable = true; +} diff --git a/modules/boot.nix b/modules/boot.nix new file mode 100644 index 0000000..1f87399 --- /dev/null +++ b/modules/boot.nix @@ -0,0 +1,24 @@ +{ ... }: + +{ + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + boot.plymouth.enable = true; + + boot.initrd.systemd.enable = true; + + boot.kernelParams = [ + "quiet" + "splash" + ]; + + boot.consoleLogLevel = 3; + boot.initrd.verbose = false; + + boot.initrd.luks.devices."cryptroot" = { + device = "/dev/disk/by-partlabel/luks"; + }; + + fileSystems."/".device = "/dev/mapper/cryptroot"; +} diff --git a/modules/desktop.nix b/modules/desktop.nix new file mode 100644 index 0000000..180bce9 --- /dev/null +++ b/modules/desktop.nix @@ -0,0 +1,7 @@ +{ ... }: + +{ + hardware.graphics = { + enable = true; + }; +} diff --git a/modules/ephemeral-root.nix b/modules/ephemeral-root.nix new file mode 100644 index 0000000..b95434a --- /dev/null +++ b/modules/ephemeral-root.nix @@ -0,0 +1,16 @@ +{ ... }: + +{ + fileSystems."/" = { + device = "tmpfs"; + fsType = "tmpfs"; + + options = [ + "mode=755" + "nosuid" + "nodev" + ]; + + neededForBoot = true; + }; +} diff --git a/modules/impermanence.nix b/modules/impermanence.nix new file mode 100644 index 0000000..04618ac --- /dev/null +++ b/modules/impermanence.nix @@ -0,0 +1,4 @@ +{ ... }: + +{ +} diff --git a/modules/persistence.nix b/modules/persistence.nix new file mode 100644 index 0000000..ad9267c --- /dev/null +++ b/modules/persistence.nix @@ -0,0 +1,25 @@ +{ ... }: + +{ + fileSystems."/persist".neededForBoot = true; + + environment.persistence."/persist" = { + hideMounts = true; + + directories = [ + { + directory = "/home/user/nixos-config"; + user = "user"; + group = "users"; + } + + "/var/lib/nixos" + ]; + + files = [ + "/etc/machine-id" + "/etc/ssh/ssh_host_ed25519_key" + "/etc/ssh/ssh_host_ed25519_key.pub" + ]; + }; +} diff --git a/modules/secrets.nix b/modules/secrets.nix new file mode 100644 index 0000000..89b00f5 --- /dev/null +++ b/modules/secrets.nix @@ -0,0 +1,5 @@ +{ ... }: + +{ + sops.age.keyFile = "/persist/keys/host-age.key"; +} diff --git a/scripts/bootstrap-host.sh b/scripts/bootstrap-host.sh new file mode 100755 index 0000000..a6c1260 --- /dev/null +++ b/scripts/bootstrap-host.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash + +set -euo pipefail + +REPO_DIR="/home/user/nixos-config" +PERSIST_DIR="/persist" +KEY_DIR="${PERSIST_DIR}/keys" +HOST_KEY="${KEY_DIR}/host-age.key" + +SOPS_TEMPLATE="${REPO_DIR}/.sops.yaml.template" +LOCAL_SOPS="${REPO_DIR}/.sops.yaml" + +echo "==> NixOS host bootstrap" + +if [[ "${EUID}" -ne 0 ]]; then + echo "ERROR: Dieses Script muss als root ausgeführt werden." + exit 1 +fi + +if ! command -v age-keygen >/dev/null 2>&1; then + echo "ERROR: age-keygen wurde nicht gefunden." + exit 1 +fi + +if ! command -v sops >/dev/null 2>&1; then + echo "ERROR: sops wurde nicht gefunden." + exit 1 +fi + +if [[ -z "${SOPS_AGE_KEY_FILE:-}" ]]; then + echo "ERROR: SOPS_AGE_KEY_FILE ist nicht gesetzt." + echo + echo "Beispiel:" + echo + echo " SOPS_AGE_KEY_FILE=${REPO_DIR}/age/yubikey-a.txt \\" + echo " sudo -E ${REPO_DIR}/scripts/bootstrap-host.sh" + exit 1 +fi + +if [[ ! -f "${SOPS_AGE_KEY_FILE}" ]]; then + echo "ERROR: SOPS_AGE_KEY_FILE existiert nicht:" + echo " ${SOPS_AGE_KEY_FILE}" + exit 1 +fi + +if ! mountpoint -q "${PERSIST_DIR}"; then + echo "ERROR: ${PERSIST_DIR} ist nicht gemountet." + exit 1 +fi + +if [[ ! -d "${REPO_DIR}" ]]; then + echo "ERROR: Repository fehlt:" + echo " ${REPO_DIR}" + exit 1 +fi + +if [[ ! -f "${SOPS_TEMPLATE}" ]]; then + echo "ERROR: SOPS template fehlt:" + echo " ${SOPS_TEMPLATE}" + exit 1 +fi + +if [[ -e "${HOST_KEY}" ]]; then + echo "ERROR: Host-Key existiert bereits:" + echo " ${HOST_KEY}" + echo + echo "Es wird nichts überschrieben." + exit 1 +fi + +if [[ -e "${LOCAL_SOPS}" ]]; then + echo "ERROR: Lokale .sops.yaml existiert bereits:" + echo " ${LOCAL_SOPS}" + echo + echo "Es wird nichts überschrieben." + exit 1 +fi + +echo +echo "==> SOPS identity:" +echo " ${SOPS_AGE_KEY_FILE}" + +echo +echo "==> Erzeuge Host-age-Key" + +install -d -m 0700 "${KEY_DIR}" + +age-keygen -o "${HOST_KEY}" + +chmod 0600 "${HOST_KEY}" +chown root:root "${HOST_KEY}" + +HOST_RECIPIENT="$(age-keygen -y "${HOST_KEY}")" + +echo +echo "==> Host recipient:" +echo +echo " ${HOST_RECIPIENT}" + +echo +echo "==> Erzeuge lokale .sops.yaml" + +sed "s/AGE_HOST_D/${HOST_RECIPIENT}/g" \ + "${SOPS_TEMPLATE}" > "${LOCAL_SOPS}" + +chmod 0600 "${LOCAL_SOPS}" + +echo +echo "============================================================" +echo "Der Host-Key wurde erzeugt." +echo +echo "Private Key:" +echo " ${HOST_KEY}" +echo +echo "Lokale SOPS-Konfiguration:" +echo " ${LOCAL_SOPS}" +echo +echo "Jetzt werden die vorhandenen Secrets auf" +echo "YubiKey A + YubiKey B + Recovery + Host" +echo "rekeyed." +echo "============================================================" +echo + +read -r -p "Rekeying jetzt durchführen? [y/N] " answer + +if [[ "${answer}" != "y" && "${answer}" != "Y" ]]; then + echo + echo "Abgebrochen." + echo + echo "Der Host-Key und .sops.yaml wurden bereits erzeugt." + echo "Das Rekeying wurde NICHT durchgeführt." + exit 0 +fi + +shopt -s nullglob +secret_files=( "${REPO_DIR}"/secrets/*.yaml ) +shopt -u nullglob + +if [[ "${#secret_files[@]}" -eq 0 ]]; then + echo + echo "Keine secrets/*.yaml gefunden." + echo "Host-Key wurde erfolgreich erzeugt." + exit 0 +fi + +echo +echo "==> Rekeye Secrets" + +for secret in "${secret_files[@]}"; do + echo + echo "Updating:" + echo " ${secret}" + + SOPS_AGE_KEY_FILE="${SOPS_AGE_KEY_FILE}" \ + sops updatekeys --yes "${secret}" +done + +echo +echo "==> Bootstrap abgeschlossen." +echo +echo "Host recipient:" +echo " ${HOST_RECIPIENT}" +echo +echo "Host key:" +echo " ${HOST_KEY}" +echo +echo "SOPS config:" +echo " ${LOCAL_SOPS}" diff --git a/secrets/test.yaml b/secrets/test.yaml new file mode 100644 index 0000000..82fa3d0 --- /dev/null +++ b/secrets/test.yaml @@ -0,0 +1,38 @@ +test: + message: ENC[AES256_GCM,data:eF4xvJYIkGg1,iv:CVf9AA5hjPBq+wZLpOjmcOZhve2Tsyk/eULXdOp9Q/s=,tag:BrRYXcHOuftG0Vap4bK4ig==,type:str] + number: ENC[AES256_GCM,data:M8M=,iv:hgv/5PHEIS1ENu2UGMsKMc+liHMjEafd6ITyZvpGnb8=,tag:Xs/cUc/Ny17Ka8qtQHWMUw==,type:int] +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHBpdi1wMjU2IE5MdVJBQSBBOStPcWx0 + YkhRNkFKNmhNeHlvQWFYRDJ2UmNMR2lyTWZEV3MrbldmSWd1Sgp4a0FidVJwZ2tv + V2JlZjRQT05xZlpoTEFiVUdKVTRVemx3a0dnTm9MRENjCi0tLSBQMGE0aXVQRFQx + NjVEWEtNcldHUWlXbmhQRWxXdzk5bzd1RjNmaFdRS1Z3Crl/J6U3WSvIZRreNIXO + aTqjTZaahbHjX4pmkDRmSd2o5x1oNksutIoWO5l32tX8SEU3quDj9bDymZIJBp8X + 86g= + -----END AGE ENCRYPTED FILE----- + recipient: age1yubikey1q26trpwnpym6t9m5qlcfm00kddy3tzex7u8r6ck7sqkq39wxc5wwwafuq89 + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHBpdi1wMjU2IEcyMVBZdyBBdktHQ0px + ZVBMNDhteElxVXlUczd5K2I5K25iejNCOEZkTjFDRkwzcUlJWAptRmlpazhsbkY5 + QnRhamtXR1JObFlTY1FjZ1hIUmxzTTZEYXVrZzlaY2lBCi0tLSBSUlAxOFZlcTFn + cEFRR2tIT2liSVAxaHhkcWs4MlQ1cVBTeW9vUm9PcW5VCpQ82CSSMYHMK5Su/An9 + o6Ybb//z92Xm1fbuCv/1E/yQg12sOClbsJvNPWbwynoWkAmVzureuDzXNgIsv2oA + Mt4= + -----END AGE ENCRYPTED FILE----- + recipient: age1yubikey1qgvu222dwg2gtvc4mq2txk79t4ep5gk9qwgfh520lc3m4kyxjmtgzexyljy + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzMWthMjdFTElpbzcvcFFQ + UzdzbUtpSkVQQWpWTVpxNUcwamlqOE5Nd2hVCkZNeUdvOVF0eWxwZ2F5WlY0UHNv + QlFlNUdRSlh6M0xub1dUNkNTWVdxZzQKLS0tIGNQcGdzazZTRll1MEhuZzlxZUM0 + UEdvV0xsRzRRRUZxZ0NsUFpUSEpKZ2sKQxxSJOQen+whTB4awc7EGSUJO/CWmlOc + Srryah6025tEfH+scMxYJcpSMKrQlQ778yvK92xxSJbFgoQi0sReoA== + -----END AGE ENCRYPTED FILE----- + recipient: age1r993gvlratqk5h5seuzrd8fjv356csnrvtdt5ugcfdu3cw66hdls9gtuwd + lastmodified: "2026-09-10T08:56:29Z" + mac: ENC[AES256_GCM,data:YOEymqQFlK/4iaQpcA3e7OtuTnqz/52iS+TAc2sSIdKP2TV9NG6tr32Fn66AQrjFfYmfY81LIYFRnqRNV1IH9rccNTvxxklF6wKCz8p0/ESqpKbDEaNsoRzaTHCNRvDu3t8GvrxudDtRXQtQ94yx/WCk/M7XHQuY0LbagJv0Jm0=,iv:AIyGRpYuAsxaD2Rm/CZMUCLEkHCR3ImvJ/GoRwG9FAk=,tag:Fr5T15lfHUv1cIvJmXwZkA==,type:str] + unencrypted_suffix: _unencrypted + version: 3.13.3