From 44d8f9c90d306f14b96f815b5e340a7f1768ad01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Sj=C3=B6str=C3=B6m?= Date: Thu, 12 Sep 2024 21:57:01 +0200 Subject: [PATCH] PatOS is born! --- .envrc | 1 + .gitignore | 6 +++ base.nix | 11 +++++ flake.lock | 27 ++++++++++++ flake.nix | 70 +++++++++++++++++++++++++++++ modules/filesystems.nix | 44 +++++++++++++++++++ modules/generic.nix | 41 +++++++++++++++++ modules/minimize.nix | 19 ++++++++ modules/network.nix | 11 +++++ modules/partitions.nix | 85 ++++++++++++++++++++++++++++++++++++ modules/system_overrides.nix | 5 +++ modules/sysupdate.nix | 63 ++++++++++++++++++++++++++ 12 files changed, 383 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 base.nix create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 modules/filesystems.nix create mode 100644 modules/generic.nix create mode 100644 modules/minimize.nix create mode 100644 modules/network.nix create mode 100644 modules/partitions.nix create mode 100644 modules/system_overrides.nix create mode 100644 modules/sysupdate.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4e52812 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.env +.direnv +.task +result +.*.swp +.*.swo diff --git a/base.nix b/base.nix new file mode 100644 index 0000000..2c1076a --- /dev/null +++ b/base.nix @@ -0,0 +1,11 @@ +{ ... }: { + imports = [ + ./modules/system_overrides.nix + ./modules/minimize.nix + ./modules/generic.nix + ./modules/filesystems.nix + ./modules/partitions.nix + ./modules/network.nix + ./modules/sysupdate.nix + ]; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..db42797 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1725983898, + "narHash": "sha256-4b3A9zPpxAxLnkF9MawJNHDtOOl6ruL0r6Og1TEDGCE=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "1355a0cbfeac61d785b7183c0caaec1f97361b43", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..d62d9c3 --- /dev/null +++ b/flake.nix @@ -0,0 +1,70 @@ +{ + description = "PatOS is a minimal, immutable Linux distribution specialized for the Patagia Platform."; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + }; + + outputs = { self, nixpkgs }: { + lib = { + # Prepare a ready-to-boot disk image. + mkInstallImage = nixos: + let + config = nixos.config; + pkgs = nixpkgs.legacyPackages.x86_64-linux.pkgs; + in + nixos.pkgs.runCommand "update-${config.system.image.version}" + { + nativeBuildInputs = with pkgs; [ qemu ]; + } '' + mkdir -p $out + qemu-img convert -f raw -O qcow2 -C ${config.system.build.image}/${config.boot.uki.name}_${config.system.image.version}.raw $out/disk.qcow2 + ''; + }; + + devShells.x86_64-linux.default = + let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + in + pkgs.mkShell { + packages = [ + self.packages.x86_64-linux.qemu-efi + ]; + }; + + packages.x86_64-linux = { + default = self.packages.x86_64-linux.patos_image; + + patos_image = self.lib.mkInstallImage self.nixosConfigurations.patos; + + # A helper script to run the disk images above. + qemu-efi = + let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + in + pkgs.writeShellApplication { + name = "qemu-efi"; + + runtimeInputs = [ pkgs.qemu_kvm ]; + + text = '' + qemu-system-x86_64 \ + -smp 2 -m 2048 -machine q35,accel=kvm \ + -bios ${pkgs.OVMF.fd}/FV/OVMF.fd \ + -snapshot \ + -display none \ + -serial stdio "$@" + ''; + }; + }; + + nixosConfigurations = { + patos = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + ./base.nix + ]; + }; + }; + }; +} diff --git a/modules/filesystems.nix b/modules/filesystems.nix new file mode 100644 index 0000000..01753be --- /dev/null +++ b/modules/filesystems.nix @@ -0,0 +1,44 @@ +{ config, ... }: { + + zramSwap = { + enable = true; + algorithm = "zstd"; + memoryPercent = 20; + }; + + fileSystems = { + "/" = { + fsType = "tmpfs"; + options = [ + "size=20%" + ]; + }; + + "/var" = + let + partConf = config.image.repart.partitions."var".repartConfig; + in + { + device = "/dev/disk/by-partuuid/${partConf.UUID}"; + fsType = partConf.Format; + }; + + "/boot" = + let + partConf = config.image.repart.partitions."esp".repartConfig; + in + { + device = "/dev/disk/by-partuuid/${partConf.UUID}"; + fsType = partConf.Format; + }; + + "/nix/store" = + let + partConf = config.image.repart.partitions."store".repartConfig; + in + { + device = "/dev/disk/by-partlabel/${partConf.Label}"; + fsType = partConf.Format; + }; + }; +} diff --git a/modules/generic.nix b/modules/generic.nix new file mode 100644 index 0000000..00058ef --- /dev/null +++ b/modules/generic.nix @@ -0,0 +1,41 @@ +{ pkgs, config, ... }: { + + boot.uki.name = "patos"; + boot.kernelParams = [ "console=ttyS0" ]; + + system.nixos.release = "2024-09"; + system.nixos.codeName = "Finn"; + + system.nixos.distroId = "patos"; + system.nixos.distroName = "PatOS"; + system.image.version = "0.0.1"; # FIXME: Use epoch version. + + # Make the current system version visible in the prompt. + programs.bash.promptInit = '' + export PS1="\u@\h (version ${config.system.image.version}) $ " + ''; + + # Not compatible with system.etc.overlay.enable yet. + # users.mutableUsers = false; + + services.getty.autologinUser = "root"; + + boot.initrd.systemd.enable = true; + + # Don't accumulate crap. + boot.tmp.cleanOnBoot = true; + services.journald.extraConfig = '' + SystemMaxUse=10M + ''; + + # Debugging + environment.systemPackages = with pkgs; [ + parted + (runCommand "systemd-sysupdate" { } '' + mkdir -p $out/bin + ln -s ${config.systemd.package}/lib/systemd/systemd-sysupdate $out/bin + '') + ]; + + system.stateVersion = "24.11"; +} diff --git a/modules/minimize.nix b/modules/minimize.nix new file mode 100644 index 0000000..1e62db1 --- /dev/null +++ b/modules/minimize.nix @@ -0,0 +1,19 @@ +{ modulesPath, ... }: { + imports = [ + "${modulesPath}/profiles/minimal.nix" + ]; + + boot.loader.grub.enable = false; + + system.switch.enable = false; + nix.enable = false; + + system.etc.overlay.enable = true; + systemd.sysusers.enable = true; + + system.disableInstallerTools = true; + programs.less.lessopen = null; + programs.command-not-found.enable = false; + boot.enableContainers = false; + environment.defaultPackages = [ ]; +} diff --git a/modules/network.nix b/modules/network.nix new file mode 100644 index 0000000..c08bc3c --- /dev/null +++ b/modules/network.nix @@ -0,0 +1,11 @@ +{ config, ... }: { + networking = { + useNetworkd = true; + + # Easy debugging. + firewall.enable = false; + }; + + # Faster boot. + systemd.network.wait-online.enable = false; +} diff --git a/modules/partitions.nix b/modules/partitions.nix new file mode 100644 index 0000000..c900c24 --- /dev/null +++ b/modules/partitions.nix @@ -0,0 +1,85 @@ +{ config, pkgs, lib, modulesPath, ... }: { + + imports = [ + "${modulesPath}/image/repart.nix" + ]; + + image.repart = + let + efiArch = pkgs.stdenv.hostPlatform.efiArch; + in + { + name = config.boot.uki.name; + split = true; + + partitions = { + "esp" = { + contents = { + "/EFI/BOOT/BOOT${lib.toUpper efiArch}.EFI".source = + "${pkgs.systemd}/lib/systemd/boot/efi/systemd-boot${efiArch}.efi"; + + "/EFI/Linux/${config.system.boot.loader.ukiFile}".source = + "${config.system.build.uki}/${config.system.boot.loader.ukiFile}"; + + # systemd-boot configuration + "/loader/loader.conf".source = (pkgs.writeText "$out" '' + timeout 3 + ''); + }; + repartConfig = { + Type = "esp"; + UUID = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"; # Well known + Format = "vfat"; + SizeMinBytes = "256M"; + SplitName = "-"; + }; + }; + "store" = { + storePaths = [ config.system.build.toplevel ]; + stripNixStorePrefix = true; + repartConfig = { + Type = "linux-generic"; + Label = "store_${config.system.image.version}"; + Format = "squashfs"; + Minimize = "off"; + ReadOnly = "yes"; + + SizeMinBytes = "1G"; + SizeMaxBytes = "1G"; + SplitName = "store"; + }; + }; + + # Placeholder for the second installed Nix store. + "store-empty" = { + repartConfig = { + Type = "linux-generic"; + Label = "_empty"; + Minimize = "off"; + SizeMinBytes = "1G"; + SizeMaxBytes = "1G"; + SplitName = "-"; + }; + }; + + # Persistent storage + "var" = { + repartConfig = { + Type = "var"; + UUID = "4d21b016-b534-45c2-a9fb-5c16e091fd2d"; # Well known + Format = "xfs"; + Label = "nixos-persistent"; + Minimize = "off"; + + # Has to be large enough to hold update files. + SizeMinBytes = "2G"; + SizeMaxBytes = "2G"; + SplitName = "-"; + + # Wiping this gives us a clean state. + FactoryReset = "yes"; + }; + }; + }; + }; +} diff --git a/modules/system_overrides.nix b/modules/system_overrides.nix new file mode 100644 index 0000000..1627d28 --- /dev/null +++ b/modules/system_overrides.nix @@ -0,0 +1,5 @@ +{ lib, options, ... }: { + # This fields is immutable by default, but can be overridden. + options.system.nixos.codeName = lib.mkOption { readOnly = false; }; + options.system.nixos.release = lib.mkOption { readOnly = false; }; +} diff --git a/modules/sysupdate.nix b/modules/sysupdate.nix new file mode 100644 index 0000000..ed9cf21 --- /dev/null +++ b/modules/sysupdate.nix @@ -0,0 +1,63 @@ +{ config, ... }: { + systemd.sysupdate = { + enable = true; + + transfers = { + "10-uki" = { + Source = { + MatchPattern = [ + "${config.boot.uki.name}_@v.efi.xz" + ]; + + # We could fetch updates from the network as well: + # + # Path = "https://download.example.com/"; + # Type = "url-file"; + Path = "/var/updates/"; + Type = "regular-file"; + }; + Target = { + InstancesMax = 2; + MatchPattern = [ + "${config.boot.uki.name}_@v.efi" + ]; + + Mode = "0444"; + Path = "/EFI/Linux"; + PathRelativeTo = "boot"; + + Type = "regular-file"; + }; + Transfer = { + ProtectVersion = "%A"; + }; + }; + + "20-store" = { + Source = { + MatchPattern = [ + "store_@v.img.xz" + ]; + # Path = "https://download.example.com/"; + # Type = "url-file"; + Path = "/var/updates/"; + Type = "regular-file"; + }; + + Target = { + InstancesMax = 2; + + # This doesn't work, because / is a tmpfs and the heuristic is not that smart. + # + # Path = "auto"; + Path = "/dev/sda"; + + MatchPattern = "store_@v"; + + Type = "partition"; + ReadOnly = "yes"; + }; + }; + }; + }; +}