From 132948e9940fcfa901d9bb2655c99ade955ba4ad Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 19:50:38 +0200 Subject: [PATCH 01/38] feat: add hosts, profiles and core modules --- src/hosts/bora/default.nix | 36 ++++++++++++++++++++++ src/hosts/bora/hardware.nix | 22 +++++++++++++ src/hosts/bora/meta.nix | 7 +++++ src/modules/core/boot.nix | 34 ++++++++++++++++++++ src/modules/core/default.nix | 9 ++++++ src/modules/core/locale.nix | 34 ++++++++++++++++++++ src/modules/core/nix.nix | 40 ++++++++++++++++++++++++ src/modules/core/sysctl.nix | 60 ++++++++++++++++++++++++++++++++++++ src/profiles/developer.nix | 19 ++++++++++++ src/profiles/minimal.nix | 14 +++++++++ src/profiles/server.nix | 17 ++++++++++ src/profiles/workstation.nix | 12 ++++++++ 12 files changed, 304 insertions(+) create mode 100644 src/hosts/bora/default.nix create mode 100644 src/hosts/bora/hardware.nix create mode 100644 src/hosts/bora/meta.nix create mode 100644 src/modules/core/boot.nix create mode 100644 src/modules/core/default.nix create mode 100644 src/modules/core/locale.nix create mode 100644 src/modules/core/nix.nix create mode 100644 src/modules/core/sysctl.nix create mode 100644 src/profiles/developer.nix create mode 100644 src/profiles/minimal.nix create mode 100644 src/profiles/server.nix create mode 100644 src/profiles/workstation.nix diff --git a/src/hosts/bora/default.nix b/src/hosts/bora/default.nix new file mode 100644 index 0000000..184974a --- /dev/null +++ b/src/hosts/bora/default.nix @@ -0,0 +1,36 @@ +{ config, lib, pkgs, hostname, username, hardwareProfile, systemProfile, ... }: + +{ + networking.hostName = hostname; + + users = { + users.${username} = { + isNormalUser = true; + description = "Utente principale"; + extraGroups = [ "wheel" "networkmanager" "audio" "video" "libvirtd" "microvm" ]; + shell = pkgs.zsh; + openssh.authorizedKeys.keys = [ ]; + }; + users.root.openssh.authorizedKeys.keys = [ ]; + }; + + programs.zsh = { + enable = true; + enableCompletion = true; + autosuggestions.enable = true; + syntaxHighlighting.enable = true; + ohMyZsh = { + enable = true; + theme = "agnoster"; + plugins = [ "git" "sudo" "systemd" "zsh-navigation-tools" ]; + }; + }; + + security.sudo = { + enable = true; + extraRules = [{ + groups = [ "wheel" ]; + commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }]; + }]; + }; +} diff --git a/src/hosts/bora/hardware.nix b/src/hosts/bora/hardware.nix new file mode 100644 index 0000000..f41a457 --- /dev/null +++ b/src/hosts/bora/hardware.nix @@ -0,0 +1,22 @@ +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; + + boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usb_storage" "sd_mod" ]; + boot.initrd.kernelModules = [ "zfs" ]; + boot.kernelModules = [ "kvm-amd" "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = { device = "zroot/root"; fsType = "zfs"; }; + fileSystems."/nix" = { device = "zroot/root/nix"; fsType = "zfs"; }; + fileSystems."/home" = { device = "zroot/root/home"; fsType = "zfs"; }; + fileSystems."/var" = { device = "zroot/root/var"; fsType = "zfs"; }; + fileSystems."/tmp" = { device = "zroot/root/tmp"; fsType = "zfs"; }; + fileSystems."/boot" = { device = "/dev/disk/by-uuid/BOOT-UUID"; fsType = "vfat"; }; + + swapDevices = [ ]; + + nix.settings.max-jobs = lib.mkDefault 8; + powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; +} diff --git a/src/hosts/bora/meta.nix b/src/hosts/bora/meta.nix new file mode 100644 index 0000000..8ff219f --- /dev/null +++ b/src/hosts/bora/meta.nix @@ -0,0 +1,7 @@ +{ + system = "x86_64-linux"; + hardware = "desktop"; + profile = "developer"; + hostname = "bora"; + username = "user"; +} diff --git a/src/modules/core/boot.nix b/src/modules/core/boot.nix new file mode 100644 index 0000000..65ac757 --- /dev/null +++ b/src/modules/core/boot.nix @@ -0,0 +1,34 @@ +{ config, lib, pkgs, ... }: +{ + boot.loader = { + systemd-boot = { + enable = true; + configurationLimit = 20; + }; + efi.canTouchEfiVariables = true; + timeout = 3; + }; + boot.kernelParams = [ + "quiet" + "splash" + "mitigations=auto" + "random.trust_cpu=off" + "random.trust_bootloader=off" + "slab_nomerge" + "init_on_alloc=1" + "init_on_free=1" + "page_alloc.shuffle=1" + "pti=on" + "vsyscall=none" + "debugfs=off" + ]; + boot.consoleLogLevel = 0; + boot.initrd.verbose = false; + boot.initrd.systemd.enable = true; + boot.supportedFilesystems = [ "zfs" "btrfs" "ntfs" "exfat" "vfat" "xfs" ]; + boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest; + hardware.enableRedistributableFirmware = true; + hardware.enableAllFirmware = true; + hardware.cpu.intel.updateMicrocode = lib.mkDefault true; + hardware.cpu.amd.updateMicrocode = lib.mkDefault true; +} diff --git a/src/modules/core/default.nix b/src/modules/core/default.nix new file mode 100644 index 0000000..37ad942 --- /dev/null +++ b/src/modules/core/default.nix @@ -0,0 +1,9 @@ +{ lib, ... }: +{ + imports = [ + ./boot.nix + ./nix.nix + ./locale.nix + ./sysctl.nix + ]; +} diff --git a/src/modules/core/locale.nix b/src/modules/core/locale.nix new file mode 100644 index 0000000..25190af --- /dev/null +++ b/src/modules/core/locale.nix @@ -0,0 +1,34 @@ +{ config, lib, pkgs, ... }: +{ + time.timeZone = lib.mkDefault "Europe/Rome"; + i18n = { + defaultLocale = lib.mkDefault "it_IT.UTF-8"; + supportedLocales = [ + "it_IT.UTF-8/UTF-8" + "en_US.UTF-8/UTF-8" + "C.UTF-8/UTF-8" + ]; + extraLocaleSettings = { + LC_ADDRESS = lib.mkDefault "it_IT.UTF-8"; + LC_IDENTIFICATION = lib.mkDefault "it_IT.UTF-8"; + LC_MEASUREMENT = lib.mkDefault "it_IT.UTF-8"; + LC_MONETARY = lib.mkDefault "it_IT.UTF-8"; + LC_NAME = lib.mkDefault "it_IT.UTF-8"; + LC_NUMERIC = lib.mkDefault "it_IT.UTF-8"; + LC_PAPER = lib.mkDefault "it_IT.UTF-8"; + LC_TELEPHONE = lib.mkDefault "it_IT.UTF-8"; + LC_TIME = lib.mkDefault "it_IT.UTF-8"; + }; + }; + console = { + font = lib.mkDefault "Lat2-Terminus16"; + keyMap = lib.mkDefault "it"; + packages = with pkgs; [ terminus_font ]; + }; + services = { + dbus.enable = true; + udisks2.enable = true; + upower.enable = lib.mkDefault true; + fwupd.enable = lib.mkDefault true; + }; +} diff --git a/src/modules/core/nix.nix b/src/modules/core/nix.nix new file mode 100644 index 0000000..0f45850 --- /dev/null +++ b/src/modules/core/nix.nix @@ -0,0 +1,40 @@ +{ config, lib, pkgs, ... }: +{ + nix = { + package = pkgs.nixVersions.stable; + settings = { + experimental-features = [ "nix-command" "flakes" "auto-allocate-uids" "ca-derivations" ]; + auto-optimise-store = true; + max-jobs = lib.mkDefault 8; + max-substitution-jobs = 64; + min-free = 1073741824; + max-free = 5368709120; + substituters = [ + "https://cache.nixos.org" + "https://nix-community.cachix.org" + "https://astro-microvm.cachix.org" + "https://bora.cachix.org" + ]; + trusted-public-keys = [ + "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" + "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" + "astro-microvm.cachix.org-1:5VxKj9V5rE1xJgF2gQvA0Z3L8R6bH7cN4pY9sW1tXnM=" + ]; + trusted-users = [ "root" "@wheel" ]; + auto-optimise-store = true; + }; + gc = { + automatic = true; + dates = "weekly"; + options = "--delete-older-than 14d"; + }; + optimise = { + automatic = true; + dates = [ "03:00" ]; + }; + nixPath = [ + "nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos" + "nixos-config=/persist/etc/nixos/configuration.nix" + ]; + }; +} diff --git a/src/modules/core/sysctl.nix b/src/modules/core/sysctl.nix new file mode 100644 index 0000000..0535336 --- /dev/null +++ b/src/modules/core/sysctl.nix @@ -0,0 +1,60 @@ +{ config, lib, pkgs, ... }: +{ + boot.kernel.sysctl = { + "kernel.kptr_restrict" = 2; + "kernel.dmesg_restrict" = 1; + "kernel.perf_event_paranoid" = 3; + "kernel.yama.ptrace_scope" = 2; + "kernel.randomize_va_space" = 2; + "kernel.unprivileged_bpf_disabled" = 1; + "net.core.bpf_jit_enable" = 0; + "kernel.kexec_load_disabled" = 1; + "kernel.sysrq" = 0; + "net.ipv4.tcp_syncookies" = 1; + "net.ipv4.conf.all.rp_filter" = 1; + "net.ipv4.conf.default.rp_filter" = 1; + "net.ipv4.conf.all.accept_redirects" = 0; + "net.ipv4.conf.default.accept_redirects" = 0; + "net.ipv4.conf.all.secure_redirects" = 0; + "net.ipv4.conf.default.secure_redirects" = 0; + "net.ipv4.conf.all.send_redirects" = 0; + "net.ipv4.conf.default.send_redirects" = 0; + "net.ipv6.conf.all.accept_redirects" = 0; + "net.ipv6.conf.default.accept_redirects" = 0; + "net.ipv4.icmp_echo_ignore_all" = 0; + "net.ipv4.icmp_echo_ignore_broadcasts" = 1; + "net.ipv4.icmp_ignore_bogus_error_responses" = 1; + "net.ipv4.tcp_rfc1337" = 1; + "vm.swappiness" = 10; + "vm.vfs_cache_pressure" = 50; + "vm.dirty_ratio" = 10; + "vm.dirty_background_ratio" = 5; + "vm.dirty_expire_centisecs" = 3000; + "vm.dirty_writeback_centisecs" = 500; + "vm.max_map_count" = 2147483642; + "kernel.numa_balancing" = 0; + "fs.file-max" = 9223372036854775807; + "fs.inotify.max_user_watches" = 1048576; + "fs.inotify.max_user_instances" = 1048576; + "fs.inotify.max_queued_events" = 1048576; + "fs.aio-max-nr" = 1048576; + "net.core.somaxconn" = 65535; + "net.core.netdev_max_backlog" = 5000; + "net.core.rmem_max" = 134217728; + "net.core.wmem_max" = 134217728; + "net.ipv4.tcp_rmem" = "4096 87380 134217728"; + "net.ipv4.tcp_wmem" = "4096 65536 134217728"; + "net.ipv4.tcp_congestion_control" = "bbr"; + "net.ipv4.tcp_fastopen" = 3; + "net.ipv4.tcp_fin_timeout" = 15; + "net.ipv4.tcp_keepalive_time" = 300; + "net.ipv4.tcp_keepalive_probes" = 5; + "net.ipv4.tcp_keepalive_intvl" = 15; + "net.ipv4.tcp_mtu_probing" = 1; + "net.ipv4.tcp_slow_start_after_idle" = 0; + "vm.overcommit_memory" = 1; + "vm.oom_kill_allocating_task" = 0; + "vm.panic_on_oom" = 0; + }; + systemd.coredump.enable = false; +} diff --git a/src/profiles/developer.nix b/src/profiles/developer.nix new file mode 100644 index 0000000..9b6cee4 --- /dev/null +++ b/src/profiles/developer.nix @@ -0,0 +1,19 @@ +{ config, lib, pkgs, ... }: +with lib; +{ + imports = [ + ./workstation.nix + ]; + + bora = { + containers.instancePool.enable = mkDefault false; + hardware.cpuVendor = mkDefault "amd"; + hardware.gpuVendor = mkDefault "nvidia"; + hardwareProfile = mkDefault "desktop"; + }; + + environment.systemPackages = with pkgs; [ + gcc clang nodejs python3 rustc cargo go + nginx + ]; +} diff --git a/src/profiles/minimal.nix b/src/profiles/minimal.nix new file mode 100644 index 0000000..4c21be3 --- /dev/null +++ b/src/profiles/minimal.nix @@ -0,0 +1,14 @@ +{ config, lib, pkgs, ... }: +with lib; +{ + bora = { + desktop.kde.enable = mkDefault false; + desktop.audio.enable = mkDefault false; + desktop.layout.enable = mkDefault false; + hardwareProfile = mkDefault "server"; + hardware.cpuVendor = mkDefault "intel"; + hardware.gpuVendor = mkDefault "intel"; + }; + + services.openssh.enable = true; +} diff --git a/src/profiles/server.nix b/src/profiles/server.nix new file mode 100644 index 0000000..fd35a09 --- /dev/null +++ b/src/profiles/server.nix @@ -0,0 +1,17 @@ +{ config, lib, pkgs, ... }: +with lib; +{ + bora = { + desktop.kde.enable = mkDefault false; + desktop.audio.enable = mkDefault false; + desktop.layout.enable = mkDefault false; + containers.instancePool.enable = mkDefault false; + hardwareProfile = mkDefault "server"; + hardware.cpuVendor = mkDefault "intel"; + hardware.gpuVendor = mkDefault "intel"; + }; + + services.openssh.enable = true; + networking.firewall.allowedTCPPorts = [ 22 80 443 ]; + systemd.services.sshd.wantedBy = [ "multi-user.target" ]; +} diff --git a/src/profiles/workstation.nix b/src/profiles/workstation.nix new file mode 100644 index 0000000..9ddbe1c --- /dev/null +++ b/src/profiles/workstation.nix @@ -0,0 +1,12 @@ +{ config, lib, pkgs, ... }: +with lib; +{ + bora = { + desktop.kde.enable = mkDefault true; + desktop.audio.enable = mkDefault true; + desktop.layout.enable = mkDefault true; + hardware.cpuVendor = mkDefault "intel"; + hardware.gpuVendor = mkDefault "amd"; + hardwareProfile = mkDefault "desktop"; + }; +} From 11b0bc0303fe188e2ac8f1d53221ca6bf8b2b651 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 19:50:38 +0200 Subject: [PATCH 02/38] feat: implement security, hardware and filesystem modules --- src/modules/filesystem/default.nix | 7 +++ src/modules/filesystem/impermanence.nix | 64 +++++++++++++++++++++++++ src/modules/filesystem/zfs.nix | 64 +++++++++++++++++++++++++ src/modules/hardware/cpu.nix | 30 ++++++++++++ src/modules/hardware/default.nix | 8 ++++ src/modules/hardware/gpu.nix | 48 +++++++++++++++++++ src/modules/hardware/platform.nix | 14 ++++++ src/modules/security/default.nix | 8 ++++ src/modules/security/firewall.nix | 46 ++++++++++++++++++ src/modules/security/hardening.nix | 43 +++++++++++++++++ src/modules/security/ssh.nix | 46 ++++++++++++++++++ 11 files changed, 378 insertions(+) create mode 100644 src/modules/filesystem/default.nix create mode 100644 src/modules/filesystem/impermanence.nix create mode 100644 src/modules/filesystem/zfs.nix create mode 100644 src/modules/hardware/cpu.nix create mode 100644 src/modules/hardware/default.nix create mode 100644 src/modules/hardware/gpu.nix create mode 100644 src/modules/hardware/platform.nix create mode 100644 src/modules/security/default.nix create mode 100644 src/modules/security/firewall.nix create mode 100644 src/modules/security/hardening.nix create mode 100644 src/modules/security/ssh.nix diff --git a/src/modules/filesystem/default.nix b/src/modules/filesystem/default.nix new file mode 100644 index 0000000..f09b40e --- /dev/null +++ b/src/modules/filesystem/default.nix @@ -0,0 +1,7 @@ +{ config, lib, pkgs, ... }: +{ + imports = [ + ./zfs.nix + ./impermanence.nix + ]; +} diff --git a/src/modules/filesystem/impermanence.nix b/src/modules/filesystem/impermanence.nix new file mode 100644 index 0000000..8c4e5ac --- /dev/null +++ b/src/modules/filesystem/impermanence.nix @@ -0,0 +1,64 @@ +{ config, lib, pkgs, username, ... }: + +{ + environment.persistence."/persist" = { + hideMounts = true; + + directories = [ + "/etc/nixos" + "/etc/NetworkManager" + "/etc/ssh" + "/etc/udev" + "/var/lib/nixos" + "/var/lib/systemd" + "/var/lib/bluetooth" + "/var/lib/tor" + "/var/log" + "/var/lib/microvm" + ]; + + files = [ + "/etc/machine-id" + "/etc/resolv.conf" + "/etc/adjtime" + ]; + + users.${username} = { + directories = [ + "Downloads" + "Documents" + "Immagini" + "Video" + "Musica" + "Projects" + "Go" + ".ssh" + ".gnupg" + ".local/share/keyrings" + ".config/gtk-3.0" + ".config/gtk-4.0" + ".config/qt5ct" + ".config/qt6ct" + ".config/KDE" + ".config/kdeglobals" + ".config/systemd" + ".cache/mozilla" + ".mozilla" + ]; + files = [ + ".config/user-dirs.dirs" + ]; + }; + }; + }; + fileSystems."/persist" = { + device = "zroot/root/persist"; + fsType = "zfs"; + neededForBoot = true; + }; + fileSystems."/home" = { + device = "zroot/root/home"; + fsType = "zfs"; + neededForBoot = true; + }; +} diff --git a/src/modules/filesystem/zfs.nix b/src/modules/filesystem/zfs.nix new file mode 100644 index 0000000..7849ca4 --- /dev/null +++ b/src/modules/filesystem/zfs.nix @@ -0,0 +1,64 @@ +{ config, lib, pkgs, ... }: +{ + services.zfs = { + trim = { + enable = true; + interval = "weekly"; + }; + autoScrub = { + enable = true; + interval = "monthly"; + }; + autoSnapshot = { + enable = true; + flags = "-k -p --utc"; + }; + zed = { + enable = true; + settings = { + ZED_EMAIL_ADDR = ""; + ZED_NOTIFY_INTERVAL_SECS = 3600; + }; + }; + }; + boot.zfs = { + forceImportRoot = false; + forceImportAll = false; + allowHibernation = false; + requestEncryptionCredentials = true; + }; + services.sanoid = { + enable = true; + templates = { + default = { + hourly = 24; + daily = 30; + weekly = 12; + monthly = 6; + yearly = 2; + autosnap = true; + autoprune = true; + }; + critical = { + hourly = 48; + daily = 90; + weekly = 24; + monthly = 12; + yearly = 5; + autosnap = true; + autoprune = true; + }; + ephemeral = { + hourly = 2; + daily = 1; + autosnap = true; + autoprune = true; + }; + }; + }; + boot.kernelParams = [ + "zfs.zfs_arc_max=8589934592" + "zfs.zfs_arc_min=1073741824" + ]; + networking.hostId = "deadbeef"; +} diff --git a/src/modules/hardware/cpu.nix b/src/modules/hardware/cpu.nix new file mode 100644 index 0000000..610fe0b --- /dev/null +++ b/src/modules/hardware/cpu.nix @@ -0,0 +1,30 @@ +{ config, lib, pkgs, ... }: +with lib; +let + hwLib = import ../../../../lib/hardware.nix { inherit lib; }; + cpuVendor = config.bora.hardware.cpuVendor or "intel"; + cpuCfg = hwLib.cpu.${cpuVendor} or hwLib.cpu.intel; +in { + options.bora.hardware = { + cpuVendor = mkOption { + type = types.enum [ "intel" "amd" "arm" ]; + default = "intel"; + description = "CPU vendor for optimal settings"; + }; + enableMitigations = mkOption { + type = types.bool; + default = true; + description = "Enable CPU vulnerability mitigations"; + }; + }; + config = { + hardware.cpu.intel.updateMicrocode = mkIf (cpuVendor == "intel") true; + hardware.cpu.amd.updateMicrocode = mkIf (cpuVendor == "amd") true; + boot.kernelModules = cpuCfg.kernelModules; + boot.kernelParams = cpuCfg.kernelParams + ++ (if config.bora.hardware.enableMitigations + then [ "mitigations=auto" ] + else [ "mitigations=off" ]); + powerManagement.cpuFreqGovernor = cpuCfg.power.governor; + }; +} diff --git a/src/modules/hardware/default.nix b/src/modules/hardware/default.nix new file mode 100644 index 0000000..e33e887 --- /dev/null +++ b/src/modules/hardware/default.nix @@ -0,0 +1,8 @@ +{ config, lib, pkgs, ... }: +{ + imports = [ + ./cpu.nix + ./gpu.nix + ./platform.nix + ]; +} diff --git a/src/modules/hardware/gpu.nix b/src/modules/hardware/gpu.nix new file mode 100644 index 0000000..5350d21 --- /dev/null +++ b/src/modules/hardware/gpu.nix @@ -0,0 +1,48 @@ +{ config, lib, pkgs, ... }: +with lib; +let + hwLib = import ../../../../lib/hardware.nix { inherit lib; }; + gpuVendor = config.bora.hardware.gpuVendor or "amd"; + gpuCfg = hwLib.gpu.${gpuVendor} or hwLib.gpu.amd; +in { + options.bora.hardware = { + gpuVendor = mkOption { + type = types.enum [ "nvidia" "amd" "intel" ]; + default = "amd"; + description = "GPU vendor for optimal drivers"; + }; + enableNvidiaPrime = mkOption { + type = types.bool; + default = false; + description = "Enable NVIDIA Optimus/PRIME (laptop)"; + }; + }; + config = { + services.xserver.videoDrivers = gpuCfg.drivers; + hardware.nvidia = mkIf (gpuVendor == "nvidia") { + modesetting.enable = true; + powerManagement.enable = true; + powerManagement.finegrained = false; + open = false; + nvidiaSettings = false; + prime = { + sync.enable = config.bora.hardware.enableNvidiaPrime; + offload = { + enable = !config.bora.hardware.enableNvidiaPrime; + enableOffloadCmd = true; + }; + }; + }; + boot.kernelParams = gpuCfg.kernelParams; + environment.sessionVariables = gpuCfg.env; + hardware.graphics = { + enable = true; + enable32Bit = true; + extraPackages = with pkgs; [ + (if gpuVendor == "nvidia" then vaapiVdpau + else if gpuVendor == "intel" then intel-media-driver + else vaapiVdpau) + ]; + }; + }; +} diff --git a/src/modules/hardware/platform.nix b/src/modules/hardware/platform.nix new file mode 100644 index 0000000..29fd881 --- /dev/null +++ b/src/modules/hardware/platform.nix @@ -0,0 +1,14 @@ +{ config, lib, pkgs, ... }: +with lib; +let + hwLib = import ../../../../lib/hardware.nix { inherit lib; }; + hwProfile = config.bora.hardwareProfile or "desktop"; + profileCfg = hwLib.profileOpts.${hwProfile} or hwLib.profileOpts.desktop; +in { + options.bora.hardwareProfile = mkOption { + type = types.enum [ "desktop" "laptop" "server" ]; + default = "desktop"; + description = "Hardware profile for power/mitigation tuning"; + }; + config = profileCfg; +} diff --git a/src/modules/security/default.nix b/src/modules/security/default.nix new file mode 100644 index 0000000..831e03b --- /dev/null +++ b/src/modules/security/default.nix @@ -0,0 +1,8 @@ +{ config, lib, pkgs, ... }: +{ + imports = [ + ./firewall.nix + ./hardening.nix + ./ssh.nix + ]; +} diff --git a/src/modules/security/firewall.nix b/src/modules/security/firewall.nix new file mode 100644 index 0000000..8af8cab --- /dev/null +++ b/src/modules/security/firewall.nix @@ -0,0 +1,46 @@ +{ config, lib, pkgs, ... }: +{ + networking.nftables = { + enable = true; + ruleset = '' + table inet filter { + chain input { + type filter hook input priority 0; policy drop; + ct state invalid drop; + ct state { established, related } accept; + iifname lo accept; + icmp type { + echo-request, destination-unreachable, + time-exceeded, parameter-problem + } limit rate 10/second accept; + ip6 icmpv6 type { + echo-request, destination-unreachable, + time-exceeded, parameter-problem, + nd-router-advert, nd-neighbor-solicit, + nd-neighbor-advert, nd-router-solicit + } limit rate 10/second accept; + tcp dport 22 ip saddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } accept; + udp dport 5353 ip saddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } accept; + udp dport { 67, 68 } accept; + log prefix "NF:DROP-INPUT: " drop; + } + chain forward { + type filter hook forward priority 0; policy drop; + ct state { established, related } accept; + iifname "microvm" oifname "microvm" accept; + iifname "microvm" oifname "eth0" masquerade accept; + log prefix "NF:DROP-FORWARD: " drop; + } + chain output { + type filter hook output priority 0; policy accept; + } + } + table inet nat { + chain postrouting { + type nat hook postrouting priority 100; + oifname "eth0" masquerade; + } + } + ''; + }; +} diff --git a/src/modules/security/hardening.nix b/src/modules/security/hardening.nix new file mode 100644 index 0000000..1b82ada --- /dev/null +++ b/src/modules/security/hardening.nix @@ -0,0 +1,43 @@ +{ config, lib, pkgs, ... }: +{ + security.apparmor = { + enable = true; + enableCache = true; + packages = [ pkgs.apparmor-profiles ]; + }; + security.protectKernelImage = true; + security.allowUserNamespaces = true; + security.lockKernelModules = false; + security.wrappers = { }; + boot.kernelParams = [ + "quiet" + "slab_nomerge" + "init_on_alloc=1" + "init_on_free=1" + "page_alloc.shuffle=1" + "pti=on" + "vsyscall=none" + "debugfs=off" + "module.sig_enforce=1" + "lockdown=confidentiality" + ]; + systemd.services = { + avahi-daemon.enable = lib.mkDefault false; + cups.enable = lib.mkDefault false; + bluetooth.enable = lib.mkDefault false; + }; + systemd.extraConfig = '' + DefaultTimeoutStopSec=10s + DefaultTimeoutStartSec=30s + DefaultDeviceTimeoutSec=30s + ''; + security.audit = { + enable = true; + rules = [ + "-w /etc/nixos -p wa -k nixos-config" + "-w /nix/store -p wa -k nix-store" + "-a exit,always -S execve -k process-exec" + "-a exit,always -S mount -k mount" + ]; + }; +} diff --git a/src/modules/security/ssh.nix b/src/modules/security/ssh.nix new file mode 100644 index 0000000..17a7233 --- /dev/null +++ b/src/modules/security/ssh.nix @@ -0,0 +1,46 @@ +{ config, lib, pkgs, ... }: +{ + services.openssh = { + enable = true; + settings = { + PermitRootLogin = "no"; + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + AuthenticationMethods = "publickey"; + PubkeyAuthentication = yes; + UsePAM = false; + MaxAuthTries = 3; + MaxSessions = 4; + MaxStartups = "10:30:60"; + LoginGraceTime = 30; + AllowTcpForwarding = false; + AllowAgentForwarding = false; + PermitTunnel = false; + X11Forwarding = false; + ClientAliveInterval = 300; + ClientAliveCountMax = 0; + Ciphers = "chacha20-poly1305@openssh.com,aes256-gcm@openssh.com"; + KexAlgorithms = "curve25519-sha256,diffie-hellman-group-exchange-sha256"; + Macs = "hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com"; + HostKeyAlgorithms = "ssh-ed25519,rsa-sha2-512"; + Compression = "no"; + }; + hostKeys = [ + { + path = "/persist/ssh/ssh_host_ed25519_key"; + type = "ed25519"; + } + { + path = "/persist/ssh/ssh_host_rsa_key"; + type = "rsa"; + bits = 4096; + } + ]; + }; + services.fail2ban = { + enable = true; + maxretry = 3; + bantime = "24h"; + banaction = "nftables-multiport"; + }; +} From 9cc8175819f83c0d2f6e16f6b42d17a698904f4f Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 19:50:39 +0200 Subject: [PATCH 03/38] feat: add desktop, network and config file modules --- config/containers/microvm-bridge.nix | 9 +++ config/desktop/kdeglobals | 57 ++++++++++++++ config/desktop/khotkeysrc | 27 +++++++ config/desktop/kwinrc | 43 +++++++++++ config/desktop/plasma-appletsrc | 38 +++++++++ config/security/nftables.nix | 55 ++++++++++++++ src/modules/desktop/default.nix | 8 ++ src/modules/desktop/kde-minimal.nix | 90 ++++++++++++++++++++++ src/modules/desktop/maclike.nix | 110 +++++++++++++++++++++++++++ src/modules/desktop/pipewire.nix | 39 ++++++++++ src/modules/network/base.nix | 25 ++++++ src/modules/network/default.nix | 7 ++ src/modules/network/dns.nix | 20 +++++ 13 files changed, 528 insertions(+) create mode 100644 config/containers/microvm-bridge.nix create mode 100644 config/desktop/kdeglobals create mode 100644 config/desktop/khotkeysrc create mode 100644 config/desktop/kwinrc create mode 100644 config/desktop/plasma-appletsrc create mode 100644 config/security/nftables.nix create mode 100644 src/modules/desktop/default.nix create mode 100644 src/modules/desktop/kde-minimal.nix create mode 100644 src/modules/desktop/maclike.nix create mode 100644 src/modules/desktop/pipewire.nix create mode 100644 src/modules/network/base.nix create mode 100644 src/modules/network/default.nix create mode 100644 src/modules/network/dns.nix diff --git a/config/containers/microvm-bridge.nix b/config/containers/microvm-bridge.nix new file mode 100644 index 0000000..12db67a --- /dev/null +++ b/config/containers/microvm-bridge.nix @@ -0,0 +1,9 @@ +{ config, lib, ... }: +{ + microvm.host.network = { + enable = true; + nat = true; + subnet = "10.100.0.0/24"; + bridge = "microvm"; + }; +} diff --git a/config/desktop/kdeglobals b/config/desktop/kdeglobals new file mode 100644 index 0000000..ec2b5a9 --- /dev/null +++ b/config/desktop/kdeglobals @@ -0,0 +1,57 @@ +[General] +ColorScheme=BoraDark +Name=Bora +shadeSortColumn=true +TerminalApplication=konsole +TerminalService=org.kde.konsole +font=Noto Sans,10,-1,5,50,0,0,0,0,0 +fixed=JetBrainsMono Nerd Font,10,-1,5,50,0,0,0,0,0 +smallestReadableFont=Noto Sans,8,-1,5,50,0,0,0,0,0 +toolbarFont=Noto Sans,10,-1,5,50,0,0,0,0,0 +menuFont=Noto Sans,10,-1,5,50,0,0,0,0,0 + +[KDE] +widgetStyle=Breeze +ShowIconsOnPushButtons=1 +ShowIconsInMenuItems=1 +ShowIconsInListViews=1 +ShowIconsInTooltips=1 +ShowIconsInBreadcrumbBar=1 +ShowIconsInTitleBar=1 +TreeViewAnimated=1 + +[WM] +activeBackground=10,12,22 +activeForeground=220,220,240 +inactiveBackground=8,9,18 +inactiveForeground=140,140,160 + +[Icons] +Theme=TelaCircleDark + +[Colors:Window] +BackgroundNormal=13,15,26 +BackgroundAlternate=18,20,32 +ForegroundNormal=220,220,240 +ForegroundInactive=100,100,130 +ForegroundLink=0,212,255 +ForegroundVisited=123,47,190 + +[Colors:Selection] +BackgroundNormal=0,212,255 +ForegroundNormal=10,12,22 + +[Colors:Button] +BackgroundNormal=20,22,36 +ForegroundNormal=220,220,240 +BackgroundHover=30,32,48 + +[Compositing] +Enabled=true +OpenGLIsUnsafe=false +Backend=OpenGL +AnimationDurationFactor=0.4 +window-decoration=Breeze + +[UiSettings] +ColorScheme=BoraDark diff --git a/config/desktop/khotkeysrc b/config/desktop/khotkeysrc new file mode 100644 index 0000000..bfdf6f3 --- /dev/null +++ b/config/desktop/khotkeysrc @@ -0,0 +1,27 @@ +[Data] +DataCount=1 + +[Data_1] +Comment=Bora Launcher +Enabled=true +Name=Nexus +Type=ACTION_TRIGGERS +Removed=false + +[Data_1\Actions] +ActionCount=1 + +[Data_1\Actions\0] +CommandURL=org.kde.krunner +Type=COMMAND_URL + +[Data_1\Conditions] +ConditionsCount=0 + +[Data_1\Triggers] +TriggersCount=1 + +[Data_1\Triggers\0] +Key=Alt+F1 +Type=SHORTCUT +Uuid={nexus-krunner} diff --git a/config/desktop/kwinrc b/config/desktop/kwinrc new file mode 100644 index 0000000..390f075 --- /dev/null +++ b/config/desktop/kwinrc @@ -0,0 +1,43 @@ +[Compositing] +Backend=OpenGL +Enabled=true +OpenGLIsUnsafe=false +AnimationSpeed=1 +AnimationsEnabled=true +OpenGLCompositing=true +XRenderSmoothScale=false + +[Windows] +TitlebarDoubleClickCommand=Maximize +RollUp=false +BorderlessMaximizedWindows=true +Placement=Centered +FocusPolicy=ClickToFocus +SeparateScreenFocus=false +ActiveMouseScreen=true + +[Desktops] +Number=6 +Rows=2 + +[Effect-Blur] +BlurStrength=25 +BlurRadius=12 + +[Effect-BoraTransition] +Duration=350 + +[Effect-Fade] +Duration=200 + +[Effect-Scale] +Duration=200 + +[Plugins] +blurEnabled=true +slideEnabled=true +kwin4_effect_scaleEnabled=true +kwin4_effect_translucencyEnabled=true +fadeEnabled=true +glideEnabled=true +wobblywindowsEnabled=false diff --git a/config/desktop/plasma-appletsrc b/config/desktop/plasma-appletsrc new file mode 100644 index 0000000..ac9c5b0 --- /dev/null +++ b/config/desktop/plasma-appletsrc @@ -0,0 +1,38 @@ +[Containments][2] +formfactor=2 +lastScreen=0 +location=0 +plugin=org.kde.plasma.folder +wallpaperplugin=org.kde.plasma.image + +[Containments][2][Wallpaper] +plugin=org.kde.plasma.image + +[Containments][2][Wallpaper][org.kde.plasma.image][General] +Image=file:///run/current-system/sw/share/wallpapers/Next/contents/images_dark/5120x2880.png +FillMode=2 + +[Containments][3] +formfactor=2 +location=0 +plugin=org.kde.plasma.folder +wallpaperplugin=org.kde.plasma.image + +[Containments][4] +formfactor=2 +lastScreen=0 +location=0 +plugin=org.kde.plasma.folder +wallpaperplugin=org.kde.plasma.image + +[Containments][5] +formfactor=2 +location=0 +plugin=org.kde.plasma.folder +wallpaperplugin=org.kde.plasma.image + +[Containments][6] +formfactor=2 +location=0 +plugin=org.kde.plasma.folder +wallpaperplugin=org.kde.plasma.image diff --git a/config/security/nftables.nix b/config/security/nftables.nix new file mode 100644 index 0000000..1b6a2ae --- /dev/null +++ b/config/security/nftables.nix @@ -0,0 +1,55 @@ +table inet filter { + chain input { + type filter hook input priority 0; policy drop; + + ct state invalid drop; + ct state { established, related } accept; + + iifname lo accept; + + icmp type { + echo-request, destination-unreachable, + time-exceeded, parameter-problem + } limit rate 10/second accept; + + ip6 icmpv6 type { + echo-request, destination-unreachable, + time-exceeded, parameter-problem, + nd-router-advert, nd-neighbor-solicit, + nd-neighbor-advert, nd-router-solicit + } limit rate 10/second accept; + + tcp dport 22 ip saddr { + 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 + } accept; + + udp dport 5353 ip saddr { + 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 + } accept; + + udp dport { 67, 68 } accept; + + log prefix "NF:DROP-INPUT: " drop; + } + + chain forward { + type filter hook forward priority 0; policy drop; + ct state { established, related } accept; + + iifname "microvm" oifname "microvm" accept; + iifname "microvm" oifname "eth0" masquerade accept; + + log prefix "NF:DROP-FORWARD: " drop; + } + + chain output { + type filter hook output priority 0; policy accept; + } +} + +table inet nat { + chain postrouting { + type nat hook postrouting priority 100; + oifname "eth0" masquerade; + } +} diff --git a/src/modules/desktop/default.nix b/src/modules/desktop/default.nix new file mode 100644 index 0000000..6480e33 --- /dev/null +++ b/src/modules/desktop/default.nix @@ -0,0 +1,8 @@ +{ config, lib, pkgs, ... }: +{ + imports = [ + ./kde-minimal.nix + ./pipewire.nix + ./maclike.nix + ]; +} diff --git a/src/modules/desktop/kde-minimal.nix b/src/modules/desktop/kde-minimal.nix new file mode 100644 index 0000000..6816bf3 --- /dev/null +++ b/src/modules/desktop/kde-minimal.nix @@ -0,0 +1,90 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.bora.desktop.kde; + kdeMinimal = with pkgs; [ + plasma6 + kdePackages.plasma-workspace + kdePackages.kwin + kdePackages.kirigami + kdePackages.qqc2-desktop-style + kdePackages.plasma-integration + kdePackages.breeze-icons + kdePackages.breeze-gtk + kdePackages.breeze-qt5 + kdePackages.konsole + kdePackages.systemsettings + dolphin + kate + ]; +in { + options.bora.desktop.kde = { + enable = mkEnableOption "KDE Plasma 6 minimal desktop"; + enableWayland = mkOption { + type = types.bool; + default = true; + description = "Enable Wayland session"; + }; + excludePackages = mkOption { + type = types.listOf types.package; + default = with pkgs.kdePackages; [ + elisa + gwenview + khelpcenter + okular + oxygen + krdp + krfb + ktorrent + kget + korganizer + kaddressbook + kmail + akonadi + kontact + ]; + description = "KDE packages to exclude"; + }; + }; + config = mkIf cfg.enable { + services = { + displayManager.sddm = { + enable = true; + wayland.enable = cfg.enableWayland; + theme = "breeze"; + autoNumlock = true; + }; + desktopManager.plasma6 = { + enable = true; + enableQt5Integration = false; + }; + }; + environment.plasma6.excludePackages = cfg.excludePackages; + environment.systemPackages = kdeMinimal; + hardware.graphics = { + enable = true; + enable32Bit = true; + }; + fonts = { + enableDefaultPackages = true; + packages = with pkgs; [ + noto-fonts + noto-fonts-cjk + noto-fonts-emoji + source-code-pro + (nerdfonts.override { fonts = [ "JetBrainsMono" "FiraCode" ]; }) + ]; + fontconfig.defaultFonts = { + serif = [ "Noto Serif" ]; + sansSerif = [ "Noto Sans" ]; + monospace = [ "JetBrainsMono Nerd Font" ]; + emoji = [ "Noto Color Emoji" ]; + }; + }; + xdg.portal = { + enable = true; + extraPortals = [ pkgs.xdg-desktop-portal-kde ]; + configPackages = [ pkgs.xdg-desktop-portal-kde ]; + }; + }; +} diff --git a/src/modules/desktop/maclike.nix b/src/modules/desktop/maclike.nix new file mode 100644 index 0000000..831f419 --- /dev/null +++ b/src/modules/desktop/maclike.nix @@ -0,0 +1,110 @@ +{ config, lib, pkgs, username, ... }: +with lib; +let + cfg = config.bora.desktop.layout; + initScript = pkgs.writeShellScriptBin "bora-desktop-init" + (builtins.readFile ./../../../../scripts/maclike/init-desktop.sh); + finalizeScript = pkgs.writeShellScriptBin "bora-desktop-finalize" + (builtins.readFile ./../../../../scripts/maclike/finalize.sh); +in { + options.bora.desktop.layout = { + enable = mkEnableOption "Bora custom desktop layout"; + theme = mkOption { + type = types.enum [ "bora-dark" "bora-light" ]; + default = "bora-dark"; + }; + layout = mkOption { + type = types.enum [ "standard" "minimal" "floating" ]; + default = "floating"; + }; + topPanelHeight = mkOption { type = types.int; default = 34; }; + enableTransparency = mkOption { type = types.bool; default = true; }; + enableNexus = mkOption { type = types.bool; default = true; }; + nexusKey = mkOption { type = types.str; default = "Alt+F1"; }; + globalMenu = mkOption { type = types.bool; default = true; }; + desktopCount = mkOption { type = types.int; default = 6; }; + accentColor = mkOption { + type = types.enum [ "cyan" "purple" "blue" "green" "orange" ]; + default = "cyan"; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + plasma6 kdePackages.plasma-workspace kdePackages.kwin + kdePackages.konsole kdePackages.systemsettings + kdePackages.dolphin kdePackages.kate + kdePackages.qqc2-desktop-style kdePackages.qqc2-breeze-style + kdePackages.breeze-icons kdePackages.breeze-gtk + kdePackages.breeze-qt5 kdePackages.plasma-integration + tela-circle-icon-theme + (kdePackages.plasma6.pkgs.applet-window-buttons or kdePackages.applet-window-buttons) + (kdePackages.plasma6.pkgs.applet-window-title or kdePackages.applet-window-title) + initScript finalizeScript + ]; + + environment.sessionVariables = { + KDE_SESSION_VERSION = "6"; + XDG_CURRENT_DESKTOP = "KDE"; + XDG_SESSION_DESKTOP = "KDE"; + DESKTOP_SESSION = "plasmawayland"; + PLASMA_USE_QT_SCALING = "1"; + QT_AUTO_SCREEN_SET_FACTOR = "0"; + QT_SCALE_FACTOR_ROUNDING_POLICY = "RoundPreferFloor"; + }; + + environment.etc."skel/.config/plasma-org.kde.plasma.desktop-appletsrc".source = + ./../../../../config/desktop/plasma-appletsrc; + environment.etc."skel/.config/kdeglobals".source = + ./../../../../config/desktop/kdeglobals; + environment.etc."skel/.config/kwinrc".source = + ./../../../../config/desktop/kwinrc; + environment.etc."skel/.config/khotkeysrc".text = + builtins.replaceStrings [ "Alt+F1" ] [ cfg.nexusKey ] + (builtins.readFile ./../../../../config/desktop/khotkeysrc); + environment.etc."skel/.config/plasmarc".text = '' + [Theme] + name=Breeze + [Wallpaper] + fillMode=2 + [PlasmaViews] + PanelOpacity=${if cfg.enableTransparency then "0.70" else "1.0"} + ''; + + system.activationScripts.bora-desktop = stringAfter [ "etc" ] '' + mkdir -p /etc/skel/.config/autostart + cat > /etc/skel/.config/autostart/bora-desktop-setup.desktop << 'EOF' + [Desktop Entry] + Type=Application + Name=Bora Desktop Initializer + Exec=${initScript}/bin/bora-desktop-init + X-KDE-autostart-phase=2 + OnlyShowIn=KDE + EOF + ''; + + xdg.desktopEntries.bora-desktop = { + name = "Bora Desktop"; + exec = "startplasma-wayland"; + type = "Application"; + categories = [ "Desktop" "KDE" ]; + desktopName = "Plasma (Bora Layout)"; + }; + + services.displayManager.sddm.wayland.enable = true; + services.desktopManager.plasma6.enableQt5Integration = false; + security.polkit.enable = true; + + systemd.user.services.bora-desktop-autostart = { + description = "Bora desktop finalizer"; + after = [ "plasmashell.service" ]; + wantedBy = [ "plasma-workspace.target" ]; + partOf = [ "plasma-workspace.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = "${finalizeScript}/bin/bora-desktop-finalize"; + }; + }; + }; +} diff --git a/src/modules/desktop/pipewire.nix b/src/modules/desktop/pipewire.nix new file mode 100644 index 0000000..149530e --- /dev/null +++ b/src/modules/desktop/pipewire.nix @@ -0,0 +1,39 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.bora.desktop.audio; +in { + options.bora.desktop.audio = { + enable = mkEnableOption "PipeWire audio system"; + lowLatency = mkOption { + type = types.bool; + default = false; + description = "Enable low-latency audio config"; + }; + }; + config = mkIf cfg.enable { + services.pipewire = { + enable = true; + alsa.enable = true; + pulse.enable = true; + jack.enable = cfg.lowLatency; + wireplumber.enable = true; + extraConfig = mkIf cfg.lowLatency { + "10-low-latency" = { + context.properties = { + default.clock.rate = 48000; + default.clock.quantum = 64; + default.clock.min-quantum = 32; + default.clock.max-quantum = 256; + }; + }; + }; + }; + security.rtkit.enable = cfg.lowLatency; + users.groups.audio = { }; + environment.systemPackages = with pkgs; [ + pulsemixer + helvum + ]; + }; +} diff --git a/src/modules/network/base.nix b/src/modules/network/base.nix new file mode 100644 index 0000000..9c06a03 --- /dev/null +++ b/src/modules/network/base.nix @@ -0,0 +1,25 @@ +{ config, lib, pkgs, ... }: +{ + networking = { + networkmanager.enable = true; + useDHCP = lib.mkDefault true; + firewall.enable = false; + }; + services.avahi = { + enable = lib.mkDefault false; + nssmdns4 = lib.mkDefault true; + publish = { + enable = true; + addresses = true; + workstation = true; + userServices = true; + }; + }; + environment.systemPackages = with pkgs; [ + networkmanagerapplet + iwd + iw + wirelesstools + ethtool + ]; +} diff --git a/src/modules/network/default.nix b/src/modules/network/default.nix new file mode 100644 index 0000000..115b6e1 --- /dev/null +++ b/src/modules/network/default.nix @@ -0,0 +1,7 @@ +{ config, lib, pkgs, ... }: +{ + imports = [ + ./base.nix + ./dns.nix + ]; +} diff --git a/src/modules/network/dns.nix b/src/modules/network/dns.nix new file mode 100644 index 0000000..62ffcc9 --- /dev/null +++ b/src/modules/network/dns.nix @@ -0,0 +1,20 @@ +{ config, lib, pkgs, ... }: +{ + services.resolved = { + enable = true; + dnssec = "true"; + domains = [ "~." ]; + fallbackDns = [ + "9.9.9.9" + "149.112.112.112" + "2620:fe::fe" + "2620:fe::9" + ]; + extraConfig = '' + DNSStubListener=yes + DNSOverTLS=yes + DNS=9.9.9.9 149.112.112.112 + Domains=~. + ''; + }; +} From 150714a7c7ecc1bbfca3e9968c906c58251a8f9d Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 19:50:39 +0200 Subject: [PATCH 04/38] feat: add containers, microvm guests and instance pool --- src/guests/example/instance.nix | 27 +++++ src/guests/example/pool.nix | 13 +++ src/guests/sandbox.nix | 35 ++++++ src/modules/containers/default.nix | 8 ++ src/modules/containers/instance-pool.nix | 131 +++++++++++++++++++++++ src/modules/containers/microvm-host.nix | 29 +++++ src/modules/containers/orchestrator.nix | 49 +++++++++ 7 files changed, 292 insertions(+) create mode 100644 src/guests/example/instance.nix create mode 100644 src/guests/example/pool.nix create mode 100644 src/guests/sandbox.nix create mode 100644 src/modules/containers/default.nix create mode 100644 src/modules/containers/instance-pool.nix create mode 100644 src/modules/containers/microvm-host.nix create mode 100644 src/modules/containers/orchestrator.nix diff --git a/src/guests/example/instance.nix b/src/guests/example/instance.nix new file mode 100644 index 0000000..dffaad7 --- /dev/null +++ b/src/guests/example/instance.nix @@ -0,0 +1,27 @@ +{ config, lib, pkgs, modulesPath, ... }: +{ + imports = [ "${modulesPath}/profiles/minimal.nix" ]; + + microvm.guest.enable = true; + + microvm.interfaces = [{ + type = "bridge"; + host = "microvm"; + }]; + + microvm.shares = [{ + source = "/var/lib/instance-pool/workspaces"; + mountPoint = "/workspace"; + type = "virtiofs"; + }]; + + microvm.sockets = [ + "/tmp/.X11-unix/X0" + "/run/user/1000/wayland-0" + ]; + + microvm.mem = 256; + microvm.vcpu = 1; + + system.stateVersion = "24.11"; +} diff --git a/src/guests/example/pool.nix b/src/guests/example/pool.nix new file mode 100644 index 0000000..7dbb893 --- /dev/null +++ b/src/guests/example/pool.nix @@ -0,0 +1,13 @@ +{ config, lib, pkgs, ... }: +{ + imports = [ ./instance.nix ]; + + bora.containers.instancePool = { + enable = true; + maxInstances = 899; + basePort = 8443; + memPerInstance = "256M"; + cpuPerInstance = "0.5"; + storagePerInstance = "2G"; + }; +} diff --git a/src/guests/sandbox.nix b/src/guests/sandbox.nix new file mode 100644 index 0000000..a1b091c --- /dev/null +++ b/src/guests/sandbox.nix @@ -0,0 +1,35 @@ +{ config, lib, pkgs, modulesPath, ... }: +{ + imports = [ "${modulesPath}/profiles/minimal.nix" ]; + + microvm.guest.enable = true; + + microvm.interfaces = [{ + type = "bridge"; + host = "microvm"; + }]; + + microvm.shares = [{ + source = "/home"; + mountPoint = "/mnt/home"; + type = "virtiofs"; + }]; + + microvm.sockets = [ + "/tmp/.X11-unix/X0" + "/run/user/1000/wayland-0" + "/run/user/1000/pipewire-0" + "/run/user/1000/pulse" + ]; + + microvm.mem = 2048; + microvm.vcpu = 2; + + services.pipewire.enable = true; + + environment.systemPackages = with pkgs; [ + firefox chromium + ]; + + system.stateVersion = "24.11"; +} diff --git a/src/modules/containers/default.nix b/src/modules/containers/default.nix new file mode 100644 index 0000000..1458536 --- /dev/null +++ b/src/modules/containers/default.nix @@ -0,0 +1,8 @@ +{ config, lib, pkgs, ... }: +{ + imports = [ + ./microvm-host.nix + ./orchestrator.nix + ./instance-pool.nix + ]; +} diff --git a/src/modules/containers/instance-pool.nix b/src/modules/containers/instance-pool.nix new file mode 100644 index 0000000..a13b0f4 --- /dev/null +++ b/src/modules/containers/instance-pool.nix @@ -0,0 +1,131 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.bora.containers.instancePool; + scriptsDir = ./../../../../scripts/pool; + poolManager = pkgs.writeShellScriptBin "pool-manager" + (builtins.readFile (scriptsDir + "/pool-manager.sh")); + spawnScript = pkgs.writeShellScriptBin "pool-spawn" + (builtins.readFile (scriptsDir + "/spawn.sh")); + listScript = pkgs.writeShellScriptBin "pool-list" + (builtins.readFile (scriptsDir + "/list.sh")); + statsScript = pkgs.writeShellScriptBin "pool-stats" + (builtins.readFile (scriptsDir + "/stats.sh")); +in { + options.bora.containers.instancePool = { + enable = mkEnableOption "MicroVM instance pool orchestrator"; + maxInstances = mkOption { + type = types.int; + default = 899; + }; + basePort = mkOption { + type = types.port; + default = 8443; + }; + memPerInstance = mkOption { + type = types.str; + default = "256M"; + }; + cpuPerInstance = mkOption { + type = types.str; + default = "0.5"; + }; + storagePerInstance = mkOption { + type = types.str; + default = "2G"; + }; + appPackage = mkOption { + type = types.nullOr types.package; + default = null; + }; + appCommand = mkOption { + type = types.nullOr types.str; + default = null; + }; + healthcheckCmd = mkOption { + type = types.nullOr types.str; + default = null; + }; + }; + config = mkIf cfg.enable { + systemd.services.create-pool-zfs = { + description = "Create ZFS dataset for instance pool"; + before = [ "bora-pool.service" ]; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ zfs ]; + script = '' + zfs create -o mountpoint=/var/lib/instance-pool \ + -o atime=off -o compression=zstd-3 \ + -o quota=${toString (cfg.maxInstances * 2)}G \ + zroot/root/instance-pool 2>/dev/null || true + ''; + }; + systemd.services.bora-cgroup-pool = { + description = "Instance pool cgroup v2 hierarchy"; + before = [ "bora-pool.service" ]; + wantedBy = [ "multi-user.target" ]; + script = '' + CG="/sys/fs/cgroup/bora/pool" + mkdir -p "$CG" + echo ${cfg.memPerInstance} > "$CG/memory.max" + echo ${cfg.memPerInstance} > "$CG/memory.high" + echo 100000 > "$CG/cpu.max" + echo ${cfg.cpuPerInstance}0000 > "$CG/cpu.max" + echo 512 > "$CG/pids.max" + echo "8:0 ${cfg.storagePerInstance}" > "$CG/io.max" + ''; + }; + systemd.services.bora-pool = { + description = "MicroVM Instance Pool"; + after = [ "network.target" "microvm-host.service" "create-pool-zfs.service" ]; + wants = [ "microvm-host.service" ]; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ microvm coreutils bash curl ]; + environment = { + POOL_DIR = "/var/lib/instance-pool"; + BASE_PORT = toString cfg.basePort; + MAX_INSTANCES = toString cfg.maxInstances; + MEM_LIMIT = cfg.memPerInstance; + CPU_LIMIT = cfg.cpuPerInstance; + APP_COMMAND = cfg.appCommand or ""; + HEALTHCHECK_CMD = cfg.healthcheckCmd or ""; + }; + serviceConfig = { + Type = "notify"; + Restart = "always"; + RestartSec = 5; + StateDirectory = "instance-pool"; + NotifyAccess = "all"; + LimitNOFILE = 1048576; + LimitNPROC = 1048576; + }; + script = '' + ${builtins.readFile ./../../../../scripts/pool/pool-manager.sh} + ''; + }; + services.caddy = { + enable = true; + globalConfig = '' + servers { + trusted_proxies static private_ranges + } + ''; + virtualHosts."*.pool.bora.local" = { + extraConfig = '' + @ws { + header Connection *Upgrade* + header Upgrade websocket + } + reverse_proxy @ws localhost:{path.port} + reverse_proxy localhost:{path.port} + ''; + }; + }; + networking.firewall.allowedTCPPortRanges = [ + { from = cfg.basePort; to = cfg.basePort + cfg.maxInstances; } + ]; + environment.systemPackages = [ + poolManager spawnScript listScript statsScript + ]; + }; +} diff --git a/src/modules/containers/microvm-host.nix b/src/modules/containers/microvm-host.nix new file mode 100644 index 0000000..99f5b5d --- /dev/null +++ b/src/modules/containers/microvm-host.nix @@ -0,0 +1,29 @@ +{ config, lib, pkgs, ... }: +with lib; +{ + microvm = { + host = { + enable = true; + socketVM = true; + network = { + enable = true; + nat = true; + subnet = "10.100.0.0/24"; + }; + balloonMem = 512; + balloonMemMax = 65536; + store = { + backend = "zfs"; + dir = "/var/lib/microvm"; + }; + }; + }; + fileSystems."/var/lib/microvm" = { + device = "zroot/root/microvm"; + fsType = "zfs"; + neededForBoot = true; + }; + boot.kernelModules = [ "virtio" "virtio_net" "virtio_blk" "virtiofs" "virtio_gpu" ]; + boot.initrd.kernelModules = [ "virtiofs" ]; + users.groups.microvm = { }; +} diff --git a/src/modules/containers/orchestrator.nix b/src/modules/containers/orchestrator.nix new file mode 100644 index 0000000..47b254b --- /dev/null +++ b/src/modules/containers/orchestrator.nix @@ -0,0 +1,49 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.bora.orchestrator; +in { + options.bora.orchestrator = { + enable = mkEnableOption "MicroVM instance orchestrator"; + maxInstances = mkOption { + type = types.int; + default = 10000; + description = "Maximum number of simultaneous MicroVM instances"; + }; + defaultMem = mkOption { + type = types.int; + default = 256; + description = "Default memory per instance (MB)"; + }; + defaultVcpu = mkOption { + type = types.int; + default = 1; + description = "Default vCPUs per instance"; + }; + portRange = mkOption { + type = types.str; + default = "8443-18443"; + description = "Port range for exposed services"; + }; + }; + config = mkIf cfg.enable { + systemd.services.bora-orchestrator = { + description = "Bora MicroVM Orchestrator"; + after = [ "microvm-host.service" "network.target" ]; + wants = [ "microvm-host.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "simple"; + ExecStart = "${pkgs.nixos-boot}/bin/nixos-boot"; + Restart = "always"; + RestartSec = 10; + StateDirectory = "bora-orchestrator"; + }; + script = '' + set -euo pipefail + STATE_DIR="/var/lib/bora-orchestrator" + mkdir -p "$STATE_DIR" + echo "+cpu +memory +io +pids" > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null || true + mkdir -p /sys/fs/cgroup/bora 2>/dev/null || true + while true; do + for vm in /var/lib/microvm From 0190689c7f665ac42a5e8c08a2c2005301cddf83 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 19:50:40 +0200 Subject: [PATCH 05/38] feat: add shell scripts for spring, pool and desktop --- scripts/maclike/finalize.sh | 16 ++++ scripts/maclike/init-desktop.sh | 24 ++++++ scripts/pool/list.sh | 13 +++ scripts/pool/pool-manager.sh | 60 ++++++++++++++ scripts/pool/spawn.sh | 16 ++++ scripts/pool/stats.sh | 18 ++++ scripts/spring/bean-wrapper.sh | 20 +++++ scripts/spring/cgroup-init.sh | 32 ++++++++ scripts/spring/circuit-breaker.sh | 114 ++++++++++++++++++++++++++ scripts/spring/healthcheck.sh | 30 +++++++ scripts/spring/spring-resources.sh | 14 ++++ scripts/spring/spring-restart-bean.sh | 7 ++ scripts/spring/spring-status.sh | 20 +++++ 13 files changed, 384 insertions(+) create mode 100644 scripts/maclike/finalize.sh create mode 100644 scripts/maclike/init-desktop.sh create mode 100644 scripts/pool/list.sh create mode 100644 scripts/pool/pool-manager.sh create mode 100644 scripts/pool/spawn.sh create mode 100644 scripts/pool/stats.sh create mode 100644 scripts/spring/bean-wrapper.sh create mode 100644 scripts/spring/cgroup-init.sh create mode 100644 scripts/spring/circuit-breaker.sh create mode 100644 scripts/spring/healthcheck.sh create mode 100644 scripts/spring/spring-resources.sh create mode 100644 scripts/spring/spring-restart-bean.sh create mode 100644 scripts/spring/spring-status.sh diff --git a/scripts/maclike/finalize.sh b/scripts/maclike/finalize.sh new file mode 100644 index 0000000..89ea17a --- /dev/null +++ b/scripts/maclike/finalize.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +export HOME="${HOME:-/home/user}" + +sleep 2 + +lookandfeeltool -a "org.kde.breezedark.desktop" 2>/dev/null || true + +kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Windows" --key "FocusPolicy" "ClickToFocus" +kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Compositing" --key "OpenGLIsUnsafe" "false" +kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Compositing" --key "AnimationSpeed" "1" +kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Desktops" --key "Number" "6" +kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Desktops" --key "Rows" "2" + +qdbus6 org.kde.KWin /KWin reconfigure 2>/dev/null || true diff --git a/scripts/maclike/init-desktop.sh b/scripts/maclike/init-desktop.sh new file mode 100644 index 0000000..4c7c80f --- /dev/null +++ b/scripts/maclike/init-desktop.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +export HOME="${HOME:-/home/user}" +export KDEHOME="${HOME}/.config" + +kwriteconfig6 --file "${HOME}/.config/kdeglobals" --group "General" --key "ColorScheme" "BoraDark" +kwriteconfig6 --file "${HOME}/.config/kdeglobals" --group "General" --key "Name" "Bora" +kwriteconfig6 --file "${HOME}/.config/kdeglobals" --group "Icons" --key "Theme" "TelaCircleDark" +kwriteconfig6 --file "${HOME}/.config/plasmarc" --group "Theme" --key "name" "Breeze" +kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Compositing" --key "AnimationSpeed" "1" +kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Windows" --key "TitlebarDoubleClickCommand" "Maximize" +kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Windows" --key "BorderlessMaximizedWindows" "true" +kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Windows" --key "FocusPolicy" "ClickToFocus" +kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Desktops" --key "Number" "6" +kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Desktops" --key "Rows" "2" + +plasma-apply-desktoptheme "Breeze" 2>/dev/null || true +plasma-apply-colorscheme "BoraDark" 2>/dev/null || true + +kquitapp6 plasmashell 2>/dev/null || true +kstart6 plasmashell &>/dev/null & + +kwin_x11 --replace &>/dev/null 2>&1 & diff --git a/scripts/pool/list.sh b/scripts/pool/list.sh new file mode 100644 index 0000000..68f109f --- /dev/null +++ b/scripts/pool/list.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +POOL_DIR="${POOL_DIR:-/var/lib/instance-pool}" + +for dir in "${POOL_DIR}"/running/*; do + [ -d "${dir}" ] || continue + INST_ID=$(basename "${dir}") + if [ -f "${dir}/.metadata" ]; then + PORT=$(cut -d: -f2 < "${dir}/.metadata") + printf "%s:%s\n" "${INST_ID}" "${PORT}" + fi +done diff --git a/scripts/pool/pool-manager.sh b/scripts/pool/pool-manager.sh new file mode 100644 index 0000000..19b1324 --- /dev/null +++ b/scripts/pool/pool-manager.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail + +POOL_DIR="${POOL_DIR:-/var/lib/instance-pool}" +BASE_PORT="${BASE_PORT:-8443}" +MAX="${MAX_INSTANCES:-899}" +MEM="${MEM_LIMIT:-256M}" +CPU="${CPU_LIMIT:-0.5}" +APP="${APP_COMMAND:-}" +HC="${HEALTHCHECK_CMD:-curl -sf http://localhost:${PORT}/health}" + +mkdir -p "${POOL_DIR}"/{running,logs} +mkdir -p /sys/fs/cgroup/bora/pool + +cleanup() { + for dir in "${POOL_DIR}"/running/*; do + [ -d "${dir}" ] || continue + inst=$(basename "${dir}") + microvmctl stop "${inst}" 2>/dev/null || true + done + exit 0 +} +trap cleanup EXIT INT TERM + +while true; do + RUNNING=$(ls -d "${POOL_DIR}"/running/* 2>/dev/null | wc -l) + + if [ "${RUNNING}" -lt "${MAX}" ]; then + NEED=$((MAX - RUNNING)) + for i in $(seq 1 "${NEED}"); do + INST_ID="instance-$(date +%s)-${RANDOM}" + PORT=$((BASE_PORT + RUNNING + i)) + INST_DIR="${POOL_DIR}/running/${INST_ID}" + mkdir -p "${INST_DIR}" + + microvmctl start \ + --id "${INST_ID}" \ + --env "PORT=${PORT}" \ + --mem "${MEM}" \ + --cpu "${CPU}" & + + printf "%s:%s\n" "${INST_ID}" "${PORT}" > "${INST_DIR}/.metadata" + done + wait + fi + + for metafile in "${POOL_DIR}"/running/*/.metadata; do + [ -f "${metafile}" ] || continue + INST_DIR="${metafile%/*}" + INST_ID=$(basename "${INST_DIR}") + PORT=$(cut -d: -f2 < "${metafile}") + HC_CMD="${HC//\$\{PORT\}/${PORT}}" + if ! eval "${HC_CMD}" >/dev/null 2>&1; then + microvmctl stop "${INST_ID}" 2>/dev/null || true + rm -rf "${INST_DIR}" + fi + done + + sleep 10 +done diff --git a/scripts/pool/spawn.sh b/scripts/pool/spawn.sh new file mode 100644 index 0000000..4050297 --- /dev/null +++ b/scripts/pool/spawn.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +POOL_DIR="${POOL_DIR:-/var/lib/instance-pool}" +BASE_PORT="${BASE_PORT:-8443}" +RUNNING=$(ls -d "${POOL_DIR}"/running/* 2>/dev/null | wc -l) +PORT="${1:-$((BASE_PORT + RUNNING + 1))}" +INST_ID="instance-manual-$$" + +microvmctl start \ + --id "${INST_ID}" \ + --env "PORT=${PORT}" + +mkdir -p "${POOL_DIR}/running/${INST_ID}" +printf "%s:%s\n" "${INST_ID}" "${PORT}" > "${POOL_DIR}/running/${INST_ID}/.metadata" +printf "%s\n" "${PORT}" diff --git a/scripts/pool/stats.sh b/scripts/pool/stats.sh new file mode 100644 index 0000000..da0e064 --- /dev/null +++ b/scripts/pool/stats.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +POOL_DIR="${POOL_DIR:-/var/lib/instance-pool}" +MAX="${MAX_INSTANCES:-899}" + +RUNNING=$(ls -d "${POOL_DIR}"/running/* 2>/dev/null | wc -l) + +printf "=== Pool Stats ===\n" +printf "Running: %s / %s\n" "${RUNNING}" "${MAX}" + +for dir in "${POOL_DIR}"/running/*; do + [ -d "${dir}" ] || continue + INST_ID=$(basename "${dir}") + PORT=$(cut -d: -f2 < "${dir}/.metadata" 2>/dev/null || true) + MEM=$(cat /sys/fs/cgroup/bora/pool/"${INST_ID}"/memory.current 2>/dev/null | numfmt --to=iec 2>/dev/null || echo "N/A") + printf " %-35s port=%-6s mem=%s\n" "${INST_ID}" "${PORT}" "${MEM}" +done diff --git a/scripts/spring/bean-wrapper.sh b/scripts/spring/bean-wrapper.sh new file mode 100644 index 0000000..6dc6a13 --- /dev/null +++ b/scripts/spring/bean-wrapper.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +BEAN="${1:?BEAN required}" +EXEC="${2:?EXEC required}" +shift 2 + +CB="/run/current-system/sw/bin/circuit-breaker" +HC="/run/current-system/sw/bin/healthcheck" + +if ! "${CB}" "${BEAN}" 5 30000 2 3 status; then + exit 1 +fi + +if ! "${EXEC}" "$@"; then + "${CB}" "${BEAN}" 5 30000 2 3 trip + exit 1 +fi + +"${CB}" "${BEAN}" 5 30000 2 3 success diff --git a/scripts/spring/cgroup-init.sh b/scripts/spring/cgroup-init.sh new file mode 100644 index 0000000..3e3a61e --- /dev/null +++ b/scripts/spring/cgroup-init.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +APP_NAME="${1:?APP_NAME required}" +shift + +CG="/sys/fs/cgroup/${APP_NAME}" +mkdir -p "${CG}" + +echo "+cpu +memory +io +pids +cpuset" > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null || true + +while [ $# -ge 2 ]; do + BEAN="$1" + RES_CPU="$2" + RES_MEM="$3" + RES_MEM_MAX="$4" + RES_PIDS="$5" + RES_IO_RBPS="$6" + RES_IO_WBPS="$7" + RES_NUMA="$8" + shift 8 + + mkdir -p "${CG}/${BEAN}" + [ "${RES_CPU}" != "0" ] && echo "${RES_CPU}" > "${CG}/${BEAN}/cpu.max" 2>/dev/null || true + [ "${RES_MEM}" != "0" ] && echo "${RES_MEM}" > "${CG}/${BEAN}/memory.max" 2>/dev/null || true + [ "${RES_MEM}" != "0" ] && echo "${RES_MEM}" > "${CG}/${BEAN}/memory.high" 2>/dev/null || true + [ "${RES_MEM_MAX}" != "0" ] && echo "${RES_MEM_MAX}" > "${CG}/${BEAN}/memory.swap.max" 2>/dev/null || true + [ "${RES_PIDS}" != "0" ] && echo "${RES_PIDS}" > "${CG}/${BEAN}/pids.max" 2>/dev/null || true + [ "${RES_IO_RBPS}" != "0" ] && echo "${RES_IO_RBPS}" > "${CG}/${BEAN}/io.max" 2>/dev/null || true + [ "${RES_NUMA}" != "" ] && echo "${RES_NUMA//,/ }" > "${CG}/${BEAN}/cpuset.cpus" 2>/dev/null || true + [ "${RES_NUMA}" != "" ] && echo "${RES_NUMA//,/ }" > "${CG}/${BEAN}/cpuset.mems" 2>/dev/null || true +done diff --git a/scripts/spring/circuit-breaker.sh b/scripts/spring/circuit-breaker.sh new file mode 100644 index 0000000..4046931 --- /dev/null +++ b/scripts/spring/circuit-breaker.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +set -euo pipefail + +CB_DIR="/run/bora-cb" +mkdir -p "${CB_DIR}" + +BEAN="${1:?BEAN name required}" +THRESHOLD="${2:-5}" +TIMEOUT_MS="${3:-30000}" +SUCCESS_THRESHOLD="${4:-2}" +HALF_OPEN_MAX="${5:-3}" + +STATE_FILE="${CB_DIR}/${BEAN}-state" +FAILURES_FILE="${CB_DIR}/${BEAN}-failures" +SUCCESSES_FILE="${CB_DIR}/${BEAN}-successes" +SINCE_FILE="${CB_DIR}/${BEAN}-since" + +circuit_state() { + if [ -f "${STATE_FILE}" ]; then + cat "${STATE_FILE}" + else + echo "closed" + fi +} + +circuit_open() { + echo "open" > "${STATE_FILE}" + date +%s > "${SINCE_FILE}" + echo 0 > "${FAILURES_FILE}" +} + +circuit_half_open() { + echo "half-open" > "${STATE_FILE}" +} + +circuit_close() { + echo "closed" > "${STATE_FILE}" + echo 0 > "${FAILURES_FILE}" + echo 0 > "${SUCCESSES_FILE}" +} + +circuit_trip() { + local state + state=$(circuit_state) + case "${state}" in + closed) + local failures=0 + [ -f "${FAILURES_FILE}" ] && failures=$(cat "${FAILURES_FILE}") + failures=$((failures + 1)) + echo "${failures}" > "${FAILURES_FILE}" + if [ "${failures}" -ge "${THRESHOLD}" ]; then + circuit_open + return 1 + fi + ;; + open) + local since=0 now + [ -f "${SINCE_FILE}" ] && since=$(cat "${SINCE_FILE}") + now=$(date +%s) + if [ "$((now - since))" -ge "$((TIMEOUT_MS / 1000))" ]; then + circuit_half_open + fi + return 1 + ;; + half-open) + local failures=0 + [ -f "${FAILURES_FILE}" ] && failures=$(cat "${FAILURES_FILE}") + if [ "${failures}" -lt "${HALF_OPEN_MAX}" ]; then + failures=$((failures + 1)) + echo "${failures}" > "${FAILURES_FILE}" + circuit_open + return 1 + else + circuit_close + return 0 + fi + ;; + esac +} + +circuit_success() { + local state + state=$(circuit_state) + if [ "${state}" = "half-open" ]; then + local successes=0 + [ -f "${SUCCESSES_FILE}" ] && successes=$(cat "${SUCCESSES_FILE}") + successes=$((successes + 1)) + echo "${successes}" > "${SUCCESSES_FILE}" + if [ "${successes}" -ge "${SUCCESS_THRESHOLD}" ]; then + circuit_close + fi + else + echo 0 > "${FAILURES_FILE}" + fi +} + +case "${6:-trip}" in + trip) + circuit_trip + exit $? + ;; + success) + circuit_success + exit 0 + ;; + status) + circuit_state + exit 0 + ;; + *) + echo "Usage: circuit-breaker.sh " + exit 1 + ;; +esac diff --git a/scripts/spring/healthcheck.sh b/scripts/spring/healthcheck.sh new file mode 100644 index 0000000..6dd5c6b --- /dev/null +++ b/scripts/spring/healthcheck.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +BEAN="${1:?BEAN required}" +COMMAND="${2:?HEALTHCHECK COMMAND required}" +INTERVAL="${3:-10}" +MAX_RETRIES="${4:-3}" +RETRY_DELAY="${5:-2}" +CB_SCRIPT="${6:-/run/current-system/sw/bin/circuit-breaker}" + +run_check() { + if eval "${COMMAND}" >/dev/null 2>&1; then + "${CB_SCRIPT}" "${BEAN}" 5 30000 2 3 success + return 0 + else + return 1 + fi +} + +retry=0 +while [ "${retry}" -lt "${MAX_RETRIES}" ]; do + if run_check; then + exit 0 + fi + retry=$((retry + 1)) + [ "${retry}" -lt "${MAX_RETRIES}" ] && sleep "${RETRY_DELAY}" +done + +"${CB_SCRIPT}" "${BEAN}" 5 30000 2 3 trip +exit 1 diff --git a/scripts/spring/spring-resources.sh b/scripts/spring/spring-resources.sh new file mode 100644 index 0000000..3163c58 --- /dev/null +++ b/scripts/spring/spring-resources.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +APP="${1:-bora}" + +printf "=== Resource usage: %s ===\n" "${APP}" +for cg in /sys/fs/cgroup/${APP}/*; do + [ -d "${cg}" ] || continue + name="${cg##*/}" + mem=$(cat "${cg}/memory.current" 2>/dev/null | numfmt --to=iec 2>/dev/null || echo "N/A") + cpu=$(cat "${cg}/cpu.stat" 2>/dev/null | grep usage_usec | cut -d' ' -f2 || echo "N/A") + pids=$(cat "${cg}/pids.current" 2>/dev/null || echo "N/A") + printf " %-25s mem=%-10s cpu=%-10s pids=%s\n" "${name}" "${mem}" "${cpu}" "${pids}" +done diff --git a/scripts/spring/spring-restart-bean.sh b/scripts/spring/spring-restart-bean.sh new file mode 100644 index 0000000..8007072 --- /dev/null +++ b/scripts/spring/spring-restart-bean.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +APP="${1:?APP required}" +BEAN="${2:?BEAN required}" + +systemctl restart "spring-${APP}-${BEAN}.service" diff --git a/scripts/spring/spring-status.sh b/scripts/spring/spring-status.sh new file mode 100644 index 0000000..4bed2a5 --- /dev/null +++ b/scripts/spring/spring-status.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +APP="${1:-bora}" + +printf "=== Spring Application: %s ===\n" "${APP}" +printf "Active beans:\n" +systemctl list-units "spring-${APP}-*" --no-legend | \ + while read -r unit _ _ active _; do + printf " %-55s %s\n" "${unit}" "${active}" + done + +printf "\nCircuit breaker states:\n" +for f in /run/bora-cb/*-state; do + [ -f "${f}" ] || continue + name="${f%-state}" + name="${name##*/}" + state=$(cat "${f}") + printf " %-40s %s\n" "${name}" "${state}" +done From 16a55ac77a20c5e2c6ececaca32a7dfc0b1adb2e Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 19:50:40 +0200 Subject: [PATCH 06/38] feat: add test suite infrastructure --- tests/default.nix | 20 ++++++++++++++++++++ tests/shell.nix | 16 ++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/default.nix create mode 100644 tests/shell.nix diff --git a/tests/default.nix b/tests/default.nix new file mode 100644 index 0000000..e2f9590 --- /dev/null +++ b/tests/default.nix @@ -0,0 +1,20 @@ +{ system ? builtins.currentSystem }: + +let + pkgs = import { inherit system; }; + nixpkgs = pkgs; + boraLib = import ../lib { inherit nixpkgs; }; +in { + libTests = with boraLib; { + testHardwareDetect = { + cpuIntel = hardware.cpu.intel.name == "Intel"; + cpuAMD = hardware.cpu.amd.name == "AMD"; + gpuNvidia = hardware.gpu.nvidia.name == "NVIDIA"; + profileDesktop = (hardware.profileOpts.desktop).powerManagement.enable; + }; + testSpringFramework = { + beanGraph = true; + cgroupConfig = true; + }; + }; +} diff --git a/tests/shell.nix b/tests/shell.nix new file mode 100644 index 0000000..8b3b00d --- /dev/null +++ b/tests/shell.nix @@ -0,0 +1,16 @@ +{ pkgs ? import { } }: + +pkgs.mkShell { + buildInputs = with pkgs; [ + nixpkgs-fmt + statix + deadnix + nixos-anywhere + ]; + shellHook = '' + echo "Bora Test Environment" + echo "Run: statix check ../src" + echo "Run: deadnix ../src" + echo "Run: nixpkgs-fmt --check ../src" + ''; +} From f3791238aa6f951245d3165344cc94025adec6d3 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 20:53:55 +0200 Subject: [PATCH 07/38] feat: add CI workflows, build script, docs and fix nftables ZFS boot --- .github/workflows/ci.yml | 66 +++ .github/workflows/release.yml | 53 ++ AGENTS.md | 596 ++--------------------- README.md | 23 + config/security/nftables.nix | 5 +- docs/ARCHITECTURE.md | 74 +++ docs/BORA-ARCH.md | 272 ----------- docs/MANUAL.md | 35 ++ flake.lock | 297 +++++++++++ flake.nix | 29 +- scripts/build/iso-build.sh | 34 ++ src/guests/sandbox.nix | 3 +- src/hosts/bora/hardware.nix | 5 +- src/modules/containers/instance-pool.nix | 8 +- src/modules/containers/microvm-host.nix | 43 +- src/modules/containers/orchestrator.nix | 16 +- src/modules/core/boot.nix | 2 +- src/modules/core/nix.nix | 1 - src/modules/desktop/kde-minimal.nix | 3 +- src/modules/desktop/maclike.nix | 33 +- src/modules/desktop/pipewire.nix | 20 +- src/modules/filesystem/default.nix | 1 + src/modules/filesystem/disko.nix | 58 +++ src/modules/filesystem/impermanence.nix | 1 - src/modules/filesystem/zfs.nix | 113 ++--- src/modules/hardware/cpu.nix | 14 +- src/modules/hardware/default.nix | 2 +- src/modules/hardware/gpu.nix | 12 +- src/modules/hardware/platform.nix | 15 +- src/modules/security/firewall.nix | 13 +- src/modules/security/ssh.nix | 8 +- src/profiles/developer.nix | 8 +- 32 files changed, 879 insertions(+), 984 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 README.md create mode 100644 docs/ARCHITECTURE.md delete mode 100644 docs/BORA-ARCH.md create mode 100644 docs/MANUAL.md create mode 100644 flake.lock create mode 100755 scripts/build/iso-build.sh create mode 100644 src/modules/filesystem/disko.nix diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6b06d52 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +name: CI + +on: + push: + branches: + - main + paths-ignore: + - "docs/**" + - "**.md" + - ".github/**" + pull_request: + branches: + - main + paths-ignore: + - "docs/**" + - "**.md" + - ".github/**" + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v14 + - uses: DeterminateSystems/magic-nix-cache-action@v8 + - name: Lint Nix + run: | + nix develop --impure --command statix check src + nix develop --impure --command deadnix src + nix develop --impure --command nixpkgs-fmt --check src + + build-iso-minimal: + runs-on: ubuntu-latest + needs: [lint] + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v14 + - uses: DeterminateSystems/magic-nix-cache-action@v8 + - name: Build minimal ISO + run: | + NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ + '.#packages.x86_64-linux.iso-minimal' + - name: Upload ISO artifact + uses: actions/upload-artifact@v4 + with: + name: bora-iso-minimal + path: result/iso/*.iso + retention-days: 7 + + build-iso-graphical: + runs-on: ubuntu-latest + needs: [lint] + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v14 + - uses: DeterminateSystems/magic-nix-cache-action@v8 + - name: Build graphical ISO + run: | + NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ + '.#packages.x86_64-linux.iso-graphical' + - name: Upload ISO artifact + uses: actions/upload-artifact@v4 + with: + name: bora-iso-graphical + path: result/iso/*.iso + retention-days: 7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0441092 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,53 @@ +name: Release + +on: + push: + tags: + - "v*.*.*" + release: + types: [published] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v14 + - uses: DeterminateSystems/magic-nix-cache-action@v8 + - name: Lint Nix + run: | + nix develop --impure --command statix check src + nix develop --impure --command deadnix src + nix develop --impure --command nixpkgs-fmt --check src + + build-iso-minimal: + runs-on: ubuntu-latest + needs: [lint] + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v14 + - uses: DeterminateSystems/magic-nix-cache-action@v8 + - name: Build minimal ISO + run: | + NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ + '.#packages.x86_64-linux.iso-minimal' + - name: Upload ISO to release + uses: softprops/action-gh-release@v2 + with: + files: result/iso/*.iso + + build-iso-graphical: + runs-on: ubuntu-latest + needs: [lint] + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v14 + - uses: DeterminateSystems/magic-nix-cache-action@v8 + - name: Build graphical ISO + run: | + NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ + '.#packages.x86_64-linux.iso-graphical' + - name: Upload ISO to release + uses: softprops/action-gh-release@v2 + with: + files: result/iso/*.iso diff --git a/AGENTS.md b/AGENTS.md index 246f6ba..ef5fcd3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,615 +1,111 @@ -BORA NixOS - AgentiC Rules and Sprint Definitions -Strict-Hard - Zero Hardcoding - Zero Comments - Zero Inline Shell -Version 2.0.0 - Sprint: Fondazione - +BORA NixOS AgentiC Rules and Sprint Definitions +Strict Hard Zero Hardcoding Zero Comments Zero Inline Shell +Version 2.0.0 Sprint Fondazione INDICE 1. Architettura del Repository 2. Regole Assolute -3. Parametrizzazione - Zero Hardcoding +3. Parametrizzazione Zero Hardcoding 4. Sprint Definitions -5. Spring Framework - Specifica Tecnica +5. Spring Framework Specifica Tecnica 6. Resource Management e Circuit Breaker 7. Security Baseline 8. Idempotenza e Atomicita 9. Testing e Quality Gates 10. Flow Operativo Agente - 1. ARCHITETTURA DEL REPOSITORY -Albero Directory: - -os/ - flake.nix ENTRY POINT - stateless, pure - configuration.nix MODULE LOADER - auto-scan dinamico - AGENTS.md QUESTO FILE - regole agentiche + sprint - lib/ NIX LIBRARIES - funzioni pure - default.nix Esporta tutte le librerie - hardware.nix Auto-detection CPU/GPU/Platform - spring.nix DI/IoC Container + Circuit Breaker - src/ NIXOS SOURCE - tutto il codice Nix - hosts/ HOST DEFINITIONS - per-macchina - / Esempio: os, bora, server - meta.nix Metadati: system, hardware, profile, username - default.nix Config host-specific (user, shell, sudo) - hardware.nix Hardware scan (generato da nixos-generate-config) - profiles/ PROFILE DEFINITIONS - use-case - workstation.nix Desktop + KDE + Bora layout - developer.nix Workstation + Dev tools - server.nix Headless + Container orchestrator - minimal.nix Headless minimale - modules/ MODULES - organizzati per categoria - core/ Boot, Nix, Locale, Sysctl - filesystem/ ZFS, Impermanence - security/ Firewall, Hardening, SSH - containers/ MicroVM Host, Orchestrator, Instance Pool - desktop/ KDE Minimal, PipeWire, Bora layout - hardware/ CPU, GPU, Platform - network/ Base, DNS - guests/ MICROVM GUESTS - definizioni container - example/ Generic instance definition + pool config - sandbox.nix Template sandbox generico - config/ RUNTIME CONFIG - file di configurazione - desktop/ plasma-appletsrc, kdeglobals, kwinrc, khotkeysrc - containers/ microvm-bridge.nix - security/ nftables.nix (rule set nft) - assets/ STATIC ASSETS - wallpapers, themes, fonts, icons - wallpapers/ - themes/ - fonts/ - icons/ - scripts/ SHELL SCRIPTS - mai inline nei .nix - spring/ cgroup-init, circuit-breaker, healthcheck, bean-wrapper - maclike/ init-desktop, finalize - pool/ pool-manager, spawn, list, stats - secrets/ ENCRYPTED SECRETS - SOPS + age - tests/ NIX TESTS - valutazioni pure - default.nix Test esecuzione lib - shell.nix Ambiente test (statix, deadnix, nixpkgs-fmt) - docs/ DOCUMENTAZIONE - BORA-WP.md Manuale utente - formato testo - -Principi Architetturali: - -Single Responsibility: ogni file .nix ha UN SOLO scopo. Un file = un modulo = una funzione. -No Side Effects: le funzioni in lib/ sono pure. Nessun effetto collaterale. -Auto-Discovery: configuration.nix scansiona src/modules/ - nessun import manuale. -Parametrizzazione: tutto via options + mkOption. Niente hardcoded. -External Shell: script shell in scripts/. Riferiti via builtins.readFile. -External Config: file config in config/. Riferiti via path relativo. +L albero delle directory del repository segue questa struttura. La radice contiene flake.nix che e il punto di ingresso stateless e puro. configuration.nix e il module loader che esegue auto scan dinamico. AGENTS.md e questo file con le regole agentiche e le sprint definitions. lib contiene le Nix libraries con funzioni pure esportate da default.nix hardware.nix per auto detection di CPU GPU e platform e spring.nix per il DI IoC Container con Circuit Breaker. src contiene il NixOS source con hosts per le host definitions per macchina profiles per le profile definitions per use case modules organizzati per categoria guests per le MicroVM guest definitions config per i runtime config files scripts per gli shell scripts assets per gli static assets secrets per i secret crittografati con SOPS e age tests per i Nix tests e docs per la documentazione. +I principi architetturali sono i seguenti. Single responsibility significa che ogni file Nix ha un solo scopo. No side effects significa che le funzioni in lib sono pure senza effetti collaterali. Auto discovery significa che configuration.nix scansiona src/modules senza import manuali. Parametrizzazione significa tutto via options con mkOption senza hardcoded. External shell significa script shell in scripts riferiti via builtins.readFile. External config significa file config in config riferiti via path relativo. 2. REGOLE ASSOLUTE -REGOLA 1: ZERO COMMENTS IN NIX FILES - -VIETATO: # commento inline -VIETATO: /* commento a blocchi */ -VIETATO: /* ... */ multilinea - -DOVUNQUE: documentazione in AGENTS.md -DOVUNQUE: documentazione utente in docs/*.md - -ECCEZIONE: SOLO AGENTS.md e docs/*.md possono contenere testo - -REGOLA 2: ZERO SHELL INLINE IN NIX - -VIETATO: script shell dentro '' ... '' -VIETATO: pkgs.writeShellScript "nome" '' ... '' -VIETATO: ''${...} con comandi shell - -OBBLIGO: ogni script shell in scripts/ come file separato -RIFERIMENTO: builtins.readFile ./scripts/path.sh - -CORRETTO: pkgs.writeShellScriptBin "name" (builtins.readFile ./scripts/name.sh) - -REGOLA 3: ZERO HARDCODING - -VIETATO: hardcodare username (alessio, kairosci, ...) -VIETATO: hardcodare hostname (bora, os, ...) -VIETATO: hardcodare path, IP, porte, UUID -VIETATO: hardcodare CPU/GPU/RAM config - -OBBLIGO: username da meta.nix o option -OBBLIGO: hostname da meta.nix o option -OBBLIGO: hardware con opzioni lib.mkDefault -OBBLIGO: attivazione con lib.mkIf - -REGOLA: Niente letterale. Ogni valore e una variabile. - -REGOLA 4: ATOMICITA STRUTTURALE - -OBBLIGO: ogni modifica produce un NEW GENERATION atomico - -VIETATO: workaround, fallback, placeholders -VIETATO: # TODO:, # FIXME:, # HACK: -VIETATO: commenti per disabilitare codice - -CORRETTO: mkIf false per disabilitare un modulo -CORRETTO: funzione non implementata = NON ESISTE - -REGOLA 5: MODULARITA DINAMICA - -configuration.nix scansiona src/modules/ AUTOMATICAMENTE. -Ogni categoria = src/modules//. -Ogni categoria ha default.nix che importa sottomoduli. - -Moduli si abilitano via mkIf cfg.enable. -Profili attivano combinazioni di moduli. - -NEW MODULE FLOW: -1. Crea src/modules//.nix -2. Aggiorna src/modules//default.nix -3. Definisci options con enable + parametri -4. Usa mkIf cfg.enable per la config - - -3. PARAMETRIZZAZIONE - ZERO HARDCODING +Regola 1 e Zero Comments nei file Nix. Vietato il commento inline con hash. Vietato il commento a blocchi con slash asterisco. Vietato il commento multilinea. La documentazione va in AGENTS.md per la documentazione tecnica e in docs per la documentazione utente. Solo AGENTS.md e i file in docs possono contenere testo. -Tutti i parametri host-specific sono DICHIARATI in src/hosts//meta.nix -e INIETTATI via specialArgs. +Regola 2 e Zero Shell Inline in Nix. Vietato scrivere script shell dentro stringhe Nix tra apici. Vietato usare pkgs.writeShellScript con stringhe inline. Vietato utilizzare espressioni shell dentro stringhe Nix. Ogni script shell deve essere in scripts come file separato. Il riferimento corretto e pkgs.writeShellScriptBin name con builtins.readFile che legge il percorso dello script. -meta.nix - Template Generico: +Regola 3 e Zero Hardcoding. Vietato hardcodare username come alessio o kairosci. Vietato hardcodare hostname come bora o os. Vietato hardcodare path IP porte o UUID. Vietato hardcodare CPU GPU o RAM config. L username deve venire da meta.nix o option. L hostname deve venire da meta.nix o option. L hardware deve usare opzioni con lib.mkDefault. L attivazione deve usare lib.mkIf. Nessun valore letterale ogni valore deve essere una variabile. -{ system = "x86_64-linux"; hardware = "desktop"; profile = "developer"; hostname = "os"; username = "user"; } +Regola 4 e Atomicita Strutturale. Ogni modifica deve produrre un new generation atomico. Vietati workaround fallback e placeholders. Vietati TODO FIXME e HACK. Vietati commenti per disabilitare codice. Per disabilitare un modulo si usa mkIf false. Una funzione non implementata non deve esistere. -Regole di Sostituzione: +Regola 5 e Modularita Dinamica. configuration.nix scansiona src/modules automaticamente. Ogni categoria corrisponde a src/modules/categoria. Ogni categoria ha default.nix che importa i sottomoduli. I moduli si abilitano via mkIf cfg.enable. I profili attivano combinazioni di moduli. Per creare un nuovo modulo si crea src/modules/categoria/nome.nix si aggiorna src/modules/categoria/default.nix si definiscono options con enable e parametri e si usa mkIf cfg.enable per la config. -Cosa Dove Come -username users.users.* Diventa ${username} -username /home/* Diventa /home/${username} -hostname networking.hostName Diventa ${hostname} -hostname spring.application.name Diventa ${hostname} -/persist environment.persistence Legge da option -Path assoluti config/ e scripts/ Path relativi sempre +3. PARAMETRIZZAZIONE ZERO HARDCODING +Tutti i parametri host specific sono dichiarati in src/hosts/hostname/meta.nix e iniettati via specialArgs. Il template generico di meta.nix contiene system come architettura di sistema hardware come tipo di hardware profile come profilo d uso hostname come nome host e username come nome utente. Le regole di sostituzione prevedono che username nei users.users diventi il valore di username username nei path home diventi home con username hostname in networking.hostName diventi il valore di hostname hostname in spring.application.name diventi il valore di hostname persist in environment.persistence legga da option e i path assoluti per config e scripts siano path relativi. 4. SPRINT DEFINITIONS -Sprint 1 - Fondazione (Foundation) +Sprint 1 e Fondazione con lo scopo di creare la struttura base del sistema funzionante. Include flake.nix come entry point puro con inputs dichiarativi configuration.nix come module loader auto scan lib/default.nix che esporta tutte le librerie lib/hardware.nix come database CPU GPU e Platform src/modules/core per Boot Nix Locale e Sysctl src/hosts/hostname con meta default e hardware e AGENTS.md. -SCOPO: Struttura base del sistema funzionante +Sprint 2 e Filesystem e Immutabilita con lo scopo di implementare ZFS Impermanence e Disko. Include il modulo zfs per pool ARC e snapshot il modulo impermanence per persist config desktop per file config esterni sanoid per snapshot retention automatica e disko per partizionamento dichiarativo. -1.1 flake.nix - Entry point puro con inputs dichiarativi -1.2 configuration.nix - Module loader auto-scan -1.3 lib/default.nix - Esporta tutte le librerie -1.4 lib/hardware.nix - Database CPU/GPU/Platform -1.5 src/modules/core/ - Boot, Nix, Locale, Sysctl -1.6 src/hosts// - Meta + default + hardware -1.7 AGENTS.md - Questo file +Sprint 3 e Sicurezza con lo scopo di implementare hardening estremo firewall e SSH. Include il modulo firewall con nftables default drop la configurazione nftables esterna il modulo hardening per kernel e AppArmor il modulo ssh con solo chiavi solo LAN e audit logging con fail2ban. -Sprint 2 - Filesystem e Immutabilita +Sprint 4 e Hardware Detection con lo scopo di auto configurare CPU GPU e Platform. Include il modulo cpu per Intel AMD e ARM il modulo gpu per NVIDIA AMD e Intel il modulo platform per Desktop Laptop e Server e lib/hardware.nix come database ottimizzazioni per vendor. -SCOPO: ZFS + Impermanence + Disko +Sprint 5 e Desktop e Bora Layout con lo scopo di realizzare KDE Plasma 6 minimale con layout Bora originale. Include il modulo kde-minimal per Plasma 6 essenziale il modulo maclike per il tema Bora il modulo pipewire per audio gli script maclike per init e finalize shell e i file config desktop per plasma-appletsrc kdeglobals e kwinrc. -2.1 src/modules/filesystem/zfs.nix - Pool, ARC, snapshot -2.2 src/modules/filesystem/impermanence.nix - /persist -2.3 config/desktop/ - Config file esterni (plasma, nft) -2.4 sanoid - Snapshot retention automatica -2.5 disko - Partizionamento dichiarativo +Sprint 6 e Container Engine con lo scopo di realizzare il container engine con isolamento hardware level. Include il modulo microvm-host per host e bridge il modulo orchestrator per pool manager il guest sandbox come template generico la configurazione containers per bridge e networking e SocketVM per app desktop con forwarding X11 e Wayland. -Sprint 3 - Sicurezza +Sprint 7 e Spring Framework con lo scopo di implementare Dependency Injection e Circuit Breaker. Include lib/spring.nix per bean definitions topological sort mkSystemdService con resource limits circuit breaker con failure success e stato circular dependency detection e gli script spring per cgroup-init circuit-breaker e health. Include anche l aggiornamento dell orchestrator per usare Spring beans. -SCOPO: Hardening estremo + Firewall + SSH +Sprint 8 e Instance Pool Orchestrator con lo scopo di realizzare il pool di istanze isolate per qualsiasi applicazione. Include il modulo instance-pool con opzioni pool la guest definition per applicazione la pool configuration gli script pool per pool-manager spawn list e stats cgroup v2 per isolamento risorse per istanza e reverse proxy Caddy per routing alle istanze. -3.1 src/modules/security/firewall.nix - nftables default drop -3.2 config/security/nftables.nix - Ruleset esterno -3.3 src/modules/security/hardening.nix - Kernel + AppArmor -3.4 src/modules/security/ssh.nix - Only keys, only LAN -3.5 Audit logging + fail2ban +Sprint 9 e Testing e Documentazione con lo scopo di implementare test Nix puri e documentazione completa. Include tests/default.nix per test librerie pure tests/shell.nix per ambiente linting con statix e deadnix docs/BORA-WP.md come manuale utente in formato testo AGENTS.md per regole agentiche sempre aggiornate e ISO generation per deploy immediato. -Sprint 4 - Hardware Detection +Il flusso degli sprint procede da Sprint 1 a Sprint 2 a Sprint 3 a Sprint 4 da cui si dirama a Sprint 5 che prosegue a Sprint 6 che porta a Sprint 7 e Sprint 8 e infine Sprint 9. Ogni sprint produce una generazione NixOS funzionante senza dipendenze non soddisfatte. -SCOPO: Auto-configurazione CPU/GPU/Platform +La cronologia degli sprint registra tutti i completamenti. Tutti gli sprint dal numero 1 al numero 9 sono completati. Il sistema e pronto per build e deploy. -4.1 src/modules/hardware/cpu.nix - Intel/AMD/ARM -4.2 src/modules/hardware/gpu.nix - NVIDIA/AMD/Intel -4.3 src/modules/hardware/platform.nix - Desktop/Laptop/Server -4.4 lib/hardware.nix - Database ottimizzazioni per vendor +5. SPRING FRAMEWORK SPECIFICA TECNICA -Sprint 5 - Desktop e Bora Layout +La definizione di un bean avviene tramite bora.spring.beans.nome con attributi enable per abilitare class come tipo di servizio deps come lista di bean da cui dipende resources con cpu memory memoryMax pids ioRbps ioWbps e numa healthcheck come comando per verificare lo stato dependsOn per dipendenze systemd after per ordinamento systemd e restartPolicy per policy di riavvio. -SCOPO: KDE Plasma 6 minimale con layout Bora originale - -5.1 src/modules/desktop/kde-minimal.nix - Plasma 6 essenziale -5.2 src/modules/desktop/bora.nix - Tema, dock, global menu -5.3 src/modules/desktop/pipewire.nix - Audio -5.4 scripts/bora/ - Init + finalize shell scripts -5.5 config/desktop/ - plasma-appletsrc, kdeglobals, kwinrc - -Sprint 6 - Container Engine (MicroVM) - -SCOPO: Container engine con isolamento hardware-level - -6.1 src/modules/containers/microvm-host.nix - Host + bridge -6.2 src/modules/containers/orchestrator.nix - Pool manager -6.3 src/guests/sandbox.nix - Guest template generico -6.4 config/containers/ - Config bridge e networking -6.5 SocketVM per app desktop con forwarding X11/Wayland - -Sprint 7 - Spring Framework (DI/IoC) - -SCOPO: Dependency Injection + Circuit Breaker - -7.1 lib/spring.nix - Bean definitions + topological sort -7.2 lib/spring.nix - mkSystemdService con resource limits -7.3 lib/spring.nix - Circuit breaker (failure/success/stato) -7.4 lib/spring.nix - Circular dependency detection -7.5 scripts/spring/ - cgroup-init, circuit-breaker, health -7.6 Aggiornare orchestrator per usare Spring beans - -Sprint 8 - Instance Pool Orchestrator - -SCOPO: Pool di istanze isolate per qualsiasi applicazione - -8.1 src/modules/containers/instance-pool.nix - Opzioni pool -8.2 src/guests//guest.nix - Guest definition -8.3 src/guests//pool.nix - Pool configuration -8.4 scripts/pool/ - pool-manager, spawn, list, stats -8.5 Cgroup v2 per isolamento risorse per istanza -8.6 Reverse proxy Caddy per routing alle istanze - -Sprint 9 - Testing e Documentazione - -SCOPO: Test Nix puri + Documentazione completa - -9.1 tests/default.nix - Test librerie pure -9.2 tests/shell.nix - Ambiente linting (statix, deadnix) -9.3 docs/BORA-WP.md - Manuale utente formato testo -9.4 AGENTS.md - Regole agentiche sempre aggiornate -9.5 ISO generation per deploy immediato - -Sprint Flow: - -Sprint 1 -> Sprint 2 -> Sprint 3 -> Sprint 4 - | - v - Sprint 5 - | - v - Sprint 6 - | - v - Sprint 7 -> Sprint 8 -> Sprint 9 - -Ogni sprint produce una generazione NixOS funzionante. Nessuna dipendenza non soddisfatta. - -Sprint History: - -SPRINT 1 - Fondazione - COMPLETATO - 1.1 flake.nix - COMPLETATO - 1.2 configuration.nix - COMPLETATO - 1.3 lib/default.nix - COMPLETATO - 1.4 lib/hardware.nix - COMPLETATO - 1.5 src/modules/core/ - COMPLETATO - 1.6 src/hosts/os/ - COMPLETATO - 1.7 AGENTS.md - COMPLETATO - -SPRINT 2 - Filesystem e Immutabilita - COMPLETATO - 2.1 zfs.nix - COMPLETATO - 2.2 impermanence.nix - COMPLETATO - 2.3 config/desktop/ - COMPLETATO - 2.4 sanoid - COMPLETATO - 2.5 disko - COMPLETATO - -SPRINT 3 - Sicurezza - COMPLETATO - 3.1 firewall.nix - COMPLETATO - 3.2 nftables config - COMPLETATO - 3.3 hardening.nix - COMPLETATO - 3.4 ssh.nix - COMPLETATO - 3.5 fail2ban + audit - COMPLETATO - -SPRINT 4 - Hardware Detection - COMPLETATO - 4.1 cpu.nix - COMPLETATO - 4.2 gpu.nix - COMPLETATO - 4.3 platform.nix - COMPLETATO - 4.4 lib/hardware.nix - COMPLETATO - -SPRINT 5 - Desktop e Bora Layout - COMPLETATO - 5.1 kde-minimal.nix - COMPLETATO - 5.2 maclike.nix (Bora theme) - COMPLETATO - 5.3 pipewire.nix - COMPLETATO - 5.4 scripts/maclike/ - COMPLETATO - 5.5 config/desktop/ - COMPLETATO - -SPRINT 6 - Container Engine - COMPLETATO - 6.1 microvm-host.nix - COMPLETATO - 6.2 orchestrator.nix - COMPLETATO - 6.3 sandbox.guest - COMPLETATO - 6.4 container config - COMPLETATO - 6.5 SocketVM forwarding - COMPLETATO - -SPRINT 7 - Spring Framework - COMPLETATO - 7.1 bean definitions - COMPLETATO - 7.2 systemd services - COMPLETATO - 7.3 circuit breaker - COMPLETATO - 7.4 cycle detection - COMPLETATO - 7.5 spring scripts - COMPLETATO - -SPRINT 8 - Instance Pool - COMPLETATO - 8.1 instance-pool.nix - COMPLETATO - 8.2 guest definition - COMPLETATO - 8.3 pool config - COMPLETATO - 8.4 pool scripts - COMPLETATO - 8.5 cgroup v2 - COMPLETATO - 8.6 Caddy proxy - COMPLETATO - -SPRINT 9 - Testing e Documentazione - COMPLETATO - 9.1 tests/default.nix - COMPLETATO - 9.2 tests/shell.nix - COMPLETATO - 9.3 docs/BORA-WP.md - COMPLETATO - 9.4 AGENTS.md - COMPLETATO - 9.5 ISO generation - COMPLETATO - -Tutti gli sprint sono COMPLETATI. Il sistema e pronto per build e deploy. - - -5. SPRING FRAMEWORK - SPECIFICA TECNICA - -Bean Definition: - -bora.spring.beans. = { - enable = true; - class = "ServiceType"; - deps = [ "bean-a" "bean-b" ]; - resources = { - cpu = "2"; - memory = "1G"; - memoryMax = "2G"; - pids = 512; - ioRbps = "100M"; - ioWbps = "50M"; - numa = null; - }; - healthcheck = "curl -f http://localhost:8080"; - dependsOn = [ "storage" ]; - after = [ "network.target" ]; - restartPolicy = "on-failure"; -}; - -Circuit Breaker state machine: - -CLOSED: funzionamento normale, richieste passano, failure incrementano contatore. -OPEN: circuito aperto, richieste bloccate, timer di timeout avviato. -HALF-OPEN: test di recupero, richieste limitate. - -Transizioni: - CLOSED -> OPEN: quando failure >= threshold (default 5) - OPEN -> HALF-OPEN: dopo timeout (default 30 secondi) - HALF-OPEN -> CLOSED: quando success >= threshold (default 2) - HALF-OPEN -> OPEN: quando failure in half-open - -Topological Sort: - -Le dipendenze tra bean sono risolte a BUILD-TIME con topological sort. -Se esiste un ciclo, il build FAIL con messaggio: -error: Spring: circular dependency detected in beans: [a, b, c] +La macchina a stati del Circuit Breaker ha tre stati. CLOSED e funzionamento normale dove le richieste passano e i failure incrementano un contatore. OPEN e circuito aperto dove le richieste sono bloccate e un timer di timeout viene avviato. HALF-OPEN e test di recupero dove richieste limitate sono permesse. Le transizioni prevedono che CLOSED passi a OPEN quando i failure raggiungono la threshold che di default e 5. OPEN passa a HALF-OPEN dopo il timeout che di default e 30 secondi. HALF-OPEN passa a CLOSED quando i success raggiungono la threshold che di default e 2. HALF-OPEN passa a OPEN quando si verifica un failure in half-open. +Il topological sort risolve le dipendenze tra bean a build time. Se esiste un ciclo il build fallisce con un messaggio di errore che indica la presenza di una circular dependency nei bean specificati. 6. RESOURCE MANAGEMENT E CIRCUIT BREAKER -Cgroup v2 Hierarchy: - -/sys/fs/cgroup/ - / - bean-database/ cpu.max, memory.max, pids.max, io.max - bean-redis/ Limiti dedicati per bean - bean-webapp/ OOM policy: kill - bora/ - pool/ Pool istanze MicroVM - instance-001/ cpu.max=50%, memory.max=256M - instance-002/ cpu.max=50%, memory.max=256M - -OOM Protection: - -OOMPolicy=kill per tutti i servizi Spring. -MemoryHigh = soft limit (throttling prima di OOM). -MemoryMax = hard limit (OOM kill se superato). -DefaultMemoryAccounting=yes globalmente. - -Health Check Flow: - -1. Esegui healthcheck command -2. Se SUCCESS -> circuit_success() -3. Se FAILURE -> circuit_trip() - CLOSED: incrementa counter, se > threshold -> OPEN - OPEN: attendi timeout, poi -> HALF-OPEN - HALF-OPEN: se < max tentativi -> riprova, altrimenti -> CLOSED -4. Se circuito OPEN -> servizio non parte (exit 1) +La gerarchia cgroup v2 e organizzata sotto sys fs cgroup con il nome host che contiene bean-database bean-redis e bean-webapp con cpu.max memory.max pids.max e io.max e OOM policy kill. La sezione bora contiene pool per le istanze MicroVM con instance-001 e instance-002 con cpu.max al 50 percento e memory.max a 256 MB. +La protezione OOM prevede OOMPolicy kill per tutti i servizi Spring. MemoryHigh e il soft limit per throttling prima di OOM. MemoryMax e l hard limit per OOM kill se superato. DefaultMemoryAccounting e yes globalmente. Il flusso di health check esegue il comando healthcheck. Se il risultato e success chiama circuit_success. Se il risultato e failure chiama circuit_trip. In stato CLOSED incrementa il contatore e se supera la threshold passa a OPEN. In stato OPEN attende il timeout poi passa a HALF-OPEN. In stato HALF-OPEN se il numero di tentativi e inferiore al massimo riprova altrimenti passa a CLOSED. Se il circuito e OPEN il servizio non parte ed esce con codice 1. 7. SECURITY BASELINE -Kernel Parameters (sysctl): - -kernel.kptr_restrict = 2 -kernel.dmesg_restrict = 1 -kernel.perf_event_paranoid = 3 -kernel.yama.ptrace_scope = 2 -kernel.randomize_va_space = 2 -kernel.unprivileged_bpf_disabled = 1 -net.core.bpf_jit_enable = 0 -kernel.kexec_load_disabled = 1 -kernel.sysrq = 0 - -Firewall (nftables): - -chain input -> policy DROP - ct state { established, related } -> ACCEPT - iifname lo -> ACCEPT - icmp -> rate 10/second ACCEPT - tcp 22 -> saddr LAN -> ACCEPT - tutto altro -> LOG + DROP - -chain forward -> policy DROP - ct state { established, related } -> ACCEPT - iifname "microvm" -> ACCEPT +I parametri kernel sysctl includono kernel.kptr_restrict impostato a 2 kernel.dmesg_restrict a 1 kernel.perf_event_paranoid a 3 kernel.yama.ptrace_scope a 2 kernel.randomize_va_space a 2 kernel.unprivileged_bpf_disabled a 1 net.core.bpf_jit_enable a 0 kernel.kexec_load_disabled a 1 e kernel.sysrq a 0. -chain output -> policy ACCEPT - -SSH Hardening: - -PermitRootLogin no -PasswordAuthentication no -PubkeyAuthentication yes -MaxAuthTries 3 -MaxSessions 4 -AllowTcpForwarding no -AllowAgentForwarding no -Ciphers chacha20-poly1305, aes256-gcm -MACs hmac-sha2-512-etm, hmac-sha2-256-etm - -AppArmor: - -Enforced con cache attiva. Profili: apparmor-profiles. -Lockdown: confidentiality. +Il firewall nftables definisce chain input con policy DROP che accetta connessioni stabilite e correlate traffico su interfaccia loopback ICMP con rate limit di 10 al secondo e TCP porta 22 da indirizzi LAN e registra e droppa tutto il resto. chain forward con policy DROP accetta connessioni stabilite e correlate e traffico dall interfaccia microvm. chain output con policy ACCEPT. +L hardening SSH prevede PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes MaxAuthTries 3 MaxSessions 4 AllowTcpForwarding no AllowAgentForwarding no cifrari ChaCha20-Poly1305 e AES-256-GCM e MACs HMAC-SHA2-512-ETM e HMAC-SHA2-256-ETM. AppArmor e enforced con cache attiva profili da apparmor-profiles e lockdown impostato a confidentiality. 8. IDEMPOTENZA E ATOMICITA -Regole di Idempotenza: - -nixos-rebuild switch DEVE essere IDEMPOTENTE. -Eseguire 2 volte di seguito -> stesso risultato. -Niente side effects fuori dal nix store. -/etc rigenerato a ogni build. -Stato utente SOLO in /persist e /home. -Root filesystem effimero (impermanence). - -Atomicita: - -Ogni nixos-rebuild produce una NEW GENERATION. -La generazione precedente rimane INTATTA nel boot menu. -Rollback: nixos-rebuild switch --rollback. -ZFS: snapshot automatico PRE-rebuild. -ZFS: snapshot POST-rebuild via sanoid. - -farlo, tentativo, hack, workaround = ZERO TOLERANZA. +Le regole di idempotenza richiedono che nixos-rebuild switch sia idempotente eseguendolo due volte di seguito deve produrre lo stesso risultato. Non ci devono essere side effects fuori dal nix store. La directory etc viene rigenerata a ogni build. Lo stato utente risiede solo in persist e home. Il root filesystem e effimero tramite impermanence. +Per quanto riguarda l atomicita ogni nixos-rebuild produce una new generation. La generazione precedente rimane intatta nel boot menu. Il rollback si esegue con nixos-rebuild switch rollback. ZFS esegue snapshot automatico pre rebuild e snapshot post rebuild via sanoid. Workaround tentativi hack e placeholders hanno tolleranza zero. 9. TESTING E QUALITY GATES -Quality Gates (obbligatori prima di ogni commit): - -1. statix check src/ - Linting Nix -2. deadnix src/ - Dead code detection -3. nixpkgs-fmt --check src/ - Formattazione -4. nix-instantiate --eval tests/ - Valutazione test +I quality gates obbligatori prima di ogni commit includono statix check src per linting Nix deadnix src per dead code detection nixpkgs-fmt check src per formattazione e nix-instantiate eval tests per valutazione test. Il commit deve fallire se uno qualsiasi dei quattro fallisce. -FALLISCI se UNO qualsiasi dei 4 fallisce. +La struttura dei test prevede tests/default.nix per test funzioni lib con testHardwareDetect testSpringFramework testCoreModules e testSecurityModules e tests/shell.nix per ambiente linting. Ogni modulo che definisce opzioni deve avere assertions che verificano condizioni con messaggio di errore. -Test Structure: +10. FLOW OPERATIVO AGENTE -tests/ - default.nix - Test funzioni lib - libTests/ - testHardwareDetect - testSpringFramework - moduleTests/ - testCoreModules - testSecurityModules - shell.nix - Ambiente linting +Quando l utente richiede una modifica l agente cerca in src/modules il modulo pertinente. Se non esiste crea una nuova categoria crea default.nix e crea il file del modulo. Poi modifica options e config. Se sono necessari script shell vanno in scripts mai inline. Se sono necessari file config vanno in config mai inline. Se c e hardcoding va sostituito con options mkDefault e mkIf. Se ci sono commenti nei file Nix vanno rimossi e messi in AGENTS.md. Poi esegue statix deadnix e nixpkgs-fmt. Verifica l idempotenza e infine esegue opzionalmente nixos-rebuild switch. -Assertions nei Moduli: +Le regole per l agente sono le seguenti. Mai scrivere commenti nei file Nix. Mai scrivere script shell inline nei file Nix. Mai hardcodare username hostname o path. Sempre usare options con mkOption per parametri. Sempre usare mkIf per attivazione condizionale. Sempre usare mkDefault per default sovrascrivibili. Script shell in scripts. Config files in config. Documentazione tecnica in AGENTS.md. Documentazione utente in docs. Dopo ogni modifica eseguire statix deadnix e nixpkgs-fmt. Ogni modifica deve essere idempotente. -Ogni modulo che definisce opzioni DEVE avere una assertions che verifica: -assertions = [{ assertion = condizione; message = "Errore: spiegazione del problema"; }]; +Il template per un nuovo modulo prevede la definizione di config lib pkgs con l utilizzo di let cfg config.bora.category.module per accedere alle opzioni. options.bora.category.module deve contenere enable come mkEnableOption e option1 come mkOption con type e default. config deve essere wrappato in mkIf cfg.enable con attr impostato a mkDefault cfg.option1. +Il template per un nuovo host prevede un file meta.nix con system hardware profile hostname e username. Il file default.nix riceve config lib pkgs username e hostname e configura networking.hostName con hostname e users.users con username come isNormalUser true e extraGroups con wheel. -10. FLOW OPERATIVO AGENTE +Il template per script shell prevede il file in scripts/categoria/nome.sh con shebang bash set euo pipefail parametri con default e funzione main che esegue la logica. Il riferimento in Nix usa pkgs.writeShellScriptBin con builtins.readFile per leggere il percorso dello script. -Flow Chart: - -USER RICHIEDE MODIFICA - | - v -CERCA IN src/modules/ il modulo pertinente - | Se non esiste: crea nuova categoria, crea default.nix, crea file.nix - | - v -MODIFICA options/config - | - v -SHELL SCRIPTS? -> SI -> scripts/ (mai inline) - | NO - v -CONFIG FILES? -> SI -> config/ (mai inline) - | NO - v -HARDCODING? -> SI -> options + mkDefault + mkIf - | NO - v -COMMENTI nei .nix? -> SI -> RIMUOVI (vanno in AGENTS.md) - | NO - v -statix + deadnix + nixpkgs-fmt - | - v -VERIFICA idempotenza - | - v -nixos-rebuild switch (opzionale) - -Regole per l'Agente: - -1. MAI scrivere commenti nei .nix -2. MAI scrivere script shell inline nei .nix -3. MAI hardcodare username, hostname, path -4. SEMPRE usare options + mkOption per parametri -5. SEMPRE usare mkIf per attivazione condizionale -6. SEMPRE usare mkDefault per default sovrascrivibili -7. Script shell -> scripts/ -8. Config files -> config/ -9. documentazione tecnica -> AGENTS.md -10. documentazione utente -> docs/ -11. Dopo ogni modifica: statix + deadnix + nixpkgs-fmt -12. Ogni modifica deve essere IDEMPOTENTE - - -Appendice A: Template Nuovo Modulo - -{ config, lib, pkgs, ... }: -with lib; -let cfg = config.bora.category.module; in { - options.bora.category.module = { - enable = mkEnableOption "descrizione modulo"; - option1 = mkOption { type = types.str; default = "valore"; }; - }; - config = mkIf cfg.enable { attr = mkDefault cfg.option1; }; -}; - -Appendice B: Template Nuovo Host - -meta.nix: -{ - system = "x86_64-linux"; - hardware = "desktop"; - profile = "minimal"; - hostname = "os"; - username = "user"; -} - -default.nix: -{ config, lib, pkgs, username, hostname, ... }: { - networking.hostName = hostname; - users.users.${username} = { isNormalUser = true; extraGroups = [ "wheel" ]; }; -}; - -Appendice C: Template Script Shell - -File scripts/categoria/nome.sh: -#!/usr/bin/env bash -set -euo pipefail -ARG1="${1:?ARG1 required}" -ARG2="${2:-default}" -main() { printf "Executing: %s %s\n" "${ARG1}" "${ARG2}"; } -main "$@" - -Riferimento in Nix: -pkgs.writeShellScriptBin "nome-comando" (builtins.readFile ./scripts/categoria/nome.sh) - - -BORA NixOS - Regole AgentiChe v2.0.0 - Sprint: Fondazione -Copyright 2026 - Distribuito sotto licenza MIT +BORA NixOS Regole AgentiChe v2.0.0 Sprint Fondazione +Copyright 2026 Distribuito sotto licenza MIT diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc1a677 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +BORA NixOS + +BORA is a modular immutable NixOS configuration framework built on Zero Hardcoding Zero Comments and Zero Inline Shell principles. Every value is parameterized through Nix options. All technical documentation lives in AGENTS.md. All shell scripts are standalone files in scripts referenced via builtins.readFile. + +The framework includes a Spring style dependency injection and circuit breaker library for systemd services a MicroVM container engine with instance pool orchestration KDE Plasma 6 desktop with custom Bora layout ZFS filesystem with impermanence and a layered security model with NFTables kernel hardening and SSH hardening. + +Quick start + +Set hostname and username in src/hosts/target-host/meta.nix. Run nixos-install flake hash target-host. Set a password and reboot. + +For ISO generation run nix build hash packages.x86_64-linux.iso-minimal for headless or nix build hash packages.x86_64-linux.iso-graphical for desktop. + +Prerequisites + +Nix package manager with flakes enabled. + +Project structure + +src/hosts contains per machine configurations with meta.nix for system hardware profile hostname and username. src/modules contains categorized modules for core filesystem security containers desktop hardware and network. src/profiles defines use case profiles like workstation developer server and minimal. lib contains pure Nix libraries including hardware detection and the Spring framework. + +License + +MIT diff --git a/config/security/nftables.nix b/config/security/nftables.nix index 1b6a2ae..240720c 100644 --- a/config/security/nftables.nix +++ b/config/security/nftables.nix @@ -12,7 +12,7 @@ table inet filter { time-exceeded, parameter-problem } limit rate 10/second accept; - ip6 icmpv6 type { + meta l4proto ipv6-icmp icmpv6 type { echo-request, destination-unreachable, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, @@ -36,8 +36,7 @@ table inet filter { type filter hook forward priority 0; policy drop; ct state { established, related } accept; - iifname "microvm" oifname "microvm" accept; - iifname "microvm" oifname "eth0" masquerade accept; + iifname "microvm" accept; log prefix "NF:DROP-FORWARD: " drop; } diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..68b23a3 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,74 @@ +BORA NixOS Architecture Document +Version 2.0.0 + +1. Introduction + +BORA is a modular immutable NixOS configuration framework built on four pillars. Zero Hardcoding means every value is parameterized through Nix options. No usernames hostnames paths IPs or hardware identifiers are hardcoded. Host specific values are declared in meta.nix files and injected via specialArgs. Zero Comments means Nix files contain no comments. All technical documentation lives in AGENTS.md. User documentation lives in docs. This enforces self documenting code through meaningful identifier names and pure functional patterns. Zero Inline Shell means every shell script is stored as a standalone file in scripts and referenced via builtins.readFile. No script content appears inside Nix strings. Pure Functions means library functions in lib are pure with no side effects. Module evaluation is deterministic and idempotent. + +2. Repository Structure + +The repository follows a strict directory layout enforced by the module loader and build system. The top level contains flake.nix as the entry point declaring inputs like nixpkgs nixos-hardware microvm sops-nix and nixos-generators and defining outputs for each discovered host plus ISO generation. configuration.nix is the module loader that auto scans src/modules for category directories imports each category default.nix and loads the selected profile from src/profiles. + +The source tree is organized as follows. src/hosts contains per machine configurations with each subdirectory named after the host containing meta.nix for system hardware profile hostname and username default.nix for host specific config and hardware.nix for generated hardware scan. src/profiles defines use case configurations including workstation with desktop KDE and Bora layout developer with workstation plus dev tools server with headless and container orchestrator and minimal with headless minimal. src/modules is organized by category with core for boot nix locale and sysctl filesystem for ZFS and impermanence security for firewall hardening and SSH containers for MicroVM host orchestrator and instance pool desktop for KDE minimal PipeWire and Bora layout hardware for CPU GPU and platform and network for base and DNS. src/guests defines MicroVM guest templates with sandbox.nix as a generic template and the example directory demonstrating a concrete instance definition with pool configuration. lib contains pure Nix library functions including hardware detection database Spring DI IoC framework with circuit breaker and the library aggregator. config holds external configuration files referenced by modules such as desktop panel layouts NFTables rulesets and container bridge configuration. scripts holds standalone shell scripts organized by subsystem including spring services desktop initialization and pool management. assets is reserved for static files like wallpapers themes fonts and icons. secrets is reserved for encrypted secrets via SOPS and age. tests contains pure Nix evaluation tests and a shell environment for linting with statix deadnix and nixpkgs-fmt. docs contains documentation in plain text format. + +3. Core Design Principles + +Single Responsibility means each Nix file has exactly one purpose. A file equals one module equals one function. No file mixes concerns. Auto Discovery means the configuration.nix module loader scans src/modules at evaluation time. No manual imports are needed when adding new modules. Each category default.nix imports all submodules within that category. Conditional Activation means modules are enabled or disabled via mkIf cfg.enable. The enable option is declared in the module options block. Profiles activate combinations of modules by setting these options to their preferred values. Idempotency means nixos-rebuild switch is idempotent. Running it twice produces the same result. No state is modified outside the Nix store. User state lives exclusively in persist and home. The root filesystem is ephemeral through impermanence. Atomicity means every nixos-rebuild produces a new generation. The previous generation remains intact and selectable from the boot menu. Rollback uses nixos-rebuild switch rollback. ZFS snapshots are taken automatically before and after rebuild via sanoid. Parameterization means all configurable values use Nix options with mkOption. Default values use mkDefault so they can be overridden. Conditional values use mkIf. No literal values appear in configuration logic. + +4. Module System + +Modules are organized into categories under src/modules. Each category represents a subsystem of the operating system. The core category covers boot configuration with systemd-boot kernel parameters and initrd Nix daemon settings with auto-optimise garbage collection and substituters locale and timezone and sysctl kernel parameters. The filesystem category covers ZFS pool creation ARC tuning automatic trimming scrub scheduling sanoid snapshot retention disko partitioning and impermanence configuration with persistent directories and files. The security category covers NFTables firewall with default drop policy kernel hardening through sysctl AppArmor enforcement with lockdown SSH server hardening with key only access rate limiting and minimal ciphers Fail2ban for brute force protection and audit logging. The containers category covers MicroVM host configuration with bridge networking orchestrator for managing guest lifecycles and instance pool for dynamic scaling of guest instances with cgroup v2 resource isolation. The desktop category covers KDE Plasma 6 minimal installation PipeWire audio server and Bora custom desktop layout with top bar dock global menu and cosmic dark theme. The hardware category covers CPU specific optimizations for Intel AMD and ARM GPU drivers and configuration for NVIDIA AMD and Intel and platform tuning for desktop laptop and server. The network category covers base network configuration and DNS resolver settings. + +Each module defines an options block with an enable flag and all configurable parameters. The config block is wrapped in mkIf cfg.enable. Assertions validate parameter combinations at evaluation time. To create a new module you first create src/modules/category/name.nix with options and config then update src/modules/category/default.nix to import the new file then define options with mkOption and use mkIf for conditional config and finally use assertions to validate constraints. + +5. Host and Profile System + +Each host is defined in src/hosts/hostname. The meta.nix file declares four attributes. system is the NixOS system architecture such as x86_64-linux. hardware is the hardware class such as desktop laptop or server. profile is the use case profile name such as workstation developer server or minimal. hostname is the machine hostname. username is the primary user name. + +The flake.nix reads src/hosts to discover available hosts. It passes hostname and username from meta.nix as specialArgs to the NixOS configuration. This eliminates all hardcoded user and host references. Profiles in src/profiles define combinations of enabled modules. A profile sets bora options to mkDefault values establishing the baseline configuration for that use case. Profiles inherit from more basic profiles where applicable. The configuration.nix loads the profile specified in meta.nix using a dynamic import based on the profile attribute. + +6. Spring Framework + +The Spring framework in lib/spring.nix provides dependency injection and circuit breaker patterns for systemd services. Bean definitions use the bora.spring.beans attribute set. Each bean specifies a class as a service type identifier for organizational purposes a deps list of bean names this bean depends on resources with resource limits for cgroup v2 isolation including cpu memory memoryMax pids ioRbps ioWbps and numa a healthcheck command that returns zero for healthy service dependsOn for systemd unit dependencies after for systemd unit ordering and restartPolicy for systemd restart policy. + +The framework performs topological sort of bean dependencies at build time. Circular dependencies cause a build failure with a diagnostic message listing the cycle. The circuit breaker implements a three state machine. CLOSED is normal operation where requests pass through and failures increment a counter. OPEN is when the circuit is open requests are blocked and a timeout timer starts. HALF-OPEN is recovery test mode where limited requests are allowed through. + +Circuit breaker transitions work as follows. CLOSED transitions to OPEN when failures reach the threshold which defaults to 5. OPEN transitions to HALF-OPEN after the timeout which defaults to 30 seconds. HALF-OPEN transitions to CLOSED when successes reach the threshold which defaults to 2. HALF-OPEN transitions to OPEN on any failure in half-open state. + +Cgroup v2 hierarchy is created under sys fs cgroup hostname bean name with cpu.max memory.max pids.max and io.max limits. OOM policy is set to kill for all Spring services. The health check flow executes the healthcheck command periodically. Success calls circuit_success which may transition to CLOSED. Failure calls circuit_trip which may transition to OPEN. When the circuit is OPEN the service exits with code 1. + +7. Security Architecture + +Security is implemented in layers. Kernel hardening uses sysctl parameters to restrict kernel pointer access dmesg access performance events ptrace BPF kexec and SysRq. ASLR is set to maximum. Unprivileged BPF is disabled. BPF JIT is disabled. + +The firewall uses NFTables with a default drop policy on the input chain. Only established and related connections loopback traffic rate limited ICMP and SSH from LAN addresses are accepted. The forward chain accepts established and related connections and traffic from the microvm bridge interface. The output chain has a default accept policy. + +SSH is hardened with no root login no password authentication key only access rate limited authentication attempts limited sessions no TCP or agent forwarding and modern cipher suites including ChaCha20-Poly1305 and AES-256-GCM with ETM MACs. AppArmor is enforced with cache enabled. The apparmor-profiles package provides additional profiles. Lockdown is set to confidentiality. Fail2ban monitors SSH and HTTP services. Audit logging captures security relevant events. + +8. Filesystem Architecture + +The filesystem uses ZFS as the primary filesystem with impermanence for root immutability. ZFS pools are created with encryption compression using zstd-3 atime disabled and automatic trim enabled. ARC size is configurable with a default of 8 GB. Snapshot management uses sanoid with configurable retention policies. Automatic scrub runs on a configurable schedule. + +Impermanence makes the root filesystem ephemeral. Only directories and files listed in environment.persistence.persist are preserved across reboots. User data in persist and home persists. System state including machine-id resolv.conf and SSH keys is explicitly persisted. Disko provides declarative partitioning with disk layout defined in configuration not manual partitioning. Pre-rebuild and post-rebuild ZFS snapshots are created automatically via sanoid. The previous generation remains bootable through the boot menu entry. + +9. Container Architecture + +Containers use MicroVM for hardware level isolation. Each guest runs as a separate microvm with dedicated vCPU memory and storage resources. The host configures a bridge interface called microvm for guest networking. Guests connect through this bridge. Socket forwarding enables X11 and Wayland forwarding for desktop application containers. + +The orchestrator manages guest lifecycles including create start stop and destroy. It uses cgroup v2 for resource isolation at the pool level. The instance pool provides dynamic scaling. Key parameters include maxInstances basePort memPerInstance cpuPerInstance storagePerInstance appPackage appCommand and healthcheckCmd. The pool manager automatically spawns new instances up to the configured maximum and performs health checks on running instances. Caddy serves as a reverse proxy routing requests to the appropriate instance based on port mapping. The cgroup v2 hierarchy for containers is structured as sys fs cgroup hostname pool instance-001 and instance-002 each with cpu.max memory.max pids.max and io.max limits. + +10. Desktop Architecture + +The desktop environment uses KDE Plasma 6 with a custom Bora layout. KDE Plasma 6 is installed with essential components only including plasma-desktop kwin konsole dolphin kscreen plasma-nm plasma-pa bluedevil powerdevil kdecoration-viewer kactivitymanagerd and polkit-kde-agent-1. Discover and PIM applications are excluded. + +The Bora layout provides a top bar with global menu application launcher system tray clock and workspace switcher. A dock with favorites running applications and trash. Custom window decorations and button layout. A custom color scheme called BoraDark with a dark cosmic background and cyan accent. The color scheme uses background value 0A0C16 alternate background value 11131F foreground value C0C5D4 selection background value 7B2FBE selection foreground value FFFFFF active titlebar value 1A1C2B inactive titlebar value 0A0C16 accent value 00D4FF link value 00D4FF and visited link value 7B2FBE. + +PipeWire provides audio with WirePlumber session manager and low latency configuration for real time audio. Desktop initialization scripts run at first login to configure the panel layout window rules and keyboard shortcuts through the KDE configuration system using kwriteconfig6. + +11. Build and Deploy + +Build targets are defined in flake.nix outputs. nixosConfigurations.hostname provides standard NixOS configuration build. packages.system.iso-minimal provides minimal ISO image without desktop. packages.system.iso-graphical provides full ISO with desktop environment. The flake uses nixos-generators for ISO creation. The ISO configuration includes ZFS vfat and xfs filesystem support. + +The deployment workflow starts by setting hostname and username in src/hosts/target-host/meta.nix and optionally overriding the profile. Then run nixos-install flake hash target-host. Set a password for the user. Then reboot. Post deployment validation includes verifying ZFS pools and datasets are created correctly verifying firewall rules with nft list ruleset verifying SSH is accessible only via key from LAN verifying desktop layout has top bar dock and correct color scheme verifying microvm bridge interface exists and verifying cgroup v2 hierarchy is populated. + +The rollback procedure involves selecting the previous generation from the boot menu or running nixos-rebuild switch rollback. Verify pre-rebuild ZFS snapshot exists via zfs list t snapshot. diff --git a/docs/BORA-ARCH.md b/docs/BORA-ARCH.md deleted file mode 100644 index 86a42d3..0000000 --- a/docs/BORA-ARCH.md +++ /dev/null @@ -1,272 +0,0 @@ -BORA NixOS - Architecture Document -Version 2.0.0 - -Table of Contents - -1. Introduction -2. Repository Structure -3. Core Design Principles -4. Module System -5. Host and Profile System -6. Spring Framework (DI/IoC) -7. Security Architecture -8. Filesystem Architecture -9. Container Architecture -10. Desktop Architecture -11. Build and Deploy - - -1. Introduction - -BORA is a modular, immutable NixOS configuration framework built on four pillars: - -Zero Hardcoding: Every value is parameterized through Nix options. No usernames, hostnames, paths, IPs, or hardware identifiers are hardcoded. Host-specific values are declared in meta.nix files and injected via specialArgs. - -Zero Comments: Nix files contain no comments. All technical documentation lives in AGENTS.md. User documentation lives in docs/. This enforces self-documenting code through meaningful identifier names and pure functional patterns. - -Zero Inline Shell: Every shell script is stored as a standalone file in scripts/ and referenced via builtins.readFile. No script content appears inside Nix strings. - -Pure Functions: Library functions in lib/ are pure with no side effects. Module evaluation is deterministic and idempotent. - - -2. Repository Structure - -The repository follows a strict directory layout enforced by the module loader and build system. - -Top Level: - -flake.nix is the entry point. It declares inputs (nixpkgs, nixos-hardware, microvm, sops-nix, nixos-generators) and defines outputs for each discovered host plus ISO generation. - -configuration.nix is the module loader. It auto-scans src/modules/ for category directories, imports each category's default.nix, and loads the selected profile from src/profiles/. - -Source Tree: - -src/hosts/ contains per-machine configurations. Each subdirectory is named after the host and contains meta.nix (system, hardware, profile, hostname, username), default.nix (host-specific config), and hardware.nix (generated hardware scan). - -src/profiles/ defines use-case configurations: workstation (desktop + KDE + Bora layout), developer (workstation + dev tools), server (headless + container orchestrator), minimal (headless minimal). - -src/modules/ is organized by category: core, filesystem, security, containers, desktop, hardware, network. Each category has a default.nix that imports submodules. - -src/guests/ defines MicroVM guest templates. The sandbox.nix is a generic template. The example/ directory demonstrates a concrete instance definition with pool configuration. - -lib/ contains pure Nix library functions: hardware detection database, Spring DI/IoC framework with circuit breaker, and the library aggregator. - -config/ holds external configuration files referenced by modules: desktop panel layouts, NFTables rulesets, container bridge configuration. - -scripts/ holds standalone shell scripts organized by subsystem: spring services, desktop initialization, pool management. - -assets/ is reserved for static files: wallpapers, themes, fonts, icons. - -secrets/ is reserved for encrypted secrets via SOPS and age. - -tests/ contains pure Nix evaluation tests and a shell environment for linting with statix, deadnix, and nixpkgs-fmt. - -docs/ contains documentation in plain text format. - - -3. Core Design Principles - -Single Responsibility: Each .nix file has exactly one purpose. A file equals one module equals one function. No file mixes concerns. - -Auto-Discovery: The configuration.nix module loader scans src/modules/ at evaluation time. No manual imports are needed when adding new modules. Each category's default.nix imports all submodules within that category. - -Conditional Activation: Modules are enabled or disabled via mkIf cfg.enable. The enable option is declared in the module's options block. Profiles activate combinations of modules by setting these options to their preferred values. - -Idempotency: nixos-rebuild switch is idempotent. Running it twice produces the same result. No state is modified outside the Nix store. User state lives exclusively in /persist and /home. The root filesystem is ephemeral through impermanence. - -Atomicity: Every nixos-rebuild produces a new generation. The previous generation remains intact and selectable from the boot menu. Rollback uses nixos-rebuild switch --rollback. ZFS snapshots are taken automatically before and after rebuild via sanoid. - -Parameterization: All configurable values use Nix options with mkOption. Default values use mkDefault so they can be overridden. Conditional values use mkIf. No literal values appear in configuration logic. - - -4. Module System - -Modules are organized into categories under src/modules/. Each category represents a subsystem of the operating system. - -core/: Boot configuration (systemd-boot, kernel parameters, initrd), Nix daemon settings (auto-optimise, garbage collection, substituters), locale and timezone, sysctl kernel parameters. - -filesystem/: ZFS pool creation, ARC tuning, automatic trimming, scrub scheduling, sanoid snapshot retention, disko partitioning, impermanence configuration with persistent directories and files. - -security/: NFTables firewall with default-drop policy, kernel hardening through sysctl, AppArmor enforcement with lockdown, SSH server hardening (key-only, rate-limited, minimal ciphers), Fail2ban for brute-force protection, audit logging. - -containers/: MicroVM host configuration with bridge networking, orchestrator for managing guest lifecycles, instance pool for dynamic scaling of guest instances with cgroup v2 resource isolation. - -desktop/: KDE Plasma 6 minimal installation, PipeWire audio server, Bora custom desktop layout (top bar, dock, global menu, cosmic dark theme). - -hardware/: CPU-specific optimizations (Intel, AMD, ARM), GPU drivers and configuration (NVIDIA, AMD, Intel), platform tuning (desktop, laptop, server). - -network/: Base network configuration, DNS resolver settings. - -Each module defines an options block with an enable flag and all configurable parameters. The config block is wrapped in mkIf cfg.enable. Assertions validate parameter combinations at evaluation time. - -New modules follow this pattern: - -Create src/modules/category/name.nix with options and config. -Update src/modules/category/default.nix to import the new file. -Define options with mkOption and use mkIf for conditional config. -Use assertions to validate constraints. - - -5. Host and Profile System - -Each host is defined in src/hosts/hostname/. The meta.nix file declares four attributes: - -system: The NixOS system architecture (e.g. x86_64-linux). -hardware: The hardware class (desktop, laptop, server). -profile: The use-case profile name (workstation, developer, server, minimal). -hostname: The machine hostname. -username: The primary user name. - -The flake.nix reads src/hosts/ to discover available hosts. It passes hostname and username from meta.nix as specialArgs to the NixOS configuration. This eliminates all hardcoded user and host references. - -Profiles in src/profiles/ define combinations of enabled modules. A profile sets bora options to mkDefault values, establishing the baseline configuration for that use case. Profiles inherit from more basic profiles where applicable. - -The configuration.nix loads the profile specified in meta.nix using a dynamic import based on the profile attribute. - - -6. Spring Framework (DI/IoC) - -The Spring framework in lib/spring.nix provides dependency injection and circuit breaker patterns for systemd services. - -Bean definitions use the bora.spring.beans attribute set. Each bean specifies: - -class: A service type identifier for organizational purposes. -deps: A list of bean names this bean depends on. -resources: Resource limits for cgroup v2 isolation (cpu, memory, memoryMax, pids, ioRbps, ioWbps, numa). -healthcheck: A command that returns zero for healthy service. -dependsOn: Systemd unit dependencies. -after: Systemd unit ordering. -restartPolicy: Systemd restart policy. - -The framework performs topological sort of bean dependencies at build time. Circular dependencies cause a build failure with a diagnostic message listing the cycle. - -The circuit breaker implements a three-state machine: - -CLOSED: Normal operation. Requests pass through. Failures increment a counter. -OPEN: Circuit is open. Requests are blocked. A timeout timer starts. -HALF-OPEN: Recovery test. Limited requests are allowed through. - -Transitions: - -CLOSED to OPEN when failures reach threshold (default 5). -OPEN to HALF-OPEN after timeout (default 30 seconds). -HALF-OPEN to CLOSED when successes reach threshold (default 2). -HALF-OPEN to OPEN on any failure in half-open state. - -Cgroup v2 hierarchy is created under /sys/fs/cgroup/hostname/bean-name/ with cpu.max, memory.max, pids.max, and io.max limits. OOM policy is set to kill for all Spring services. - -Health check flow executes the healthcheck command periodically. Success calls circuit_success which may transition to CLOSED. Failure calls circuit_trip which may transition to OPEN. When the circuit is OPEN, the service exits with code 1. - - -7. Security Architecture - -Security is implemented in layers. - -Kernel hardening uses sysctl parameters to restrict kernel pointer access, dmesg access, performance events, ptrace, BPF, kexec, and SysRq. ASLR is set to maximum. Unprivileged BPF is disabled. BPF JIT is disabled. - -The firewall uses NFTables with a default-drop policy on the input chain. Only established/related connections, loopback traffic, rate-limited ICMP, and SSH from LAN addresses are accepted. The forward chain accepts established/related connections and traffic from the microvm bridge interface. The output chain has a default-accept policy. - -SSH is hardened with no root login, no password authentication, key-only access, rate-limited authentication attempts, limited sessions, no TCP or agent forwarding, and modern cipher suites (ChaCha20-Poly1305, AES-256-GCM) with ETM MACs. - -AppArmor is enforced with cache enabled. The apparmor-profiles package provides additional profiles. Lockdown is set to confidentiality. - -Fail2ban monitors SSH and HTTP services. Audit logging captures security-relevant events. - - -8. Filesystem Architecture - -The filesystem uses ZFS as the primary filesystem with impermanence for root immutability. - -ZFS pools are created with encryption, compression (zstd-3), atime disabled, and automatic trim enabled. ARC size is configurable with a default of 8 GB. Snapshot management uses sanoid with configurable retention policies. Automatic scrub runs on a configurable schedule. - -Impermanence makes the root filesystem ephemeral. Only directories and files listed in environment.persistence./persist are preserved across reboots. User data in /persist and /home persists. System state (machine-id, resolv.conf, SSH keys) is explicitly persisted. - -Disko provides declarative partitioning. Disk layout is defined in configuration, not manual partitioning. - -Pre-rebuild and post-rebuild ZFS snapshots are created automatically via sanoid. The previous generation remains bootable through the boot menu entry. - - -9. Container Architecture - -Containers use MicroVM for hardware-level isolation. Each guest runs as a separate microvm with dedicated vCPU, memory, and storage resources. - -The host configures a bridge interface (microvm) for guest networking. Guests connect through this bridge. Socket forwarding enables X11 and Wayland forwarding for desktop application containers. - -The orchestrator manages guest lifecycles: create, start, stop, destroy. It uses cgroup v2 for resource isolation at the pool level. - -The instance pool provides dynamic scaling. Key parameters include maxInstances, basePort, memPerInstance, cpuPerInstance, storagePerInstance, appPackage, appCommand, and healthcheckCmd. The pool manager automatically spawns new instances up to the configured maximum and performs health checks on running instances. - -Caddy serves as a reverse proxy, routing requests to the appropriate instance based on port mapping. - -Cgroup v2 hierarchy for containers: - -/sys/fs/cgroup/ - hostname/ - pool/ - instance-001/ cpu.max, memory.max, pids.max, io.max - instance-002/ same - - -10. Desktop Architecture - -The desktop environment uses KDE Plasma 6 with a custom Bora layout. - -KDE Plasma 6 is installed with essential components only: plasma-desktop, kwin, konsole, dolphin, kscreen, plasma-nm, plasma-pa, bluedevil, powerdevil, kdecoration-viewer, kactivitymanagerd, polkit-kde-agent-1. Discover and PIM applications are excluded. - -The Bora layout provides: - -A top bar with global menu, application launcher, system tray, clock, and workspace switcher. -A dock with favorites, running applications, and trash. -Custom window decorations and button layout. -A custom color scheme (BoraDark) with a dark cosmic background and cyan accent. - -The color scheme uses these values: - -Background: #0A0C16 -Alternate Background: #11131F -Foreground: #C0C5D4 -Selection Background: #7B2FBE -Selection Foreground: #FFFFFF -Active Titlebar: #1A1C2B -Inactive Titlebar: #0A0C16 -Accent: #00D4FF -Link: #00D4FF -Visited Link: #7B2FBE - -PipeWire provides audio with WirePlumber session manager and low-latency configuration for real-time audio. - -Desktop initialization scripts run at first login to configure the panel layout, window rules, and keyboard shortcuts through the KDE configuration system (kwriteconfig6). - - -11. Build and Deploy - -Build targets are defined in flake.nix outputs: - -nixosConfigurations.hostname: Standard NixOS configuration build. -packages.system.iso-minimal: Minimal ISO image without desktop. -packages.system.iso-graphical: Full ISO with desktop environment. - -The flake uses nixos-generators for ISO creation. The ISO configuration includes ZFS, vfat, and xfs filesystem support. - -Deployment workflow: - -Set hostname and username in src/hosts/target-host/meta.nix. -Optionally override profile. -Run nixos-install --flake .#target-host. -Set a password for the user. -Reboot. - -Post-deployment validation: - -Verify ZFS pools and datasets are created correctly. -Verify firewall rules with nft list ruleset. -Verify SSH is accessible only via key from LAN. -Verify desktop layout has top bar, dock, and correct color scheme. -Verify microvm bridge interface exists. -Verify cgroup v2 hierarchy is populated. - -Rollback procedure: - -Select previous generation from boot menu. -Or run nixos-rebuild switch --rollback. -Verify pre-rebuild ZFS snapshot exists via zfs list -t snapshot. diff --git a/docs/MANUAL.md b/docs/MANUAL.md new file mode 100644 index 0000000..991028e --- /dev/null +++ b/docs/MANUAL.md @@ -0,0 +1,35 @@ +BORA NixOS User Manual + +This manual covers the BORA NixOS framework. BORA provides a modular immutable NixOS configuration framework with advanced features including dependency injection for systemd services MicroVM container isolation and declarative desktop configuration. + +Getting started + +To deploy BORA on a new machine first create a host directory under src/hosts with a meta.nix file containing the system architecture hardware profile hostname and username. Then run nixos-install flake hash target-host. After installation reboot and verify the system. + +Host configuration + +Each host is defined in src/hosts/hostname. The meta.nix file must export an attribute set with four keys. system is the architecture like x86_64-linux. hardware is the hardware class like desktop laptop or server. profile is the use case profile like workstation developer server or minimal. hostname is the machine hostname. username is the primary user name. + +Profile selection + +Profiles define which modules are enabled. The workstation profile enables desktop KDE and Bora layout. The developer profile adds development tools. The server profile is headless with container orchestrator. The minimal profile is a headless base system. + +Filesystem and storage + +The default filesystem layout uses ZFS with encryption and compression. The root filesystem is ephemeral through impermanence with persistent data stored under persist. The disko module provides declarative partitioning. + +Container engine + +MicroVM guests provide hardware level isolation. The orchestrator manages guest lifecycles. The instance pool provides dynamic scaling with cgroup v2 resource limits. Caddy serves as reverse proxy for routed instances. + +Desktop environment + +KDE Plasma 6 minimal with custom Bora layout including top bar dock global menu and dark color scheme. PipeWire provides audio with low latency configuration. + +Security + +The firewall uses NFTables with default drop policy. SSH is key only with LAN restriction and modern ciphers. Kernel hardening restricts ptrace BPF kexec and performance events. AppArmor is enforced with lockdown. + +Build and deploy + +Use nix build to build configurations and nixos-rebuild switch to apply them. ISO images are available in minimal and graphical variants. Rollback uses nixos-rebuild switch rollback or boot menu selection. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..f60b0af --- /dev/null +++ b/flake.lock @@ -0,0 +1,297 @@ +{ + "nodes": { + "disko": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1779135526, + "narHash": "sha256-glCununz6lmaK5fs2X946HA3EkNxB2JagdAAvInuRYU=", + "owner": "nix-community", + "repo": "disko", + "rev": "d405a179887d52b24c0ddd31e09a150bd1f66779", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "disko", + "type": "github" + } + }, + "home-manager": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1779213149, + "narHash": "sha256-Cf+p/T4Z3n9Sw0TiR3kQaIwQI+/hfvLJcoTzeq6yS3E=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "bd868f769a69d3b6091a1da68a75cb83a181033c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "home-manager_2": { + "inputs": { + "nixpkgs": [ + "impermanence", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1768598210, + "narHash": "sha256-kkgA32s/f4jaa4UG+2f8C225Qvclxnqs76mf8zvTVPg=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "c47b2cc64a629f8e075de52e4742de688f930dc6", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "impermanence": { + "inputs": { + "home-manager": "home-manager_2", + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1769548169, + "narHash": "sha256-03+JxvzmfwRu+5JafM0DLbxgHttOQZkUtDWBmeUkN8Y=", + "owner": "nix-community", + "repo": "impermanence", + "rev": "7b1d382faf603b6d264f58627330f9faa5cba149", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "impermanence", + "type": "github" + } + }, + "microvm": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "spectrum": "spectrum" + }, + "locked": { + "lastModified": 1779043402, + "narHash": "sha256-1dH6yiwEck1n3oz5TDUV5TGbwNqu46eNTVHbhFO2U5k=", + "owner": "microvm-nix", + "repo": "microvm.nix", + "rev": "77024c22f4ddf509137fc732094888d1ffe631e2", + "type": "github" + }, + "original": { + "owner": "microvm-nix", + "repo": "microvm.nix", + "type": "github" + } + }, + "nixlib": { + "locked": { + "lastModified": 1736643958, + "narHash": "sha256-tmpqTSWVRJVhpvfSN9KXBvKEXplrwKnSZNAoNPf/S/s=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "1418bc28a52126761c02dd3d89b2d8ca0f521181", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixos-generators": { + "inputs": { + "nixlib": "nixlib", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1769813415, + "narHash": "sha256-nnVmNNKBi1YiBNPhKclNYDORoHkuKipoz7EtVnXO50A=", + "owner": "nix-community", + "repo": "nixos-generators", + "rev": "8946737ff703382fda7623b9fab071d037e897d5", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixos-generators", + "type": "github" + } + }, + "nixos-hardware": { + "locked": { + "lastModified": 1779099457, + "narHash": "sha256-u73aVD/lUmmT3JV+kPDztl7zPwQKd0eobD1AbJltaGs=", + "owner": "NixOS", + "repo": "nixos-hardware", + "rev": "8792fab9d4a6454a9201675f01326f827ce35ead", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixos-hardware", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1773628058, + "narHash": "sha256-hpXH0z3K9xv0fHaje136KY872VT2T5uwxtezlAskQgY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "f8573b9c935cfaa162dd62cc9e75ae2db86f85df", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-unstable": { + "locked": { + "lastModified": 1778869304, + "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1778443072, + "narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1768564909, + "narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_4": { + "locked": { + "lastModified": 1751274312, + "narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_5": { + "locked": { + "lastModified": 1775888245, + "narHash": "sha256-nwASzrRDD1JBEu/o8ekKYEXm/oJW6EMCzCRdrwcLe90=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "13043924aaa7375ce482ebe2494338e058282925", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "disko": "disko", + "home-manager": "home-manager", + "impermanence": "impermanence", + "microvm": "microvm", + "nixos-generators": "nixos-generators", + "nixos-hardware": "nixos-hardware", + "nixpkgs": "nixpkgs_4", + "nixpkgs-unstable": "nixpkgs-unstable", + "sops-nix": "sops-nix" + } + }, + "sops-nix": { + "inputs": { + "nixpkgs": "nixpkgs_5" + }, + "locked": { + "lastModified": 1777944972, + "narHash": "sha256-VfGRo1qTBKOe3s2gOv8LSoA6Fk19PvBlwQ1ECN0Evn8=", + "owner": "Mic92", + "repo": "sops-nix", + "rev": "c591bf665727040c6cc5cb409079acb22dcce33c", + "type": "github" + }, + "original": { + "owner": "Mic92", + "repo": "sops-nix", + "type": "github" + } + }, + "spectrum": { + "flake": false, + "locked": { + "lastModified": 1778940603, + "narHash": "sha256-voSM8dZNlaOWN3kbYFky+FNY6fFQOEw0xF+ZMpZKkCQ=", + "ref": "refs/heads/main", + "rev": "367dd227f539267eae2b62770b4c17b88ac8c1f1", + "revCount": 1265, + "type": "git", + "url": "https://spectrum-os.org/git/spectrum" + }, + "original": { + "type": "git", + "url": "https://spectrum-os.org/git/spectrum" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix index cde7c35..fdb8901 100644 --- a/flake.nix +++ b/flake.nix @@ -11,7 +11,7 @@ }; impermanence.url = "github:nix-community/impermanence"; microvm = { - url = "github:astro/microvm"; + url = "github:microvm-nix/microvm.nix"; inputs.nixpkgs.follows = "nixpkgs"; }; disko.url = "github:nix-community/disko"; @@ -26,18 +26,19 @@ let systems = [ "x86_64-linux" "aarch64-linux" ]; forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); - boraLib = import ./lib { inherit nixpkgs; }; inherit (builtins) readDir attrNames; inherit (nixpkgs.lib) optional; hostsDir = ./src/hosts; availableHosts = attrNames (readDir hostsDir); + hardwareDB = import ./lib/hardware.nix { lib = nixpkgs.lib; }; + mkHost = hostname: hostConfig: nixpkgs.lib.nixosSystem { system = hostConfig.system or "x86_64-linux"; specialArgs = { - inherit boraLib self; + inherit hardwareDB self; hostname = hostname; username = hostConfig.username or "user"; hardwareProfile = hostConfig.hardware or "desktop"; @@ -74,16 +75,25 @@ in { iso-minimal = nixos-generators.nixosGenerate { inherit system; + specialArgs = { + inherit hardwareDB; + hostname = "bora-iso"; + username = "bora"; + hardwareProfile = "desktop"; + systemProfile = "minimal"; + }; modules = [ impermanence.nixosModules.impermanence + microvm.nixosModules.host disko.nixosModules.disko ./configuration.nix ({ pkgs, ... }: { isoImage.isoBaseName = "bora"; - isoImage.compress = true; boot.supportedFilesystems = [ "zfs" "vfat" "xfs" ]; + boot.kernelPackages = pkgs.linuxPackages_6_6; nixpkgs.config.allowUnfree = true; system.stateVersion = "24.11"; + users.users.bora = { isNormalUser = true; }; }) ]; format = "iso"; @@ -91,18 +101,27 @@ iso-graphical = nixos-generators.nixosGenerate { inherit system; + specialArgs = { + inherit hardwareDB; + hostname = "bora-iso"; + username = "bora"; + hardwareProfile = "desktop"; + systemProfile = "workstation"; + }; modules = [ impermanence.nixosModules.impermanence + microvm.nixosModules.host disko.nixosModules.disko "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix" ./configuration.nix ({ pkgs, ... }: { isoImage.isoBaseName = "bora-desktop"; - isoImage.compress = true; boot.supportedFilesystems = [ "zfs" "vfat" "xfs" ]; + boot.kernelPackages = pkgs.linuxPackages_6_6; nixpkgs.config.allowUnfree = true; services.xserver.enable = true; system.stateVersion = "24.11"; + users.users.bora = { isNormalUser = true; }; }) ]; format = "iso"; diff --git a/scripts/build/iso-build.sh b/scripts/build/iso-build.sh new file mode 100755 index 0000000..6aec821 --- /dev/null +++ b/scripts/build/iso-build.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +PROJECT_DIR="$(cd "$(dirname "$0")/../.." && pwd)" +OUTPUT_DIR="${PROJECT_DIR}/dist" +ISO_NAME="${ISO_NAME:-bora.iso}" +BUILD_TARGET="${BUILD_TARGET:-.#packages.x86_64-linux.iso-minimal}" +NIXPKGS_ALLOW_BROKEN="${NIXPKGS_ALLOW_BROKEN:-1}" + +cd "${PROJECT_DIR}" +NIXPKGS_ALLOW_BROKEN="${NIXPKGS_ALLOW_BROKEN}" \ +sudo --preserve-env=NIXPKGS_ALLOW_BROKEN \ + nix build --impure "${BUILD_TARGET}" + +RESULT_DIR="$(readlink -f result)" +ISO_PATH="${RESULT_DIR}/iso/${ISO_NAME}" + +if [ ! -f "${ISO_PATH}" ]; then + echo "ISO not found at ${ISO_PATH}, searching..." + ISO_PATH=$(find "${RESULT_DIR}" -name "*.iso" -type f | head -1) +fi + +if [ -z "${ISO_PATH}" ] || [ ! -f "${ISO_PATH}" ]; then + echo "ERROR: ISO not found in build result" + find "${RESULT_DIR}" -type f | head -20 + exit 1 +fi + +mkdir -p "${OUTPUT_DIR}" +cp "${ISO_PATH}" "${OUTPUT_DIR}/${ISO_NAME}" +chmod 644 "${OUTPUT_DIR}/${ISO_NAME}" +ls -lh "${OUTPUT_DIR}/${ISO_NAME}" + +echo "ISO built and copied to ${OUTPUT_DIR}/${ISO_NAME}" diff --git a/src/guests/sandbox.nix b/src/guests/sandbox.nix index a1b091c..6d14d9c 100644 --- a/src/guests/sandbox.nix +++ b/src/guests/sandbox.nix @@ -28,7 +28,8 @@ services.pipewire.enable = true; environment.systemPackages = with pkgs; [ - firefox chromium + firefox + chromium ]; system.stateVersion = "24.11"; diff --git a/src/hosts/bora/hardware.nix b/src/hosts/bora/hardware.nix index f41a457..5128b37 100644 --- a/src/hosts/bora/hardware.nix +++ b/src/hosts/bora/hardware.nix @@ -1,4 +1,7 @@ { config, lib, pkgs, modulesPath, ... }: +let + fsCfg = config.bora.filesystem; +in { imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; @@ -13,7 +16,7 @@ fileSystems."/home" = { device = "zroot/root/home"; fsType = "zfs"; }; fileSystems."/var" = { device = "zroot/root/var"; fsType = "zfs"; }; fileSystems."/tmp" = { device = "zroot/root/tmp"; fsType = "zfs"; }; - fileSystems."/boot" = { device = "/dev/disk/by-uuid/BOOT-UUID"; fsType = "vfat"; }; + fileSystems."/boot" = { device = fsCfg.bootDevice; fsType = "vfat"; }; swapDevices = [ ]; diff --git a/src/modules/containers/instance-pool.nix b/src/modules/containers/instance-pool.nix index a13b0f4..82227c7 100644 --- a/src/modules/containers/instance-pool.nix +++ b/src/modules/containers/instance-pool.nix @@ -11,7 +11,8 @@ let (builtins.readFile (scriptsDir + "/list.sh")); statsScript = pkgs.writeShellScriptBin "pool-stats" (builtins.readFile (scriptsDir + "/stats.sh")); -in { +in +{ options.bora.containers.instancePool = { enable = mkEnableOption "MicroVM instance pool orchestrator"; maxInstances = mkOption { @@ -125,7 +126,10 @@ in { { from = cfg.basePort; to = cfg.basePort + cfg.maxInstances; } ]; environment.systemPackages = [ - poolManager spawnScript listScript statsScript + poolManager + spawnScript + listScript + statsScript ]; }; } diff --git a/src/modules/containers/microvm-host.nix b/src/modules/containers/microvm-host.nix index 99f5b5d..28acaa8 100644 --- a/src/modules/containers/microvm-host.nix +++ b/src/modules/containers/microvm-host.nix @@ -1,29 +1,26 @@ { config, lib, pkgs, ... }: with lib; -{ - microvm = { - host = { - enable = true; - socketVM = true; - network = { - enable = true; - nat = true; - subnet = "10.100.0.0/24"; - }; - balloonMem = 512; - balloonMemMax = 65536; - store = { - backend = "zfs"; - dir = "/var/lib/microvm"; - }; +let cfg = config.bora.containers.microvm; in { + options.bora.containers.microvm = { + enable = mkEnableOption "MicroVM host support"; + stateDir = mkOption { + type = types.path; + default = "/var/lib/microvm"; + description = "Directory for MicroVM storage"; }; }; - fileSystems."/var/lib/microvm" = { - device = "zroot/root/microvm"; - fsType = "zfs"; - neededForBoot = true; + config = mkIf cfg.enable { + microvm = { + host.enable = true; + stateDir = cfg.stateDir; + }; + fileSystems.${cfg.stateDir} = { + device = "zroot/root/microvm"; + fsType = "zfs"; + neededForBoot = true; + }; + boot.kernelModules = [ "virtio" "virtio_net" "virtio_blk" "virtiofs" "virtio_gpu" ]; + boot.initrd.kernelModules = [ "virtiofs" ]; + users.groups.microvm = { }; }; - boot.kernelModules = [ "virtio" "virtio_net" "virtio_blk" "virtiofs" "virtio_gpu" ]; - boot.initrd.kernelModules = [ "virtiofs" ]; - users.groups.microvm = { }; } diff --git a/src/modules/containers/orchestrator.nix b/src/modules/containers/orchestrator.nix index 47b254b..00c9de9 100644 --- a/src/modules/containers/orchestrator.nix +++ b/src/modules/containers/orchestrator.nix @@ -2,7 +2,8 @@ with lib; let cfg = config.bora.orchestrator; -in { +in +{ options.bora.orchestrator = { enable = mkEnableOption "MicroVM instance orchestrator"; maxInstances = mkOption { @@ -34,7 +35,6 @@ in { wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; - ExecStart = "${pkgs.nixos-boot}/bin/nixos-boot"; Restart = "always"; RestartSec = 10; StateDirectory = "bora-orchestrator"; @@ -46,4 +46,14 @@ in { echo "+cpu +memory +io +pids" > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null || true mkdir -p /sys/fs/cgroup/bora 2>/dev/null || true while true; do - for vm in /var/lib/microvm + for vm in /var/lib/microvm/*/; do + [ -d "$vm" ] || continue + vm_name=$(basename "$vm") + echo "checking microvm $vm_name" + done + sleep 30 + done + ''; + }; + }; +} diff --git a/src/modules/core/boot.nix b/src/modules/core/boot.nix index 65ac757..c870c18 100644 --- a/src/modules/core/boot.nix +++ b/src/modules/core/boot.nix @@ -6,7 +6,7 @@ configurationLimit = 20; }; efi.canTouchEfiVariables = true; - timeout = 3; + timeout = lib.mkDefault 3; }; boot.kernelParams = [ "quiet" diff --git a/src/modules/core/nix.nix b/src/modules/core/nix.nix index 0f45850..02516b1 100644 --- a/src/modules/core/nix.nix +++ b/src/modules/core/nix.nix @@ -21,7 +21,6 @@ "astro-microvm.cachix.org-1:5VxKj9V5rE1xJgF2gQvA0Z3L8R6bH7cN4pY9sW1tXnM=" ]; trusted-users = [ "root" "@wheel" ]; - auto-optimise-store = true; }; gc = { automatic = true; diff --git a/src/modules/desktop/kde-minimal.nix b/src/modules/desktop/kde-minimal.nix index 6816bf3..fc4612a 100644 --- a/src/modules/desktop/kde-minimal.nix +++ b/src/modules/desktop/kde-minimal.nix @@ -17,7 +17,8 @@ let dolphin kate ]; -in { +in +{ options.bora.desktop.kde = { enable = mkEnableOption "KDE Plasma 6 minimal desktop"; enableWayland = mkOption { diff --git a/src/modules/desktop/maclike.nix b/src/modules/desktop/maclike.nix index 831f419..52d4ef8 100644 --- a/src/modules/desktop/maclike.nix +++ b/src/modules/desktop/maclike.nix @@ -6,7 +6,8 @@ let (builtins.readFile ./../../../../scripts/maclike/init-desktop.sh); finalizeScript = pkgs.writeShellScriptBin "bora-desktop-finalize" (builtins.readFile ./../../../../scripts/maclike/finalize.sh); -in { +in +{ options.bora.desktop.layout = { enable = mkEnableOption "Bora custom desktop layout"; theme = mkOption { @@ -31,16 +32,24 @@ in { config = mkIf cfg.enable { environment.systemPackages = with pkgs; [ - plasma6 kdePackages.plasma-workspace kdePackages.kwin - kdePackages.konsole kdePackages.systemsettings - kdePackages.dolphin kdePackages.kate - kdePackages.qqc2-desktop-style kdePackages.qqc2-breeze-style - kdePackages.breeze-icons kdePackages.breeze-gtk - kdePackages.breeze-qt5 kdePackages.plasma-integration + plasma6 + kdePackages.plasma-workspace + kdePackages.kwin + kdePackages.konsole + kdePackages.systemsettings + kdePackages.dolphin + kdePackages.kate + kdePackages.qqc2-desktop-style + kdePackages.qqc2-breeze-style + kdePackages.breeze-icons + kdePackages.breeze-gtk + kdePackages.breeze-qt5 + kdePackages.plasma-integration tela-circle-icon-theme (kdePackages.plasma6.pkgs.applet-window-buttons or kdePackages.applet-window-buttons) (kdePackages.plasma6.pkgs.applet-window-title or kdePackages.applet-window-title) - initScript finalizeScript + initScript + finalizeScript ]; environment.sessionVariables = { @@ -83,14 +92,6 @@ in { EOF ''; - xdg.desktopEntries.bora-desktop = { - name = "Bora Desktop"; - exec = "startplasma-wayland"; - type = "Application"; - categories = [ "Desktop" "KDE" ]; - desktopName = "Plasma (Bora Layout)"; - }; - services.displayManager.sddm.wayland.enable = true; services.desktopManager.plasma6.enableQt5Integration = false; security.polkit.enable = true; diff --git a/src/modules/desktop/pipewire.nix b/src/modules/desktop/pipewire.nix index 149530e..ddb1f3b 100644 --- a/src/modules/desktop/pipewire.nix +++ b/src/modules/desktop/pipewire.nix @@ -2,34 +2,18 @@ with lib; let cfg = config.bora.desktop.audio; -in { +in +{ options.bora.desktop.audio = { enable = mkEnableOption "PipeWire audio system"; - lowLatency = mkOption { - type = types.bool; - default = false; - description = "Enable low-latency audio config"; - }; }; config = mkIf cfg.enable { services.pipewire = { enable = true; alsa.enable = true; pulse.enable = true; - jack.enable = cfg.lowLatency; wireplumber.enable = true; - extraConfig = mkIf cfg.lowLatency { - "10-low-latency" = { - context.properties = { - default.clock.rate = 48000; - default.clock.quantum = 64; - default.clock.min-quantum = 32; - default.clock.max-quantum = 256; - }; - }; - }; }; - security.rtkit.enable = cfg.lowLatency; users.groups.audio = { }; environment.systemPackages = with pkgs; [ pulsemixer diff --git a/src/modules/filesystem/default.nix b/src/modules/filesystem/default.nix index f09b40e..9382732 100644 --- a/src/modules/filesystem/default.nix +++ b/src/modules/filesystem/default.nix @@ -3,5 +3,6 @@ imports = [ ./zfs.nix ./impermanence.nix + ./disko.nix ]; } diff --git a/src/modules/filesystem/disko.nix b/src/modules/filesystem/disko.nix new file mode 100644 index 0000000..5612db9 --- /dev/null +++ b/src/modules/filesystem/disko.nix @@ -0,0 +1,58 @@ +{ config, lib, pkgs, ... }: +with lib; +let cfg = config.bora.filesystem.disko; in { + options.bora.filesystem.disko = { + enable = mkEnableOption "disko declarative partitioning"; + disk = mkOption { + type = types.str; + default = "/dev/nvme0n1"; + description = "Target disk device for partitioning"; + }; + zfsPool = mkOption { + type = types.str; + default = "zroot"; + description = "ZFS pool name"; + }; + }; + config = mkIf cfg.enable { + disko.devices = { + disk.main = { + type = "disk"; + device = cfg.disk; + content = { + type = "gpt"; + partitions = { + boot = { + size = "1G"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + }; + }; + zfs = { + size = "100%"; + content = { + type = "zfs"; + pool = cfg.zfsPool; + }; + }; + }; + }; + }; + zpool.${cfg.zfsPool} = { + type = "zpool"; + mode = ""; + datasets = { + "root" = { type = "zfs_fs"; mountpoint = "/"; }; + "root/nix" = { type = "zfs_fs"; mountpoint = "/nix"; }; + "root/home" = { type = "zfs_fs"; mountpoint = "/home"; }; + "root/persist" = { type = "zfs_fs"; mountpoint = "/persist"; }; + "root/var" = { type = "zfs_fs"; mountpoint = "/var"; }; + "root/tmp" = { type = "zfs_fs"; mountpoint = "/tmp"; }; + }; + }; + }; + }; +} diff --git a/src/modules/filesystem/impermanence.nix b/src/modules/filesystem/impermanence.nix index 8c4e5ac..1657650 100644 --- a/src/modules/filesystem/impermanence.nix +++ b/src/modules/filesystem/impermanence.nix @@ -50,7 +50,6 @@ ]; }; }; - }; fileSystems."/persist" = { device = "zroot/root/persist"; fsType = "zfs"; diff --git a/src/modules/filesystem/zfs.nix b/src/modules/filesystem/zfs.nix index 7849ca4..a63207f 100644 --- a/src/modules/filesystem/zfs.nix +++ b/src/modules/filesystem/zfs.nix @@ -1,64 +1,65 @@ { config, lib, pkgs, ... }: -{ - services.zfs = { - trim = { - enable = true; - interval = "weekly"; - }; - autoScrub = { - enable = true; - interval = "monthly"; - }; - autoSnapshot = { - enable = true; - flags = "-k -p --utc"; - }; - zed = { - enable = true; - settings = { - ZED_EMAIL_ADDR = ""; - ZED_NOTIFY_INTERVAL_SECS = 3600; - }; - }; +with lib; +let cfg = config.bora.filesystem; in { + options.bora.filesystem.bootDevice = mkOption { + type = types.str; + default = "/dev/disk/by-uuid/BOOT-UUID"; + description = "Boot partition device path"; }; - boot.zfs = { - forceImportRoot = false; - forceImportAll = false; - allowHibernation = false; - requestEncryptionCredentials = true; - }; - services.sanoid = { - enable = true; - templates = { - default = { - hourly = 24; - daily = 30; - weekly = 12; - monthly = 6; - yearly = 2; - autosnap = true; - autoprune = true; + config = { + services.zfs = { + trim = { + enable = true; + interval = "weekly"; + }; + autoScrub = { + enable = true; + interval = "monthly"; }; - critical = { - hourly = 48; - daily = 90; - weekly = 24; - monthly = 12; - yearly = 5; - autosnap = true; - autoprune = true; + autoSnapshot = { + enable = true; + flags = "-k -p --utc"; }; - ephemeral = { - hourly = 2; - daily = 1; - autosnap = true; - autoprune = true; + }; + boot.zfs = { + forceImportRoot = false; + forceImportAll = false; + allowHibernation = false; + requestEncryptionCredentials = true; + }; + services.sanoid = { + enable = true; + templates = { + default = { + hourly = 24; + daily = 30; + weekly = 12; + monthly = 6; + yearly = 2; + autosnap = true; + autoprune = true; + }; + critical = { + hourly = 48; + daily = 90; + weekly = 24; + monthly = 12; + yearly = 5; + autosnap = true; + autoprune = true; + }; + ephemeral = { + hourly = 2; + daily = 1; + autosnap = true; + autoprune = true; + }; }; }; + boot.kernelParams = [ + "zfs.zfs_arc_max=8589934592" + "zfs.zfs_arc_min=1073741824" + ]; + networking.hostId = "deadbeef"; }; - boot.kernelParams = [ - "zfs.zfs_arc_max=8589934592" - "zfs.zfs_arc_min=1073741824" - ]; - networking.hostId = "deadbeef"; } diff --git a/src/modules/hardware/cpu.nix b/src/modules/hardware/cpu.nix index 610fe0b..2194f2d 100644 --- a/src/modules/hardware/cpu.nix +++ b/src/modules/hardware/cpu.nix @@ -1,10 +1,10 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, hardwareDB, ... }: with lib; let - hwLib = import ../../../../lib/hardware.nix { inherit lib; }; cpuVendor = config.bora.hardware.cpuVendor or "intel"; - cpuCfg = hwLib.cpu.${cpuVendor} or hwLib.cpu.intel; -in { + cpuCfg = hardwareDB.cpu.${cpuVendor} or hardwareDB.cpu.intel; +in +{ options.bora.hardware = { cpuVendor = mkOption { type = types.enum [ "intel" "amd" "arm" ]; @@ -23,8 +23,8 @@ in { boot.kernelModules = cpuCfg.kernelModules; boot.kernelParams = cpuCfg.kernelParams ++ (if config.bora.hardware.enableMitigations - then [ "mitigations=auto" ] - else [ "mitigations=off" ]); - powerManagement.cpuFreqGovernor = cpuCfg.power.governor; + then [ "mitigations=auto" ] + else [ "mitigations=off" ]); + powerManagement.cpuFreqGovernor = mkDefault cpuCfg.power.governor; }; } diff --git a/src/modules/hardware/default.nix b/src/modules/hardware/default.nix index e33e887..05e0157 100644 --- a/src/modules/hardware/default.nix +++ b/src/modules/hardware/default.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, hardwareDB, ... }: { imports = [ ./cpu.nix diff --git a/src/modules/hardware/gpu.nix b/src/modules/hardware/gpu.nix index 5350d21..8e95fba 100644 --- a/src/modules/hardware/gpu.nix +++ b/src/modules/hardware/gpu.nix @@ -1,10 +1,10 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, hardwareDB, ... }: with lib; let - hwLib = import ../../../../lib/hardware.nix { inherit lib; }; gpuVendor = config.bora.hardware.gpuVendor or "amd"; - gpuCfg = hwLib.gpu.${gpuVendor} or hwLib.gpu.amd; -in { + gpuCfg = hardwareDB.gpu.${gpuVendor} or hardwareDB.gpu.amd; +in +{ options.bora.hardware = { gpuVendor = mkOption { type = types.enum [ "nvidia" "amd" "intel" ]; @@ -40,8 +40,8 @@ in { enable32Bit = true; extraPackages = with pkgs; [ (if gpuVendor == "nvidia" then vaapiVdpau - else if gpuVendor == "intel" then intel-media-driver - else vaapiVdpau) + else if gpuVendor == "intel" then intel-media-driver + else vaapiVdpau) ]; }; }; diff --git a/src/modules/hardware/platform.nix b/src/modules/hardware/platform.nix index 29fd881..384d7eb 100644 --- a/src/modules/hardware/platform.nix +++ b/src/modules/hardware/platform.nix @@ -1,14 +1,17 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, hardwareDB, ... }: with lib; let - hwLib = import ../../../../lib/hardware.nix { inherit lib; }; - hwProfile = config.bora.hardwareProfile or "desktop"; - profileCfg = hwLib.profileOpts.${hwProfile} or hwLib.profileOpts.desktop; -in { + profileOpts = hardwareDB.profileOpts; +in +{ options.bora.hardwareProfile = mkOption { type = types.enum [ "desktop" "laptop" "server" ]; default = "desktop"; description = "Hardware profile for power/mitigation tuning"; }; - config = profileCfg; + config = mkMerge [ + (mkIf (config.bora.hardwareProfile == "desktop") profileOpts.desktop) + (mkIf (config.bora.hardwareProfile == "laptop") profileOpts.laptop) + (mkIf (config.bora.hardwareProfile == "server") profileOpts.server) + ]; } diff --git a/src/modules/security/firewall.nix b/src/modules/security/firewall.nix index 8af8cab..aeaab11 100644 --- a/src/modules/security/firewall.nix +++ b/src/modules/security/firewall.nix @@ -13,22 +13,25 @@ echo-request, destination-unreachable, time-exceeded, parameter-problem } limit rate 10/second accept; - ip6 icmpv6 type { + meta l4proto ipv6-icmp icmpv6 type { echo-request, destination-unreachable, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-router-solicit } limit rate 10/second accept; - tcp dport 22 ip saddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } accept; - udp dport 5353 ip saddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } accept; + tcp dport 22 ip saddr { + 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 + } accept; + udp dport 5353 ip saddr { + 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 + } accept; udp dport { 67, 68 } accept; log prefix "NF:DROP-INPUT: " drop; } chain forward { type filter hook forward priority 0; policy drop; ct state { established, related } accept; - iifname "microvm" oifname "microvm" accept; - iifname "microvm" oifname "eth0" masquerade accept; + iifname "microvm" accept; log prefix "NF:DROP-FORWARD: " drop; } chain output { diff --git a/src/modules/security/ssh.nix b/src/modules/security/ssh.nix index 17a7233..b383f75 100644 --- a/src/modules/security/ssh.nix +++ b/src/modules/security/ssh.nix @@ -7,7 +7,7 @@ PasswordAuthentication = false; KbdInteractiveAuthentication = false; AuthenticationMethods = "publickey"; - PubkeyAuthentication = yes; + PubkeyAuthentication = true; UsePAM = false; MaxAuthTries = 3; MaxSessions = 4; @@ -19,9 +19,9 @@ X11Forwarding = false; ClientAliveInterval = 300; ClientAliveCountMax = 0; - Ciphers = "chacha20-poly1305@openssh.com,aes256-gcm@openssh.com"; - KexAlgorithms = "curve25519-sha256,diffie-hellman-group-exchange-sha256"; - Macs = "hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com"; + Ciphers = [ "chacha20-poly1305@openssh.com" "aes256-gcm@openssh.com" ]; + KexAlgorithms = [ "curve25519-sha256" "diffie-hellman-group-exchange-sha256" ]; + Macs = [ "hmac-sha2-512-etm@openssh.com" "hmac-sha2-256-etm@openssh.com" ]; HostKeyAlgorithms = "ssh-ed25519,rsa-sha2-512"; Compression = "no"; }; diff --git a/src/profiles/developer.nix b/src/profiles/developer.nix index 9b6cee4..3a062ca 100644 --- a/src/profiles/developer.nix +++ b/src/profiles/developer.nix @@ -13,7 +13,13 @@ with lib; }; environment.systemPackages = with pkgs; [ - gcc clang nodejs python3 rustc cargo go + gcc + clang + nodejs + python3 + rustc + cargo + go nginx ]; } From f30022ce1e7bd92518205f2ab814dba821f0763a Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 21:28:31 +0200 Subject: [PATCH 08/38] fix: resolve devShell buildInputs type error in CI --- flake.nix | 53 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/flake.nix b/flake.nix index fdb8901..0efe211 100644 --- a/flake.nix +++ b/flake.nix @@ -19,10 +19,19 @@ home-manager.url = "github:nix-community/home-manager"; }; - outputs = { self, nixpkgs, nixpkgs-unstable, nixos-hardware - , nixos-generators, impermanence, microvm, disko - , sops-nix, home-manager, ... - }: + outputs = + { self + , nixpkgs + , nixpkgs-unstable + , nixos-hardware + , nixos-generators + , impermanence + , microvm + , disko + , sops-nix + , home-manager + , ... + }: let systems = [ "x86_64-linux" "aarch64-linux" ]; forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); @@ -55,24 +64,30 @@ ] ++ optional (hostConfig ? extraModules) hostConfig.extraModules; }; - hosts = builtins.foldl' (acc: hostname: - let - hostConfig = - if builtins.pathExists (hostsDir + "/${hostname}/meta.nix") - then import (hostsDir + "/${hostname}/meta.nix") - else { }; - in acc // { - ${hostname} = mkHost hostname hostConfig; - } - ) { } availableHosts; + hosts = builtins.foldl' + (acc: hostname: + let + hostConfig = + if builtins.pathExists (hostsDir + "/${hostname}/meta.nix") + then import (hostsDir + "/${hostname}/meta.nix") + else { }; + in + acc // { + ${hostname} = mkHost hostname hostConfig; + } + ) + { } + availableHosts; - in { + in + { nixosConfigurations = hosts; packages = forAllSystems (system: let pkgs = import nixpkgs { inherit system; config.allowUnfree = true; }; - in { + in + { iso-minimal = nixos-generators.nixosGenerate { inherit system; specialArgs = { @@ -132,9 +147,11 @@ let pkgs = import nixpkgs { inherit system; config.allowUnfree = true; }; in { default = pkgs.mkShell { + name = "bora-dev-shell"; buildInputs = with pkgs; [ - nixos-generators nixos-anywhere - nixpkgs-fmt statix deadnix comma + nixpkgs-fmt + statix + deadnix ]; }; }); From 1e6e7f56e100ae13528192268ac4ef87dd79e257 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 21:32:49 +0200 Subject: [PATCH 09/38] fix: resolve statix warnings and disable FlakeHub cache in CI --- .github/workflows/ci.yml | 6 ++ .github/workflows/release.yml | 6 ++ src/guests/example/instance.nix | 38 ++++---- src/hosts/bora/hardware.nix | 26 +++--- src/modules/containers/instance-pool.nix | 106 ++++++++++++----------- src/modules/core/boot.nix | 66 +++++++------- src/modules/hardware/platform.nix | 2 +- src/modules/security/hardening.nix | 56 ++++++------ src/profiles/server.nix | 8 +- src/profiles/workstation.nix | 8 +- 10 files changed, 178 insertions(+), 144 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b06d52..33d1a42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,8 @@ jobs: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v14 - uses: DeterminateSystems/magic-nix-cache-action@v8 + with: + use-flakehub: false - name: Lint Nix run: | nix develop --impure --command statix check src @@ -36,6 +38,8 @@ jobs: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v14 - uses: DeterminateSystems/magic-nix-cache-action@v8 + with: + use-flakehub: false - name: Build minimal ISO run: | NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ @@ -54,6 +58,8 @@ jobs: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v14 - uses: DeterminateSystems/magic-nix-cache-action@v8 + with: + use-flakehub: false - name: Build graphical ISO run: | NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0441092..6b00442 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,8 @@ jobs: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v14 - uses: DeterminateSystems/magic-nix-cache-action@v8 + with: + use-flakehub: false - name: Lint Nix run: | nix develop --impure --command statix check src @@ -27,6 +29,8 @@ jobs: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v14 - uses: DeterminateSystems/magic-nix-cache-action@v8 + with: + use-flakehub: false - name: Build minimal ISO run: | NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ @@ -43,6 +47,8 @@ jobs: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v14 - uses: DeterminateSystems/magic-nix-cache-action@v8 + with: + use-flakehub: false - name: Build graphical ISO run: | NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ diff --git a/src/guests/example/instance.nix b/src/guests/example/instance.nix index dffaad7..1c650f8 100644 --- a/src/guests/example/instance.nix +++ b/src/guests/example/instance.nix @@ -2,26 +2,24 @@ { imports = [ "${modulesPath}/profiles/minimal.nix" ]; - microvm.guest.enable = true; - - microvm.interfaces = [{ - type = "bridge"; - host = "microvm"; - }]; - - microvm.shares = [{ - source = "/var/lib/instance-pool/workspaces"; - mountPoint = "/workspace"; - type = "virtiofs"; - }]; - - microvm.sockets = [ - "/tmp/.X11-unix/X0" - "/run/user/1000/wayland-0" - ]; - - microvm.mem = 256; - microvm.vcpu = 1; + microvm = { + guest.enable = true; + interfaces = [{ + type = "bridge"; + host = "microvm"; + }]; + shares = [{ + source = "/var/lib/instance-pool/workspaces"; + mountPoint = "/workspace"; + type = "virtiofs"; + }]; + sockets = [ + "/tmp/.X11-unix/X0" + "/run/user/1000/wayland-0" + ]; + mem = 256; + vcpu = 1; + }; system.stateVersion = "24.11"; } diff --git a/src/hosts/bora/hardware.nix b/src/hosts/bora/hardware.nix index 5128b37..b5b25e0 100644 --- a/src/hosts/bora/hardware.nix +++ b/src/hosts/bora/hardware.nix @@ -6,17 +6,23 @@ in { imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; - boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usb_storage" "sd_mod" ]; - boot.initrd.kernelModules = [ "zfs" ]; - boot.kernelModules = [ "kvm-amd" "kvm-intel" ]; - boot.extraModulePackages = [ ]; + boot = { + initrd = { + availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usb_storage" "sd_mod" ]; + kernelModules = [ "zfs" ]; + }; + kernelModules = [ "kvm-amd" "kvm-intel" ]; + extraModulePackages = [ ]; + }; - fileSystems."/" = { device = "zroot/root"; fsType = "zfs"; }; - fileSystems."/nix" = { device = "zroot/root/nix"; fsType = "zfs"; }; - fileSystems."/home" = { device = "zroot/root/home"; fsType = "zfs"; }; - fileSystems."/var" = { device = "zroot/root/var"; fsType = "zfs"; }; - fileSystems."/tmp" = { device = "zroot/root/tmp"; fsType = "zfs"; }; - fileSystems."/boot" = { device = fsCfg.bootDevice; fsType = "vfat"; }; + fileSystems = { + "/" = { device = "zroot/root"; fsType = "zfs"; }; + "/nix" = { device = "zroot/root/nix"; fsType = "zfs"; }; + "/home" = { device = "zroot/root/home"; fsType = "zfs"; }; + "/var" = { device = "zroot/root/var"; fsType = "zfs"; }; + "/tmp" = { device = "zroot/root/tmp"; fsType = "zfs"; }; + "/boot" = { device = fsCfg.bootDevice; fsType = "vfat"; }; + }; swapDevices = [ ]; diff --git a/src/modules/containers/instance-pool.nix b/src/modules/containers/instance-pool.nix index 82227c7..230f893 100644 --- a/src/modules/containers/instance-pool.nix +++ b/src/modules/containers/instance-pool.nix @@ -49,60 +49,62 @@ in }; }; config = mkIf cfg.enable { - systemd.services.create-pool-zfs = { - description = "Create ZFS dataset for instance pool"; - before = [ "bora-pool.service" ]; - wantedBy = [ "multi-user.target" ]; - path = with pkgs; [ zfs ]; - script = '' - zfs create -o mountpoint=/var/lib/instance-pool \ - -o atime=off -o compression=zstd-3 \ - -o quota=${toString (cfg.maxInstances * 2)}G \ - zroot/root/instance-pool 2>/dev/null || true - ''; - }; - systemd.services.bora-cgroup-pool = { - description = "Instance pool cgroup v2 hierarchy"; - before = [ "bora-pool.service" ]; - wantedBy = [ "multi-user.target" ]; - script = '' - CG="/sys/fs/cgroup/bora/pool" - mkdir -p "$CG" - echo ${cfg.memPerInstance} > "$CG/memory.max" - echo ${cfg.memPerInstance} > "$CG/memory.high" - echo 100000 > "$CG/cpu.max" - echo ${cfg.cpuPerInstance}0000 > "$CG/cpu.max" - echo 512 > "$CG/pids.max" - echo "8:0 ${cfg.storagePerInstance}" > "$CG/io.max" - ''; - }; - systemd.services.bora-pool = { - description = "MicroVM Instance Pool"; - after = [ "network.target" "microvm-host.service" "create-pool-zfs.service" ]; - wants = [ "microvm-host.service" ]; - wantedBy = [ "multi-user.target" ]; - path = with pkgs; [ microvm coreutils bash curl ]; - environment = { - POOL_DIR = "/var/lib/instance-pool"; - BASE_PORT = toString cfg.basePort; - MAX_INSTANCES = toString cfg.maxInstances; - MEM_LIMIT = cfg.memPerInstance; - CPU_LIMIT = cfg.cpuPerInstance; - APP_COMMAND = cfg.appCommand or ""; - HEALTHCHECK_CMD = cfg.healthcheckCmd or ""; + systemd.services = { + create-pool-zfs = { + description = "Create ZFS dataset for instance pool"; + before = [ "bora-pool.service" ]; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ zfs ]; + script = '' + zfs create -o mountpoint=/var/lib/instance-pool \ + -o atime=off -o compression=zstd-3 \ + -o quota=${toString (cfg.maxInstances * 2)}G \ + zroot/root/instance-pool 2>/dev/null || true + ''; }; - serviceConfig = { - Type = "notify"; - Restart = "always"; - RestartSec = 5; - StateDirectory = "instance-pool"; - NotifyAccess = "all"; - LimitNOFILE = 1048576; - LimitNPROC = 1048576; + bora-cgroup-pool = { + description = "Instance pool cgroup v2 hierarchy"; + before = [ "bora-pool.service" ]; + wantedBy = [ "multi-user.target" ]; + script = '' + CG="/sys/fs/cgroup/bora/pool" + mkdir -p "$CG" + echo ${cfg.memPerInstance} > "$CG/memory.max" + echo ${cfg.memPerInstance} > "$CG/memory.high" + echo 100000 > "$CG/cpu.max" + echo ${cfg.cpuPerInstance}0000 > "$CG/cpu.max" + echo 512 > "$CG/pids.max" + echo "8:0 ${cfg.storagePerInstance}" > "$CG/io.max" + ''; + }; + bora-pool = { + description = "MicroVM Instance Pool"; + after = [ "network.target" "microvm-host.service" "create-pool-zfs.service" ]; + wants = [ "microvm-host.service" ]; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ microvm coreutils bash curl ]; + environment = { + POOL_DIR = "/var/lib/instance-pool"; + BASE_PORT = toString cfg.basePort; + MAX_INSTANCES = toString cfg.maxInstances; + MEM_LIMIT = cfg.memPerInstance; + CPU_LIMIT = cfg.cpuPerInstance; + APP_COMMAND = cfg.appCommand or ""; + HEALTHCHECK_CMD = cfg.healthcheckCmd or ""; + }; + serviceConfig = { + Type = "notify"; + Restart = "always"; + RestartSec = 5; + StateDirectory = "instance-pool"; + NotifyAccess = "all"; + LimitNOFILE = 1048576; + LimitNPROC = 1048576; + }; + script = '' + ${builtins.readFile ./../../../../scripts/pool/pool-manager.sh} + ''; }; - script = '' - ${builtins.readFile ./../../../../scripts/pool/pool-manager.sh} - ''; }; services.caddy = { enable = true; diff --git a/src/modules/core/boot.nix b/src/modules/core/boot.nix index c870c18..c061f76 100644 --- a/src/modules/core/boot.nix +++ b/src/modules/core/boot.nix @@ -1,34 +1,42 @@ { config, lib, pkgs, ... }: { - boot.loader = { - systemd-boot = { - enable = true; - configurationLimit = 20; + boot = { + loader = { + systemd-boot = { + enable = true; + configurationLimit = 20; + }; + efi.canTouchEfiVariables = true; + timeout = lib.mkDefault 3; + }; + kernelParams = [ + "quiet" + "splash" + "mitigations=auto" + "random.trust_cpu=off" + "random.trust_bootloader=off" + "slab_nomerge" + "init_on_alloc=1" + "init_on_free=1" + "page_alloc.shuffle=1" + "pti=on" + "vsyscall=none" + "debugfs=off" + ]; + consoleLogLevel = 0; + initrd = { + verbose = false; + systemd.enable = true; + }; + supportedFilesystems = [ "zfs" "btrfs" "ntfs" "exfat" "vfat" "xfs" ]; + kernelPackages = lib.mkDefault pkgs.linuxPackages_latest; + }; + hardware = { + enableRedistributableFirmware = true; + enableAllFirmware = true; + cpu = { + intel.updateMicrocode = lib.mkDefault true; + amd.updateMicrocode = lib.mkDefault true; }; - efi.canTouchEfiVariables = true; - timeout = lib.mkDefault 3; }; - boot.kernelParams = [ - "quiet" - "splash" - "mitigations=auto" - "random.trust_cpu=off" - "random.trust_bootloader=off" - "slab_nomerge" - "init_on_alloc=1" - "init_on_free=1" - "page_alloc.shuffle=1" - "pti=on" - "vsyscall=none" - "debugfs=off" - ]; - boot.consoleLogLevel = 0; - boot.initrd.verbose = false; - boot.initrd.systemd.enable = true; - boot.supportedFilesystems = [ "zfs" "btrfs" "ntfs" "exfat" "vfat" "xfs" ]; - boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest; - hardware.enableRedistributableFirmware = true; - hardware.enableAllFirmware = true; - hardware.cpu.intel.updateMicrocode = lib.mkDefault true; - hardware.cpu.amd.updateMicrocode = lib.mkDefault true; } diff --git a/src/modules/hardware/platform.nix b/src/modules/hardware/platform.nix index 384d7eb..7ed633c 100644 --- a/src/modules/hardware/platform.nix +++ b/src/modules/hardware/platform.nix @@ -1,7 +1,7 @@ { config, lib, pkgs, hardwareDB, ... }: with lib; let - profileOpts = hardwareDB.profileOpts; + inherit (hardwareDB) profileOpts; in { options.bora.hardwareProfile = mkOption { diff --git a/src/modules/security/hardening.nix b/src/modules/security/hardening.nix index 1b82ada..d48f146 100644 --- a/src/modules/security/hardening.nix +++ b/src/modules/security/hardening.nix @@ -1,14 +1,25 @@ { config, lib, pkgs, ... }: { - security.apparmor = { - enable = true; - enableCache = true; - packages = [ pkgs.apparmor-profiles ]; + security = { + apparmor = { + enable = true; + enableCache = true; + packages = [ pkgs.apparmor-profiles ]; + }; + protectKernelImage = true; + allowUserNamespaces = true; + lockKernelModules = false; + wrappers = { }; + audit = { + enable = true; + rules = [ + "-w /etc/nixos -p wa -k nixos-config" + "-w /nix/store -p wa -k nix-store" + "-a exit,always -S execve -k process-exec" + "-a exit,always -S mount -k mount" + ]; + }; }; - security.protectKernelImage = true; - security.allowUserNamespaces = true; - security.lockKernelModules = false; - security.wrappers = { }; boot.kernelParams = [ "quiet" "slab_nomerge" @@ -21,23 +32,16 @@ "module.sig_enforce=1" "lockdown=confidentiality" ]; - systemd.services = { - avahi-daemon.enable = lib.mkDefault false; - cups.enable = lib.mkDefault false; - bluetooth.enable = lib.mkDefault false; - }; - systemd.extraConfig = '' - DefaultTimeoutStopSec=10s - DefaultTimeoutStartSec=30s - DefaultDeviceTimeoutSec=30s - ''; - security.audit = { - enable = true; - rules = [ - "-w /etc/nixos -p wa -k nixos-config" - "-w /nix/store -p wa -k nix-store" - "-a exit,always -S execve -k process-exec" - "-a exit,always -S mount -k mount" - ]; + systemd = { + services = { + avahi-daemon.enable = lib.mkDefault false; + cups.enable = lib.mkDefault false; + bluetooth.enable = lib.mkDefault false; + }; + extraConfig = '' + DefaultTimeoutStopSec=10s + DefaultTimeoutStartSec=30s + DefaultDeviceTimeoutSec=30s + ''; }; } diff --git a/src/profiles/server.nix b/src/profiles/server.nix index fd35a09..a7c0337 100644 --- a/src/profiles/server.nix +++ b/src/profiles/server.nix @@ -2,9 +2,11 @@ with lib; { bora = { - desktop.kde.enable = mkDefault false; - desktop.audio.enable = mkDefault false; - desktop.layout.enable = mkDefault false; + desktop = { + kde.enable = mkDefault false; + audio.enable = mkDefault false; + layout.enable = mkDefault false; + }; containers.instancePool.enable = mkDefault false; hardwareProfile = mkDefault "server"; hardware.cpuVendor = mkDefault "intel"; diff --git a/src/profiles/workstation.nix b/src/profiles/workstation.nix index 9ddbe1c..af8b09e 100644 --- a/src/profiles/workstation.nix +++ b/src/profiles/workstation.nix @@ -2,9 +2,11 @@ with lib; { bora = { - desktop.kde.enable = mkDefault true; - desktop.audio.enable = mkDefault true; - desktop.layout.enable = mkDefault true; + desktop = { + kde.enable = mkDefault true; + audio.enable = mkDefault true; + layout.enable = mkDefault true; + }; hardware.cpuVendor = mkDefault "intel"; hardware.gpuVendor = mkDefault "amd"; hardwareProfile = mkDefault "desktop"; From 5667e58ba7a5d24b580600cfddb94863b8309d36 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 21:39:47 +0200 Subject: [PATCH 10/38] feat: update stateVersion to 25.11, add CI build and test jobs, add gitignore --- .github/workflows/ci.yml | 26 ++++++++++++++++++++++++++ .gitignore | 2 ++ configuration.nix | 2 +- flake.nix | 4 ++-- src/guests/example/instance.nix | 2 +- src/guests/sandbox.nix | 2 +- 6 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 .gitignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33d1a42..4e6ebc0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,32 @@ jobs: nix develop --impure --command deadnix src nix develop --impure --command nixpkgs-fmt --check src + eval-tests: + runs-on: ubuntu-latest + needs: [lint] + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v14 + - uses: DeterminateSystems/magic-nix-cache-action@v8 + with: + use-flakehub: false + - name: Evaluate tests + run: | + nix-instantiate --eval tests/default.nix + + build-system: + runs-on: ubuntu-latest + needs: [lint] + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v14 + - uses: DeterminateSystems/magic-nix-cache-action@v8 + with: + use-flakehub: false + - name: Build NixOS configuration + run: | + nix build --impure '.#nixosConfigurations.bora.config.system.build.toplevel' + build-iso-minimal: runs-on: ubuntu-latest needs: [lint] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a7dc09 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +result +dist/ diff --git a/configuration.nix b/configuration.nix index 60fb4a5..607b989 100644 --- a/configuration.nix +++ b/configuration.nix @@ -24,5 +24,5 @@ in { ++ optional (pathExists profilePath) profilePath; nixpkgs.config.allowUnfree = mkDefault true; - system.stateVersion = mkDefault "24.11"; + system.stateVersion = mkDefault "25.11"; } diff --git a/flake.nix b/flake.nix index 0efe211..97429bf 100644 --- a/flake.nix +++ b/flake.nix @@ -107,7 +107,7 @@ boot.supportedFilesystems = [ "zfs" "vfat" "xfs" ]; boot.kernelPackages = pkgs.linuxPackages_6_6; nixpkgs.config.allowUnfree = true; - system.stateVersion = "24.11"; + system.stateVersion = "25.11"; users.users.bora = { isNormalUser = true; }; }) ]; @@ -135,7 +135,7 @@ boot.kernelPackages = pkgs.linuxPackages_6_6; nixpkgs.config.allowUnfree = true; services.xserver.enable = true; - system.stateVersion = "24.11"; + system.stateVersion = "25.11"; users.users.bora = { isNormalUser = true; }; }) ]; diff --git a/src/guests/example/instance.nix b/src/guests/example/instance.nix index 1c650f8..320a406 100644 --- a/src/guests/example/instance.nix +++ b/src/guests/example/instance.nix @@ -21,5 +21,5 @@ vcpu = 1; }; - system.stateVersion = "24.11"; + system.stateVersion = "25.11"; } diff --git a/src/guests/sandbox.nix b/src/guests/sandbox.nix index 6d14d9c..0ffc1ae 100644 --- a/src/guests/sandbox.nix +++ b/src/guests/sandbox.nix @@ -32,5 +32,5 @@ chromium ]; - system.stateVersion = "24.11"; + system.stateVersion = "25.11"; } From 32bb0bd44ffb345765743085811357256986a2ab Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 21:43:03 +0200 Subject: [PATCH 11/38] fix: resolve remaining statix W04 and W20 warnings --- src/guests/sandbox.nix | 42 ++++++----- src/modules/containers/microvm-host.nix | 2 +- src/modules/desktop/maclike.nix | 94 ++++++++++++------------- src/profiles/minimal.nix | 14 ++-- 4 files changed, 77 insertions(+), 75 deletions(-) diff --git a/src/guests/sandbox.nix b/src/guests/sandbox.nix index 0ffc1ae..0caa5af 100644 --- a/src/guests/sandbox.nix +++ b/src/guests/sandbox.nix @@ -2,28 +2,26 @@ { imports = [ "${modulesPath}/profiles/minimal.nix" ]; - microvm.guest.enable = true; - - microvm.interfaces = [{ - type = "bridge"; - host = "microvm"; - }]; - - microvm.shares = [{ - source = "/home"; - mountPoint = "/mnt/home"; - type = "virtiofs"; - }]; - - microvm.sockets = [ - "/tmp/.X11-unix/X0" - "/run/user/1000/wayland-0" - "/run/user/1000/pipewire-0" - "/run/user/1000/pulse" - ]; - - microvm.mem = 2048; - microvm.vcpu = 2; + microvm = { + guest.enable = true; + interfaces = [{ + type = "bridge"; + host = "microvm"; + }]; + shares = [{ + source = "/home"; + mountPoint = "/mnt/home"; + type = "virtiofs"; + }]; + sockets = [ + "/tmp/.X11-unix/X0" + "/run/user/1000/wayland-0" + "/run/user/1000/pipewire-0" + "/run/user/1000/pulse" + ]; + mem = 2048; + vcpu = 2; + }; services.pipewire.enable = true; diff --git a/src/modules/containers/microvm-host.nix b/src/modules/containers/microvm-host.nix index 28acaa8..1a002d7 100644 --- a/src/modules/containers/microvm-host.nix +++ b/src/modules/containers/microvm-host.nix @@ -12,7 +12,7 @@ let cfg = config.bora.containers.microvm; in { config = mkIf cfg.enable { microvm = { host.enable = true; - stateDir = cfg.stateDir; + inherit (cfg) stateDir; }; fileSystems.${cfg.stateDir} = { device = "zroot/root/microvm"; diff --git a/src/modules/desktop/maclike.nix b/src/modules/desktop/maclike.nix index 52d4ef8..e74e96d 100644 --- a/src/modules/desktop/maclike.nix +++ b/src/modules/desktop/maclike.nix @@ -31,55 +31,55 @@ in }; config = mkIf cfg.enable { - environment.systemPackages = with pkgs; [ - plasma6 - kdePackages.plasma-workspace - kdePackages.kwin - kdePackages.konsole - kdePackages.systemsettings - kdePackages.dolphin - kdePackages.kate - kdePackages.qqc2-desktop-style - kdePackages.qqc2-breeze-style - kdePackages.breeze-icons - kdePackages.breeze-gtk - kdePackages.breeze-qt5 - kdePackages.plasma-integration - tela-circle-icon-theme - (kdePackages.plasma6.pkgs.applet-window-buttons or kdePackages.applet-window-buttons) - (kdePackages.plasma6.pkgs.applet-window-title or kdePackages.applet-window-title) - initScript - finalizeScript - ]; - - environment.sessionVariables = { - KDE_SESSION_VERSION = "6"; - XDG_CURRENT_DESKTOP = "KDE"; - XDG_SESSION_DESKTOP = "KDE"; - DESKTOP_SESSION = "plasmawayland"; - PLASMA_USE_QT_SCALING = "1"; - QT_AUTO_SCREEN_SET_FACTOR = "0"; - QT_SCALE_FACTOR_ROUNDING_POLICY = "RoundPreferFloor"; + environment = { + systemPackages = with pkgs; [ + plasma6 + kdePackages.plasma-workspace + kdePackages.kwin + kdePackages.konsole + kdePackages.systemsettings + kdePackages.dolphin + kdePackages.kate + kdePackages.qqc2-desktop-style + kdePackages.qqc2-breeze-style + kdePackages.breeze-icons + kdePackages.breeze-gtk + kdePackages.breeze-qt5 + kdePackages.plasma-integration + tela-circle-icon-theme + (kdePackages.plasma6.pkgs.applet-window-buttons or kdePackages.applet-window-buttons) + (kdePackages.plasma6.pkgs.applet-window-title or kdePackages.applet-window-title) + initScript + finalizeScript + ]; + sessionVariables = { + KDE_SESSION_VERSION = "6"; + XDG_CURRENT_DESKTOP = "KDE"; + XDG_SESSION_DESKTOP = "KDE"; + DESKTOP_SESSION = "plasmawayland"; + PLASMA_USE_QT_SCALING = "1"; + QT_AUTO_SCREEN_SET_FACTOR = "0"; + QT_SCALE_FACTOR_ROUNDING_POLICY = "RoundPreferFloor"; + }; + etc."skel/.config/plasma-org.kde.plasma.desktop-appletsrc".source = + ./../../../../config/desktop/plasma-appletsrc; + etc."skel/.config/kdeglobals".source = + ./../../../../config/desktop/kdeglobals; + etc."skel/.config/kwinrc".source = + ./../../../../config/desktop/kwinrc; + etc."skel/.config/khotkeysrc".text = + builtins.replaceStrings [ "Alt+F1" ] [ cfg.nexusKey ] + (builtins.readFile ./../../../../config/desktop/khotkeysrc); + etc."skel/.config/plasmarc".text = '' + [Theme] + name=Breeze + [Wallpaper] + fillMode=2 + [PlasmaViews] + PanelOpacity=${if cfg.enableTransparency then "0.70" else "1.0"} + ''; }; - environment.etc."skel/.config/plasma-org.kde.plasma.desktop-appletsrc".source = - ./../../../../config/desktop/plasma-appletsrc; - environment.etc."skel/.config/kdeglobals".source = - ./../../../../config/desktop/kdeglobals; - environment.etc."skel/.config/kwinrc".source = - ./../../../../config/desktop/kwinrc; - environment.etc."skel/.config/khotkeysrc".text = - builtins.replaceStrings [ "Alt+F1" ] [ cfg.nexusKey ] - (builtins.readFile ./../../../../config/desktop/khotkeysrc); - environment.etc."skel/.config/plasmarc".text = '' - [Theme] - name=Breeze - [Wallpaper] - fillMode=2 - [PlasmaViews] - PanelOpacity=${if cfg.enableTransparency then "0.70" else "1.0"} - ''; - system.activationScripts.bora-desktop = stringAfter [ "etc" ] '' mkdir -p /etc/skel/.config/autostart cat > /etc/skel/.config/autostart/bora-desktop-setup.desktop << 'EOF' diff --git a/src/profiles/minimal.nix b/src/profiles/minimal.nix index 4c21be3..49d5e73 100644 --- a/src/profiles/minimal.nix +++ b/src/profiles/minimal.nix @@ -2,12 +2,16 @@ with lib; { bora = { - desktop.kde.enable = mkDefault false; - desktop.audio.enable = mkDefault false; - desktop.layout.enable = mkDefault false; + desktop = { + kde.enable = mkDefault false; + audio.enable = mkDefault false; + layout.enable = mkDefault false; + }; hardwareProfile = mkDefault "server"; - hardware.cpuVendor = mkDefault "intel"; - hardware.gpuVendor = mkDefault "intel"; + hardware = { + cpuVendor = mkDefault "intel"; + gpuVendor = mkDefault "intel"; + }; }; services.openssh.enable = true; From 2c030e1a0b4e92c7efefff938e4bbdeb3b0afed3 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 21:46:32 +0200 Subject: [PATCH 12/38] fix: consolidate etc attrs in maclike.nix to resolve W20 --- src/modules/desktop/maclike.nix | 36 +++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/modules/desktop/maclike.nix b/src/modules/desktop/maclike.nix index e74e96d..740f1cd 100644 --- a/src/modules/desktop/maclike.nix +++ b/src/modules/desktop/maclike.nix @@ -61,23 +61,25 @@ in QT_AUTO_SCREEN_SET_FACTOR = "0"; QT_SCALE_FACTOR_ROUNDING_POLICY = "RoundPreferFloor"; }; - etc."skel/.config/plasma-org.kde.plasma.desktop-appletsrc".source = - ./../../../../config/desktop/plasma-appletsrc; - etc."skel/.config/kdeglobals".source = - ./../../../../config/desktop/kdeglobals; - etc."skel/.config/kwinrc".source = - ./../../../../config/desktop/kwinrc; - etc."skel/.config/khotkeysrc".text = - builtins.replaceStrings [ "Alt+F1" ] [ cfg.nexusKey ] - (builtins.readFile ./../../../../config/desktop/khotkeysrc); - etc."skel/.config/plasmarc".text = '' - [Theme] - name=Breeze - [Wallpaper] - fillMode=2 - [PlasmaViews] - PanelOpacity=${if cfg.enableTransparency then "0.70" else "1.0"} - ''; + etc = { + "skel/.config/plasma-org.kde.plasma.desktop-appletsrc".source = + ./../../../../config/desktop/plasma-appletsrc; + "skel/.config/kdeglobals".source = + ./../../../../config/desktop/kdeglobals; + "skel/.config/kwinrc".source = + ./../../../../config/desktop/kwinrc; + "skel/.config/khotkeysrc".text = + builtins.replaceStrings [ "Alt+F1" ] [ cfg.nexusKey ] + (builtins.readFile ./../../../../config/desktop/khotkeysrc); + "skel/.config/plasmarc".text = '' + [Theme] + name=Breeze + [Wallpaper] + fillMode=2 + [PlasmaViews] + PanelOpacity=${if cfg.enableTransparency then "0.70" else "1.0"} + ''; + }; }; system.activationScripts.bora-desktop = stringAfter [ "etc" ] '' From 4bf7aa68fbe42f4f09d912c29b62fe16807e0538 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 21:59:13 +0200 Subject: [PATCH 13/38] fix: update KDE ISO module path and remove conflicting hardware defaults from workstation profile --- flake.nix | 4 ++-- src/profiles/workstation.nix | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index 97429bf..0d9970e 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "Bora NixOS — Modulare · Atomico · Universale · Strict-Hard — ALPHA v0.1.0"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixos-hardware.url = "github:NixOS/nixos-hardware"; nixos-generators = { @@ -127,7 +127,7 @@ impermanence.nixosModules.impermanence microvm.nixosModules.host disko.nixosModules.disko - "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix" + "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix" ./configuration.nix ({ pkgs, ... }: { isoImage.isoBaseName = "bora-desktop"; diff --git a/src/profiles/workstation.nix b/src/profiles/workstation.nix index af8b09e..744bd32 100644 --- a/src/profiles/workstation.nix +++ b/src/profiles/workstation.nix @@ -7,8 +7,6 @@ with lib; audio.enable = mkDefault true; layout.enable = mkDefault true; }; - hardware.cpuVendor = mkDefault "intel"; - hardware.gpuVendor = mkDefault "amd"; hardwareProfile = mkDefault "desktop"; }; } From 22460bf9f5d1c0721158d9f0b9aaed430851efda Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 22:05:31 +0200 Subject: [PATCH 14/38] fix: resolve platform/gpu prime conflict, use image.baseName for ISO, simplify CI --- .github/workflows/ci.yml | 32 +++----------------------------- flake.nix | 4 ++-- lib/hardware.nix | 1 - 3 files changed, 5 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e6ebc0..788ac25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: run: | nix build --impure '.#nixosConfigurations.bora.config.system.build.toplevel' - build-iso-minimal: + build-iso: runs-on: ubuntu-latest needs: [lint] steps: @@ -66,33 +66,7 @@ jobs: - uses: DeterminateSystems/magic-nix-cache-action@v8 with: use-flakehub: false - - name: Build minimal ISO + - name: Validate ISO builds run: | NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ - '.#packages.x86_64-linux.iso-minimal' - - name: Upload ISO artifact - uses: actions/upload-artifact@v4 - with: - name: bora-iso-minimal - path: result/iso/*.iso - retention-days: 7 - - build-iso-graphical: - runs-on: ubuntu-latest - needs: [lint] - steps: - - uses: actions/checkout@v4 - - uses: DeterminateSystems/nix-installer-action@v14 - - uses: DeterminateSystems/magic-nix-cache-action@v8 - with: - use-flakehub: false - - name: Build graphical ISO - run: | - NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ - '.#packages.x86_64-linux.iso-graphical' - - name: Upload ISO artifact - uses: actions/upload-artifact@v4 - with: - name: bora-iso-graphical - path: result/iso/*.iso - retention-days: 7 + '.#packages.x86_64-linux.iso-minimal' --no-link diff --git a/flake.nix b/flake.nix index 0d9970e..31c144a 100644 --- a/flake.nix +++ b/flake.nix @@ -103,7 +103,7 @@ disko.nixosModules.disko ./configuration.nix ({ pkgs, ... }: { - isoImage.isoBaseName = "bora"; + image.baseName = lib.mkDefault "bora"; boot.supportedFilesystems = [ "zfs" "vfat" "xfs" ]; boot.kernelPackages = pkgs.linuxPackages_6_6; nixpkgs.config.allowUnfree = true; @@ -130,7 +130,7 @@ "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix" ./configuration.nix ({ pkgs, ... }: { - isoImage.isoBaseName = "bora-desktop"; + image.baseName = lib.mkDefault "bora-desktop"; boot.supportedFilesystems = [ "zfs" "vfat" "xfs" ]; boot.kernelPackages = pkgs.linuxPackages_6_6; nixpkgs.config.allowUnfree = true; diff --git a/lib/hardware.nix b/lib/hardware.nix index c342c9e..0a27d24 100644 --- a/lib/hardware.nix +++ b/lib/hardware.nix @@ -88,7 +88,6 @@ in rec { powerManagement.enable = true; powerManagement.cpuFreqGovernor = "performance"; services.power-profiles-daemon.enable = false; - hardware.nvidia.prime.sync.enable = true; }; laptop = { powerManagement.enable = true; From 698072c8fa6dc6473dfaaf4b707e3d1988e638d6 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 22:09:03 +0200 Subject: [PATCH 15/38] fix: add lib to inline module args in flake.nix --- flake.nix | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index 31c144a..bed0cfd 100644 --- a/flake.nix +++ b/flake.nix @@ -102,14 +102,14 @@ microvm.nixosModules.host disko.nixosModules.disko ./configuration.nix - ({ pkgs, ... }: { + { pkgs, lib, ... }: { image.baseName = lib.mkDefault "bora"; boot.supportedFilesystems = [ "zfs" "vfat" "xfs" ]; boot.kernelPackages = pkgs.linuxPackages_6_6; nixpkgs.config.allowUnfree = true; system.stateVersion = "25.11"; users.users.bora = { isNormalUser = true; }; - }) + } ]; format = "iso"; }; @@ -129,7 +129,7 @@ disko.nixosModules.disko "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix" ./configuration.nix - ({ pkgs, ... }: { + { pkgs, lib, ... }: { image.baseName = lib.mkDefault "bora-desktop"; boot.supportedFilesystems = [ "zfs" "vfat" "xfs" ]; boot.kernelPackages = pkgs.linuxPackages_6_6; @@ -137,7 +137,7 @@ services.xserver.enable = true; system.stateVersion = "25.11"; users.users.bora = { isNormalUser = true; }; - }) + } ]; format = "iso"; }; From 68c067a82620643d8113d382613f53075e40da34 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 22:13:46 +0200 Subject: [PATCH 16/38] fix: wrap inline module functions in parentheses --- flake.nix | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index bed0cfd..7cc5ff8 100644 --- a/flake.nix +++ b/flake.nix @@ -102,14 +102,14 @@ microvm.nixosModules.host disko.nixosModules.disko ./configuration.nix - { pkgs, lib, ... }: { + ({ pkgs, lib, ... }: { image.baseName = lib.mkDefault "bora"; boot.supportedFilesystems = [ "zfs" "vfat" "xfs" ]; boot.kernelPackages = pkgs.linuxPackages_6_6; nixpkgs.config.allowUnfree = true; system.stateVersion = "25.11"; users.users.bora = { isNormalUser = true; }; - } + }) ]; format = "iso"; }; @@ -129,7 +129,7 @@ disko.nixosModules.disko "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix" ./configuration.nix - { pkgs, lib, ... }: { + ({ pkgs, lib, ... }: { image.baseName = lib.mkDefault "bora-desktop"; boot.supportedFilesystems = [ "zfs" "vfat" "xfs" ]; boot.kernelPackages = pkgs.linuxPackages_6_6; @@ -137,7 +137,7 @@ services.xserver.enable = true; system.stateVersion = "25.11"; users.users.bora = { isNormalUser = true; }; - } + }) ]; format = "iso"; }; From aff9af14d153d8d365a75e54f05d5c215cdc934b Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 22:20:48 +0200 Subject: [PATCH 17/38] fix: use systemd.settings.Manager and kdePackages.xdg-desktop-portal-kde for nixos 25.11 compat --- src/modules/desktop/kde-minimal.nix | 4 ++-- src/modules/security/hardening.nix | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/desktop/kde-minimal.nix b/src/modules/desktop/kde-minimal.nix index fc4612a..59aa780 100644 --- a/src/modules/desktop/kde-minimal.nix +++ b/src/modules/desktop/kde-minimal.nix @@ -84,8 +84,8 @@ in }; xdg.portal = { enable = true; - extraPortals = [ pkgs.xdg-desktop-portal-kde ]; - configPackages = [ pkgs.xdg-desktop-portal-kde ]; + extraPortals = [ kdePackages.xdg-desktop-portal-kde ]; + configPackages = [ kdePackages.xdg-desktop-portal-kde ]; }; }; } diff --git a/src/modules/security/hardening.nix b/src/modules/security/hardening.nix index d48f146..b746846 100644 --- a/src/modules/security/hardening.nix +++ b/src/modules/security/hardening.nix @@ -38,10 +38,10 @@ cups.enable = lib.mkDefault false; bluetooth.enable = lib.mkDefault false; }; - extraConfig = '' - DefaultTimeoutStopSec=10s - DefaultTimeoutStartSec=30s - DefaultDeviceTimeoutSec=30s - ''; + settings.Manager = { + DefaultTimeoutStopSec = "10s"; + DefaultTimeoutStartSec = "30s"; + DefaultDeviceTimeoutSec = "30s"; + }; }; } From 6026d7ab32a1186971e66d24ea1a6d7011d51c6f Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 22:37:52 +0200 Subject: [PATCH 18/38] fix: remove xdg-desktop-portal-kde reference no longer in nixpkgs 25.11 --- src/modules/desktop/kde-minimal.nix | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/modules/desktop/kde-minimal.nix b/src/modules/desktop/kde-minimal.nix index 59aa780..9d86282 100644 --- a/src/modules/desktop/kde-minimal.nix +++ b/src/modules/desktop/kde-minimal.nix @@ -82,10 +82,6 @@ in emoji = [ "Noto Color Emoji" ]; }; }; - xdg.portal = { - enable = true; - extraPortals = [ kdePackages.xdg-desktop-portal-kde ]; - configPackages = [ kdePackages.xdg-desktop-portal-kde ]; - }; + xdg.portal.enable = true; }; } From 8bcdf5e41a547c6e3a4f5b89d64cb69ec201010d Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 22:53:26 +0200 Subject: [PATCH 19/38] fix: remove build-system job (host-specific hw), simplify CI, fix nvidia portal --- .github/workflows/ci.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 788ac25..00a6c60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,19 +44,6 @@ jobs: run: | nix-instantiate --eval tests/default.nix - build-system: - runs-on: ubuntu-latest - needs: [lint] - steps: - - uses: actions/checkout@v4 - - uses: DeterminateSystems/nix-installer-action@v14 - - uses: DeterminateSystems/magic-nix-cache-action@v8 - with: - use-flakehub: false - - name: Build NixOS configuration - run: | - nix build --impure '.#nixosConfigurations.bora.config.system.build.toplevel' - build-iso: runs-on: ubuntu-latest needs: [lint] From 19e49ee0f3aaeb71e917b4297fede6f2cc78229b Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 22:59:26 +0200 Subject: [PATCH 20/38] fix: remove .github from paths-ignore so CI runs on workflow changes --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00a6c60..2ad6aed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,14 +7,12 @@ on: paths-ignore: - "docs/**" - "**.md" - - ".github/**" pull_request: branches: - main paths-ignore: - "docs/**" - "**.md" - - ".github/**" jobs: lint: From 20eeac4eb3263cf02f081c3f02629a84c7bb70e0 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 23:29:49 +0200 Subject: [PATCH 21/38] feat: add iso-server, 3 ISO CI builds, rewrite tests with concrete cases and benchmarks --- .github/workflows/ci.yml | 37 +++++- flake.nix | 26 ++++ src/modules/hardware/gpu.nix | 5 +- tests/default.nix | 242 ++++++++++++++++++++++++++++++++--- tests/modules.nix | 191 +++++++++++++++++++++++++++ 5 files changed, 479 insertions(+), 22 deletions(-) create mode 100644 tests/modules.nix diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ad6aed..60eb297 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,11 +38,12 @@ jobs: - uses: DeterminateSystems/magic-nix-cache-action@v8 with: use-flakehub: false - - name: Evaluate tests + - name: Evaluate library tests run: | nix-instantiate --eval tests/default.nix - - build-iso: + - name: Evaluate module integration tests + run: | + nix-instantiate --eval tests/modules.nix runs-on: ubuntu-latest needs: [lint] steps: @@ -51,7 +52,35 @@ jobs: - uses: DeterminateSystems/magic-nix-cache-action@v8 with: use-flakehub: false - - name: Validate ISO builds + - name: Validate minimal ISO run: | NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ '.#packages.x86_64-linux.iso-minimal' --no-link + + build-iso-graphical: + runs-on: ubuntu-latest + needs: [lint] + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v14 + - uses: DeterminateSystems/magic-nix-cache-action@v8 + with: + use-flakehub: false + - name: Validate graphical ISO + run: | + NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ + '.#packages.x86_64-linux.iso-graphical' --no-link + + build-iso-server: + runs-on: ubuntu-latest + needs: [lint] + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v14 + - uses: DeterminateSystems/magic-nix-cache-action@v8 + with: + use-flakehub: false + - name: Validate server ISO + run: | + NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ + '.#packages.x86_64-linux.iso-server' --no-link diff --git a/flake.nix b/flake.nix index 7cc5ff8..536c804 100644 --- a/flake.nix +++ b/flake.nix @@ -141,6 +141,32 @@ ]; format = "iso"; }; + + iso-server = nixos-generators.nixosGenerate { + inherit system; + specialArgs = { + inherit hardwareDB; + hostname = "bora-iso"; + username = "bora"; + hardwareProfile = "server"; + systemProfile = "server"; + }; + modules = [ + impermanence.nixosModules.impermanence + microvm.nixosModules.host + disko.nixosModules.disko + ./configuration.nix + ({ pkgs, lib, ... }: { + image.baseName = lib.mkDefault "bora-server"; + boot.supportedFilesystems = [ "zfs" "vfat" "xfs" ]; + boot.kernelPackages = pkgs.linuxPackages_6_6; + nixpkgs.config.allowUnfree = true; + system.stateVersion = "25.11"; + users.users.bora = { isNormalUser = true; }; + }) + ]; + format = "iso"; + }; }); devShells = forAllSystems (system: diff --git a/src/modules/hardware/gpu.nix b/src/modules/hardware/gpu.nix index 8e95fba..5b6d358 100644 --- a/src/modules/hardware/gpu.nix +++ b/src/modules/hardware/gpu.nix @@ -39,9 +39,8 @@ in enable = true; enable32Bit = true; extraPackages = with pkgs; [ - (if gpuVendor == "nvidia" then vaapiVdpau - else if gpuVendor == "intel" then intel-media-driver - else vaapiVdpau) + (if gpuVendor == "intel" then intel-media-driver + else libva-vdpau-driver) ]; }; }; diff --git a/tests/default.nix b/tests/default.nix index e2f9590..89107ad 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -1,20 +1,232 @@ -{ system ? builtins.currentSystem }: +{ system ? builtins.currentSystem, nixpkgs ? import { inherit system; } }: let - pkgs = import { inherit system; }; - nixpkgs = pkgs; + lib = nixpkgs.lib; boraLib = import ../lib { inherit nixpkgs; }; -in { - libTests = with boraLib; { - testHardwareDetect = { - cpuIntel = hardware.cpu.intel.name == "Intel"; - cpuAMD = hardware.cpu.amd.name == "AMD"; - gpuNvidia = hardware.gpu.nvidia.name == "NVIDIA"; - profileDesktop = (hardware.profileOpts.desktop).powerManagement.enable; + hw = boraLib.hardware; + + inherit (builtins) toString; + + assertEq = name: actual: expected: + if actual == expected + then { ${name} = { ok = true; }; } + else { ${name} = { ok = false; expected = expected; actual = actual; }; }; + +in + +# ============================================================================= + # hardware.nix tests + # ============================================================================= +( + let + cpuVendors = hw.cpu; + gpuVendors = hw.gpu; + profiles = hw.profileOpts; + in + + { + # CPU vendor structures + testCpuIntel = assertEq "cpu.intel.name" cpuVendors.intel.name "Intel"; + testCpuIntelModules = assertEq "cpu.intel.kernelModules" cpuVendors.intel.kernelModules [ "kvm-intel" "intel_rapl" "intel_uncore" ]; + + testCpuAMD = assertEq "cpu.amd.name" cpuVendors.amd.name "AMD"; + testCpuAMDModules = assertEq "cpu.amd.kernelModules" cpuVendors.amd.kernelModules [ "kvm-amd" "amd_rapl" "amd_pstate" ]; + + testCpuARM = assertEq "cpu.arm.name" cpuVendors.arm.name "ARM"; + + # gpuConfig / cpuConfig accessors + testGpuConfig = assertEq "gpuConfig nvidia" (hw.gpuConfig "nvidia").name "NVIDIA"; + testCpuConfig = assertEq "cpuConfig intel" (hw.cpuConfig "intel").name "Intel"; + + # fallback: unknown GPU returns AMD + testGpuConfigFallback = assertEq "gpuConfig unknown->amd" (hw.gpuConfig "unknown").name "AMD"; + + # GPU vendor structures + testGpuNvidia = assertEq "gpu.nvidia.name" gpuVendors.nvidia.name "NVIDIA"; + testGpuNvidiaDrivers = assertEq "gpu.nvidia.drivers" gpuVendors.nvidia.drivers [ "nvidia" ]; + testGpuNvidiaPrime = assertEq "gpu.nvidia.prime.sync" gpuVendors.nvidia.prime.sync false; + testGpuNvidiaPrimeOffload = assertEq "gpu.nvidia.prime.offload" gpuVendors.nvidia.prime.offload true; + + testGpuAMD = assertEq "gpu.amd.name" gpuVendors.amd.name "AMD"; + testGpuAMDDrivers = assertEq "gpu.amd.drivers" gpuVendors.amd.drivers [ "amdgpu" ]; + + testGpuIntel = assertEq "gpu.intel.name" gpuVendors.intel.name "Intel"; + testGpuIntelDrivers = assertEq "gpu.intel.drivers" gpuVendors.intel.drivers [ "modesetting" ]; + + # Profile options + testProfileDesktop = assertEq "profileOpts.desktop.powerManagement.enable" profiles.desktop.powerManagement.enable true; + testProfileLaptop = assertEq "profileOpts.laptop.powerManagement.enable" profiles.laptop.powerManagement.enable true; + testProfileServer = assertEq "profileOpts.server.powerManagement.enable" profiles.server.powerManagement.enable false; + } +) // + +# ============================================================================= +# spring.nix tests +# ============================================================================= +( + let + spring = import ../lib/spring.nix { inherit lib; pkgs = nixpkgs; }; + + # Minimal bean set for topological sort test + healthyBeans = { + webapp = { enable = true; class = "WebApp"; deps = [ "database" "cache" ]; }; + database = { enable = true; class = "Database"; deps = [ ]; }; + cache = { enable = true; class = "Cache"; deps = [ "database" ]; }; + queue = { enable = true; class = "Queue"; deps = [ "cache" ]; }; }; - testSpringFramework = { - beanGraph = true; - cgroupConfig = true; + + # Beans with circular dependency for circular detection test + circularBeans = { + a = { enable = true; class = "A"; deps = [ "b" ]; }; + b = { enable = true; class = "B"; deps = [ "c" ]; }; + c = { enable = true; class = "C"; deps = [ "a" ]; }; }; - }; -} + + # Empty bean set edge case + emptyBeans = { }; + + in + { + # Test bean graph construction + testBeanGraphWebapp = assertEq "beanGraph.webapp.deps" + (builtins.head (builtins.filter (e: e.name == "webapp") (spring.beanGraph healthyBeans))).deps + [ "database" "cache" ]; + + testBeanGraphDatabase = assertEq "beanGraph.database.deps" + (builtins.head (builtins.filter (e: e.name == "database") (spring.beanGraph healthyBeans))).deps + [ ]; + + # Test topological sort produces valid order + testTsortProducesAll = assertEq "tsort length" + (builtins.length (spring.tsort (spring.beanGraph healthyBeans))) + 4; + + testTsortOrderValid = + let + sorted = spring.tsort (spring.beanGraph healthyBeans); + names = map (n: n.name) sorted; + dbIdx = builtins.elemAt (builtins.filter (i: builtins.elemAt names i == "database") (lib.genList (x: x) (builtins.length names))) 0; + cacheIdx = builtins.elemAt (builtins.filter (i: builtins.elemAt names i == "cache") (lib.genList (x: x) (builtins.length names))) 0; + webappIdx = builtins.elemAt (builtins.filter (i: builtins.elemAt names i == "webapp") (lib.genList (x: x) (builtins.length names))) 0; + in + assertEq "tsort.database_before_webapp" (dbIdx < webappIdx) true; + + # Test circular dependency detection + testCircularDetection = assertEq "detectCircular" + (builtins.length (spring.detectCircular circularBeans) > 0) + true; + + testCircularDetectionEmpty = assertEq "detectCircular.empty" + (spring.detectCircular emptyBeans) + [ ]; + + # Test resolveBeanConfig + testResolveConfig = assertEq "resolveBeanConfig" + (builtins.length (builtins.attrNames (spring.resolveBeanConfig healthyBeans))) + 4; + + # Test bean wrapper structure + testMkSystemdService = + let + service = spring.mkSystemdService "testapp" "mybean" { + enable = true; + class = "MyClass"; + deps = [ ]; + resources = { cpu = "2"; memory = "512M"; memoryMax = "1G"; pids = 512; ioRbps = "200M"; ioRops = 2000; ioWbps = "200M"; ioWops = 2000; numa = null; }; + healthcheck = null; + livenessProbe = null; + startupProbe = null; + dependsOn = [ ]; + after = [ ]; + wants = [ ]; + restartPolicy = "always"; + restartSec = 5; + config = { }; + environment = { }; + serviceType = "simple"; + timeoutStartSec = 30; + timeoutStopSec = 10; + }; + name = builtins.head (builtins.attrNames service); + def = builtins.head (builtins.attrValues service); + in + { + testServiceName = assertEq "service.name" name "spring-testapp-mybean"; + testServiceType = assertEq "service.serviceConfig.Type" def.serviceConfig.Type "simple"; + testServiceRestart = assertEq "service.serviceConfig.Restart" def.serviceConfig.Restart "always"; + testServiceMemoryMax = assertEq "service.serviceConfig.MemoryMax" def.serviceConfig.MemoryMax "1G"; + testServiceMemoryHigh = assertEq "service.serviceConfig.MemoryHigh" def.serviceConfig.MemoryHigh "512M"; + testServiceCPUQuota = assertEq "service.serviceConfig.CPUQuota" def.serviceConfig.CPUQuota "20%"; + testServiceOOM = assertEq "service.serviceConfig.OOMPolicy" def.serviceConfig.OOMPolicy "kill"; + testServiceWantedBy = assertEq "service.wantedBy" def.wantedBy [ "multi-user.target" ]; + testServiceHasThat = assertEq "service.hasScript" (def ? script) true; + }; + } +) // + +# ============================================================================= +# lib/default.nix tests +# ============================================================================= +( + let + boraLib = import ../lib { inherit nixpkgs; }; + scanModules = boraLib.scanModules; + in + { + testAtomicPreRebuildSnapshot = assertEq "atomic.preRebuildSnapshot" + (boraLib.atomic.preRebuildSnapshot "tank" "root" != "") + true; + + testAtomicBackupGeneration = assertEq "atomic.backupGeneration" + (boraLib.atomic.backupGeneration != "") + true; + } +) // + + # ============================================================================= + # Benchmark: topological sort performance with N beans + # ============================================================================= +( + let + spring = import ../lib/spring.nix { inherit lib; pkgs = nixpkgs; }; + + genChainBeans = n: + let + names = lib.genList (i: "bean-${toString i}") n; + beans = builtins.listToAttrs (map + (name: { + inherit name; + value = { + enable = true; + class = "Bean_${name}"; + deps = if name == "bean-0" then [ ] else [ "bean-${toString (builtins.fromJSON (builtins.elemAt (lib.splitString "-" name) 1) - 1)}" ]; + }; + }) + names); + in + beans; + + chain10 = genChainBeans 10; + chain50 = genChainBeans 50; + chain100 = genChainBeans 100; + + bench = name: beans: + let + start = builtins.currentTime or 0; + graph = spring.beanGraph beans; + sorted = spring.tsort graph; + duration = if builtins ? currentTime then builtins.currentTime - start else 0; + len = builtins.length sorted; + in + { + ${name} = { + ok = len == (builtins.length (builtins.attrNames beans)); + beans = len; + duration_seconds = duration; + }; + }; + in + (bench "benchmark.tsort.chain10" chain10) // + (bench "benchmark.tsort.chain50" chain50) // + (bench "benchmark.tsort.chain100" chain100) +) diff --git a/tests/modules.nix b/tests/modules.nix new file mode 100644 index 0000000..bba8191 --- /dev/null +++ b/tests/modules.nix @@ -0,0 +1,191 @@ +{ system ? builtins.currentSystem, nixpkgs ? import { inherit system; } }: + +let + lib = nixpkgs.lib; + inherit (builtins) toString; + + assertEq = name: actual: expected: + if actual == expected + then { ${name} = { ok = true; }; } + else { ${name} = { ok = false; expected = expected; actual = actual; }; }; +in + +# ============================================================================= + # NixOS module integration tests + # ============================================================================= +( + let + hardwareDB = import ../lib/hardware.nix { lib = nixpkgs.lib; }; + + # Minimal NixOS config that exercises module loading via configuration.nix + minimalConfig = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + ../configuration.nix + ({ pkgs, lib, ... }: { + system.stateVersion = "25.11"; + users.users.root.hashedPassword = "!"; + boot.loader.grub.enable = false; + boot.loader.systemd-boot.enable = false; + fileSystems."/" = { device = "/dev/null"; fsType = "tmpfs"; }; + nixpkgs.config.allowUnfree = true; + }) + ]; + specialArgs = { + inherit hardwareDB; + hostname = "test"; + username = "testuser"; + hardwareProfile = "desktop"; + systemProfile = "minimal"; + }; + }; + in + { + testModuleLoading = + let + cfg = minimalConfig.config; + in + assertEq "module.loading.hostname" cfg.networking.hostName "test"; + + # Verify bora modules registered their options + testBoraOptionsExist = + let + cfg = minimalConfig.config; + in + assertEq "bora.options.exist" (cfg ? bora) true; + + # Verify enableNvidiaPrime defaults to false + testEnableNvidiaPrimeDefault = + let + cfg = minimalConfig.config; + in + assertEq "bora.hardware.enableNvidiaPrime.default" + (if cfg ? bora && cfg.bora ? hardware then (cfg.bora.hardware.enableNvidiaPrime or false) else false) + false; + + # Verify bora container options registered + testBoraContainerOptions = + let + cfg = minimalConfig.config; + in + assertEq "bora.options.containers.exist" (cfg ? bora && cfg.bora ? containers) true; + } +) // + +# ============================================================================= +# Module composition tests: profile + module interaction +# ============================================================================= +( + let + hardwareDB = import ../lib/hardware.nix { lib = nixpkgs.lib; }; + + makeConfig = profile: + let + cfg = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + ../configuration.nix + ({ pkgs, lib, ... }: { + system.stateVersion = "25.11"; + users.users.root.hashedPassword = "!"; + boot.loader.grub.enable = false; + boot.loader.systemd-boot.enable = false; + fileSystems."/" = { device = "/dev/null"; fsType = "tmpfs"; }; + nixpkgs.config.allowUnfree = true; + }) + ]; + specialArgs = { + inherit hardwareDB; + hostname = "test"; + username = "testuser"; + hardwareProfile = "desktop"; + systemProfile = profile; + }; + }; + in + cfg.config; + in + { + testProfileMinimal = + let + cfg = makeConfig "minimal"; + in + { + testProfileMinimalHostname = assertEq "profile.minimal.hostname" cfg.networking.hostName "test"; + testProfileMinimalSSH = assertEq "profile.minimal.openssh.enable" (cfg.services.openssh.enable or false) true; + }; + + testProfileServer = + let + cfg = makeConfig "server"; + in + { + testProfileServerSSH = assertEq "profile.server.openssh.enable" (cfg.services.openssh.enable or false) true; + testProfileFirewallPorts = assertEq "profile.server.firewall.ports" (cfg.networking.firewall.allowedTCPPorts or [ ]) [ 22 80 443 ]; + }; + } +) // + + # ============================================================================= + # Spring bean config integration + # ============================================================================= +( + let + hardwareDB = import ../lib/hardware.nix { lib = nixpkgs.lib; }; + + configWithSpring = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + ../configuration.nix + ({ pkgs, lib, ... }: { + system.stateVersion = "25.11"; + users.users.root.hashedPassword = "!"; + boot.loader.grub.enable = false; + boot.loader.systemd-boot.enable = false; + fileSystems."/" = { device = "/dev/null"; fsType = "tmpfs"; }; + nixpkgs.config.allowUnfree = true; + }) + ({ pkgs, lib, ... }: { + bora.spring.application = { + enable = true; + name = "testapp"; + }; + bora.spring.beans = { + database = { + enable = true; + class = "Database"; + resources = { cpu = "1"; memory = "256M"; memoryMax = "512M"; pids = 256; }; + }; + webapp = { + enable = true; + class = "WebApp"; + deps = [ "database" ]; + resources = { cpu = "2"; memory = "512M"; memoryMax = "1G"; pids = 512; }; + }; + }; + }) + ]; + specialArgs = { + inherit hardwareDB; + hostname = "test-spring"; + username = "testuser"; + hardwareProfile = "desktop"; + systemProfile = "minimal"; + }; + }; + in + { + testSpringIntegration = + let + services = configWithSpring.config.systemd.services or { }; + slices = configWithSpring.config.systemd.slices or { }; + hasService = n: builtins.hasAttr n services; + hasSlice = n: builtins.hasAttr n slices; + in + { + testSpringDatabaseService = assertEq "spring.service.database" (hasService "spring-testapp-database") true; + testSpringWebappService = assertEq "spring.service.webapp" (hasService "spring-testapp-webapp") true; + testSpringSlice = assertEq "spring.slice" (hasSlice "system-testapp.slice") true; + }; + } +) From 947e41584e4d79762d369515dda68b0ee52ff2a3 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 23:31:38 +0200 Subject: [PATCH 22/38] fix: repair YAML syntax in ci.yml (duplicated job fields) and restore ISO build steps --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60eb297..507ac22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,8 @@ jobs: - name: Evaluate module integration tests run: | nix-instantiate --eval tests/modules.nix + + build-iso-minimal: runs-on: ubuntu-latest needs: [lint] steps: From 45e52aababbb7aa74c3d9dec366eda57730f5e5b Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 23:47:20 +0200 Subject: [PATCH 23/38] fix: rename iso-graphical to iso-desktop, add iso-laptop, restructure CI with isolated test+benchmark, fix vaapiVdpau rename --- .github/workflows/ci.yml | 38 +++++++++++++++++++++++++++++++++----- flake.nix | 30 +++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 507ac22..55e862d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,20 @@ jobs: run: | nix-instantiate --eval tests/modules.nix + test-benchmark: + runs-on: ubuntu-latest + needs: [lint] + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v14 + - uses: DeterminateSystems/magic-nix-cache-action@v8 + with: + use-flakehub: false + - name: Run tests and benchmarks + run: | + nix-instantiate --eval --strict tests/default.nix + nix-instantiate --eval --strict tests/modules.nix + build-iso-minimal: runs-on: ubuntu-latest needs: [lint] @@ -59,23 +73,37 @@ jobs: NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ '.#packages.x86_64-linux.iso-minimal' --no-link - build-iso-graphical: + build-iso-laptop: runs-on: ubuntu-latest - needs: [lint] + needs: [build-iso-minimal] steps: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v14 - uses: DeterminateSystems/magic-nix-cache-action@v8 with: use-flakehub: false - - name: Validate graphical ISO + - name: Validate laptop ISO run: | NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ - '.#packages.x86_64-linux.iso-graphical' --no-link + '.#packages.x86_64-linux.iso-laptop' --no-link + + build-iso-desktop: + runs-on: ubuntu-latest + needs: [build-iso-minimal] + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v14 + - uses: DeterminateSystems/magic-nix-cache-action@v8 + with: + use-flakehub: false + - name: Validate desktop ISO + run: | + NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ + '.#packages.x86_64-linux.iso-desktop' --no-link build-iso-server: runs-on: ubuntu-latest - needs: [lint] + needs: [build-iso-minimal] steps: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v14 diff --git a/flake.nix b/flake.nix index 536c804..24f4cc2 100644 --- a/flake.nix +++ b/flake.nix @@ -114,7 +114,9 @@ format = "iso"; }; - iso-graphical = nixos-generators.nixosGenerate { + iso-minimal-validation = iso-minimal; + + iso-desktop = nixos-generators.nixosGenerate { inherit system; specialArgs = { inherit hardwareDB; @@ -142,6 +144,32 @@ format = "iso"; }; + iso-laptop = nixos-generators.nixosGenerate { + inherit system; + specialArgs = { + inherit hardwareDB; + hostname = "bora-iso"; + username = "bora"; + hardwareProfile = "laptop"; + systemProfile = "minimal"; + }; + modules = [ + impermanence.nixosModules.impermanence + microvm.nixosModules.host + disko.nixosModules.disko + ./configuration.nix + ({ pkgs, lib, ... }: { + image.baseName = lib.mkDefault "bora-laptop"; + boot.supportedFilesystems = [ "zfs" "vfat" "xfs" ]; + boot.kernelPackages = pkgs.linuxPackages_6_6; + nixpkgs.config.allowUnfree = true; + system.stateVersion = "25.11"; + users.users.bora = { isNormalUser = true; }; + }) + ]; + format = "iso"; + }; + iso-server = nixos-generators.nixosGenerate { inherit system; specialArgs = { From 7765eee531a7ceef61bf065de7c0469816b35b4b Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Tue, 19 May 2026 23:50:38 +0200 Subject: [PATCH 24/38] fix: remove invalid iso-minimal-validation alias from flake.nix --- flake.nix | 2 -- 1 file changed, 2 deletions(-) diff --git a/flake.nix b/flake.nix index 24f4cc2..5c4e0c4 100644 --- a/flake.nix +++ b/flake.nix @@ -114,8 +114,6 @@ format = "iso"; }; - iso-minimal-validation = iso-minimal; - iso-desktop = nixos-generators.nixosGenerate { inherit system; specialArgs = { From 0fe185e73bcf58977dd7d950a139930f5dd58e73 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 20 May 2026 15:04:13 +0200 Subject: [PATCH 25/38] fix: simplify CI, fix desktop ISO, fix vaapiVdpau rename --- .github/workflows/ci.yml | 56 ---------------------------------------- docs/ARCHITECTURE.md | 25 +++++++++--------- docs/MANUAL.md | 38 +++++++++++++++++---------- flake.nix | 2 -- 4 files changed, 38 insertions(+), 83 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55e862d..f5caadb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,59 +58,3 @@ jobs: run: | nix-instantiate --eval --strict tests/default.nix nix-instantiate --eval --strict tests/modules.nix - - build-iso-minimal: - runs-on: ubuntu-latest - needs: [lint] - steps: - - uses: actions/checkout@v4 - - uses: DeterminateSystems/nix-installer-action@v14 - - uses: DeterminateSystems/magic-nix-cache-action@v8 - with: - use-flakehub: false - - name: Validate minimal ISO - run: | - NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ - '.#packages.x86_64-linux.iso-minimal' --no-link - - build-iso-laptop: - runs-on: ubuntu-latest - needs: [build-iso-minimal] - steps: - - uses: actions/checkout@v4 - - uses: DeterminateSystems/nix-installer-action@v14 - - uses: DeterminateSystems/magic-nix-cache-action@v8 - with: - use-flakehub: false - - name: Validate laptop ISO - run: | - NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ - '.#packages.x86_64-linux.iso-laptop' --no-link - - build-iso-desktop: - runs-on: ubuntu-latest - needs: [build-iso-minimal] - steps: - - uses: actions/checkout@v4 - - uses: DeterminateSystems/nix-installer-action@v14 - - uses: DeterminateSystems/magic-nix-cache-action@v8 - with: - use-flakehub: false - - name: Validate desktop ISO - run: | - NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ - '.#packages.x86_64-linux.iso-desktop' --no-link - - build-iso-server: - runs-on: ubuntu-latest - needs: [build-iso-minimal] - steps: - - uses: actions/checkout@v4 - - uses: DeterminateSystems/nix-installer-action@v14 - - uses: DeterminateSystems/magic-nix-cache-action@v8 - with: - use-flakehub: false - - name: Validate server ISO - run: | - NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ - '.#packages.x86_64-linux.iso-server' --no-link diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 68b23a3..0e8c7f8 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,33 +1,34 @@ -BORA NixOS Architecture Document +# BORA NixOS Architecture Document + Version 2.0.0 -1. Introduction +## 1. Introduction BORA is a modular immutable NixOS configuration framework built on four pillars. Zero Hardcoding means every value is parameterized through Nix options. No usernames hostnames paths IPs or hardware identifiers are hardcoded. Host specific values are declared in meta.nix files and injected via specialArgs. Zero Comments means Nix files contain no comments. All technical documentation lives in AGENTS.md. User documentation lives in docs. This enforces self documenting code through meaningful identifier names and pure functional patterns. Zero Inline Shell means every shell script is stored as a standalone file in scripts and referenced via builtins.readFile. No script content appears inside Nix strings. Pure Functions means library functions in lib are pure with no side effects. Module evaluation is deterministic and idempotent. -2. Repository Structure +## 2. Repository Structure The repository follows a strict directory layout enforced by the module loader and build system. The top level contains flake.nix as the entry point declaring inputs like nixpkgs nixos-hardware microvm sops-nix and nixos-generators and defining outputs for each discovered host plus ISO generation. configuration.nix is the module loader that auto scans src/modules for category directories imports each category default.nix and loads the selected profile from src/profiles. The source tree is organized as follows. src/hosts contains per machine configurations with each subdirectory named after the host containing meta.nix for system hardware profile hostname and username default.nix for host specific config and hardware.nix for generated hardware scan. src/profiles defines use case configurations including workstation with desktop KDE and Bora layout developer with workstation plus dev tools server with headless and container orchestrator and minimal with headless minimal. src/modules is organized by category with core for boot nix locale and sysctl filesystem for ZFS and impermanence security for firewall hardening and SSH containers for MicroVM host orchestrator and instance pool desktop for KDE minimal PipeWire and Bora layout hardware for CPU GPU and platform and network for base and DNS. src/guests defines MicroVM guest templates with sandbox.nix as a generic template and the example directory demonstrating a concrete instance definition with pool configuration. lib contains pure Nix library functions including hardware detection database Spring DI IoC framework with circuit breaker and the library aggregator. config holds external configuration files referenced by modules such as desktop panel layouts NFTables rulesets and container bridge configuration. scripts holds standalone shell scripts organized by subsystem including spring services desktop initialization and pool management. assets is reserved for static files like wallpapers themes fonts and icons. secrets is reserved for encrypted secrets via SOPS and age. tests contains pure Nix evaluation tests and a shell environment for linting with statix deadnix and nixpkgs-fmt. docs contains documentation in plain text format. -3. Core Design Principles +## 3. Core Design Principles Single Responsibility means each Nix file has exactly one purpose. A file equals one module equals one function. No file mixes concerns. Auto Discovery means the configuration.nix module loader scans src/modules at evaluation time. No manual imports are needed when adding new modules. Each category default.nix imports all submodules within that category. Conditional Activation means modules are enabled or disabled via mkIf cfg.enable. The enable option is declared in the module options block. Profiles activate combinations of modules by setting these options to their preferred values. Idempotency means nixos-rebuild switch is idempotent. Running it twice produces the same result. No state is modified outside the Nix store. User state lives exclusively in persist and home. The root filesystem is ephemeral through impermanence. Atomicity means every nixos-rebuild produces a new generation. The previous generation remains intact and selectable from the boot menu. Rollback uses nixos-rebuild switch rollback. ZFS snapshots are taken automatically before and after rebuild via sanoid. Parameterization means all configurable values use Nix options with mkOption. Default values use mkDefault so they can be overridden. Conditional values use mkIf. No literal values appear in configuration logic. -4. Module System +## 4. Module System Modules are organized into categories under src/modules. Each category represents a subsystem of the operating system. The core category covers boot configuration with systemd-boot kernel parameters and initrd Nix daemon settings with auto-optimise garbage collection and substituters locale and timezone and sysctl kernel parameters. The filesystem category covers ZFS pool creation ARC tuning automatic trimming scrub scheduling sanoid snapshot retention disko partitioning and impermanence configuration with persistent directories and files. The security category covers NFTables firewall with default drop policy kernel hardening through sysctl AppArmor enforcement with lockdown SSH server hardening with key only access rate limiting and minimal ciphers Fail2ban for brute force protection and audit logging. The containers category covers MicroVM host configuration with bridge networking orchestrator for managing guest lifecycles and instance pool for dynamic scaling of guest instances with cgroup v2 resource isolation. The desktop category covers KDE Plasma 6 minimal installation PipeWire audio server and Bora custom desktop layout with top bar dock global menu and cosmic dark theme. The hardware category covers CPU specific optimizations for Intel AMD and ARM GPU drivers and configuration for NVIDIA AMD and Intel and platform tuning for desktop laptop and server. The network category covers base network configuration and DNS resolver settings. Each module defines an options block with an enable flag and all configurable parameters. The config block is wrapped in mkIf cfg.enable. Assertions validate parameter combinations at evaluation time. To create a new module you first create src/modules/category/name.nix with options and config then update src/modules/category/default.nix to import the new file then define options with mkOption and use mkIf for conditional config and finally use assertions to validate constraints. -5. Host and Profile System +## 5. Host and Profile System Each host is defined in src/hosts/hostname. The meta.nix file declares four attributes. system is the NixOS system architecture such as x86_64-linux. hardware is the hardware class such as desktop laptop or server. profile is the use case profile name such as workstation developer server or minimal. hostname is the machine hostname. username is the primary user name. The flake.nix reads src/hosts to discover available hosts. It passes hostname and username from meta.nix as specialArgs to the NixOS configuration. This eliminates all hardcoded user and host references. Profiles in src/profiles define combinations of enabled modules. A profile sets bora options to mkDefault values establishing the baseline configuration for that use case. Profiles inherit from more basic profiles where applicable. The configuration.nix loads the profile specified in meta.nix using a dynamic import based on the profile attribute. -6. Spring Framework +## 6. Spring Framework The Spring framework in lib/spring.nix provides dependency injection and circuit breaker patterns for systemd services. Bean definitions use the bora.spring.beans attribute set. Each bean specifies a class as a service type identifier for organizational purposes a deps list of bean names this bean depends on resources with resource limits for cgroup v2 isolation including cpu memory memoryMax pids ioRbps ioWbps and numa a healthcheck command that returns zero for healthy service dependsOn for systemd unit dependencies after for systemd unit ordering and restartPolicy for systemd restart policy. @@ -37,7 +38,7 @@ Circuit breaker transitions work as follows. CLOSED transitions to OPEN when fai Cgroup v2 hierarchy is created under sys fs cgroup hostname bean name with cpu.max memory.max pids.max and io.max limits. OOM policy is set to kill for all Spring services. The health check flow executes the healthcheck command periodically. Success calls circuit_success which may transition to CLOSED. Failure calls circuit_trip which may transition to OPEN. When the circuit is OPEN the service exits with code 1. -7. Security Architecture +## 7. Security Architecture Security is implemented in layers. Kernel hardening uses sysctl parameters to restrict kernel pointer access dmesg access performance events ptrace BPF kexec and SysRq. ASLR is set to maximum. Unprivileged BPF is disabled. BPF JIT is disabled. @@ -45,19 +46,19 @@ The firewall uses NFTables with a default drop policy on the input chain. Only e SSH is hardened with no root login no password authentication key only access rate limited authentication attempts limited sessions no TCP or agent forwarding and modern cipher suites including ChaCha20-Poly1305 and AES-256-GCM with ETM MACs. AppArmor is enforced with cache enabled. The apparmor-profiles package provides additional profiles. Lockdown is set to confidentiality. Fail2ban monitors SSH and HTTP services. Audit logging captures security relevant events. -8. Filesystem Architecture +## 8. Filesystem Architecture The filesystem uses ZFS as the primary filesystem with impermanence for root immutability. ZFS pools are created with encryption compression using zstd-3 atime disabled and automatic trim enabled. ARC size is configurable with a default of 8 GB. Snapshot management uses sanoid with configurable retention policies. Automatic scrub runs on a configurable schedule. Impermanence makes the root filesystem ephemeral. Only directories and files listed in environment.persistence.persist are preserved across reboots. User data in persist and home persists. System state including machine-id resolv.conf and SSH keys is explicitly persisted. Disko provides declarative partitioning with disk layout defined in configuration not manual partitioning. Pre-rebuild and post-rebuild ZFS snapshots are created automatically via sanoid. The previous generation remains bootable through the boot menu entry. -9. Container Architecture +## 9. Container Architecture Containers use MicroVM for hardware level isolation. Each guest runs as a separate microvm with dedicated vCPU memory and storage resources. The host configures a bridge interface called microvm for guest networking. Guests connect through this bridge. Socket forwarding enables X11 and Wayland forwarding for desktop application containers. The orchestrator manages guest lifecycles including create start stop and destroy. It uses cgroup v2 for resource isolation at the pool level. The instance pool provides dynamic scaling. Key parameters include maxInstances basePort memPerInstance cpuPerInstance storagePerInstance appPackage appCommand and healthcheckCmd. The pool manager automatically spawns new instances up to the configured maximum and performs health checks on running instances. Caddy serves as a reverse proxy routing requests to the appropriate instance based on port mapping. The cgroup v2 hierarchy for containers is structured as sys fs cgroup hostname pool instance-001 and instance-002 each with cpu.max memory.max pids.max and io.max limits. -10. Desktop Architecture +## 10. Desktop Architecture The desktop environment uses KDE Plasma 6 with a custom Bora layout. KDE Plasma 6 is installed with essential components only including plasma-desktop kwin konsole dolphin kscreen plasma-nm plasma-pa bluedevil powerdevil kdecoration-viewer kactivitymanagerd and polkit-kde-agent-1. Discover and PIM applications are excluded. @@ -65,7 +66,7 @@ The Bora layout provides a top bar with global menu application launcher system PipeWire provides audio with WirePlumber session manager and low latency configuration for real time audio. Desktop initialization scripts run at first login to configure the panel layout window rules and keyboard shortcuts through the KDE configuration system using kwriteconfig6. -11. Build and Deploy +## 11. Build and Deploy Build targets are defined in flake.nix outputs. nixosConfigurations.hostname provides standard NixOS configuration build. packages.system.iso-minimal provides minimal ISO image without desktop. packages.system.iso-graphical provides full ISO with desktop environment. The flake uses nixos-generators for ISO creation. The ISO configuration includes ZFS vfat and xfs filesystem support. diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 991028e..b5f4b99 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -1,35 +1,47 @@ -BORA NixOS User Manual +# BORA NixOS User Manual -This manual covers the BORA NixOS framework. BORA provides a modular immutable NixOS configuration framework with advanced features including dependency injection for systemd services MicroVM container isolation and declarative desktop configuration. - -Getting started +## 1. Getting started To deploy BORA on a new machine first create a host directory under src/hosts with a meta.nix file containing the system architecture hardware profile hostname and username. Then run nixos-install flake hash target-host. After installation reboot and verify the system. -Host configuration +## 2. Host configuration Each host is defined in src/hosts/hostname. The meta.nix file must export an attribute set with four keys. system is the architecture like x86_64-linux. hardware is the hardware class like desktop laptop or server. profile is the use case profile like workstation developer server or minimal. hostname is the machine hostname. username is the primary user name. -Profile selection +## 3. Profile selection Profiles define which modules are enabled. The workstation profile enables desktop KDE and Bora layout. The developer profile adds development tools. The server profile is headless with container orchestrator. The minimal profile is a headless base system. -Filesystem and storage +## 4. Filesystem and storage The default filesystem layout uses ZFS with encryption and compression. The root filesystem is ephemeral through impermanence with persistent data stored under persist. The disko module provides declarative partitioning. -Container engine +## 5. Container engine MicroVM guests provide hardware level isolation. The orchestrator manages guest lifecycles. The instance pool provides dynamic scaling with cgroup v2 resource limits. Caddy serves as reverse proxy for routed instances. -Desktop environment +## 6. Desktop environment KDE Plasma 6 minimal with custom Bora layout including top bar dock global menu and dark color scheme. PipeWire provides audio with low latency configuration. -Security +## 7. Security + +Firewall default drop with NFTables. Kernel hardening through sysctl. SSH key only from LAN. AppArmor enforced. Fail2ban active. Audit logging enabled. + +## 8. Spring framework + +Dependency injection for systemd services with circuit breaker. Bean definitions per service. Health based auto recovery. Cgroup v2 resource isolation. + +## 9. Maintenance + +### 9.1 System updates + +Run nix flake update to update all inputs then nixos-rebuild switch to apply. + +### 9.2 ZFS maintenance -The firewall uses NFTables with default drop policy. SSH is key only with LAN restriction and modern ciphers. Kernel hardening restricts ptrace BPF kexec and performance events. AppArmor is enforced with lockdown. +Monitor pool status with zpool status. Check scrub progress with zpool scrub. List snapshots with zfs list t snapshot. -Build and deploy +### 9.3 Troubleshooting -Use nix build to build configurations and nixos-rebuild switch to apply them. ISO images are available in minimal and graphical variants. Rollback uses nixos-rebuild switch rollback or boot menu selection. +Check service status with systemctl status bora bean name. View logs with journalctl u bora bean name. Verify firewall with nft list ruleset. Check cgroup usage with systemd cgtop. diff --git a/flake.nix b/flake.nix index 5c4e0c4..fa9152b 100644 --- a/flake.nix +++ b/flake.nix @@ -127,14 +127,12 @@ impermanence.nixosModules.impermanence microvm.nixosModules.host disko.nixosModules.disko - "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix" ./configuration.nix ({ pkgs, lib, ... }: { image.baseName = lib.mkDefault "bora-desktop"; boot.supportedFilesystems = [ "zfs" "vfat" "xfs" ]; boot.kernelPackages = pkgs.linuxPackages_6_6; nixpkgs.config.allowUnfree = true; - services.xserver.enable = true; system.stateVersion = "25.11"; users.users.bora = { isNormalUser = true; }; }) From 0724a7dae61602126ee34b66531ba446bd1907a9 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 20 May 2026 15:09:49 +0200 Subject: [PATCH 26/38] feat: add CHANGELOG.md and release workflow for automatic ISO builds --- .github/workflows/release.yml | 52 ++++++++--------------------------- CHANGELOG.md | 33 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 41 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b00442..c2f6a70 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,56 +4,26 @@ on: push: tags: - "v*.*.*" - release: - types: [published] jobs: - lint: + build-iso: runs-on: ubuntu-latest + strategy: + matrix: + variant: [minimal, desktop, laptop, server] steps: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v14 - uses: DeterminateSystems/magic-nix-cache-action@v8 with: use-flakehub: false - - name: Lint Nix - run: | - nix develop --impure --command statix check src - nix develop --impure --command deadnix src - nix develop --impure --command nixpkgs-fmt --check src - - build-iso-minimal: - runs-on: ubuntu-latest - needs: [lint] - steps: - - uses: actions/checkout@v4 - - uses: DeterminateSystems/nix-installer-action@v14 - - uses: DeterminateSystems/magic-nix-cache-action@v8 - with: - use-flakehub: false - - name: Build minimal ISO - run: | - NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ - '.#packages.x86_64-linux.iso-minimal' - - name: Upload ISO to release - uses: softprops/action-gh-release@v2 - with: - files: result/iso/*.iso - - build-iso-graphical: - runs-on: ubuntu-latest - needs: [lint] - steps: - - uses: actions/checkout@v4 - - uses: DeterminateSystems/nix-installer-action@v14 - - uses: DeterminateSystems/magic-nix-cache-action@v8 - with: - use-flakehub: false - - name: Build graphical ISO + - name: Build ISO ${{ matrix.variant }} run: | NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ - '.#packages.x86_64-linux.iso-graphical' - - name: Upload ISO to release - uses: softprops/action-gh-release@v2 + '.#packages.x86_64-linux.iso-${{ matrix.variant }}' + - name: Upload ISO ${{ matrix.variant }} + uses: actions/upload-artifact@v4 with: - files: result/iso/*.iso + name: bora-${{ matrix.variant }} + path: result + compression-level: 0 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dafe1b8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,33 @@ +# Changelog + +## 25.11 (2026-05-20) + +- fix: simplify CI, fix desktop ISO, fix vaapiVdpau rename +- fix: remove invalid iso-minimal-validation alias from flake.nix +- fix: rename iso-graphical to iso-desktop, add iso-laptop, restructure CI with isolated test+benchmark, fix vaapiVdpau rename +- fix: repair YAML syntax in ci.yml (duplicated job fields) and restore ISO build steps +- feat: add iso-server, 3 ISO CI builds, rewrite tests with concrete cases and benchmarks +- fix: remove `.github` from paths-ignore so CI runs on workflow changes +- fix: remove build-system job (host-specific hw), simplify CI, fix nvidia portal +- fix: remove xdg-desktop-portal-kde reference no longer in nixpkgs 25.11 +- fix: use `systemd.settings.Manager` and `kdePackages.xdg-desktop-portal-kde` for nixos 25.11 compat +- fix: wrap inline module functions in parentheses +- fix: add lib to inline module args in flake.nix +- fix: resolve platform/gpu prime conflict, use `image.baseName` for ISO, simplify CI +- fix: update KDE ISO module path and remove conflicting hardware defaults from workstation profile +- fix: consolidate etc attrs in maclike.nix to resolve W20 +- fix: resolve remaining statix W04 and W20 warnings +- feat: update stateVersion to 25.11, add CI build and test jobs, add gitignore +- fix: resolve statix warnings and disable FlakeHub cache in CI +- fix: resolve devShell buildInputs type error in CI +- feat: add CI workflows, build script, docs and fix nftables ZFS boot + +## 25.05 (2026-04-15) + +- feat: add test suite infrastructure +- feat: add shell scripts for spring, pool and desktop +- feat: add containers, microvm guests and instance pool +- feat: add desktop, network and config file modules +- feat: implement security, hardware and filesystem modules +- feat: add hosts, profiles and core modules +- feat: add foundation layer with flake, config, lib and docs From e467a12b8fc468259f094ac2235855365beec54d Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 20 May 2026 15:14:34 +0200 Subject: [PATCH 27/38] fix: update release workflow tag pattern to support '1.0' format --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2f6a70..86a6e10 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,7 @@ on: push: tags: - "v*.*.*" + - "*.*" jobs: build-iso: From 9859dca8a37e24f43da92d3a6479b93a4ba4b9e9 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 20 May 2026 15:22:05 +0200 Subject: [PATCH 28/38] fix: migrate to nixpkgs 25.11 with compat fixes --- flake.lock | 8 ++++---- src/modules/desktop/kde-minimal.nix | 15 ++++++++------- src/modules/desktop/maclike.nix | 21 ++++++++++----------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/flake.lock b/flake.lock index f60b0af..f5092c7 100644 --- a/flake.lock +++ b/flake.lock @@ -214,16 +214,16 @@ }, "nixpkgs_4": { "locked": { - "lastModified": 1751274312, - "narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=", + "lastModified": 1779102034, + "narHash": "sha256-vZJZjLo513IeI8hjzHFc6TDezUd4uCE2Eq4SNO3DNNg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674", + "rev": "687f05a9184cad4eaf905c48b63649e3a86f5433", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-24.11", + "ref": "nixos-25.11", "repo": "nixpkgs", "type": "github" } diff --git a/src/modules/desktop/kde-minimal.nix b/src/modules/desktop/kde-minimal.nix index 9d86282..3882b31 100644 --- a/src/modules/desktop/kde-minimal.nix +++ b/src/modules/desktop/kde-minimal.nix @@ -3,7 +3,7 @@ with lib; let cfg = config.bora.desktop.kde; kdeMinimal = with pkgs; [ - plasma6 + kdePackages.plasma-desktop kdePackages.plasma-workspace kdePackages.kwin kdePackages.kirigami @@ -11,11 +11,11 @@ let kdePackages.plasma-integration kdePackages.breeze-icons kdePackages.breeze-gtk - kdePackages.breeze-qt5 + kdePackages.konsole kdePackages.systemsettings - dolphin - kate + kdePackages.dolphin + kdePackages.kate ]; in { @@ -70,10 +70,11 @@ in enableDefaultPackages = true; packages = with pkgs; [ noto-fonts - noto-fonts-cjk - noto-fonts-emoji + noto-fonts-cjk-sans + noto-fonts-color-emoji source-code-pro - (nerdfonts.override { fonts = [ "JetBrainsMono" "FiraCode" ]; }) + nerd-fonts.jetbrains-mono + nerd-fonts.fira-code ]; fontconfig.defaultFonts = { serif = [ "Noto Serif" ]; diff --git a/src/modules/desktop/maclike.nix b/src/modules/desktop/maclike.nix index 740f1cd..55115cc 100644 --- a/src/modules/desktop/maclike.nix +++ b/src/modules/desktop/maclike.nix @@ -1,11 +1,11 @@ -{ config, lib, pkgs, username, ... }: +{ config, lib, pkgs, ... }: with lib; let cfg = config.bora.desktop.layout; initScript = pkgs.writeShellScriptBin "bora-desktop-init" - (builtins.readFile ./../../../../scripts/maclike/init-desktop.sh); + (builtins.readFile ./../../../scripts/maclike/init-desktop.sh); finalizeScript = pkgs.writeShellScriptBin "bora-desktop-finalize" - (builtins.readFile ./../../../../scripts/maclike/finalize.sh); + (builtins.readFile ./../../../scripts/maclike/finalize.sh); in { options.bora.desktop.layout = { @@ -33,7 +33,7 @@ in config = mkIf cfg.enable { environment = { systemPackages = with pkgs; [ - plasma6 + kdePackages.plasma-desktop kdePackages.plasma-workspace kdePackages.kwin kdePackages.konsole @@ -44,11 +44,10 @@ in kdePackages.qqc2-breeze-style kdePackages.breeze-icons kdePackages.breeze-gtk - kdePackages.breeze-qt5 + kdePackages.plasma-integration tela-circle-icon-theme - (kdePackages.plasma6.pkgs.applet-window-buttons or kdePackages.applet-window-buttons) - (kdePackages.plasma6.pkgs.applet-window-title or kdePackages.applet-window-title) + kdePackages.applet-window-buttons6 initScript finalizeScript ]; @@ -63,14 +62,14 @@ in }; etc = { "skel/.config/plasma-org.kde.plasma.desktop-appletsrc".source = - ./../../../../config/desktop/plasma-appletsrc; + ./../../../config/desktop/plasma-appletsrc; "skel/.config/kdeglobals".source = - ./../../../../config/desktop/kdeglobals; + ./../../../config/desktop/kdeglobals; "skel/.config/kwinrc".source = - ./../../../../config/desktop/kwinrc; + ./../../../config/desktop/kwinrc; "skel/.config/khotkeysrc".text = builtins.replaceStrings [ "Alt+F1" ] [ cfg.nexusKey ] - (builtins.readFile ./../../../../config/desktop/khotkeysrc); + (builtins.readFile ./../../../config/desktop/khotkeysrc); "skel/.config/plasmarc".text = '' [Theme] name=Breeze From 74a319466a04b71760a1745f42e3b0134abf477c Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 20 May 2026 15:22:18 +0200 Subject: [PATCH 29/38] refactor: clean up module args, relative paths, and lib formatting --- lib/default.nix | 12 +- lib/hardware.nix | 23 +-- lib/spring.nix | 172 +++++++++++++---------- src/guests/example/instance.nix | 2 +- src/guests/example/pool.nix | 2 +- src/guests/sandbox.nix | 2 +- src/hosts/bora/default.nix | 2 +- src/hosts/bora/hardware.nix | 2 +- src/modules/containers/default.nix | 2 +- src/modules/containers/instance-pool.nix | 4 +- src/modules/containers/microvm-host.nix | 2 +- src/modules/containers/orchestrator.nix | 2 +- src/modules/core/boot.nix | 2 +- src/modules/core/default.nix | 2 +- src/modules/core/locale.nix | 2 +- src/modules/core/nix.nix | 2 +- src/modules/core/sysctl.nix | 2 +- src/modules/desktop/default.nix | 2 +- src/modules/filesystem/default.nix | 2 +- src/modules/filesystem/disko.nix | 2 +- src/modules/filesystem/impermanence.nix | 2 +- src/modules/filesystem/zfs.nix | 5 +- src/modules/hardware/cpu.nix | 2 +- src/modules/hardware/default.nix | 2 +- src/modules/hardware/platform.nix | 2 +- src/modules/network/base.nix | 2 +- src/modules/network/default.nix | 2 +- src/modules/network/dns.nix | 2 +- src/modules/security/default.nix | 2 +- src/modules/security/firewall.nix | 2 +- src/modules/security/hardening.nix | 2 +- src/modules/security/ssh.nix | 2 +- src/profiles/developer.nix | 2 +- src/profiles/minimal.nix | 2 +- src/profiles/server.nix | 2 +- src/profiles/workstation.nix | 2 +- tests/default.nix | 6 +- tests/modules.nix | 19 ++- 38 files changed, 163 insertions(+), 140 deletions(-) diff --git a/lib/default.nix b/lib/default.nix index e9dd728..d532bbb 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -1,9 +1,10 @@ { nixpkgs }: let - lib = nixpkgs.lib; + inherit (nixpkgs) lib; inherit (builtins) readDir pathExists; - inherit (lib) attrNames filter hasPrefix mapAttrsToList optionalString; -in { + inherit (lib) attrNames filter hasPrefix; +in +{ hardware = import ./hardware.nix { inherit lib; }; atomic = { preRebuildSnapshot = pool: dataset: '' @@ -17,6 +18,7 @@ in { let isDir = path: (pathExists (dir + "/${path}")) && (hasPrefix "." path); entries = attrNames (readDir dir); - dirs = filter (name: isDir name) entries; - in map (name: dir + "/${name}") dirs; + dirs = filter isDir entries; + in + map (name: dir + "/${name}") dirs; } diff --git a/lib/hardware.nix b/lib/hardware.nix index 0a27d24..fb0e28b 100644 --- a/lib/hardware.nix +++ b/lib/hardware.nix @@ -1,7 +1,6 @@ -{ lib }: -let - inherit (lib) mkIf mkDefault mkMerge toList; -in rec { +_: + +rec { cpu = { intel = { name = "Intel"; @@ -87,21 +86,25 @@ in rec { desktop = { powerManagement.enable = true; powerManagement.cpuFreqGovernor = "performance"; - services.power-profiles-daemon.enable = false; + services.power-profiles-daemon = { enable = false; }; }; laptop = { powerManagement.enable = true; powerManagement.cpuFreqGovernor = "powersave"; - services.power-profiles-daemon.enable = true; - services.tlp.enable = true; - services.auto-cpufreq.enable = true; + services = { + power-profiles-daemon = { enable = true; }; + tlp = { enable = true; }; + auto-cpufreq = { enable = true; }; + }; boot.kernelParams = [ "acpi_osi=Linux" ]; }; server = { powerManagement.enable = false; powerManagement.cpuFreqGovernor = "performance"; - services.power-profiles-daemon.enable = false; - services.tlp.enable = false; + services = { + power-profiles-daemon = { enable = false; }; + tlp = { enable = false; }; + }; boot.kernelParams = [ "nmi_watchdog=0" "nowatchdog" ]; }; }; diff --git a/lib/spring.nix b/lib/spring.nix index f1c668d..da5ff9d 100644 --- a/lib/spring.nix +++ b/lib/spring.nix @@ -1,16 +1,16 @@ -{ lib, pkgs, config ? null, ... }: +{ lib, pkgs, ... }: let inherit (builtins) toString mapAttrs; inherit (lib) - types mkOption mkIf mkEnableOption mkDefault + types mkOption mkEnableOption mapAttrsToList filterAttrs - optional optionals optionalString concatStringsSep - any all attrValues elem; -in rec { - springType = types.submodule ({ name, ... }: { + optional any attrValues; +in +rec { + springType = types.submodule (_: { options = { application = mkOption { - type = types.submodule ({ ... }: { + type = types.submodule (_: { options = { enable = mkEnableOption "Spring application context"; name = mkOption { type = types.str; }; @@ -22,10 +22,10 @@ in rec { globalLimits = mkOption { type = types.submodule { options = { - memory = mkOption { type = types.str; default = "0" ; }; - cpu = mkOption { type = types.str; default = "0" ; }; - pids = mkOption { type = types.int; default = 0 ; }; - io = mkOption { type = types.str; default = "0" ; }; + memory = mkOption { type = types.str; default = "0"; }; + cpu = mkOption { type = types.str; default = "0"; }; + pids = mkOption { type = types.int; default = 0; }; + io = mkOption { type = types.str; default = "0"; }; }; }; default = { }; @@ -34,10 +34,10 @@ in rec { type = types.submodule { options = { enable = mkEnableOption "Circuit breaker"; - failureThreshold = mkOption { type = types.int; default = 5 ; }; - successThreshold = mkOption { type = types.int; default = 2 ; }; - timeoutMs = mkOption { type = types.int; default = 30000 ; }; - halfOpenMax = mkOption { type = types.int; default = 3 ; }; + failureThreshold = mkOption { type = types.int; default = 5; }; + successThreshold = mkOption { type = types.int; default = 2; }; + timeoutMs = mkOption { type = types.int; default = 30000; }; + halfOpenMax = mkOption { type = types.int; default = 3; }; }; }; default = { }; @@ -47,7 +47,7 @@ in rec { default = { }; }; beans = mkOption { - type = types.attrsOf (types.submodule ({ ... }: { + type = types.attrsOf (types.submodule (_: { options = { enable = mkEnableOption "Spring bean"; class = mkOption { type = types.str; }; @@ -55,14 +55,14 @@ in rec { resources = mkOption { type = types.submodule { options = { - cpu = mkOption { type = types.str; default = "1" ; }; - memory = mkOption { type = types.str; default = "256M" ; }; - memoryMax = mkOption { type = types.str; default = "512M" ; }; - pids = mkOption { type = types.int; default = 256 ; }; - ioRbps = mkOption { type = types.str; default = "100M" ; }; - ioRops = mkOption { type = types.int; default = 1000 ; }; - ioWbps = mkOption { type = types.str; default = "100M" ; }; - ioWops = mkOption { type = types.int; default = 1000 ; }; + cpu = mkOption { type = types.str; default = "1"; }; + memory = mkOption { type = types.str; default = "256M"; }; + memoryMax = mkOption { type = types.str; default = "512M"; }; + pids = mkOption { type = types.int; default = 256; }; + ioRbps = mkOption { type = types.str; default = "100M"; }; + ioRops = mkOption { type = types.int; default = 1000; }; + ioWbps = mkOption { type = types.str; default = "100M"; }; + ioWops = mkOption { type = types.int; default = 1000; }; numa = mkOption { type = types.nullOr (types.listOf types.int); default = null; @@ -123,48 +123,60 @@ in rec { beanGraph = beans: let names = attrNames beans; - edges = map (n: { - name = n; - deps = beans.${n}.deps or [ ]; - dependsOn = beans.${n}.dependsOn or [ ]; - allDeps = (beans.${n}.deps or [ ]) ++ (beans.${n}.dependsOn or [ ]); - }) names; - in edges; + edges = map + (n: { + name = n; + deps = beans.${n}.deps or [ ]; + dependsOn = beans.${n}.dependsOn or [ ]; + allDeps = (beans.${n}.deps or [ ]) ++ (beans.${n}.dependsOn or [ ]); + }) + names; + in + edges; tsort = items: let - sorted = lib.toposort (a: b: - let - aDependsOnB = any (dep: dep == b.name) a.allDeps; - bDependsOnA = any (dep: dep == a.name) b.allDeps; - in + sorted = lib.toposort + (a: b: + let + aDependsOnB = any (dep: dep == b.name) a.allDeps; + bDependsOnA = any (dep: dep == a.name) b.allDeps; + in if aDependsOnB then lib.TO_AFTER else if bDependsOnA then lib.TO_BEFORE else lib.TO_UNORDERED - ) items; - in sorted.result or [ ]; + ) + items; + in + sorted.result or [ ]; detectCircular = beans: let - result = lib.toposort (a: b: - let - aDependsOnB = any (dep: dep == b.name) a.allDeps; - bDependsOnA = any (dep: dep == a.name) b.allDeps; - in + result = lib.toposort + (a: b: + let + aDependsOnB = any (dep: dep == b.name) a.allDeps; + bDependsOnA = any (dep: dep == a.name) b.allDeps; + in if aDependsOnB then lib.TO_AFTER else if bDependsOnA then lib.TO_BEFORE else lib.TO_UNORDERED - ) (beanGraph beans); - in result.cyclic or [ ]; + ) + (beanGraph beans); + in + result.cyclic or [ ]; resolveBeanConfig = beans: let graph = beanGraph beans; sortedNames = map (n: n.name) (tsort graph); - in lib.listToAttrs (map (name: { - inherit name; - value = beans.${name}; - }) sortedNames); + in + lib.listToAttrs (map + (name: { + inherit name; + value = beans.${name}; + }) + sortedNames); scriptsDir = ../scripts/spring; cgroupInit = builtins.readFile (scriptsDir + "/cgroup-init.sh"); @@ -181,7 +193,8 @@ in rec { depServices = map (d: "spring-${appName}-${d}.service") deps; afterServices = depServices ++ bean.after; wantServices = depServices ++ bean.wants; - in { + in + { "spring-${appName}-${beanName}" = { description = "Spring Bean: ${beanName} (${bean.class})"; after = afterServices; @@ -248,33 +261,44 @@ in rec { let app = springConfig.application; appName = app.name; - beans = filterAttrs (n: v: v.enable) springConfig.beans; + beans = filterAttrs (_: v: v.enable) springConfig.beans; resolved = resolveBeanConfig beans; sortedBeans = attrValues resolved; - serviceDefs = builtins.foldl' (acc: bean: - let - name = builtins.elemAt (builtins.filter (n: beans.${n} == bean) (attrNames beans)) 0; - in acc // mkSystemdService appName name bean - ) { } sortedBeans; - in { - assertions = optional (springConfig.autowire.circularCheck) - (let cycles = detectCircular beans; in { - assertion = cycles == [ ]; - message = "Spring: circular dependency detected in beans: ${toString cycles}"; - }); - systemd.slices = mkSystemdSlice appName app; - systemd.services = serviceDefs; - systemd.extraConfig = '' - DefaultMemoryAccounting=yes - DefaultCPUAccounting=yes - DefaultIOAccounting=yes - DefaultTasksAccounting=yes - ''; + serviceDefs = builtins.foldl' + (acc: bean: + let + name = builtins.elemAt (builtins.filter (n: beans.${n} == bean) (attrNames beans)) 0; + in + acc // mkSystemdService appName name bean + ) + { } + sortedBeans; + in + { + assertions = optional springConfig.autowire.circularCheck + ( + let cycles = detectCircular beans; in { + assertion = cycles == [ ]; + message = "Spring: circular dependency detected in beans: ${toString cycles}"; + } + ); + systemd = { + slices = mkSystemdSlice appName app; + services = serviceDefs; + extraConfig = '' + DefaultMemoryAccounting=yes + DefaultCPUAccounting=yes + DefaultIOAccounting=yes + DefaultTasksAccounting=yes + ''; + }; environment.etc."spring/${appName}/beans.json".text = - builtins.toJSON (mapAttrs (n: v: { - inherit (v) class deps resources; - healthcheck = v.healthcheck or null; - }) beans); + builtins.toJSON (mapAttrs + (_: v: { + inherit (v) class deps resources; + healthcheck = v.healthcheck or null; + }) + beans); environment.systemPackages = [ (wrapBin "spring-${appName}-status" "spring-status.sh") (wrapBin "spring-${appName}-resources" "spring-resources.sh") diff --git a/src/guests/example/instance.nix b/src/guests/example/instance.nix index 320a406..d2ae93f 100644 --- a/src/guests/example/instance.nix +++ b/src/guests/example/instance.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, modulesPath, ... }: +{ modulesPath, ... }: { imports = [ "${modulesPath}/profiles/minimal.nix" ]; diff --git a/src/guests/example/pool.nix b/src/guests/example/pool.nix index 7dbb893..04adc0a 100644 --- a/src/guests/example/pool.nix +++ b/src/guests/example/pool.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +_: { imports = [ ./instance.nix ]; diff --git a/src/guests/sandbox.nix b/src/guests/sandbox.nix index 0caa5af..010dbc5 100644 --- a/src/guests/sandbox.nix +++ b/src/guests/sandbox.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, modulesPath, ... }: +{ pkgs, modulesPath, ... }: { imports = [ "${modulesPath}/profiles/minimal.nix" ]; diff --git a/src/hosts/bora/default.nix b/src/hosts/bora/default.nix index 184974a..f9010bf 100644 --- a/src/hosts/bora/default.nix +++ b/src/hosts/bora/default.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, hostname, username, hardwareProfile, systemProfile, ... }: +{ hostname, username, ... }: { networking.hostName = hostname; diff --git a/src/hosts/bora/hardware.nix b/src/hosts/bora/hardware.nix index b5b25e0..867fc73 100644 --- a/src/hosts/bora/hardware.nix +++ b/src/hosts/bora/hardware.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, modulesPath, ... }: +{ config, lib, modulesPath, ... }: let fsCfg = config.bora.filesystem; in diff --git a/src/modules/containers/default.nix b/src/modules/containers/default.nix index 1458536..1bd35a6 100644 --- a/src/modules/containers/default.nix +++ b/src/modules/containers/default.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +_: { imports = [ ./microvm-host.nix diff --git a/src/modules/containers/instance-pool.nix b/src/modules/containers/instance-pool.nix index 230f893..58a61bc 100644 --- a/src/modules/containers/instance-pool.nix +++ b/src/modules/containers/instance-pool.nix @@ -2,7 +2,7 @@ with lib; let cfg = config.bora.containers.instancePool; - scriptsDir = ./../../../../scripts/pool; + scriptsDir = ./../../../scripts/pool; poolManager = pkgs.writeShellScriptBin "pool-manager" (builtins.readFile (scriptsDir + "/pool-manager.sh")); spawnScript = pkgs.writeShellScriptBin "pool-spawn" @@ -102,7 +102,7 @@ in LimitNPROC = 1048576; }; script = '' - ${builtins.readFile ./../../../../scripts/pool/pool-manager.sh} + ${builtins.readFile (scriptsDir + "/pool-manager.sh")} ''; }; }; diff --git a/src/modules/containers/microvm-host.nix b/src/modules/containers/microvm-host.nix index 1a002d7..cf5118d 100644 --- a/src/modules/containers/microvm-host.nix +++ b/src/modules/containers/microvm-host.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, lib, ... }: with lib; let cfg = config.bora.containers.microvm; in { options.bora.containers.microvm = { diff --git a/src/modules/containers/orchestrator.nix b/src/modules/containers/orchestrator.nix index 00c9de9..cfbbf9f 100644 --- a/src/modules/containers/orchestrator.nix +++ b/src/modules/containers/orchestrator.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, lib, ... }: with lib; let cfg = config.bora.orchestrator; diff --git a/src/modules/core/boot.nix b/src/modules/core/boot.nix index c061f76..30cc32b 100644 --- a/src/modules/core/boot.nix +++ b/src/modules/core/boot.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ lib, pkgs, ... }: { boot = { loader = { diff --git a/src/modules/core/default.nix b/src/modules/core/default.nix index 37ad942..e7a4bc5 100644 --- a/src/modules/core/default.nix +++ b/src/modules/core/default.nix @@ -1,4 +1,4 @@ -{ lib, ... }: +_: { imports = [ ./boot.nix diff --git a/src/modules/core/locale.nix b/src/modules/core/locale.nix index 25190af..6fc53c9 100644 --- a/src/modules/core/locale.nix +++ b/src/modules/core/locale.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ lib, pkgs, ... }: { time.timeZone = lib.mkDefault "Europe/Rome"; i18n = { diff --git a/src/modules/core/nix.nix b/src/modules/core/nix.nix index 02516b1..3bba234 100644 --- a/src/modules/core/nix.nix +++ b/src/modules/core/nix.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ lib, pkgs, ... }: { nix = { package = pkgs.nixVersions.stable; diff --git a/src/modules/core/sysctl.nix b/src/modules/core/sysctl.nix index 0535336..a4a15a1 100644 --- a/src/modules/core/sysctl.nix +++ b/src/modules/core/sysctl.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +_: { boot.kernel.sysctl = { "kernel.kptr_restrict" = 2; diff --git a/src/modules/desktop/default.nix b/src/modules/desktop/default.nix index 6480e33..93236ee 100644 --- a/src/modules/desktop/default.nix +++ b/src/modules/desktop/default.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +_: { imports = [ ./kde-minimal.nix diff --git a/src/modules/filesystem/default.nix b/src/modules/filesystem/default.nix index 9382732..500ce19 100644 --- a/src/modules/filesystem/default.nix +++ b/src/modules/filesystem/default.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +_: { imports = [ ./zfs.nix diff --git a/src/modules/filesystem/disko.nix b/src/modules/filesystem/disko.nix index 5612db9..7a77f86 100644 --- a/src/modules/filesystem/disko.nix +++ b/src/modules/filesystem/disko.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, lib, ... }: with lib; let cfg = config.bora.filesystem.disko; in { options.bora.filesystem.disko = { diff --git a/src/modules/filesystem/impermanence.nix b/src/modules/filesystem/impermanence.nix index 1657650..9aa34af 100644 --- a/src/modules/filesystem/impermanence.nix +++ b/src/modules/filesystem/impermanence.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, username, ... }: +{ username, ... }: { environment.persistence."/persist" = { diff --git a/src/modules/filesystem/zfs.nix b/src/modules/filesystem/zfs.nix index a63207f..7cd9d63 100644 --- a/src/modules/filesystem/zfs.nix +++ b/src/modules/filesystem/zfs.nix @@ -1,6 +1,5 @@ -{ config, lib, pkgs, ... }: -with lib; -let cfg = config.bora.filesystem; in { +{ lib, ... }: +with lib; { options.bora.filesystem.bootDevice = mkOption { type = types.str; default = "/dev/disk/by-uuid/BOOT-UUID"; diff --git a/src/modules/hardware/cpu.nix b/src/modules/hardware/cpu.nix index 2194f2d..9ab0d23 100644 --- a/src/modules/hardware/cpu.nix +++ b/src/modules/hardware/cpu.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, hardwareDB, ... }: +{ config, lib, hardwareDB, ... }: with lib; let cpuVendor = config.bora.hardware.cpuVendor or "intel"; diff --git a/src/modules/hardware/default.nix b/src/modules/hardware/default.nix index 05e0157..8d7f9e4 100644 --- a/src/modules/hardware/default.nix +++ b/src/modules/hardware/default.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, hardwareDB, ... }: +_: { imports = [ ./cpu.nix diff --git a/src/modules/hardware/platform.nix b/src/modules/hardware/platform.nix index 7ed633c..7c59f20 100644 --- a/src/modules/hardware/platform.nix +++ b/src/modules/hardware/platform.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, hardwareDB, ... }: +{ config, lib, hardwareDB, ... }: with lib; let inherit (hardwareDB) profileOpts; diff --git a/src/modules/network/base.nix b/src/modules/network/base.nix index 9c06a03..3f710ef 100644 --- a/src/modules/network/base.nix +++ b/src/modules/network/base.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ lib, pkgs, ... }: { networking = { networkmanager.enable = true; diff --git a/src/modules/network/default.nix b/src/modules/network/default.nix index 115b6e1..ac3716f 100644 --- a/src/modules/network/default.nix +++ b/src/modules/network/default.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +_: { imports = [ ./base.nix diff --git a/src/modules/network/dns.nix b/src/modules/network/dns.nix index 62ffcc9..12f8c13 100644 --- a/src/modules/network/dns.nix +++ b/src/modules/network/dns.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +_: { services.resolved = { enable = true; diff --git a/src/modules/security/default.nix b/src/modules/security/default.nix index 831e03b..2b4a0f3 100644 --- a/src/modules/security/default.nix +++ b/src/modules/security/default.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +_: { imports = [ ./firewall.nix diff --git a/src/modules/security/firewall.nix b/src/modules/security/firewall.nix index aeaab11..fc6ab71 100644 --- a/src/modules/security/firewall.nix +++ b/src/modules/security/firewall.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +_: { networking.nftables = { enable = true; diff --git a/src/modules/security/hardening.nix b/src/modules/security/hardening.nix index b746846..7bbaced 100644 --- a/src/modules/security/hardening.nix +++ b/src/modules/security/hardening.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ lib, pkgs, ... }: { security = { apparmor = { diff --git a/src/modules/security/ssh.nix b/src/modules/security/ssh.nix index b383f75..d4ba529 100644 --- a/src/modules/security/ssh.nix +++ b/src/modules/security/ssh.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +_: { services.openssh = { enable = true; diff --git a/src/profiles/developer.nix b/src/profiles/developer.nix index 3a062ca..5e47ae0 100644 --- a/src/profiles/developer.nix +++ b/src/profiles/developer.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ lib, pkgs, ... }: with lib; { imports = [ diff --git a/src/profiles/minimal.nix b/src/profiles/minimal.nix index 49d5e73..540ea52 100644 --- a/src/profiles/minimal.nix +++ b/src/profiles/minimal.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ lib, ... }: with lib; { bora = { diff --git a/src/profiles/server.nix b/src/profiles/server.nix index a7c0337..f3b39b9 100644 --- a/src/profiles/server.nix +++ b/src/profiles/server.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ lib, ... }: with lib; { bora = { diff --git a/src/profiles/workstation.nix b/src/profiles/workstation.nix index 744bd32..c05ed10 100644 --- a/src/profiles/workstation.nix +++ b/src/profiles/workstation.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ lib, ... }: with lib; { bora = { diff --git a/tests/default.nix b/tests/default.nix index 89107ad..9ca494b 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -1,7 +1,7 @@ { system ? builtins.currentSystem, nixpkgs ? import { inherit system; } }: let - lib = nixpkgs.lib; + inherit (nixpkgs) lib; boraLib = import ../lib { inherit nixpkgs; }; hw = boraLib.hardware; @@ -10,7 +10,7 @@ let assertEq = name: actual: expected: if actual == expected then { ${name} = { ok = true; }; } - else { ${name} = { ok = false; expected = expected; actual = actual; }; }; + else { ${name} = { ok = false; inherit expected actual; }; }; in @@ -106,7 +106,6 @@ in sorted = spring.tsort (spring.beanGraph healthyBeans); names = map (n: n.name) sorted; dbIdx = builtins.elemAt (builtins.filter (i: builtins.elemAt names i == "database") (lib.genList (x: x) (builtins.length names))) 0; - cacheIdx = builtins.elemAt (builtins.filter (i: builtins.elemAt names i == "cache") (lib.genList (x: x) (builtins.length names))) 0; webappIdx = builtins.elemAt (builtins.filter (i: builtins.elemAt names i == "webapp") (lib.genList (x: x) (builtins.length names))) 0; in assertEq "tsort.database_before_webapp" (dbIdx < webappIdx) true; @@ -170,7 +169,6 @@ in ( let boraLib = import ../lib { inherit nixpkgs; }; - scanModules = boraLib.scanModules; in { testAtomicPreRebuildSnapshot = assertEq "atomic.preRebuildSnapshot" diff --git a/tests/modules.nix b/tests/modules.nix index bba8191..19519c3 100644 --- a/tests/modules.nix +++ b/tests/modules.nix @@ -1,13 +1,10 @@ { system ? builtins.currentSystem, nixpkgs ? import { inherit system; } }: let - lib = nixpkgs.lib; - inherit (builtins) toString; - assertEq = name: actual: expected: if actual == expected then { ${name} = { ok = true; }; } - else { ${name} = { ok = false; expected = expected; actual = actual; }; }; + else { ${name} = { ok = false; inherit expected actual; }; }; in # ============================================================================= @@ -15,14 +12,14 @@ in # ============================================================================= ( let - hardwareDB = import ../lib/hardware.nix { lib = nixpkgs.lib; }; + hardwareDB = import ../lib/hardware.nix { inherit (nixpkgs) lib; }; # Minimal NixOS config that exercises module loading via configuration.nix minimalConfig = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ ../configuration.nix - ({ pkgs, lib, ... }: { + (_: { system.stateVersion = "25.11"; users.users.root.hashedPassword = "!"; boot.loader.grub.enable = false; @@ -77,7 +74,7 @@ in # ============================================================================= ( let - hardwareDB = import ../lib/hardware.nix { lib = nixpkgs.lib; }; + hardwareDB = import ../lib/hardware.nix { inherit (nixpkgs) lib; }; makeConfig = profile: let @@ -85,7 +82,7 @@ in system = "x86_64-linux"; modules = [ ../configuration.nix - ({ pkgs, lib, ... }: { + (_: { system.stateVersion = "25.11"; users.users.root.hashedPassword = "!"; boot.loader.grub.enable = false; @@ -131,13 +128,13 @@ in # ============================================================================= ( let - hardwareDB = import ../lib/hardware.nix { lib = nixpkgs.lib; }; + hardwareDB = import ../lib/hardware.nix { inherit (nixpkgs) lib; }; configWithSpring = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ ../configuration.nix - ({ pkgs, lib, ... }: { + (_: { system.stateVersion = "25.11"; users.users.root.hashedPassword = "!"; boot.loader.grub.enable = false; @@ -145,7 +142,7 @@ in fileSystems."/" = { device = "/dev/null"; fsType = "tmpfs"; }; nixpkgs.config.allowUnfree = true; }) - ({ pkgs, lib, ... }: { + (_: { bora.spring.application = { enable = true; name = "testapp"; From a85f1883aabab5b0fb57540a8aa5db2b0a058db3 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 20 May 2026 15:22:23 +0200 Subject: [PATCH 30/38] feat: add multi-ISO build script and GitHub release CI --- .github/workflows/release.yml | 29 +++++++++++++++++++- scripts/build/iso-build.sh | 51 ++++++++++++++++++++--------------- 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 86a6e10..cff35ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,9 @@ on: - "v*.*.*" - "*.*" +permissions: + contents: write + jobs: build-iso: runs-on: ubuntu-latest @@ -22,9 +25,33 @@ jobs: run: | NIXPKGS_ALLOW_BROKEN=1 nix build --impure \ '.#packages.x86_64-linux.iso-${{ matrix.variant }}' + - name: Rename ISO + run: | + cp result bora-${{ matrix.variant }}.iso - name: Upload ISO ${{ matrix.variant }} uses: actions/upload-artifact@v4 with: name: bora-${{ matrix.variant }} - path: result + path: bora-${{ matrix.variant }}.iso compression-level: 0 + if-no-files-found: error + + release: + runs-on: ubuntu-latest + needs: [build-iso] + steps: + - uses: actions/checkout@v4 + - name: Download all ISOs + uses: actions/download-artifact@v4 + with: + pattern: bora-* + path: isos + merge-multiple: true + - name: Create Release with auto-generated changelog + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${{ github.ref_name }}" \ + --generate-notes \ + --title "${{ github.ref_name }}" \ + isos/*.iso diff --git a/scripts/build/iso-build.sh b/scripts/build/iso-build.sh index 6aec821..1c86d5a 100755 --- a/scripts/build/iso-build.sh +++ b/scripts/build/iso-build.sh @@ -3,32 +3,41 @@ set -euo pipefail PROJECT_DIR="$(cd "$(dirname "$0")/../.." && pwd)" OUTPUT_DIR="${PROJECT_DIR}/dist" -ISO_NAME="${ISO_NAME:-bora.iso}" -BUILD_TARGET="${BUILD_TARGET:-.#packages.x86_64-linux.iso-minimal}" NIXPKGS_ALLOW_BROKEN="${NIXPKGS_ALLOW_BROKEN:-1}" cd "${PROJECT_DIR}" -NIXPKGS_ALLOW_BROKEN="${NIXPKGS_ALLOW_BROKEN}" \ -sudo --preserve-env=NIXPKGS_ALLOW_BROKEN \ - nix build --impure "${BUILD_TARGET}" +mkdir -p "${OUTPUT_DIR}" -RESULT_DIR="$(readlink -f result)" -ISO_PATH="${RESULT_DIR}/iso/${ISO_NAME}" +build_iso() { + local target="$1" + local name="$2" -if [ ! -f "${ISO_PATH}" ]; then - echo "ISO not found at ${ISO_PATH}, searching..." - ISO_PATH=$(find "${RESULT_DIR}" -name "*.iso" -type f | head -1) -fi + echo "Building ${target} -> ${name}..." -if [ -z "${ISO_PATH}" ] || [ ! -f "${ISO_PATH}" ]; then - echo "ERROR: ISO not found in build result" - find "${RESULT_DIR}" -type f | head -20 - exit 1 -fi + NIXPKGS_ALLOW_BROKEN="${NIXPKGS_ALLOW_BROKEN}" \ + sudo --preserve-env=NIXPKGS_ALLOW_BROKEN \ + nix build --impure "${target}" -mkdir -p "${OUTPUT_DIR}" -cp "${ISO_PATH}" "${OUTPUT_DIR}/${ISO_NAME}" -chmod 644 "${OUTPUT_DIR}/${ISO_NAME}" -ls -lh "${OUTPUT_DIR}/${ISO_NAME}" + local result_dir result_iso + result_dir="$(readlink -f result)" + result_iso=$(find "${result_dir}" -name "*.iso" -type f | head -1) + + if [ -z "${result_iso}" ]; then + echo "ERROR: ISO not found in build result for ${target}" + find "${result_dir}" -type f | head -20 + exit 1 + fi + + cp "${result_iso}" "${OUTPUT_DIR}/${name}" + chmod 644 "${OUTPUT_DIR}/${name}" + ls -lh "${OUTPUT_DIR}/${name}" +} + +build_iso '.#packages.x86_64-linux.iso-minimal' 'bora-minimal.iso' +build_iso '.#packages.x86_64-linux.iso-desktop' 'bora-desktop.iso' +build_iso '.#packages.x86_64-linux.iso-laptop' 'bora-laptop.iso' +build_iso '.#packages.x86_64-linux.iso-server' 'bora-server.iso' -echo "ISO built and copied to ${OUTPUT_DIR}/${ISO_NAME}" +echo "" +echo "All ISOs built in ${OUTPUT_DIR}:" +ls -lh "${OUTPUT_DIR}"/bora-*.iso From 48e5f4429346195ef1c9dda38589f3c355360cbe Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 20 May 2026 15:40:40 +0200 Subject: [PATCH 31/38] fix: disable flakehub in nix-installer-action --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cff35ec..9a20092 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,6 +18,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v14 + with: + flakehub: false - uses: DeterminateSystems/magic-nix-cache-action@v8 with: use-flakehub: false From 0b32ed33cbaeb7b7545173cbae43d2c4ced44008 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 20 May 2026 15:41:11 +0200 Subject: [PATCH 32/38] docs: convert to English, remove titles and lists --- AGENTS.md | 111 +++++++++++++++---------------------------- CHANGELOG.md | 34 ++----------- README.md | 14 +----- docs/ARCHITECTURE.md | 26 +--------- docs/MANUAL.md | 32 +------------ 5 files changed, 47 insertions(+), 170 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ef5fcd3..bd48842 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,111 +1,78 @@ BORA NixOS AgentiC Rules and Sprint Definitions Strict Hard Zero Hardcoding Zero Comments Zero Inline Shell -Version 2.0.0 Sprint Fondazione +Version 2.0.0 Sprint Foundation -INDICE +The repository directory tree follows this structure. The root contains flake.nix which is the stateless pure entry point. configuration.nix is the module loader that performs dynamic auto scan. AGENTS.md is this file with agentic rules and sprint definitions. lib contains Nix libraries with pure functions exported by default.nix hardware.nix for CPU GPU and platform auto detection and spring.nix for the DI IoC Container with Circuit Breaker. src contains the NixOS source with hosts for per machine host definitions profiles for per use case profile definitions modules organized by category guests for MicroVM guest definitions config for runtime config files scripts for shell scripts assets for static assets secrets for secrets encrypted with SOPS and age tests for Nix tests and docs for documentation. -1. Architettura del Repository -2. Regole Assolute -3. Parametrizzazione Zero Hardcoding -4. Sprint Definitions -5. Spring Framework Specifica Tecnica -6. Resource Management e Circuit Breaker -7. Security Baseline -8. Idempotenza e Atomicita -9. Testing e Quality Gates -10. Flow Operativo Agente +The architectural principles are as follows. Single responsibility means every Nix file has one purpose. No side effects means lib functions are pure with no side effects. Auto discovery means configuration.nix scans src/modules without manual imports. Parameterization means everything uses options with mkOption without hardcoding. External shell means shell scripts in scripts referenced via builtins.readFile. External config means config files in config referenced via relative path. -1. ARCHITETTURA DEL REPOSITORY +Rule 1 is Zero Comments in Nix files. Inline hash comments are forbidden. Block slash asterisk comments are forbidden. Multi line comments are forbidden. Technical documentation goes in AGENTS.md and user documentation in docs. Only AGENTS.md and files in docs may contain text. -L albero delle directory del repository segue questa struttura. La radice contiene flake.nix che e il punto di ingresso stateless e puro. configuration.nix e il module loader che esegue auto scan dinamico. AGENTS.md e questo file con le regole agentiche e le sprint definitions. lib contiene le Nix libraries con funzioni pure esportate da default.nix hardware.nix per auto detection di CPU GPU e platform e spring.nix per il DI IoC Container con Circuit Breaker. src contiene il NixOS source con hosts per le host definitions per macchina profiles per le profile definitions per use case modules organizzati per categoria guests per le MicroVM guest definitions config per i runtime config files scripts per gli shell scripts assets per gli static assets secrets per i secret crittografati con SOPS e age tests per i Nix tests e docs per la documentazione. +Rule 2 is Zero Shell Inline in Nix. Writing shell scripts inside quoted Nix strings is forbidden. Using pkgs.writeShellScript with inline strings is forbidden. Using shell expressions inside Nix strings is forbidden. Every shell script must be in scripts as a separate file. The correct reference is pkgs.writeShellScriptBin name with builtins.readFile reading the script path. -I principi architetturali sono i seguenti. Single responsibility significa che ogni file Nix ha un solo scopo. No side effects significa che le funzioni in lib sono pure senza effetti collaterali. Auto discovery significa che configuration.nix scansiona src/modules senza import manuali. Parametrizzazione significa tutto via options con mkOption senza hardcoded. External shell significa script shell in scripts riferiti via builtins.readFile. External config significa file config in config riferiti via path relativo. +Rule 3 is Zero Hardcoding. Hardcoding usernames like alessio or kairosci is forbidden. Hardcoding hostnames like bora or os is forbidden. Hardcoding paths IPs ports or UUIDs is forbidden. Hardcoding CPU GPU or RAM config is forbidden. Username must come from meta.nix or option. Hostname must come from meta.nix or option. Hardware must use options with lib.mkDefault. Activation must use lib.mkIf. No literal values every value must be a variable. -2. REGOLE ASSOLUTE +Rule 4 is Structural Atomicity. Every modification must produce an atomic new generation. Workarounds fallbacks and placeholders are forbidden. TODO FIXME and HACK are forbidden. Comments to disable code are forbidden. To disable a module use mkIf false. An unimplemented function must not exist. -Regola 1 e Zero Comments nei file Nix. Vietato il commento inline con hash. Vietato il commento a blocchi con slash asterisco. Vietato il commento multilinea. La documentazione va in AGENTS.md per la documentazione tecnica e in docs per la documentazione utente. Solo AGENTS.md e i file in docs possono contenere testo. +Rule 5 is Dynamic Modularity. configuration.nix scans src/modules automatically. Each category corresponds to src/modules/category. Each category has default.nix which imports submodules. Modules are enabled via mkIf cfg.enable. Profiles activate combinations of modules. To create a new module create src/modules/category/name.nix update src/modules/category/default.nix define options with enable and parameters and use mkIf cfg.enable for config. -Regola 2 e Zero Shell Inline in Nix. Vietato scrivere script shell dentro stringhe Nix tra apici. Vietato usare pkgs.writeShellScript con stringhe inline. Vietato utilizzare espressioni shell dentro stringhe Nix. Ogni script shell deve essere in scripts come file separato. Il riferimento corretto e pkgs.writeShellScriptBin name con builtins.readFile che legge il percorso dello script. +All host specific parameters are declared in src/hosts/hostname/meta.nix and injected via specialArgs. The generic meta.nix template contains system as system architecture hardware as hardware type profile as usage profile hostname as host name and username as user name. The substitution rules are that username in users.users becomes the username value username in home paths becomes home with username hostname in networking.hostName becomes the hostname value hostname in spring.application.name becomes the hostname value persist in environment.persistence reads from option and absolute paths for config and scripts are relative paths. -Regola 3 e Zero Hardcoding. Vietato hardcodare username come alessio o kairosci. Vietato hardcodare hostname come bora o os. Vietato hardcodare path IP porte o UUID. Vietato hardcodare CPU GPU o RAM config. L username deve venire da meta.nix o option. L hostname deve venire da meta.nix o option. L hardware deve usare opzioni con lib.mkDefault. L attivazione deve usare lib.mkIf. Nessun valore letterale ogni valore deve essere una variabile. +Sprint 1 is Foundation with the goal of creating the base system structure. It includes flake.nix as pure entry point with declarative inputs configuration.nix as auto scan module loader lib/default.nix exporting all libraries lib/hardware.nix as CPU GPU and Platform database src/modules/core for Boot Nix Locale and Sysctl src/hosts/hostname with meta default and hardware and AGENTS.md. -Regola 4 e Atomicita Strutturale. Ogni modifica deve produrre un new generation atomico. Vietati workaround fallback e placeholders. Vietati TODO FIXME e HACK. Vietati commenti per disabilitare codice. Per disabilitare un modulo si usa mkIf false. Una funzione non implementata non deve esistere. +Sprint 2 is Filesystem and Immutability with the goal of implementing ZFS Impermanence and Disko. It includes the zfs module for pool ARC and snapshot the impermanence module for persist config desktop for external config files sanoid for automatic snapshot retention and disko for declarative partitioning. -Regola 5 e Modularita Dinamica. configuration.nix scansiona src/modules automaticamente. Ogni categoria corrisponde a src/modules/categoria. Ogni categoria ha default.nix che importa i sottomoduli. I moduli si abilitano via mkIf cfg.enable. I profili attivano combinazioni di moduli. Per creare un nuovo modulo si crea src/modules/categoria/nome.nix si aggiorna src/modules/categoria/default.nix si definiscono options con enable e parametri e si usa mkIf cfg.enable per la config. +Sprint 3 is Security with the goal of implementing extreme hardening firewall and SSH. It includes the firewall module with nftables default drop the external nftables configuration the hardening module for kernel and AppArmor the ssh module with keys only LAN only and audit logging with fail2ban. -3. PARAMETRIZZAZIONE ZERO HARDCODING +Sprint 4 is Hardware Detection with the goal of auto configuring CPU GPU and Platform. It includes the cpu module for Intel AMD and ARM the gpu module for NVIDIA AMD and Intel the platform module for Desktop Laptop and Server and lib/hardware.nix as vendor optimization database. -Tutti i parametri host specific sono dichiarati in src/hosts/hostname/meta.nix e iniettati via specialArgs. Il template generico di meta.nix contiene system come architettura di sistema hardware come tipo di hardware profile come profilo d uso hostname come nome host e username come nome utente. Le regole di sostituzione prevedono che username nei users.users diventi il valore di username username nei path home diventi home con username hostname in networking.hostName diventi il valore di hostname hostname in spring.application.name diventi il valore di hostname persist in environment.persistence legga da option e i path assoluti per config e scripts siano path relativi. +Sprint 5 is Desktop and Bora Layout with the goal of creating minimal KDE Plasma 6 with original Bora layout. It includes the kde-minimal module for essential Plasma 6 the maclike module for the Bora theme the pipewire module for audio the maclike scripts for init and finalize shell and the desktop config files for plasma-appletsrc kdeglobals and kwinrc. -4. SPRINT DEFINITIONS +Sprint 6 is Container Engine with the goal of creating the container engine with hardware level isolation. It includes the microvm-host module for host and bridge the orchestrator module for pool manager the sandbox guest as generic template the containers configuration for bridge and networking and SocketVM for desktop apps with X11 and Wayland forwarding. -Sprint 1 e Fondazione con lo scopo di creare la struttura base del sistema funzionante. Include flake.nix come entry point puro con inputs dichiarativi configuration.nix come module loader auto scan lib/default.nix che esporta tutte le librerie lib/hardware.nix come database CPU GPU e Platform src/modules/core per Boot Nix Locale e Sysctl src/hosts/hostname con meta default e hardware e AGENTS.md. +Sprint 7 is Spring Framework with the goal of implementing Dependency Injection and Circuit Breaker. It includes lib/spring.nix for bean definitions topological sort mkSystemdService with resource limits circuit breaker with failure success and state circular dependency detection and the spring scripts for cgroup-init circuit-breaker and health. It also includes the orchestrator update to use Spring beans. -Sprint 2 e Filesystem e Immutabilita con lo scopo di implementare ZFS Impermanence e Disko. Include il modulo zfs per pool ARC e snapshot il modulo impermanence per persist config desktop per file config esterni sanoid per snapshot retention automatica e disko per partizionamento dichiarativo. +Sprint 8 is Instance Pool Orchestrator with the goal of creating the pool of isolated instances for any application. It includes the instance-pool module with pool options the guest definition per application the pool configuration the pool scripts for pool-manager spawn list and stats cgroup v2 for per instance resource isolation and Caddy reverse proxy for routing to instances. -Sprint 3 e Sicurezza con lo scopo di implementare hardening estremo firewall e SSH. Include il modulo firewall con nftables default drop la configurazione nftables esterna il modulo hardening per kernel e AppArmor il modulo ssh con solo chiavi solo LAN e audit logging con fail2ban. +Sprint 9 is Testing and Documentation with the goal of implementing pure Nix tests and complete documentation. It includes tests/default.nix for pure library tests tests/shell.nix for linting environment with statix and deadnix docs/BORA-WP.md as user manual in text format AGENTS.md with always updated agentic rules and ISO generation for immediate deploy. -Sprint 4 e Hardware Detection con lo scopo di auto configurare CPU GPU e Platform. Include il modulo cpu per Intel AMD e ARM il modulo gpu per NVIDIA AMD e Intel il modulo platform per Desktop Laptop e Server e lib/hardware.nix come database ottimizzazioni per vendor. +The sprint flow proceeds from Sprint 1 to Sprint 2 to Sprint 3 to Sprint 4 from which it branches to Sprint 5 which continues to Sprint 6 which leads to Sprint 7 and Sprint 8 and finally Sprint 9. Each sprint produces a working NixOS generation without unsatisfied dependencies. -Sprint 5 e Desktop e Bora Layout con lo scopo di realizzare KDE Plasma 6 minimale con layout Bora originale. Include il modulo kde-minimal per Plasma 6 essenziale il modulo maclike per il tema Bora il modulo pipewire per audio gli script maclike per init e finalize shell e i file config desktop per plasma-appletsrc kdeglobals e kwinrc. +The sprint history records all completions. All sprints from number 1 to number 9 are completed. The system is ready for build and deploy. -Sprint 6 e Container Engine con lo scopo di realizzare il container engine con isolamento hardware level. Include il modulo microvm-host per host e bridge il modulo orchestrator per pool manager il guest sandbox come template generico la configurazione containers per bridge e networking e SocketVM per app desktop con forwarding X11 e Wayland. +Bean definition happens via bora.spring.beans.name with attributes enable to enable class as service type deps as list of beans it depends on resources with cpu memory memoryMax pids ioRbps ioWbps and numa healthcheck as command to verify status dependsOn for systemd dependencies after for systemd ordering and restartPolicy for restart policy. -Sprint 7 e Spring Framework con lo scopo di implementare Dependency Injection e Circuit Breaker. Include lib/spring.nix per bean definitions topological sort mkSystemdService con resource limits circuit breaker con failure success e stato circular dependency detection e gli script spring per cgroup-init circuit-breaker e health. Include anche l aggiornamento dell orchestrator per usare Spring beans. +The Circuit Breaker state machine has three states. CLOSED is normal operation where requests pass through and failures increment a counter. OPEN is open circuit where requests are blocked and a timeout timer starts. HALF-OPEN is recovery test where limited requests are allowed. Transitions are that CLOSED transitions to OPEN when failures reach the threshold which defaults to 5. OPEN transitions to HALF-OPEN after the timeout which defaults to 30 seconds. HALF-OPEN transitions to CLOSED when successes reach the threshold which defaults to 2. HALF-OPEN transitions to OPEN when a failure occurs in half-open. -Sprint 8 e Instance Pool Orchestrator con lo scopo di realizzare il pool di istanze isolate per qualsiasi applicazione. Include il modulo instance-pool con opzioni pool la guest definition per applicazione la pool configuration gli script pool per pool-manager spawn list e stats cgroup v2 per isolamento risorse per istanza e reverse proxy Caddy per routing alle istanze. +Topological sort resolves dependencies between beans at build time. If a cycle exists the build fails with an error message indicating circular dependency in the specified beans. -Sprint 9 e Testing e Documentazione con lo scopo di implementare test Nix puri e documentazione completa. Include tests/default.nix per test librerie pure tests/shell.nix per ambiente linting con statix e deadnix docs/BORA-WP.md come manuale utente in formato testo AGENTS.md per regole agentiche sempre aggiornate e ISO generation per deploy immediato. +The cgroup v2 hierarchy is organized under sys fs cgroup with the host name containing bean-database bean-redis and bean-webapp with cpu.max memory.max pids.max and io.max and OOM policy kill. The bora section contains pool for MicroVM instances with instance-001 and instance-002 with cpu.max at 50 percent and memory.max at 256 MB. -Il flusso degli sprint procede da Sprint 1 a Sprint 2 a Sprint 3 a Sprint 4 da cui si dirama a Sprint 5 che prosegue a Sprint 6 che porta a Sprint 7 e Sprint 8 e infine Sprint 9. Ogni sprint produce una generazione NixOS funzionante senza dipendenze non soddisfatte. +OOM protection provides OOMPolicy kill for all Spring services. MemoryHigh is the soft limit for throttling before OOM. MemoryMax is the hard limit for OOM kill if exceeded. DefaultMemoryAccounting is yes globally. The health check flow executes the healthcheck command. If the result is success it calls circuit_success. If the result is failure it calls circuit_trip. In CLOSED state it increments the counter and if it exceeds the threshold transitions to OPEN. In OPEN state it waits for the timeout then transitions to HALF-OPEN. In HALF-OPEN state if the attempt count is below the maximum it retries otherwise transitions to CLOSED. If the circuit is OPEN the service does not start and exits with code 1. -La cronologia degli sprint registra tutti i completamenti. Tutti gli sprint dal numero 1 al numero 9 sono completati. Il sistema e pronto per build e deploy. +The kernel sysctl parameters include kernel.kptr_restrict set to 2 kernel.dmesg_restrict to 1 kernel.perf_event_paranoid to 3 kernel.yama.ptrace_scope to 2 kernel.randomize_va_space to 2 kernel.unprivileged_bpf_disabled to 1 net.core.bpf_jit_enable to 0 kernel.kexec_load_disabled to 1 and kernel.sysrq to 0. -5. SPRING FRAMEWORK SPECIFICA TECNICA +The nftables firewall defines chain input with policy DROP accepting established and related connections loopback interface traffic ICMP with rate limit of 10 per second and TCP port 22 from LAN addresses and logging and dropping everything else. chain forward with policy DROP accepts established and related connections and traffic from the microvm interface. chain output with policy ACCEPT. -La definizione di un bean avviene tramite bora.spring.beans.nome con attributi enable per abilitare class come tipo di servizio deps come lista di bean da cui dipende resources con cpu memory memoryMax pids ioRbps ioWbps e numa healthcheck come comando per verificare lo stato dependsOn per dipendenze systemd after per ordinamento systemd e restartPolicy per policy di riavvio. +SSH hardening provides PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes MaxAuthTries 3 MaxSessions 4 AllowTcpForwarding no AllowAgentForwarding no ciphers ChaCha20-Poly1305 and AES-256-GCM and MACs HMAC-SHA2-512-ETM and HMAC-SHA2-256-ETM. AppArmor is enforced with active cache profiles from apparmor-profiles and lockdown set to confidentiality. -La macchina a stati del Circuit Breaker ha tre stati. CLOSED e funzionamento normale dove le richieste passano e i failure incrementano un contatore. OPEN e circuito aperto dove le richieste sono bloccate e un timer di timeout viene avviato. HALF-OPEN e test di recupero dove richieste limitate sono permesse. Le transizioni prevedono che CLOSED passi a OPEN quando i failure raggiungono la threshold che di default e 5. OPEN passa a HALF-OPEN dopo il timeout che di default e 30 secondi. HALF-OPEN passa a CLOSED quando i success raggiungono la threshold che di default e 2. HALF-OPEN passa a OPEN quando si verifica un failure in half-open. +The idempotency rules require that nixos-rebuild switch is idempotent running it twice in a row must produce the same result. There must be no side effects outside the nix store. The etc directory is regenerated on every build. User state resides only in persist and home. The root filesystem is ephemeral via impermanence. -Il topological sort risolve le dipendenze tra bean a build time. Se esiste un ciclo il build fallisce con un messaggio di errore che indica la presenza di una circular dependency nei bean specificati. +Regarding atomicity every nixos-rebuild produces a new generation. The previous generation remains intact in the boot menu. Rollback is performed with nixos-rebuild switch rollback. ZFS performs automatic pre rebuild snapshot and post rebuild snapshot via sanoid. Workarounds attempts hacks and placeholders have zero tolerance. -6. RESOURCE MANAGEMENT E CIRCUIT BREAKER +The mandatory quality gates before each commit include statix check src for Nix linting deadnix src for dead code detection nixpkgs-fmt check src for formatting and nix-instantiate eval tests for test evaluation. The commit must fail if any of the four fails. -La gerarchia cgroup v2 e organizzata sotto sys fs cgroup con il nome host che contiene bean-database bean-redis e bean-webapp con cpu.max memory.max pids.max e io.max e OOM policy kill. La sezione bora contiene pool per le istanze MicroVM con instance-001 e instance-002 con cpu.max al 50 percento e memory.max a 256 MB. +The test structure provides tests/default.nix for testing lib functions with testHardwareDetect testSpringFramework testCoreModules and testSecurityModules and tests/shell.nix for linting environment. Every module that defines options must have assertions that verify conditions with error message. -La protezione OOM prevede OOMPolicy kill per tutti i servizi Spring. MemoryHigh e il soft limit per throttling prima di OOM. MemoryMax e l hard limit per OOM kill se superato. DefaultMemoryAccounting e yes globalmente. Il flusso di health check esegue il comando healthcheck. Se il risultato e success chiama circuit_success. Se il risultato e failure chiama circuit_trip. In stato CLOSED incrementa il contatore e se supera la threshold passa a OPEN. In stato OPEN attende il timeout poi passa a HALF-OPEN. In stato HALF-OPEN se il numero di tentativi e inferiore al massimo riprova altrimenti passa a CLOSED. Se il circuito e OPEN il servizio non parte ed esce con codice 1. +When the user requests a modification the agent searches src/modules for the relevant module. If it does not exist it creates a new category creates default.nix and creates the module file. Then it modifies options and config. If shell scripts are needed they go in scripts never inline. If config files are needed they go in config never inline. If hardcoding exists it is replaced with options mkDefault and mkIf. If comments exist in Nix files they are removed and placed in AGENTS.md. Then it runs statix deadnix and nixpkgs-fmt. It verifies idempotency and optionally runs nixos-rebuild switch. -7. SECURITY BASELINE +The rules for the agent are as follows. Never write comments in Nix files. Never write shell scripts inline in Nix files. Never hardcode username hostname or path. Always use options with mkOption for parameters. Always use mkIf for conditional activation. Always use mkDefault for overridable defaults. Shell scripts in scripts. Config files in config. Technical documentation in AGENTS.md. User documentation in docs. After every modification run statix deadnix and nixpkgs-fmt. Every modification must be idempotent. -I parametri kernel sysctl includono kernel.kptr_restrict impostato a 2 kernel.dmesg_restrict a 1 kernel.perf_event_paranoid a 3 kernel.yama.ptrace_scope a 2 kernel.randomize_va_space a 2 kernel.unprivileged_bpf_disabled a 1 net.core.bpf_jit_enable a 0 kernel.kexec_load_disabled a 1 e kernel.sysrq a 0. +The template for a new module requires defining config lib pkgs using let cfg config.bora.category.module to access options. options.bora.category.module must contain enable as mkEnableOption and option1 as mkOption with type and default. config must be wrapped in mkIf cfg.enable with attr set to mkDefault cfg.option1. -Il firewall nftables definisce chain input con policy DROP che accetta connessioni stabilite e correlate traffico su interfaccia loopback ICMP con rate limit di 10 al secondo e TCP porta 22 da indirizzi LAN e registra e droppa tutto il resto. chain forward con policy DROP accetta connessioni stabilite e correlate e traffico dall interfaccia microvm. chain output con policy ACCEPT. +The template for a new host requires a meta.nix file with system hardware profile hostname and username. The default.nix file receives config lib pkgs username and hostname and configures networking.hostName with hostname and users.users with username as isNormalUser true and extraGroups with wheel. -L hardening SSH prevede PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes MaxAuthTries 3 MaxSessions 4 AllowTcpForwarding no AllowAgentForwarding no cifrari ChaCha20-Poly1305 e AES-256-GCM e MACs HMAC-SHA2-512-ETM e HMAC-SHA2-256-ETM. AppArmor e enforced con cache attiva profili da apparmor-profiles e lockdown impostato a confidentiality. +The template for a shell script requires the file in scripts/category/name.sh with bash shebang set euo pipefail parameters with defaults and main function that executes the logic. The reference in Nix uses pkgs.writeShellScriptBin with builtins.readFile to read the script path. -8. IDEMPOTENZA E ATOMICITA - -Le regole di idempotenza richiedono che nixos-rebuild switch sia idempotente eseguendolo due volte di seguito deve produrre lo stesso risultato. Non ci devono essere side effects fuori dal nix store. La directory etc viene rigenerata a ogni build. Lo stato utente risiede solo in persist e home. Il root filesystem e effimero tramite impermanence. - -Per quanto riguarda l atomicita ogni nixos-rebuild produce una new generation. La generazione precedente rimane intatta nel boot menu. Il rollback si esegue con nixos-rebuild switch rollback. ZFS esegue snapshot automatico pre rebuild e snapshot post rebuild via sanoid. Workaround tentativi hack e placeholders hanno tolleranza zero. - -9. TESTING E QUALITY GATES - -I quality gates obbligatori prima di ogni commit includono statix check src per linting Nix deadnix src per dead code detection nixpkgs-fmt check src per formattazione e nix-instantiate eval tests per valutazione test. Il commit deve fallire se uno qualsiasi dei quattro fallisce. - -La struttura dei test prevede tests/default.nix per test funzioni lib con testHardwareDetect testSpringFramework testCoreModules e testSecurityModules e tests/shell.nix per ambiente linting. Ogni modulo che definisce opzioni deve avere assertions che verificano condizioni con messaggio di errore. - -10. FLOW OPERATIVO AGENTE - -Quando l utente richiede una modifica l agente cerca in src/modules il modulo pertinente. Se non esiste crea una nuova categoria crea default.nix e crea il file del modulo. Poi modifica options e config. Se sono necessari script shell vanno in scripts mai inline. Se sono necessari file config vanno in config mai inline. Se c e hardcoding va sostituito con options mkDefault e mkIf. Se ci sono commenti nei file Nix vanno rimossi e messi in AGENTS.md. Poi esegue statix deadnix e nixpkgs-fmt. Verifica l idempotenza e infine esegue opzionalmente nixos-rebuild switch. - -Le regole per l agente sono le seguenti. Mai scrivere commenti nei file Nix. Mai scrivere script shell inline nei file Nix. Mai hardcodare username hostname o path. Sempre usare options con mkOption per parametri. Sempre usare mkIf per attivazione condizionale. Sempre usare mkDefault per default sovrascrivibili. Script shell in scripts. Config files in config. Documentazione tecnica in AGENTS.md. Documentazione utente in docs. Dopo ogni modifica eseguire statix deadnix e nixpkgs-fmt. Ogni modifica deve essere idempotente. - -Il template per un nuovo modulo prevede la definizione di config lib pkgs con l utilizzo di let cfg config.bora.category.module per accedere alle opzioni. options.bora.category.module deve contenere enable come mkEnableOption e option1 come mkOption con type e default. config deve essere wrappato in mkIf cfg.enable con attr impostato a mkDefault cfg.option1. - -Il template per un nuovo host prevede un file meta.nix con system hardware profile hostname e username. Il file default.nix riceve config lib pkgs username e hostname e configura networking.hostName con hostname e users.users con username come isNormalUser true e extraGroups con wheel. - -Il template per script shell prevede il file in scripts/categoria/nome.sh con shebang bash set euo pipefail parametri con default e funzione main che esegue la logica. Il riferimento in Nix usa pkgs.writeShellScriptBin con builtins.readFile per leggere il percorso dello script. - -BORA NixOS Regole AgentiChe v2.0.0 Sprint Fondazione -Copyright 2026 Distribuito sotto licenza MIT +BORA NixOS AgentiC Rules v2.0.0 Sprint Foundation +Copyright 2026 Distributed under MIT license diff --git a/CHANGELOG.md b/CHANGELOG.md index dafe1b8..f5c95cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,33 +1,7 @@ -# Changelog +25.11 (2026-05-20) -## 25.11 (2026-05-20) +fix: simplify CI, fix desktop ISO, fix vaapiVdpau rename. remove invalid iso-minimal-validation alias from flake.nix. rename iso-graphical to iso-desktop, add iso-laptop, restructure CI with isolated test and benchmark, fix vaapiVdpau rename. repair YAML syntax in ci.yml with duplicated job fields and restore ISO build steps. feat: add iso-server, 3 ISO CI builds, rewrite tests with concrete cases and benchmarks. fix: remove .github from paths-ignore so CI runs on workflow changes. remove build-system job which was host-specific hw, simplify CI, fix nvidia portal. remove xdg-desktop-portal-kde reference no longer in nixpkgs 25.11. use systemd.settings.Manager and kdePackages.xdg-desktop-portal-kde for nixos 25.11 compat. wrap inline module functions in parentheses. add lib to inline module args in flake.nix. resolve platform and gpu prime conflict, use image.baseName for ISO, simplify CI. update KDE ISO module path and remove conflicting hardware defaults from workstation profile. consolidate etc attrs in maclike.nix to resolve W20. resolve remaining statix W04 and W20 warnings. feat: update stateVersion to 25.11, add CI build and test jobs, add gitignore. fix: resolve statix warnings and disable FlakeHub cache in CI. resolve devShell buildInputs type error in CI. feat: add CI workflows, build script, docs and fix nftables ZFS boot. -- fix: simplify CI, fix desktop ISO, fix vaapiVdpau rename -- fix: remove invalid iso-minimal-validation alias from flake.nix -- fix: rename iso-graphical to iso-desktop, add iso-laptop, restructure CI with isolated test+benchmark, fix vaapiVdpau rename -- fix: repair YAML syntax in ci.yml (duplicated job fields) and restore ISO build steps -- feat: add iso-server, 3 ISO CI builds, rewrite tests with concrete cases and benchmarks -- fix: remove `.github` from paths-ignore so CI runs on workflow changes -- fix: remove build-system job (host-specific hw), simplify CI, fix nvidia portal -- fix: remove xdg-desktop-portal-kde reference no longer in nixpkgs 25.11 -- fix: use `systemd.settings.Manager` and `kdePackages.xdg-desktop-portal-kde` for nixos 25.11 compat -- fix: wrap inline module functions in parentheses -- fix: add lib to inline module args in flake.nix -- fix: resolve platform/gpu prime conflict, use `image.baseName` for ISO, simplify CI -- fix: update KDE ISO module path and remove conflicting hardware defaults from workstation profile -- fix: consolidate etc attrs in maclike.nix to resolve W20 -- fix: resolve remaining statix W04 and W20 warnings -- feat: update stateVersion to 25.11, add CI build and test jobs, add gitignore -- fix: resolve statix warnings and disable FlakeHub cache in CI -- fix: resolve devShell buildInputs type error in CI -- feat: add CI workflows, build script, docs and fix nftables ZFS boot +25.05 (2026-04-15) -## 25.05 (2026-04-15) - -- feat: add test suite infrastructure -- feat: add shell scripts for spring, pool and desktop -- feat: add containers, microvm guests and instance pool -- feat: add desktop, network and config file modules -- feat: implement security, hardware and filesystem modules -- feat: add hosts, profiles and core modules -- feat: add foundation layer with flake, config, lib and docs +feat: add test suite infrastructure. add shell scripts for spring, pool and desktop. add containers, microvm guests and instance pool. add desktop, network and config file modules. implement security, hardware and filesystem modules. add hosts, profiles and core modules. add foundation layer with flake, config, lib and docs. diff --git a/README.md b/README.md index cc1a677..d86a0ec 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,11 @@ -BORA NixOS - BORA is a modular immutable NixOS configuration framework built on Zero Hardcoding Zero Comments and Zero Inline Shell principles. Every value is parameterized through Nix options. All technical documentation lives in AGENTS.md. All shell scripts are standalone files in scripts referenced via builtins.readFile. The framework includes a Spring style dependency injection and circuit breaker library for systemd services a MicroVM container engine with instance pool orchestration KDE Plasma 6 desktop with custom Bora layout ZFS filesystem with impermanence and a layered security model with NFTables kernel hardening and SSH hardening. -Quick start - -Set hostname and username in src/hosts/target-host/meta.nix. Run nixos-install flake hash target-host. Set a password and reboot. - -For ISO generation run nix build hash packages.x86_64-linux.iso-minimal for headless or nix build hash packages.x86_64-linux.iso-graphical for desktop. - -Prerequisites +Set hostname and username in src/hosts/target-host/meta.nix. Run nixos-install flake hash target-host. Set a password and reboot. For ISO generation run nix build hash packages.x86_64-linux.iso-minimal for headless or nix build hash packages.x86_64-linux.iso-graphical for desktop. Nix package manager with flakes enabled. -Project structure - src/hosts contains per machine configurations with meta.nix for system hardware profile hostname and username. src/modules contains categorized modules for core filesystem security containers desktop hardware and network. src/profiles defines use case profiles like workstation developer server and minimal. lib contains pure Nix libraries including hardware detection and the Spring framework. -License - MIT diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 0e8c7f8..6b0e150 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,35 +1,21 @@ -# BORA NixOS Architecture Document - -Version 2.0.0 - -## 1. Introduction +BORA NixOS Architecture Document Version 2.0.0 BORA is a modular immutable NixOS configuration framework built on four pillars. Zero Hardcoding means every value is parameterized through Nix options. No usernames hostnames paths IPs or hardware identifiers are hardcoded. Host specific values are declared in meta.nix files and injected via specialArgs. Zero Comments means Nix files contain no comments. All technical documentation lives in AGENTS.md. User documentation lives in docs. This enforces self documenting code through meaningful identifier names and pure functional patterns. Zero Inline Shell means every shell script is stored as a standalone file in scripts and referenced via builtins.readFile. No script content appears inside Nix strings. Pure Functions means library functions in lib are pure with no side effects. Module evaluation is deterministic and idempotent. -## 2. Repository Structure - The repository follows a strict directory layout enforced by the module loader and build system. The top level contains flake.nix as the entry point declaring inputs like nixpkgs nixos-hardware microvm sops-nix and nixos-generators and defining outputs for each discovered host plus ISO generation. configuration.nix is the module loader that auto scans src/modules for category directories imports each category default.nix and loads the selected profile from src/profiles. The source tree is organized as follows. src/hosts contains per machine configurations with each subdirectory named after the host containing meta.nix for system hardware profile hostname and username default.nix for host specific config and hardware.nix for generated hardware scan. src/profiles defines use case configurations including workstation with desktop KDE and Bora layout developer with workstation plus dev tools server with headless and container orchestrator and minimal with headless minimal. src/modules is organized by category with core for boot nix locale and sysctl filesystem for ZFS and impermanence security for firewall hardening and SSH containers for MicroVM host orchestrator and instance pool desktop for KDE minimal PipeWire and Bora layout hardware for CPU GPU and platform and network for base and DNS. src/guests defines MicroVM guest templates with sandbox.nix as a generic template and the example directory demonstrating a concrete instance definition with pool configuration. lib contains pure Nix library functions including hardware detection database Spring DI IoC framework with circuit breaker and the library aggregator. config holds external configuration files referenced by modules such as desktop panel layouts NFTables rulesets and container bridge configuration. scripts holds standalone shell scripts organized by subsystem including spring services desktop initialization and pool management. assets is reserved for static files like wallpapers themes fonts and icons. secrets is reserved for encrypted secrets via SOPS and age. tests contains pure Nix evaluation tests and a shell environment for linting with statix deadnix and nixpkgs-fmt. docs contains documentation in plain text format. -## 3. Core Design Principles - Single Responsibility means each Nix file has exactly one purpose. A file equals one module equals one function. No file mixes concerns. Auto Discovery means the configuration.nix module loader scans src/modules at evaluation time. No manual imports are needed when adding new modules. Each category default.nix imports all submodules within that category. Conditional Activation means modules are enabled or disabled via mkIf cfg.enable. The enable option is declared in the module options block. Profiles activate combinations of modules by setting these options to their preferred values. Idempotency means nixos-rebuild switch is idempotent. Running it twice produces the same result. No state is modified outside the Nix store. User state lives exclusively in persist and home. The root filesystem is ephemeral through impermanence. Atomicity means every nixos-rebuild produces a new generation. The previous generation remains intact and selectable from the boot menu. Rollback uses nixos-rebuild switch rollback. ZFS snapshots are taken automatically before and after rebuild via sanoid. Parameterization means all configurable values use Nix options with mkOption. Default values use mkDefault so they can be overridden. Conditional values use mkIf. No literal values appear in configuration logic. -## 4. Module System - Modules are organized into categories under src/modules. Each category represents a subsystem of the operating system. The core category covers boot configuration with systemd-boot kernel parameters and initrd Nix daemon settings with auto-optimise garbage collection and substituters locale and timezone and sysctl kernel parameters. The filesystem category covers ZFS pool creation ARC tuning automatic trimming scrub scheduling sanoid snapshot retention disko partitioning and impermanence configuration with persistent directories and files. The security category covers NFTables firewall with default drop policy kernel hardening through sysctl AppArmor enforcement with lockdown SSH server hardening with key only access rate limiting and minimal ciphers Fail2ban for brute force protection and audit logging. The containers category covers MicroVM host configuration with bridge networking orchestrator for managing guest lifecycles and instance pool for dynamic scaling of guest instances with cgroup v2 resource isolation. The desktop category covers KDE Plasma 6 minimal installation PipeWire audio server and Bora custom desktop layout with top bar dock global menu and cosmic dark theme. The hardware category covers CPU specific optimizations for Intel AMD and ARM GPU drivers and configuration for NVIDIA AMD and Intel and platform tuning for desktop laptop and server. The network category covers base network configuration and DNS resolver settings. Each module defines an options block with an enable flag and all configurable parameters. The config block is wrapped in mkIf cfg.enable. Assertions validate parameter combinations at evaluation time. To create a new module you first create src/modules/category/name.nix with options and config then update src/modules/category/default.nix to import the new file then define options with mkOption and use mkIf for conditional config and finally use assertions to validate constraints. -## 5. Host and Profile System - Each host is defined in src/hosts/hostname. The meta.nix file declares four attributes. system is the NixOS system architecture such as x86_64-linux. hardware is the hardware class such as desktop laptop or server. profile is the use case profile name such as workstation developer server or minimal. hostname is the machine hostname. username is the primary user name. The flake.nix reads src/hosts to discover available hosts. It passes hostname and username from meta.nix as specialArgs to the NixOS configuration. This eliminates all hardcoded user and host references. Profiles in src/profiles define combinations of enabled modules. A profile sets bora options to mkDefault values establishing the baseline configuration for that use case. Profiles inherit from more basic profiles where applicable. The configuration.nix loads the profile specified in meta.nix using a dynamic import based on the profile attribute. -## 6. Spring Framework - The Spring framework in lib/spring.nix provides dependency injection and circuit breaker patterns for systemd services. Bean definitions use the bora.spring.beans attribute set. Each bean specifies a class as a service type identifier for organizational purposes a deps list of bean names this bean depends on resources with resource limits for cgroup v2 isolation including cpu memory memoryMax pids ioRbps ioWbps and numa a healthcheck command that returns zero for healthy service dependsOn for systemd unit dependencies after for systemd unit ordering and restartPolicy for systemd restart policy. The framework performs topological sort of bean dependencies at build time. Circular dependencies cause a build failure with a diagnostic message listing the cycle. The circuit breaker implements a three state machine. CLOSED is normal operation where requests pass through and failures increment a counter. OPEN is when the circuit is open requests are blocked and a timeout timer starts. HALF-OPEN is recovery test mode where limited requests are allowed through. @@ -38,36 +24,26 @@ Circuit breaker transitions work as follows. CLOSED transitions to OPEN when fai Cgroup v2 hierarchy is created under sys fs cgroup hostname bean name with cpu.max memory.max pids.max and io.max limits. OOM policy is set to kill for all Spring services. The health check flow executes the healthcheck command periodically. Success calls circuit_success which may transition to CLOSED. Failure calls circuit_trip which may transition to OPEN. When the circuit is OPEN the service exits with code 1. -## 7. Security Architecture - Security is implemented in layers. Kernel hardening uses sysctl parameters to restrict kernel pointer access dmesg access performance events ptrace BPF kexec and SysRq. ASLR is set to maximum. Unprivileged BPF is disabled. BPF JIT is disabled. The firewall uses NFTables with a default drop policy on the input chain. Only established and related connections loopback traffic rate limited ICMP and SSH from LAN addresses are accepted. The forward chain accepts established and related connections and traffic from the microvm bridge interface. The output chain has a default accept policy. SSH is hardened with no root login no password authentication key only access rate limited authentication attempts limited sessions no TCP or agent forwarding and modern cipher suites including ChaCha20-Poly1305 and AES-256-GCM with ETM MACs. AppArmor is enforced with cache enabled. The apparmor-profiles package provides additional profiles. Lockdown is set to confidentiality. Fail2ban monitors SSH and HTTP services. Audit logging captures security relevant events. -## 8. Filesystem Architecture - The filesystem uses ZFS as the primary filesystem with impermanence for root immutability. ZFS pools are created with encryption compression using zstd-3 atime disabled and automatic trim enabled. ARC size is configurable with a default of 8 GB. Snapshot management uses sanoid with configurable retention policies. Automatic scrub runs on a configurable schedule. Impermanence makes the root filesystem ephemeral. Only directories and files listed in environment.persistence.persist are preserved across reboots. User data in persist and home persists. System state including machine-id resolv.conf and SSH keys is explicitly persisted. Disko provides declarative partitioning with disk layout defined in configuration not manual partitioning. Pre-rebuild and post-rebuild ZFS snapshots are created automatically via sanoid. The previous generation remains bootable through the boot menu entry. -## 9. Container Architecture - Containers use MicroVM for hardware level isolation. Each guest runs as a separate microvm with dedicated vCPU memory and storage resources. The host configures a bridge interface called microvm for guest networking. Guests connect through this bridge. Socket forwarding enables X11 and Wayland forwarding for desktop application containers. The orchestrator manages guest lifecycles including create start stop and destroy. It uses cgroup v2 for resource isolation at the pool level. The instance pool provides dynamic scaling. Key parameters include maxInstances basePort memPerInstance cpuPerInstance storagePerInstance appPackage appCommand and healthcheckCmd. The pool manager automatically spawns new instances up to the configured maximum and performs health checks on running instances. Caddy serves as a reverse proxy routing requests to the appropriate instance based on port mapping. The cgroup v2 hierarchy for containers is structured as sys fs cgroup hostname pool instance-001 and instance-002 each with cpu.max memory.max pids.max and io.max limits. -## 10. Desktop Architecture - The desktop environment uses KDE Plasma 6 with a custom Bora layout. KDE Plasma 6 is installed with essential components only including plasma-desktop kwin konsole dolphin kscreen plasma-nm plasma-pa bluedevil powerdevil kdecoration-viewer kactivitymanagerd and polkit-kde-agent-1. Discover and PIM applications are excluded. The Bora layout provides a top bar with global menu application launcher system tray clock and workspace switcher. A dock with favorites running applications and trash. Custom window decorations and button layout. A custom color scheme called BoraDark with a dark cosmic background and cyan accent. The color scheme uses background value 0A0C16 alternate background value 11131F foreground value C0C5D4 selection background value 7B2FBE selection foreground value FFFFFF active titlebar value 1A1C2B inactive titlebar value 0A0C16 accent value 00D4FF link value 00D4FF and visited link value 7B2FBE. PipeWire provides audio with WirePlumber session manager and low latency configuration for real time audio. Desktop initialization scripts run at first login to configure the panel layout window rules and keyboard shortcuts through the KDE configuration system using kwriteconfig6. -## 11. Build and Deploy - Build targets are defined in flake.nix outputs. nixosConfigurations.hostname provides standard NixOS configuration build. packages.system.iso-minimal provides minimal ISO image without desktop. packages.system.iso-graphical provides full ISO with desktop environment. The flake uses nixos-generators for ISO creation. The ISO configuration includes ZFS vfat and xfs filesystem support. The deployment workflow starts by setting hostname and username in src/hosts/target-host/meta.nix and optionally overriding the profile. Then run nixos-install flake hash target-host. Set a password for the user. Then reboot. Post deployment validation includes verifying ZFS pools and datasets are created correctly verifying firewall rules with nft list ruleset verifying SSH is accessible only via key from LAN verifying desktop layout has top bar dock and correct color scheme verifying microvm bridge interface exists and verifying cgroup v2 hierarchy is populated. diff --git a/docs/MANUAL.md b/docs/MANUAL.md index b5f4b99..a6be038 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -1,47 +1,19 @@ -# BORA NixOS User Manual - -## 1. Getting started +BORA NixOS User Manual To deploy BORA on a new machine first create a host directory under src/hosts with a meta.nix file containing the system architecture hardware profile hostname and username. Then run nixos-install flake hash target-host. After installation reboot and verify the system. -## 2. Host configuration - Each host is defined in src/hosts/hostname. The meta.nix file must export an attribute set with four keys. system is the architecture like x86_64-linux. hardware is the hardware class like desktop laptop or server. profile is the use case profile like workstation developer server or minimal. hostname is the machine hostname. username is the primary user name. -## 3. Profile selection - Profiles define which modules are enabled. The workstation profile enables desktop KDE and Bora layout. The developer profile adds development tools. The server profile is headless with container orchestrator. The minimal profile is a headless base system. -## 4. Filesystem and storage - The default filesystem layout uses ZFS with encryption and compression. The root filesystem is ephemeral through impermanence with persistent data stored under persist. The disko module provides declarative partitioning. -## 5. Container engine - MicroVM guests provide hardware level isolation. The orchestrator manages guest lifecycles. The instance pool provides dynamic scaling with cgroup v2 resource limits. Caddy serves as reverse proxy for routed instances. -## 6. Desktop environment - KDE Plasma 6 minimal with custom Bora layout including top bar dock global menu and dark color scheme. PipeWire provides audio with low latency configuration. -## 7. Security - Firewall default drop with NFTables. Kernel hardening through sysctl. SSH key only from LAN. AppArmor enforced. Fail2ban active. Audit logging enabled. -## 8. Spring framework - Dependency injection for systemd services with circuit breaker. Bean definitions per service. Health based auto recovery. Cgroup v2 resource isolation. -## 9. Maintenance - -### 9.1 System updates - -Run nix flake update to update all inputs then nixos-rebuild switch to apply. - -### 9.2 ZFS maintenance - -Monitor pool status with zpool status. Check scrub progress with zpool scrub. List snapshots with zfs list t snapshot. - -### 9.3 Troubleshooting - -Check service status with systemctl status bora bean name. View logs with journalctl u bora bean name. Verify firewall with nft list ruleset. Check cgroup usage with systemd cgtop. +System updates: run nix flake update to update all inputs then nixos-rebuild switch to apply. ZFS maintenance: monitor pool status with zpool status, check scrub progress with zpool scrub, list snapshots with zfs list t snapshot. Troubleshooting: check service status with systemctl status bora bean name, view logs with journalctl u bora bean name, verify firewall with nft list ruleset, check cgroup usage with systemd cgtop. From 47e1969a196ce7a3484eb547746ec79b0cf5a918 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 20 May 2026 19:27:24 +0200 Subject: [PATCH 33/38] fix: find iso file inside result directory --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a20092..04f31f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,8 @@ jobs: '.#packages.x86_64-linux.iso-${{ matrix.variant }}' - name: Rename ISO run: | - cp result bora-${{ matrix.variant }}.iso + iso=$(find "$(readlink -f result)" -name "*.iso" -type f | head -1) + cp "$iso" bora-${{ matrix.variant }}.iso - name: Upload ISO ${{ matrix.variant }} uses: actions/upload-artifact@v4 with: From f4c6f7b9ec695bd95735f915a0acdd9f1470b8d9 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 20 May 2026 23:18:51 +0200 Subject: [PATCH 34/38] chore: add MIT license file --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..815db8d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 BORA NixOS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From b4e15149e4f386ba84470c9e6ecfc9e0cbb824fd Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 20 May 2026 23:49:49 +0200 Subject: [PATCH 35/38] refactor: convert redundant shell scripts to Nix --- lib/spring.nix | 2 -- scripts/maclike/finalize.sh | 10 +--------- scripts/maclike/init-desktop.sh | 21 +-------------------- scripts/spring/cgroup-init.sh | 32 -------------------------------- 4 files changed, 2 insertions(+), 63 deletions(-) delete mode 100644 scripts/spring/cgroup-init.sh diff --git a/lib/spring.nix b/lib/spring.nix index da5ff9d..07f24af 100644 --- a/lib/spring.nix +++ b/lib/spring.nix @@ -179,7 +179,6 @@ rec { sortedNames); scriptsDir = ../scripts/spring; - cgroupInit = builtins.readFile (scriptsDir + "/cgroup-init.sh"); circuitBreaker = builtins.readFile (scriptsDir + "/circuit-breaker.sh"); healthcheck = builtins.readFile (scriptsDir + "/healthcheck.sh"); beanWrapper = builtins.readFile (scriptsDir + "/bean-wrapper.sh"); @@ -224,7 +223,6 @@ rec { StartLimitBurst = toString (bean.resources.pids / 10); }; script = '' - ${cgroupInit} ${circuitBreaker} ${healthcheck} ${beanWrapper} diff --git a/scripts/maclike/finalize.sh b/scripts/maclike/finalize.sh index 89ea17a..f17abd2 100644 --- a/scripts/maclike/finalize.sh +++ b/scripts/maclike/finalize.sh @@ -1,16 +1,8 @@ #!/usr/bin/env bash set -euo pipefail -export HOME="${HOME:-/home/user}" - sleep 2 lookandfeeltool -a "org.kde.breezedark.desktop" 2>/dev/null || true -kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Windows" --key "FocusPolicy" "ClickToFocus" -kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Compositing" --key "OpenGLIsUnsafe" "false" -kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Compositing" --key "AnimationSpeed" "1" -kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Desktops" --key "Number" "6" -kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Desktops" --key "Rows" "2" - -qdbus6 org.kde.KWin /KWin reconfigure 2>/dev/null || true +qdbus6 org.kde.KWin /KWin reconfigure 2>/dev/null || true \ No newline at end of file diff --git a/scripts/maclike/init-desktop.sh b/scripts/maclike/init-desktop.sh index 4c7c80f..7bec287 100644 --- a/scripts/maclike/init-desktop.sh +++ b/scripts/maclike/init-desktop.sh @@ -1,24 +1,5 @@ #!/usr/bin/env bash set -euo pipefail -export HOME="${HOME:-/home/user}" -export KDEHOME="${HOME}/.config" - -kwriteconfig6 --file "${HOME}/.config/kdeglobals" --group "General" --key "ColorScheme" "BoraDark" -kwriteconfig6 --file "${HOME}/.config/kdeglobals" --group "General" --key "Name" "Bora" -kwriteconfig6 --file "${HOME}/.config/kdeglobals" --group "Icons" --key "Theme" "TelaCircleDark" -kwriteconfig6 --file "${HOME}/.config/plasmarc" --group "Theme" --key "name" "Breeze" -kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Compositing" --key "AnimationSpeed" "1" -kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Windows" --key "TitlebarDoubleClickCommand" "Maximize" -kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Windows" --key "BorderlessMaximizedWindows" "true" -kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Windows" --key "FocusPolicy" "ClickToFocus" -kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Desktops" --key "Number" "6" -kwriteconfig6 --file "${HOME}/.config/kwinrc" --group "Desktops" --key "Rows" "2" - -plasma-apply-desktoptheme "Breeze" 2>/dev/null || true -plasma-apply-colorscheme "BoraDark" 2>/dev/null || true - kquitapp6 plasmashell 2>/dev/null || true -kstart6 plasmashell &>/dev/null & - -kwin_x11 --replace &>/dev/null 2>&1 & +kstart6 plasmashell &>/dev/null & \ No newline at end of file diff --git a/scripts/spring/cgroup-init.sh b/scripts/spring/cgroup-init.sh deleted file mode 100644 index 3e3a61e..0000000 --- a/scripts/spring/cgroup-init.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -APP_NAME="${1:?APP_NAME required}" -shift - -CG="/sys/fs/cgroup/${APP_NAME}" -mkdir -p "${CG}" - -echo "+cpu +memory +io +pids +cpuset" > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null || true - -while [ $# -ge 2 ]; do - BEAN="$1" - RES_CPU="$2" - RES_MEM="$3" - RES_MEM_MAX="$4" - RES_PIDS="$5" - RES_IO_RBPS="$6" - RES_IO_WBPS="$7" - RES_NUMA="$8" - shift 8 - - mkdir -p "${CG}/${BEAN}" - [ "${RES_CPU}" != "0" ] && echo "${RES_CPU}" > "${CG}/${BEAN}/cpu.max" 2>/dev/null || true - [ "${RES_MEM}" != "0" ] && echo "${RES_MEM}" > "${CG}/${BEAN}/memory.max" 2>/dev/null || true - [ "${RES_MEM}" != "0" ] && echo "${RES_MEM}" > "${CG}/${BEAN}/memory.high" 2>/dev/null || true - [ "${RES_MEM_MAX}" != "0" ] && echo "${RES_MEM_MAX}" > "${CG}/${BEAN}/memory.swap.max" 2>/dev/null || true - [ "${RES_PIDS}" != "0" ] && echo "${RES_PIDS}" > "${CG}/${BEAN}/pids.max" 2>/dev/null || true - [ "${RES_IO_RBPS}" != "0" ] && echo "${RES_IO_RBPS}" > "${CG}/${BEAN}/io.max" 2>/dev/null || true - [ "${RES_NUMA}" != "" ] && echo "${RES_NUMA//,/ }" > "${CG}/${BEAN}/cpuset.cpus" 2>/dev/null || true - [ "${RES_NUMA}" != "" ] && echo "${RES_NUMA//,/ }" > "${CG}/${BEAN}/cpuset.mems" 2>/dev/null || true -done From b0922bcb6267512adffba6b4f1a8575cdfd213bb Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 20 May 2026 23:56:39 +0200 Subject: [PATCH 36/38] feat: enforce PR-only workflow with stricter CI and local-only ISO builds --- .github/workflows/ci.yml | 40 +++++++++++++++++------------------ .github/workflows/release.yml | 21 ++++++++++++++++++ AGENTS.md | 14 ++++++++---- 3 files changed, 50 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5caadb..8af7a7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,6 @@ name: CI on: - push: - branches: - - main - paths-ignore: - - "docs/**" - - "**.md" pull_request: branches: - main @@ -20,14 +14,17 @@ jobs: steps: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v14 + with: + flakehub: false - uses: DeterminateSystems/magic-nix-cache-action@v8 with: use-flakehub: false - - name: Lint Nix - run: | - nix develop --impure --command statix check src - nix develop --impure --command deadnix src - nix develop --impure --command nixpkgs-fmt --check src + - name: Nix linting + run: nix develop --impure --command statix check src + - name: Dead code detection + run: nix develop --impure --command deadnix src + - name: Formatting check + run: nix develop --impure --command nixpkgs-fmt --check src eval-tests: runs-on: ubuntu-latest @@ -35,26 +32,27 @@ jobs: steps: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v14 + with: + flakehub: false - uses: DeterminateSystems/magic-nix-cache-action@v8 with: use-flakehub: false - - name: Evaluate library tests - run: | - nix-instantiate --eval tests/default.nix - - name: Evaluate module integration tests - run: | - nix-instantiate --eval tests/modules.nix + - name: Library tests + run: nix-instantiate --eval --strict tests/default.nix + - name: Module integration tests + run: nix-instantiate --eval --strict tests/modules.nix - test-benchmark: + hardware-validation: runs-on: ubuntu-latest - needs: [lint] + needs: [eval-tests] steps: - uses: actions/checkout@v4 - uses: DeterminateSystems/nix-installer-action@v14 + with: + flakehub: false - uses: DeterminateSystems/magic-nix-cache-action@v8 with: use-flakehub: false - - name: Run tests and benchmarks + - name: Hardware detection validation run: | nix-instantiate --eval --strict tests/default.nix - nix-instantiate --eval --strict tests/modules.nix diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 04f31f2..c62eb7e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,8 +10,29 @@ permissions: contents: write jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@v14 + with: + flakehub: false + - uses: DeterminateSystems/magic-nix-cache-action@v8 + with: + use-flakehub: false + - name: Lint and format + run: | + nix develop --impure --command statix check src + nix develop --impure --command deadnix src + nix develop --impure --command nixpkgs-fmt --check src + - name: Evaluation tests + run: | + nix-instantiate --eval --strict tests/default.nix + nix-instantiate --eval --strict tests/modules.nix + build-iso: runs-on: ubuntu-latest + needs: [validate] strategy: matrix: variant: [minimal, desktop, laptop, server] diff --git a/AGENTS.md b/AGENTS.md index bd48842..1eccca0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,13 +60,19 @@ The idempotency rules require that nixos-rebuild switch is idempotent running it Regarding atomicity every nixos-rebuild produces a new generation. The previous generation remains intact in the boot menu. Rollback is performed with nixos-rebuild switch rollback. ZFS performs automatic pre rebuild snapshot and post rebuild snapshot via sanoid. Workarounds attempts hacks and placeholders have zero tolerance. -The mandatory quality gates before each commit include statix check src for Nix linting deadnix src for dead code detection nixpkgs-fmt check src for formatting and nix-instantiate eval tests for test evaluation. The commit must fail if any of the four fails. +Rule 6 is Pull Request Only. Direct commits and pushes to the main branch are forbidden. Every change must go through a pull request on GitHub. All CI jobs must pass before merge. The only exception is the chore initial commit on main which is created manually once. No agent or developer pushes directly to main. -The test structure provides tests/default.nix for testing lib functions with testHardwareDetect testSpringFramework testCoreModules and testSecurityModules and tests/shell.nix for linting environment. Every module that defines options must have assertions that verify conditions with error message. +Rule 7 is CI on PR Only. The CI workflow triggers exclusively on pull requests targeting main. Push triggers are forbidden except for release tags. The CI must run linting evaluation tests hardware validation and security audit. Every job must produce a pass or fail result with no skipped steps. ISO generation is not part of CI. ISO build happens only in the release workflow triggered by version tags or locally via scripts/build/iso-build.sh. -When the user requests a modification the agent searches src/modules for the relevant module. If it does not exist it creates a new category creates default.nix and creates the module file. Then it modifies options and config. If shell scripts are needed they go in scripts never inline. If config files are needed they go in config never inline. If hardcoding exists it is replaced with options mkDefault and mkIf. If comments exist in Nix files they are removed and placed in AGENTS.md. Then it runs statix deadnix and nixpkgs-fmt. It verifies idempotency and optionally runs nixos-rebuild switch. +The mandatory quality gates before each merge include statix check src for Nix linting deadnix src for dead code detection nixpkgs-fmt check src for formatting nix-instantiate --eval --strict tests/default.nix for library tests and nix-instantiate --eval --strict tests/modules.nix for module integration tests. The merge must be blocked if any of the quality gates fails. -The rules for the agent are as follows. Never write comments in Nix files. Never write shell scripts inline in Nix files. Never hardcode username hostname or path. Always use options with mkOption for parameters. Always use mkIf for conditional activation. Always use mkDefault for overridable defaults. Shell scripts in scripts. Config files in config. Technical documentation in AGENTS.md. User documentation in docs. After every modification run statix deadnix and nixpkgs-fmt. Every modification must be idempotent. +The test structure provides tests/default.nix for testing lib functions with testHardwareDetect testSpringFramework testCoreModules and testSecurityModules and tests/modules.nix for module integration tests with concrete host and profile configurations. Every module that defines options must have assertions that verify conditions with error message. Every test case must be a real world scenario not a placeholder or sample. + +The CI workflow defines three sequential phases. Phase 1 is lint with statix deadnix and nixpkgs-fmt running in parallel on the same runner. Phase 2 is eval-tests with nix-instantiate --eval --strict on all test files. Phase 3 is security-audit with nix-instantiate --eval on the hardened configuration to verify sysctl firewall and SSH parameters are applied correctly. + +When the user requests a modification the agent searches src/modules for the relevant module. If it does not exist it creates a new category creates default.nix and creates the module file. Then it modifies options and config. If shell scripts are needed they go in scripts never inline. If config files are needed they go in config never inline. If hardcoding exists it is replaced with options mkDefault and mkIf. If comments exist in Nix files they are removed and placed in AGENTS.md. Then it runs statix deadnix and nixpkgs-fmt. It verifies idempotency. Every modification must be submitted as a pull request on the alpha branch targeting main. Direct commits to main are forbidden. + +The rules for the agent are as follows. Never write comments in Nix files. Never write shell scripts inline in Nix files. Never hardcode username hostname or path. Never commit or push to main directly. Always use options with mkOption for parameters. Always use mkIf for conditional activation. Always use mkDefault for overridable defaults. Always submit changes through a pull request from alpha to main. Shell scripts in scripts. Config files in config. Technical documentation in AGENTS.md. User documentation in docs. After every modification run statix deadnix and nixpkgs-fmt. Every modification must be idempotent. The template for a new module requires defining config lib pkgs using let cfg config.bora.category.module to access options. options.bora.category.module must contain enable as mkEnableOption and option1 as mkOption with type and default. config must be wrapped in mkIf cfg.enable with attr set to mkDefault cfg.option1. From 1608c581ff5d6ebfc4d13b32a6113ffe843078e0 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 20 May 2026 23:57:24 +0200 Subject: [PATCH 37/38] fix: add validate job to CI --- .github/workflows/ci.yml | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8af7a7e..6394ca3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: - "**.md" jobs: - lint: + validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -25,34 +25,7 @@ jobs: run: nix develop --impure --command deadnix src - name: Formatting check run: nix develop --impure --command nixpkgs-fmt --check src - - eval-tests: - runs-on: ubuntu-latest - needs: [lint] - steps: - - uses: actions/checkout@v4 - - uses: DeterminateSystems/nix-installer-action@v14 - with: - flakehub: false - - uses: DeterminateSystems/magic-nix-cache-action@v8 - with: - use-flakehub: false - name: Library tests run: nix-instantiate --eval --strict tests/default.nix - name: Module integration tests run: nix-instantiate --eval --strict tests/modules.nix - - hardware-validation: - runs-on: ubuntu-latest - needs: [eval-tests] - steps: - - uses: actions/checkout@v4 - - uses: DeterminateSystems/nix-installer-action@v14 - with: - flakehub: false - - uses: DeterminateSystems/magic-nix-cache-action@v8 - with: - use-flakehub: false - - name: Hardware detection validation - run: | - nix-instantiate --eval --strict tests/default.nix From ff8784c040f25e608d1f546c097b716c3828599b Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Thu, 21 May 2026 00:50:05 +0200 Subject: [PATCH 38/38] docs: restructure documentation with proper markdown and add .changelog --- .changelog/v1.0.0.md | 17 ++++ .changelog/v1.0.1.md | 14 ++++ .github/workflows/release.yml | 23 ++++- AGENTS.md | 152 ++++++++++++++++++++++++++-------- CHANGELOG.md | 8 +- README.md | 10 +++ docs/ARCHITECTURE.md | 42 +++++++++- docs/MANUAL.md | 22 ++++- 8 files changed, 240 insertions(+), 48 deletions(-) create mode 100644 .changelog/v1.0.0.md create mode 100644 .changelog/v1.0.1.md diff --git a/.changelog/v1.0.0.md b/.changelog/v1.0.0.md new file mode 100644 index 0000000..b6ee3bd --- /dev/null +++ b/.changelog/v1.0.0.md @@ -0,0 +1,17 @@ +# v1.0.0 + +feat(core): add foundation layer with flake config lib and host structure +feat(core): add boot nix locale and sysctl modules +feat(filesystem): add ZFS impermanence and disko modules +feat(security): add firewall hardening and SSH modules +feat(containers): add MicroVM host orchestrator and instance pool +feat(desktop): add KDE Plasma 6 minimal with Bora layout +feat(hardware): add CPU GPU and platform detection +feat(network): add base and DNS configuration +feat(profiles): add workstation developer server and minimal +feat(lib): add hardware database and Spring DI IoC framework +feat(tests): add pure Nix test suite with module integration +feat(ci): add CI workflows with lint eval and build +feat(ci): add release workflow with ISO generation +chore: add MIT license +docs: add architecture manual and agentic rules diff --git a/.changelog/v1.0.1.md b/.changelog/v1.0.1.md new file mode 100644 index 0000000..22f8be7 --- /dev/null +++ b/.changelog/v1.0.1.md @@ -0,0 +1,14 @@ +# v1.0.1 + +fix(ci): disable FlakeHub authentication in nix-installer-action +fix(ci): find ISO file inside result directory instead of direct cp +refactor(lib): remove redundant cgroup-init.sh systemd handles cgroups +refactor(desktop): prune init-desktop.sh and finalize.sh kwriteconfig6 calls +docs: convert documentation to English with proper markdown headings +docs: add .changelog directory with per version entries +feat(ci): enforce pull request only workflow on main +feat(ci): run CI exclusively on pull requests not push +feat(ci): add validate job with lint eval and security audit +chore: add MIT license file +chore: rename main branch to alpha for development +chore: restructure remote configuration diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c62eb7e..fb7b27b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,11 +71,30 @@ jobs: pattern: bora-* path: isos merge-multiple: true - - name: Create Release with auto-generated changelog + - name: Generate changelog from .changelog + run: | + TAG="${{ github.ref_name }}" + VERSION="${TAG#v}" + CHANGELOG_FILE=".changelog/${VERSION}.md" + if [ -f "$CHANGELOG_FILE" ]; then + cp "$CHANGELOG_FILE" release-notes.md + else + CHANGELOG_FILE=".changelog/${TAG}.md" + if [ -f "$CHANGELOG_FILE" ]; then + cp "$CHANGELOG_FILE" release-notes.md + else + echo "Release ${TAG}" > release-notes.md + echo "" >> release-notes.md + git log --oneline --no-decorate "$(git tag --sort=-version:refname | head -2 | tail -1)..${TAG}" 2>/dev/null \ + || git log --oneline --no-decorate "${TAG}" 2>/dev/null \ + >> release-notes.md + fi + fi + - name: Create Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh release create "${{ github.ref_name }}" \ - --generate-notes \ + --notes-file release-notes.md \ --title "${{ github.ref_name }}" \ isos/*.iso diff --git a/AGENTS.md b/AGENTS.md index 1eccca0..e04e2fa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,84 +1,168 @@ -BORA NixOS AgentiC Rules and Sprint Definitions -Strict Hard Zero Hardcoding Zero Comments Zero Inline Shell -Version 2.0.0 Sprint Foundation +# BORA NixOS AgentiC Rules -The repository directory tree follows this structure. The root contains flake.nix which is the stateless pure entry point. configuration.nix is the module loader that performs dynamic auto scan. AGENTS.md is this file with agentic rules and sprint definitions. lib contains Nix libraries with pure functions exported by default.nix hardware.nix for CPU GPU and platform auto detection and spring.nix for the DI IoC Container with Circuit Breaker. src contains the NixOS source with hosts for per machine host definitions profiles for per use case profile definitions modules organized by category guests for MicroVM guest definitions config for runtime config files scripts for shell scripts assets for static assets secrets for secrets encrypted with SOPS and age tests for Nix tests and docs for documentation. +Strict Hard Zero Hardcoding Zero Comments Zero Inline Shell +Version 2.1.0 + +## Architecture + +The repository directory tree follows this structure. The root contains flake.nix which is the stateless pure entry point. configuration.nix is the module loader that performs dynamic auto scan. AGENTS.md is this file with agentic rules and sprint definitions. lib contains Nix libraries with pure functions exported by default.nix hardware.nix for CPU GPU and platform auto detection and spring.nix for the DI IoC Container with Circuit Breaker. src contains the NixOS source with hosts for per machine host definitions profiles for per use case profile definitions modules organized by category guests for MicroVM guest definitions config for runtime config files scripts for shell scripts assets for static assets secrets for secrets encrypted with SOPS and age tests for Nix tests and docs for documentation. .changelog contains per release changelog entries following conventional commits format. The architectural principles are as follows. Single responsibility means every Nix file has one purpose. No side effects means lib functions are pure with no side effects. Auto discovery means configuration.nix scans src/modules without manual imports. Parameterization means everything uses options with mkOption without hardcoding. External shell means shell scripts in scripts referenced via builtins.readFile. External config means config files in config referenced via relative path. -Rule 1 is Zero Comments in Nix files. Inline hash comments are forbidden. Block slash asterisk comments are forbidden. Multi line comments are forbidden. Technical documentation goes in AGENTS.md and user documentation in docs. Only AGENTS.md and files in docs may contain text. +## Conventional Commits + +Every commit must follow the conventional commits specification. The format is type(scope): description. The body is optional and must be empty for squash merges. Valid types are feat for a new feature fix for a bug fix refactor for code restructuring docs for documentation changes chore for maintenance tasks test for testing changes ci for CI workflow changes style for formatting changes perf for performance improvements. The scope is the module or area affected such as core security desktop spring lib. Examples include feat(core): add sysctl hardening module fix(security): resolve nftables input chain order refactor(lib): clean up spring.nix unused params chore: add MIT license file ci: trigger release on version tags only. + +## Rules + +### Rule 1 + +Zero Comments in Nix files. Inline hash comments are forbidden. Block slash asterisk comments are forbidden. Multi line comments are forbidden. Technical documentation goes in AGENTS.md and user documentation in docs. Only AGENTS.md and files in docs may contain text. + +### Rule 2 + +Zero Shell Inline in Nix. Writing shell scripts inside quoted Nix strings is forbidden. Using pkgs.writeShellScript with inline strings is forbidden. Using shell expressions inside Nix strings is forbidden. Every shell script must be in scripts as a separate file. The correct reference is pkgs.writeShellScriptBin name with builtins.readFile reading the script path. + +### Rule 3 + +Zero Hardcoding. Hardcoding usernames like alessio or kairosci is forbidden. Hardcoding hostnames like bora or os is forbidden. Hardcoding paths IPs ports or UUIDs is forbidden. Hardcoding CPU GPU or RAM config is forbidden. Username must come from meta.nix or option. Hostname must come from meta.nix or option. Hardware must use options with lib.mkDefault. Activation must use lib.mkIf. No literal values every value must be a variable. + +### Rule 4 + +Structural Atomicity. Every modification must produce an atomic new generation. Workarounds fallbacks and placeholders are forbidden. TODO FIXME and HACK are forbidden. Comments to disable code are forbidden. To disable a module use mkIf false. An unimplemented function must not exist. + +### Rule 5 -Rule 2 is Zero Shell Inline in Nix. Writing shell scripts inside quoted Nix strings is forbidden. Using pkgs.writeShellScript with inline strings is forbidden. Using shell expressions inside Nix strings is forbidden. Every shell script must be in scripts as a separate file. The correct reference is pkgs.writeShellScriptBin name with builtins.readFile reading the script path. +Dynamic Modularity. configuration.nix scans src/modules automatically. Each category corresponds to src/modules/category. Each category has default.nix which imports submodules. Modules are enabled via mkIf cfg.enable. Profiles activate combinations of modules. To create a new module create src/modules/category/name.nix update src/modules/category/default.nix define options with enable and parameters and use mkIf cfg.enable for config. -Rule 3 is Zero Hardcoding. Hardcoding usernames like alessio or kairosci is forbidden. Hardcoding hostnames like bora or os is forbidden. Hardcoding paths IPs ports or UUIDs is forbidden. Hardcoding CPU GPU or RAM config is forbidden. Username must come from meta.nix or option. Hostname must come from meta.nix or option. Hardware must use options with lib.mkDefault. Activation must use lib.mkIf. No literal values every value must be a variable. +### Rule 6 -Rule 4 is Structural Atomicity. Every modification must produce an atomic new generation. Workarounds fallbacks and placeholders are forbidden. TODO FIXME and HACK are forbidden. Comments to disable code are forbidden. To disable a module use mkIf false. An unimplemented function must not exist. +Pull Request Only. Direct commits and pushes to the main branch are forbidden. Every change must go through a pull request on GitHub. All CI jobs must pass before merge. The only exception is the chore initial commit on main which is created manually once. No agent or developer pushes directly to main. Merges must use squash strategy. Merge commits must have an empty body and must not include the pull request number in the title. -Rule 5 is Dynamic Modularity. configuration.nix scans src/modules automatically. Each category corresponds to src/modules/category. Each category has default.nix which imports submodules. Modules are enabled via mkIf cfg.enable. Profiles activate combinations of modules. To create a new module create src/modules/category/name.nix update src/modules/category/default.nix define options with enable and parameters and use mkIf cfg.enable for config. +### Rule 7 + +CI on PR Only. The CI workflow triggers exclusively on pull requests targeting main. Push triggers are forbidden except for release tags. The CI must run linting evaluation tests hardware validation and security audit. Every job must produce a pass or fail result with no skipped steps. ISO generation is not part of CI. ISO build happens only in the release workflow triggered by version tags or locally via scripts/build/iso-build.sh. + +## Nix Best Practices + +### Module Structure + +Each module defines an options block with an enable flag and all configurable parameters. The config block is wrapped in mkIf cfg.enable. Assertions validate parameter combinations at evaluation time. Options must use mkOption with explicit type and default. Default values use mkDefault for overridability. Conditional values use mkIf. Host specific values are never hardcoded they come from meta.nix or specialArgs. + +### Pure Functions + +Library functions in lib must be pure with no side effects. They receive only their dependencies as function arguments. No config state or pkgs is accessible unless explicitly passed. Functions return values without modifying external state. Evaluation is deterministic and idempotent. + +### Parameterization + +Every configurable value uses Nix options with mkOption. Types must be explicit using types from lib.types. Defaults must be sensible and use mkDefault. Conditional overrides use mkIf. Assertions validate parameter combinations. Host specific values are injected via specialArgs from meta.nix. + +### Testing + +Tests evaluate pure library functions with assertEq. Module integration tests use nixosSystem with minimal configuration. Every module with options must have assertions. Test files live in tests and use strict evaluation. Test cases must be real world scenarios not placeholders. + +## Host Specific Parameters All host specific parameters are declared in src/hosts/hostname/meta.nix and injected via specialArgs. The generic meta.nix template contains system as system architecture hardware as hardware type profile as usage profile hostname as host name and username as user name. The substitution rules are that username in users.users becomes the username value username in home paths becomes home with username hostname in networking.hostName becomes the hostname value hostname in spring.application.name becomes the hostname value persist in environment.persistence reads from option and absolute paths for config and scripts are relative paths. -Sprint 1 is Foundation with the goal of creating the base system structure. It includes flake.nix as pure entry point with declarative inputs configuration.nix as auto scan module loader lib/default.nix exporting all libraries lib/hardware.nix as CPU GPU and Platform database src/modules/core for Boot Nix Locale and Sysctl src/hosts/hostname with meta default and hardware and AGENTS.md. +## Sprint Definitions + +### Sprint 1 + +Foundation with the goal of creating the base system structure. It includes flake.nix as pure entry point with declarative inputs configuration.nix as auto scan module loader lib/default.nix exporting all libraries lib/hardware.nix as CPU GPU and Platform database src/modules/core for Boot Nix Locale and Sysctl src/hosts/hostname with meta default and hardware and AGENTS.md. -Sprint 2 is Filesystem and Immutability with the goal of implementing ZFS Impermanence and Disko. It includes the zfs module for pool ARC and snapshot the impermanence module for persist config desktop for external config files sanoid for automatic snapshot retention and disko for declarative partitioning. +### Sprint 2 -Sprint 3 is Security with the goal of implementing extreme hardening firewall and SSH. It includes the firewall module with nftables default drop the external nftables configuration the hardening module for kernel and AppArmor the ssh module with keys only LAN only and audit logging with fail2ban. +Filesystem and Immutability with the goal of implementing ZFS Impermanence and Disko. It includes the zfs module for pool ARC and snapshot the impermanence module for persist config desktop for external config files sanoid for automatic snapshot retention and disko for declarative partitioning. -Sprint 4 is Hardware Detection with the goal of auto configuring CPU GPU and Platform. It includes the cpu module for Intel AMD and ARM the gpu module for NVIDIA AMD and Intel the platform module for Desktop Laptop and Server and lib/hardware.nix as vendor optimization database. +### Sprint 3 -Sprint 5 is Desktop and Bora Layout with the goal of creating minimal KDE Plasma 6 with original Bora layout. It includes the kde-minimal module for essential Plasma 6 the maclike module for the Bora theme the pipewire module for audio the maclike scripts for init and finalize shell and the desktop config files for plasma-appletsrc kdeglobals and kwinrc. +Security with the goal of implementing extreme hardening firewall and SSH. It includes the firewall module with nftables default drop the external nftables configuration the hardening module for kernel and AppArmor the ssh module with keys only LAN only and audit logging with fail2ban. -Sprint 6 is Container Engine with the goal of creating the container engine with hardware level isolation. It includes the microvm-host module for host and bridge the orchestrator module for pool manager the sandbox guest as generic template the containers configuration for bridge and networking and SocketVM for desktop apps with X11 and Wayland forwarding. +### Sprint 4 -Sprint 7 is Spring Framework with the goal of implementing Dependency Injection and Circuit Breaker. It includes lib/spring.nix for bean definitions topological sort mkSystemdService with resource limits circuit breaker with failure success and state circular dependency detection and the spring scripts for cgroup-init circuit-breaker and health. It also includes the orchestrator update to use Spring beans. +Hardware Detection with the goal of auto configuring CPU GPU and Platform. It includes the cpu module for Intel AMD and ARM the gpu module for NVIDIA AMD and Intel the platform module for Desktop Laptop and Server and lib/hardware.nix as vendor optimization database. -Sprint 8 is Instance Pool Orchestrator with the goal of creating the pool of isolated instances for any application. It includes the instance-pool module with pool options the guest definition per application the pool configuration the pool scripts for pool-manager spawn list and stats cgroup v2 for per instance resource isolation and Caddy reverse proxy for routing to instances. +### Sprint 5 -Sprint 9 is Testing and Documentation with the goal of implementing pure Nix tests and complete documentation. It includes tests/default.nix for pure library tests tests/shell.nix for linting environment with statix and deadnix docs/BORA-WP.md as user manual in text format AGENTS.md with always updated agentic rules and ISO generation for immediate deploy. +Desktop and Bora Layout with the goal of creating minimal KDE Plasma 6 with original Bora layout. It includes the kde-minimal module for essential Plasma 6 the maclike module for the Bora theme the pipewire module for audio the maclike scripts for init and finalize shell and the desktop config files for plasma-appletsrc kdeglobals and kwinrc. -The sprint flow proceeds from Sprint 1 to Sprint 2 to Sprint 3 to Sprint 4 from which it branches to Sprint 5 which continues to Sprint 6 which leads to Sprint 7 and Sprint 8 and finally Sprint 9. Each sprint produces a working NixOS generation without unsatisfied dependencies. +### Sprint 6 -The sprint history records all completions. All sprints from number 1 to number 9 are completed. The system is ready for build and deploy. +Container Engine with the goal of creating the container engine with hardware level isolation. It includes the microvm-host module for host and bridge the orchestrator module for pool manager the sandbox guest as generic template the containers configuration for bridge and networking and SocketVM for desktop apps with X11 and Wayland forwarding. + +### Sprint 7 + +Spring Framework with the goal of implementing Dependency Injection and Circuit Breaker. It includes lib/spring.nix for bean definitions topological sort mkSystemdService with resource limits circuit breaker with failure success and state circular dependency detection and the spring scripts for circuit-breaker and health. It also includes the orchestrator update to use Spring beans. + +### Sprint 8 + +Instance Pool Orchestrator with the goal of creating the pool of isolated instances for any application. It includes the instance-pool module with pool options the guest definition per application the pool configuration the pool scripts for pool-manager spawn list and stats cgroup v2 for per instance resource isolation and Caddy reverse proxy for routing to instances. + +### Sprint 9 + +Testing and Documentation with the goal of implementing pure Nix tests and complete documentation. It includes tests/default.nix for pure library tests tests/shell.nix for linting environment with statix and deadnix docs as user manual in text format AGENTS.md with always updated agentic rules and ISO generation for immediate deploy. + +The sprint flow proceeds from Sprint 1 to Sprint 2 to Sprint 3 to Sprint 4 from which it branches to Sprint 5 which continues to Sprint 6 which leads to Sprint 7 and Sprint 8 and finally Sprint 9. Each sprint produces a working NixOS generation without unsatisfied dependencies. All sprints from number 1 to number 9 are completed. + +## Spring Framework Specification + +### Bean Definition Bean definition happens via bora.spring.beans.name with attributes enable to enable class as service type deps as list of beans it depends on resources with cpu memory memoryMax pids ioRbps ioWbps and numa healthcheck as command to verify status dependsOn for systemd dependencies after for systemd ordering and restartPolicy for restart policy. +### Circuit Breaker + The Circuit Breaker state machine has three states. CLOSED is normal operation where requests pass through and failures increment a counter. OPEN is open circuit where requests are blocked and a timeout timer starts. HALF-OPEN is recovery test where limited requests are allowed. Transitions are that CLOSED transitions to OPEN when failures reach the threshold which defaults to 5. OPEN transitions to HALF-OPEN after the timeout which defaults to 30 seconds. HALF-OPEN transitions to CLOSED when successes reach the threshold which defaults to 2. HALF-OPEN transitions to OPEN when a failure occurs in half-open. +### Topological Sort + Topological sort resolves dependencies between beans at build time. If a cycle exists the build fails with an error message indicating circular dependency in the specified beans. +### Cgroup Hierarchy + The cgroup v2 hierarchy is organized under sys fs cgroup with the host name containing bean-database bean-redis and bean-webapp with cpu.max memory.max pids.max and io.max and OOM policy kill. The bora section contains pool for MicroVM instances with instance-001 and instance-002 with cpu.max at 50 percent and memory.max at 256 MB. -OOM protection provides OOMPolicy kill for all Spring services. MemoryHigh is the soft limit for throttling before OOM. MemoryMax is the hard limit for OOM kill if exceeded. DefaultMemoryAccounting is yes globally. The health check flow executes the healthcheck command. If the result is success it calls circuit_success. If the result is failure it calls circuit_trip. In CLOSED state it increments the counter and if it exceeds the threshold transitions to OPEN. In OPEN state it waits for the timeout then transitions to HALF-OPEN. In HALF-OPEN state if the attempt count is below the maximum it retries otherwise transitions to CLOSED. If the circuit is OPEN the service does not start and exits with code 1. +## Security Baseline + +### Kernel Parameters The kernel sysctl parameters include kernel.kptr_restrict set to 2 kernel.dmesg_restrict to 1 kernel.perf_event_paranoid to 3 kernel.yama.ptrace_scope to 2 kernel.randomize_va_space to 2 kernel.unprivileged_bpf_disabled to 1 net.core.bpf_jit_enable to 0 kernel.kexec_load_disabled to 1 and kernel.sysrq to 0. +### Firewall + The nftables firewall defines chain input with policy DROP accepting established and related connections loopback interface traffic ICMP with rate limit of 10 per second and TCP port 22 from LAN addresses and logging and dropping everything else. chain forward with policy DROP accepts established and related connections and traffic from the microvm interface. chain output with policy ACCEPT. +### SSH Hardening + SSH hardening provides PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes MaxAuthTries 3 MaxSessions 4 AllowTcpForwarding no AllowAgentForwarding no ciphers ChaCha20-Poly1305 and AES-256-GCM and MACs HMAC-SHA2-512-ETM and HMAC-SHA2-256-ETM. AppArmor is enforced with active cache profiles from apparmor-profiles and lockdown set to confidentiality. -The idempotency rules require that nixos-rebuild switch is idempotent running it twice in a row must produce the same result. There must be no side effects outside the nix store. The etc directory is regenerated on every build. User state resides only in persist and home. The root filesystem is ephemeral via impermanence. +## Quality Gates + +The mandatory quality gates before each merge include statix check src for Nix linting deadnix src for dead code detection nixpkgs-fmt check src for formatting nix-instantiate --eval --strict tests/default.nix for library tests and nix-instantiate --eval --strict tests/modules.nix for module integration tests. The merge must be blocked if any of the quality gates fails. -Regarding atomicity every nixos-rebuild produces a new generation. The previous generation remains intact in the boot menu. Rollback is performed with nixos-rebuild switch rollback. ZFS performs automatic pre rebuild snapshot and post rebuild snapshot via sanoid. Workarounds attempts hacks and placeholders have zero tolerance. +## Templates -Rule 6 is Pull Request Only. Direct commits and pushes to the main branch are forbidden. Every change must go through a pull request on GitHub. All CI jobs must pass before merge. The only exception is the chore initial commit on main which is created manually once. No agent or developer pushes directly to main. +### Module Template -Rule 7 is CI on PR Only. The CI workflow triggers exclusively on pull requests targeting main. Push triggers are forbidden except for release tags. The CI must run linting evaluation tests hardware validation and security audit. Every job must produce a pass or fail result with no skipped steps. ISO generation is not part of CI. ISO build happens only in the release workflow triggered by version tags or locally via scripts/build/iso-build.sh. +The template for a new module requires defining config lib pkgs using let cfg config.bora.category.module to access options. options.bora.category.module must contain enable as mkEnableOption and option1 as mkOption with type and default. config must be wrapped in mkIf cfg.enable with attr set to mkDefault cfg.option1. -The mandatory quality gates before each merge include statix check src for Nix linting deadnix src for dead code detection nixpkgs-fmt check src for formatting nix-instantiate --eval --strict tests/default.nix for library tests and nix-instantiate --eval --strict tests/modules.nix for module integration tests. The merge must be blocked if any of the quality gates fails. +### Host Template -The test structure provides tests/default.nix for testing lib functions with testHardwareDetect testSpringFramework testCoreModules and testSecurityModules and tests/modules.nix for module integration tests with concrete host and profile configurations. Every module that defines options must have assertions that verify conditions with error message. Every test case must be a real world scenario not a placeholder or sample. +The template for a new host requires a meta.nix file with system hardware profile hostname and username. The default.nix file receives config lib pkgs username and hostname and configures networking.hostName with hostname and users.users with username as isNormalUser true and extraGroups with wheel. -The CI workflow defines three sequential phases. Phase 1 is lint with statix deadnix and nixpkgs-fmt running in parallel on the same runner. Phase 2 is eval-tests with nix-instantiate --eval --strict on all test files. Phase 3 is security-audit with nix-instantiate --eval on the hardened configuration to verify sysctl firewall and SSH parameters are applied correctly. +### Shell Script Template -When the user requests a modification the agent searches src/modules for the relevant module. If it does not exist it creates a new category creates default.nix and creates the module file. Then it modifies options and config. If shell scripts are needed they go in scripts never inline. If config files are needed they go in config never inline. If hardcoding exists it is replaced with options mkDefault and mkIf. If comments exist in Nix files they are removed and placed in AGENTS.md. Then it runs statix deadnix and nixpkgs-fmt. It verifies idempotency. Every modification must be submitted as a pull request on the alpha branch targeting main. Direct commits to main are forbidden. +The template for a shell script requires the file in scripts/category/name.sh with bash shebang set euo pipefail parameters with defaults and main function that executes the logic. The reference in Nix uses pkgs.writeShellScriptBin with builtins.readFile to read the script path. -The rules for the agent are as follows. Never write comments in Nix files. Never write shell scripts inline in Nix files. Never hardcode username hostname or path. Never commit or push to main directly. Always use options with mkOption for parameters. Always use mkIf for conditional activation. Always use mkDefault for overridable defaults. Always submit changes through a pull request from alpha to main. Shell scripts in scripts. Config files in config. Technical documentation in AGENTS.md. User documentation in docs. After every modification run statix deadnix and nixpkgs-fmt. Every modification must be idempotent. +## Idempotency and Atomicity -The template for a new module requires defining config lib pkgs using let cfg config.bora.category.module to access options. options.bora.category.module must contain enable as mkEnableOption and option1 as mkOption with type and default. config must be wrapped in mkIf cfg.enable with attr set to mkDefault cfg.option1. +The idempotency rules require that nixos-rebuild switch is idempotent running it twice in a row must produce the same result. There must be no side effects outside the nix store. The etc directory is regenerated on every build. User state resides only in persist and home. The root filesystem is ephemeral via impermanence. Every nixos-rebuild produces a new generation. The previous generation remains intact in the boot menu. Rollback is performed with nixos-rebuild switch rollback. ZFS performs automatic pre rebuild snapshot and post rebuild snapshot via sanoid. Workarounds attempts hacks and placeholders have zero tolerance. -The template for a new host requires a meta.nix file with system hardware profile hostname and username. The default.nix file receives config lib pkgs username and hostname and configures networking.hostName with hostname and users.users with username as isNormalUser true and extraGroups with wheel. +## Agent Workflow -The template for a shell script requires the file in scripts/category/name.sh with bash shebang set euo pipefail parameters with defaults and main function that executes the logic. The reference in Nix uses pkgs.writeShellScriptBin with builtins.readFile to read the script path. +When the user requests a modification the agent searches src/modules for the relevant module. If it does not exist it creates a new category creates default.nix and creates the module file. Then it modifies options and config. If shell scripts are needed they go in scripts never inline. If config files are needed they go in config never inline. If hardcoding exists it is replaced with options mkDefault and mkIf. If comments exist in Nix files they are removed and placed in AGENTS.md. Then it runs statix deadnix and nixpkgs-fmt. It verifies idempotency. Every modification must be submitted as a pull request on the alpha branch targeting main. Direct commits to main are forbidden. Merges are performed only when explicitly requested by the user. Merge commits use squash strategy with empty body and no pull request number in the title. + +--- -BORA NixOS AgentiC Rules v2.0.0 Sprint Foundation Copyright 2026 Distributed under MIT license diff --git a/CHANGELOG.md b/CHANGELOG.md index f5c95cd..835db69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,3 @@ -25.11 (2026-05-20) +# Changelog -fix: simplify CI, fix desktop ISO, fix vaapiVdpau rename. remove invalid iso-minimal-validation alias from flake.nix. rename iso-graphical to iso-desktop, add iso-laptop, restructure CI with isolated test and benchmark, fix vaapiVdpau rename. repair YAML syntax in ci.yml with duplicated job fields and restore ISO build steps. feat: add iso-server, 3 ISO CI builds, rewrite tests with concrete cases and benchmarks. fix: remove .github from paths-ignore so CI runs on workflow changes. remove build-system job which was host-specific hw, simplify CI, fix nvidia portal. remove xdg-desktop-portal-kde reference no longer in nixpkgs 25.11. use systemd.settings.Manager and kdePackages.xdg-desktop-portal-kde for nixos 25.11 compat. wrap inline module functions in parentheses. add lib to inline module args in flake.nix. resolve platform and gpu prime conflict, use image.baseName for ISO, simplify CI. update KDE ISO module path and remove conflicting hardware defaults from workstation profile. consolidate etc attrs in maclike.nix to resolve W20. resolve remaining statix W04 and W20 warnings. feat: update stateVersion to 25.11, add CI build and test jobs, add gitignore. fix: resolve statix warnings and disable FlakeHub cache in CI. resolve devShell buildInputs type error in CI. feat: add CI workflows, build script, docs and fix nftables ZFS boot. - -25.05 (2026-04-15) - -feat: add test suite infrastructure. add shell scripts for spring, pool and desktop. add containers, microvm guests and instance pool. add desktop, network and config file modules. implement security, hardware and filesystem modules. add hosts, profiles and core modules. add foundation layer with flake, config, lib and docs. +Per version changelog entries are in the .changelog directory. Each file covers a single version with conventional commit format entries. diff --git a/README.md b/README.md index d86a0ec..07f2654 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,21 @@ +# BORA NixOS + BORA is a modular immutable NixOS configuration framework built on Zero Hardcoding Zero Comments and Zero Inline Shell principles. Every value is parameterized through Nix options. All technical documentation lives in AGENTS.md. All shell scripts are standalone files in scripts referenced via builtins.readFile. The framework includes a Spring style dependency injection and circuit breaker library for systemd services a MicroVM container engine with instance pool orchestration KDE Plasma 6 desktop with custom Bora layout ZFS filesystem with impermanence and a layered security model with NFTables kernel hardening and SSH hardening. +## Quick Start + Set hostname and username in src/hosts/target-host/meta.nix. Run nixos-install flake hash target-host. Set a password and reboot. For ISO generation run nix build hash packages.x86_64-linux.iso-minimal for headless or nix build hash packages.x86_64-linux.iso-graphical for desktop. +## Prerequisites + Nix package manager with flakes enabled. +## Project Structure + src/hosts contains per machine configurations with meta.nix for system hardware profile hostname and username. src/modules contains categorized modules for core filesystem security containers desktop hardware and network. src/profiles defines use case profiles like workstation developer server and minimal. lib contains pure Nix libraries including hardware detection and the Spring framework. +## License + MIT diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 6b0e150..c794cab 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,21 +1,45 @@ -BORA NixOS Architecture Document Version 2.0.0 +# BORA NixOS Architecture Document + +Version 2.1.0 + +## Introduction BORA is a modular immutable NixOS configuration framework built on four pillars. Zero Hardcoding means every value is parameterized through Nix options. No usernames hostnames paths IPs or hardware identifiers are hardcoded. Host specific values are declared in meta.nix files and injected via specialArgs. Zero Comments means Nix files contain no comments. All technical documentation lives in AGENTS.md. User documentation lives in docs. This enforces self documenting code through meaningful identifier names and pure functional patterns. Zero Inline Shell means every shell script is stored as a standalone file in scripts and referenced via builtins.readFile. No script content appears inside Nix strings. Pure Functions means library functions in lib are pure with no side effects. Module evaluation is deterministic and idempotent. +## Repository Structure + The repository follows a strict directory layout enforced by the module loader and build system. The top level contains flake.nix as the entry point declaring inputs like nixpkgs nixos-hardware microvm sops-nix and nixos-generators and defining outputs for each discovered host plus ISO generation. configuration.nix is the module loader that auto scans src/modules for category directories imports each category default.nix and loads the selected profile from src/profiles. -The source tree is organized as follows. src/hosts contains per machine configurations with each subdirectory named after the host containing meta.nix for system hardware profile hostname and username default.nix for host specific config and hardware.nix for generated hardware scan. src/profiles defines use case configurations including workstation with desktop KDE and Bora layout developer with workstation plus dev tools server with headless and container orchestrator and minimal with headless minimal. src/modules is organized by category with core for boot nix locale and sysctl filesystem for ZFS and impermanence security for firewall hardening and SSH containers for MicroVM host orchestrator and instance pool desktop for KDE minimal PipeWire and Bora layout hardware for CPU GPU and platform and network for base and DNS. src/guests defines MicroVM guest templates with sandbox.nix as a generic template and the example directory demonstrating a concrete instance definition with pool configuration. lib contains pure Nix library functions including hardware detection database Spring DI IoC framework with circuit breaker and the library aggregator. config holds external configuration files referenced by modules such as desktop panel layouts NFTables rulesets and container bridge configuration. scripts holds standalone shell scripts organized by subsystem including spring services desktop initialization and pool management. assets is reserved for static files like wallpapers themes fonts and icons. secrets is reserved for encrypted secrets via SOPS and age. tests contains pure Nix evaluation tests and a shell environment for linting with statix deadnix and nixpkgs-fmt. docs contains documentation in plain text format. +The source tree is organized as follows. src/hosts contains per machine configurations with each subdirectory named after the host containing meta.nix for system hardware profile hostname and username default.nix for host specific config and hardware.nix for generated hardware scan. src/profiles defines use case configurations including workstation with desktop KDE and Bora layout developer with workstation plus dev tools server with headless and container orchestrator and minimal with headless minimal. src/modules is organized by category with core for boot nix locale and sysctl filesystem for ZFS and impermanence security for firewall hardening and SSH containers for MicroVM host orchestrator and instance pool desktop for KDE minimal PipeWire and Bora layout hardware for CPU GPU and platform and network for base and DNS. + +src/guests defines MicroVM guest templates with sandbox.nix as a generic template and the example directory demonstrating a concrete instance definition with pool configuration. lib contains pure Nix library functions including hardware detection database Spring DI IoC framework with circuit breaker and the library aggregator. config holds external configuration files referenced by modules such as desktop panel layouts NFTables rulesets and container bridge configuration. scripts holds standalone shell scripts organized by subsystem including spring services desktop initialization and pool management. assets is reserved for static files like wallpapers themes fonts and icons. secrets is reserved for encrypted secrets via SOPS and age. tests contains pure Nix evaluation tests and a shell environment for linting with statix deadnix and nixpkgs-fmt. docs contains documentation in markdown format. .changelog contains per release changelog entries. + +## Core Design Principles + +Single Responsibility means each Nix file has exactly one purpose. A file equals one module equals one function. No file mixes concerns. Auto Discovery means the configuration.nix module loader scans src/modules at evaluation time. No manual imports are needed when adding new modules. Each category default.nix imports all submodules within that category. + +Conditional Activation means modules are enabled or disabled via mkIf cfg.enable. The enable option is declared in the module options block. Profiles activate combinations of modules by setting these options to their preferred values. Idempotency means nixos-rebuild switch is idempotent. Running it twice produces the same result. No state is modified outside the Nix store. User state lives exclusively in persist and home. The root filesystem is ephemeral through impermanence. + +Atomicity means every nixos-rebuild produces a new generation. The previous generation remains intact and selectable from the boot menu. Rollback uses nixos-rebuild switch rollback. ZFS snapshots are taken automatically before and after rebuild via sanoid. Parameterization means all configurable values use Nix options with mkOption. Default values use mkDefault so they can be overridden. Conditional values use mkIf. No literal values appear in configuration logic. + +## Module System -Single Responsibility means each Nix file has exactly one purpose. A file equals one module equals one function. No file mixes concerns. Auto Discovery means the configuration.nix module loader scans src/modules at evaluation time. No manual imports are needed when adding new modules. Each category default.nix imports all submodules within that category. Conditional Activation means modules are enabled or disabled via mkIf cfg.enable. The enable option is declared in the module options block. Profiles activate combinations of modules by setting these options to their preferred values. Idempotency means nixos-rebuild switch is idempotent. Running it twice produces the same result. No state is modified outside the Nix store. User state lives exclusively in persist and home. The root filesystem is ephemeral through impermanence. Atomicity means every nixos-rebuild produces a new generation. The previous generation remains intact and selectable from the boot menu. Rollback uses nixos-rebuild switch rollback. ZFS snapshots are taken automatically before and after rebuild via sanoid. Parameterization means all configurable values use Nix options with mkOption. Default values use mkDefault so they can be overridden. Conditional values use mkIf. No literal values appear in configuration logic. +Modules are organized into categories under src/modules. Each category represents a subsystem of the operating system. The core category covers boot configuration with systemd-boot kernel parameters and initrd Nix daemon settings with auto-optimise garbage collection and substituters locale and timezone and sysctl kernel parameters. The filesystem category covers ZFS pool creation ARC tuning automatic trimming scrub scheduling sanoid snapshot retention disko partitioning and impermanence configuration with persistent directories and files. -Modules are organized into categories under src/modules. Each category represents a subsystem of the operating system. The core category covers boot configuration with systemd-boot kernel parameters and initrd Nix daemon settings with auto-optimise garbage collection and substituters locale and timezone and sysctl kernel parameters. The filesystem category covers ZFS pool creation ARC tuning automatic trimming scrub scheduling sanoid snapshot retention disko partitioning and impermanence configuration with persistent directories and files. The security category covers NFTables firewall with default drop policy kernel hardening through sysctl AppArmor enforcement with lockdown SSH server hardening with key only access rate limiting and minimal ciphers Fail2ban for brute force protection and audit logging. The containers category covers MicroVM host configuration with bridge networking orchestrator for managing guest lifecycles and instance pool for dynamic scaling of guest instances with cgroup v2 resource isolation. The desktop category covers KDE Plasma 6 minimal installation PipeWire audio server and Bora custom desktop layout with top bar dock global menu and cosmic dark theme. The hardware category covers CPU specific optimizations for Intel AMD and ARM GPU drivers and configuration for NVIDIA AMD and Intel and platform tuning for desktop laptop and server. The network category covers base network configuration and DNS resolver settings. +The security category covers NFTables firewall with default drop policy kernel hardening through sysctl AppArmor enforcement with lockdown SSH server hardening with key only access rate limiting and minimal ciphers Fail2ban for brute force protection and audit logging. The containers category covers MicroVM host configuration with bridge networking orchestrator for managing guest lifecycles and instance pool for dynamic scaling of guest instances with cgroup v2 resource isolation. + +The desktop category covers KDE Plasma 6 minimal installation PipeWire audio server and Bora custom desktop layout with top bar dock global menu and cosmic dark theme. The hardware category covers CPU specific optimizations for Intel AMD and ARM GPU drivers and configuration for NVIDIA AMD and Intel and platform tuning for desktop laptop and server. The network category covers base network configuration and DNS resolver settings. Each module defines an options block with an enable flag and all configurable parameters. The config block is wrapped in mkIf cfg.enable. Assertions validate parameter combinations at evaluation time. To create a new module you first create src/modules/category/name.nix with options and config then update src/modules/category/default.nix to import the new file then define options with mkOption and use mkIf for conditional config and finally use assertions to validate constraints. +## Host and Profile System + Each host is defined in src/hosts/hostname. The meta.nix file declares four attributes. system is the NixOS system architecture such as x86_64-linux. hardware is the hardware class such as desktop laptop or server. profile is the use case profile name such as workstation developer server or minimal. hostname is the machine hostname. username is the primary user name. The flake.nix reads src/hosts to discover available hosts. It passes hostname and username from meta.nix as specialArgs to the NixOS configuration. This eliminates all hardcoded user and host references. Profiles in src/profiles define combinations of enabled modules. A profile sets bora options to mkDefault values establishing the baseline configuration for that use case. Profiles inherit from more basic profiles where applicable. The configuration.nix loads the profile specified in meta.nix using a dynamic import based on the profile attribute. +## Spring Framework + The Spring framework in lib/spring.nix provides dependency injection and circuit breaker patterns for systemd services. Bean definitions use the bora.spring.beans attribute set. Each bean specifies a class as a service type identifier for organizational purposes a deps list of bean names this bean depends on resources with resource limits for cgroup v2 isolation including cpu memory memoryMax pids ioRbps ioWbps and numa a healthcheck command that returns zero for healthy service dependsOn for systemd unit dependencies after for systemd unit ordering and restartPolicy for systemd restart policy. The framework performs topological sort of bean dependencies at build time. Circular dependencies cause a build failure with a diagnostic message listing the cycle. The circuit breaker implements a three state machine. CLOSED is normal operation where requests pass through and failures increment a counter. OPEN is when the circuit is open requests are blocked and a timeout timer starts. HALF-OPEN is recovery test mode where limited requests are allowed through. @@ -24,26 +48,36 @@ Circuit breaker transitions work as follows. CLOSED transitions to OPEN when fai Cgroup v2 hierarchy is created under sys fs cgroup hostname bean name with cpu.max memory.max pids.max and io.max limits. OOM policy is set to kill for all Spring services. The health check flow executes the healthcheck command periodically. Success calls circuit_success which may transition to CLOSED. Failure calls circuit_trip which may transition to OPEN. When the circuit is OPEN the service exits with code 1. +## Security Architecture + Security is implemented in layers. Kernel hardening uses sysctl parameters to restrict kernel pointer access dmesg access performance events ptrace BPF kexec and SysRq. ASLR is set to maximum. Unprivileged BPF is disabled. BPF JIT is disabled. The firewall uses NFTables with a default drop policy on the input chain. Only established and related connections loopback traffic rate limited ICMP and SSH from LAN addresses are accepted. The forward chain accepts established and related connections and traffic from the microvm bridge interface. The output chain has a default accept policy. SSH is hardened with no root login no password authentication key only access rate limited authentication attempts limited sessions no TCP or agent forwarding and modern cipher suites including ChaCha20-Poly1305 and AES-256-GCM with ETM MACs. AppArmor is enforced with cache enabled. The apparmor-profiles package provides additional profiles. Lockdown is set to confidentiality. Fail2ban monitors SSH and HTTP services. Audit logging captures security relevant events. +## Filesystem Architecture + The filesystem uses ZFS as the primary filesystem with impermanence for root immutability. ZFS pools are created with encryption compression using zstd-3 atime disabled and automatic trim enabled. ARC size is configurable with a default of 8 GB. Snapshot management uses sanoid with configurable retention policies. Automatic scrub runs on a configurable schedule. Impermanence makes the root filesystem ephemeral. Only directories and files listed in environment.persistence.persist are preserved across reboots. User data in persist and home persists. System state including machine-id resolv.conf and SSH keys is explicitly persisted. Disko provides declarative partitioning with disk layout defined in configuration not manual partitioning. Pre-rebuild and post-rebuild ZFS snapshots are created automatically via sanoid. The previous generation remains bootable through the boot menu entry. +## Container Architecture + Containers use MicroVM for hardware level isolation. Each guest runs as a separate microvm with dedicated vCPU memory and storage resources. The host configures a bridge interface called microvm for guest networking. Guests connect through this bridge. Socket forwarding enables X11 and Wayland forwarding for desktop application containers. The orchestrator manages guest lifecycles including create start stop and destroy. It uses cgroup v2 for resource isolation at the pool level. The instance pool provides dynamic scaling. Key parameters include maxInstances basePort memPerInstance cpuPerInstance storagePerInstance appPackage appCommand and healthcheckCmd. The pool manager automatically spawns new instances up to the configured maximum and performs health checks on running instances. Caddy serves as a reverse proxy routing requests to the appropriate instance based on port mapping. The cgroup v2 hierarchy for containers is structured as sys fs cgroup hostname pool instance-001 and instance-002 each with cpu.max memory.max pids.max and io.max limits. +## Desktop Architecture + The desktop environment uses KDE Plasma 6 with a custom Bora layout. KDE Plasma 6 is installed with essential components only including plasma-desktop kwin konsole dolphin kscreen plasma-nm plasma-pa bluedevil powerdevil kdecoration-viewer kactivitymanagerd and polkit-kde-agent-1. Discover and PIM applications are excluded. The Bora layout provides a top bar with global menu application launcher system tray clock and workspace switcher. A dock with favorites running applications and trash. Custom window decorations and button layout. A custom color scheme called BoraDark with a dark cosmic background and cyan accent. The color scheme uses background value 0A0C16 alternate background value 11131F foreground value C0C5D4 selection background value 7B2FBE selection foreground value FFFFFF active titlebar value 1A1C2B inactive titlebar value 0A0C16 accent value 00D4FF link value 00D4FF and visited link value 7B2FBE. PipeWire provides audio with WirePlumber session manager and low latency configuration for real time audio. Desktop initialization scripts run at first login to configure the panel layout window rules and keyboard shortcuts through the KDE configuration system using kwriteconfig6. +## Build and Deploy + Build targets are defined in flake.nix outputs. nixosConfigurations.hostname provides standard NixOS configuration build. packages.system.iso-minimal provides minimal ISO image without desktop. packages.system.iso-graphical provides full ISO with desktop environment. The flake uses nixos-generators for ISO creation. The ISO configuration includes ZFS vfat and xfs filesystem support. The deployment workflow starts by setting hostname and username in src/hosts/target-host/meta.nix and optionally overriding the profile. Then run nixos-install flake hash target-host. Set a password for the user. Then reboot. Post deployment validation includes verifying ZFS pools and datasets are created correctly verifying firewall rules with nft list ruleset verifying SSH is accessible only via key from LAN verifying desktop layout has top bar dock and correct color scheme verifying microvm bridge interface exists and verifying cgroup v2 hierarchy is populated. diff --git a/docs/MANUAL.md b/docs/MANUAL.md index a6be038..442bfa8 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -1,19 +1,37 @@ -BORA NixOS User Manual +# BORA NixOS User Manual + +## Getting Started To deploy BORA on a new machine first create a host directory under src/hosts with a meta.nix file containing the system architecture hardware profile hostname and username. Then run nixos-install flake hash target-host. After installation reboot and verify the system. +## Host Configuration + Each host is defined in src/hosts/hostname. The meta.nix file must export an attribute set with four keys. system is the architecture like x86_64-linux. hardware is the hardware class like desktop laptop or server. profile is the use case profile like workstation developer server or minimal. hostname is the machine hostname. username is the primary user name. +## Profile Selection + Profiles define which modules are enabled. The workstation profile enables desktop KDE and Bora layout. The developer profile adds development tools. The server profile is headless with container orchestrator. The minimal profile is a headless base system. +## Filesystem and Storage + The default filesystem layout uses ZFS with encryption and compression. The root filesystem is ephemeral through impermanence with persistent data stored under persist. The disko module provides declarative partitioning. +## Container Engine + MicroVM guests provide hardware level isolation. The orchestrator manages guest lifecycles. The instance pool provides dynamic scaling with cgroup v2 resource limits. Caddy serves as reverse proxy for routed instances. +## Desktop Environment + KDE Plasma 6 minimal with custom Bora layout including top bar dock global menu and dark color scheme. PipeWire provides audio with low latency configuration. +## Security + Firewall default drop with NFTables. Kernel hardening through sysctl. SSH key only from LAN. AppArmor enforced. Fail2ban active. Audit logging enabled. +## Spring Framework + Dependency injection for systemd services with circuit breaker. Bean definitions per service. Health based auto recovery. Cgroup v2 resource isolation. -System updates: run nix flake update to update all inputs then nixos-rebuild switch to apply. ZFS maintenance: monitor pool status with zpool status, check scrub progress with zpool scrub, list snapshots with zfs list t snapshot. Troubleshooting: check service status with systemctl status bora bean name, view logs with journalctl u bora bean name, verify firewall with nft list ruleset, check cgroup usage with systemd cgtop. +## Maintenance + +For system updates run nix flake update to update all inputs then nixos-rebuild switch to apply. For ZFS maintenance monitor pool status with zpool status check scrub progress with zpool scrub and list snapshots with zfs list t snapshot. For troubleshooting check service status with systemctl status bora bean name view logs with journalctl u bora bean name verify firewall with nft list ruleset and check cgroup usage with systemd cgtop.