├── .gitignore ├── README.md ├── flake.lock ├── flake.nix └── system-path.nix /.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | *.qcow2 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NixOS (but super-minimal) 2 | ## What is this? 3 | This started as a response to the Nixpkgs issue [#21315](https://github.com/NixOS/nixpkgs/issues/21315) - a request for a super-minimal NixOS image with almost nothing in `$PATH` but strictly neccesary software. Everything else could be installed separately. 4 | 5 | ## What's the progress? 6 | Right now the `PATH` is a little bit smaller - 90 packages vs. 117. You can see the following `nix-repl` snippet to investigate the results of this work. 7 | 8 | ```nix 9 | nix-repl> self = builtins.getFlake "github:kisik21/nixos-super-minimal" 10 | 11 | nix-repl> deprecatedPrograms = ["bash" "info" "man" "oblogout" "way-cooler"] 12 | 13 | nix-repl> deprecatedServices = ["beegfs" "beegfsEnable" "buildkite-agent" "cgmanager" "chronos" "d 14 | eepin" "dnscrypt-proxy" "fourStore" "fourStoreEndpoint" "marathon" "mathics" "meguca" "mesos" "openvpn" "osquery" "prey" "rmilter" "seeks" "winstone"] 15 | 16 | nix-repl> closure = self.inputs.nixpkgs.lib.nixosSystem ({ modules = [ self.nixosModule ]; }) 17 | 18 | nix-repl> builtins.length closure.config.environment.systemPackages # profiles/minimal.nix == 117 19 | 90 20 | 21 | nix-repl> nix-repl> builtins.filter (name: system.config.programs.${name}.enable or false) (builtins.attrNames (builtins.removeAttrs system.config.programs deprecatedPrograms)) 22 | [ "command-not-found" ] 23 | 24 | nix-repl> builtins.filter (name: system.config.services.${name}.enable or false) (builtins.attrNames (builtins.removeAttrs system.config.services deprecatedServices)) 25 | [ "dbus" "nscd" "timesyncd" ] 26 | ``` 27 | 28 | ## How to use this? 29 | Use this repository as a flake. `nixosModule` contains the NixOS module, based upon the [minimal.nix](https://github.com/NixOS/nixpkgs/blob/nixos-unstable/nixos/modules/profiles/minimal.nix) in Nixpkgs. 30 | 31 | The `checks.x86_64-linux` attribute set contains `toplevelClosure` - the top-level closure for evaluating closure size - and `vm` - a VM that autologins you as `root` and allows you to inspect the system to ensure things don't break. 32 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1603758576, 6 | "narHash": "sha256-xKFmWVx+rFInBao3gtmvXcd0LjHD1+0c1Ef5PJDrbuM=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "1dc37370c489b610f8b91d7fdd40633163ffbafd", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Super-minimal NixOS module"; 3 | inputs = { 4 | nixpkgs = { 5 | type = "github"; 6 | owner = "NixOS"; 7 | repo = "nixpkgs"; 8 | ref = "nixos-unstable"; 9 | }; 10 | }; 11 | outputs = inputs: let 12 | inherit (inputs) self nixpkgs; 13 | in { 14 | nixosModule = { config, pkgs, lib, ... }: { 15 | imports = [ 16 | (inputs.nixpkgs + "/nixos/modules/profiles/minimal.nix") 17 | ./system-path.nix 18 | ]; 19 | disabledModules = ["config/system-path.nix"]; 20 | services.udisks2.enable = false; 21 | services.nscd.enable = false; 22 | # Turn off if you want to disable command-not-found 23 | programs.command-not-found.enable = lib.mkDefault true; 24 | }; 25 | 26 | # The closure to check 27 | checks.x86_64-linux.toplevel-closure = let 28 | additionalConfig = { config, pkgs, lib, ... }: { 29 | nixpkgs.localSystem.system = "x86_64-linux"; 30 | boot = { 31 | loader = { 32 | grub = { 33 | enable = true; 34 | efiSupport = true; 35 | device = "nodev"; 36 | }; 37 | }; 38 | }; 39 | fileSystems = { 40 | "/" = { 41 | device = "/dev/sda2"; 42 | fsType = "ext4"; 43 | }; 44 | "/boot" = { 45 | device = "/dev/sda1"; 46 | fsType = "vfat"; 47 | }; 48 | }; 49 | }; 50 | in (nixpkgs.lib.nixosSystem { 51 | modules = [ 52 | self.nixosModule 53 | additionalConfig 54 | ]; 55 | }).config.system.build.toplevel; 56 | checks.x86_64-linux.vm = (nixpkgs.lib.nixosSystem { 57 | modules = [ 58 | self.nixosModule 59 | ({...}: { nixpkgs.localSystem.system = "x86_64-linux"; services.mingetty.autologinUser = "root"; }) 60 | ]; 61 | }).config.system.build.vm; 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /system-path.nix: -------------------------------------------------------------------------------- 1 | # This module defines the packages that appear in 2 | # /run/current-system/sw. 3 | 4 | { config, lib, pkgs, ... }: 5 | 6 | with lib; 7 | 8 | let 9 | 10 | requiredPackages = map (pkg: setPrio ((pkg.meta.priority or 5) + 3) pkg) [ 11 | # Packages that are absolutely neccesary 12 | pkgs.bashInteractive # bash with ncurses support 13 | # Packages that are probably neccesary 14 | pkgs.su # Requires a SUID wrapper - should be installed in system path? 15 | pkgs.coreutils-full # I've seen environments that don't even need coreutils - they are pulled with Nix instead 16 | pkgs.ncurses # Includes things such as `reset` - you should have it nearby 17 | pkgs.stdenv.cc.libc # I sure hope it's here for a reason 18 | # Packages that might not be so neccesary 19 | #pkgs.acl # I don't remember the last time I used one of these 20 | #pkgs.curl # Can be pulled in case network access is required 21 | #pkgs.attr # see pkgs.acl note 22 | #pkgs.bzip2 23 | #pkgs.cpio 24 | #pkgs.diffutils 25 | #pkgs.findutils 26 | #pkgs.gawk 27 | #pkgs.getent # Clearly this is a useless utility for me 28 | #pkgs.getconf # What is this? 29 | #pkgs.gnugrep 30 | #pkgs.gnupatch 31 | #pkgs.gnused 32 | #pkgs.gnutar 33 | #pkgs.gzip 34 | #pkgs.xz 35 | #pkgs.less 36 | #pkgs.libcap 37 | #pkgs.nano # Could be downloaded separately 38 | #pkgs.netcat # Totally unneccesary in a minimal system - NixOS is generous in even providing this in the default closure, some systems don't do that 39 | #config.programs.ssh.package # Don't install by default, but include in system closure since it can't be separated from the daemon 40 | #pkgs.mkpasswd # We manage users declaratively 41 | #pkgs.procps # It can be useful, but not strictly neccesary 42 | #pkgs.time # we can measure time using the shell builtin 43 | #pkgs.utillinux 44 | #pkgs.which 45 | #pkgs.zstd 46 | ]; 47 | 48 | defaultPackages = map (pkg: setPrio ((pkg.meta.priority or 5) + 3) pkg) 49 | [ pkgs.perl 50 | pkgs.rsync 51 | pkgs.strace 52 | ]; 53 | 54 | in 55 | 56 | { 57 | options = { 58 | 59 | environment = { 60 | 61 | systemPackages = mkOption { 62 | type = types.listOf types.package; 63 | default = []; 64 | example = literalExample "[ pkgs.firefox pkgs.thunderbird ]"; 65 | description = '' 66 | The set of packages that appear in 67 | /run/current-system/sw. These packages are 68 | automatically available to all users, and are 69 | automatically updated every time you rebuild the system 70 | configuration. (The latter is the main difference with 71 | installing them in the default profile, 72 | /nix/var/nix/profiles/default. 73 | ''; 74 | }; 75 | 76 | defaultPackages = mkOption { 77 | type = types.listOf types.package; 78 | default = defaultPackages; 79 | example = literalExample "[]"; 80 | description = '' 81 | Set of packages users expect from a minimal linux istall. 82 | Like systemPackages, they appear in 83 | /run/current-system/sw. These packages are 84 | automatically available to all users, and are 85 | automatically updated every time you rebuild the system 86 | configuration. 87 | If you want a more minimal system, set it to an empty list. 88 | ''; 89 | }; 90 | 91 | pathsToLink = mkOption { 92 | type = types.listOf types.str; 93 | # Note: We need `/lib' to be among `pathsToLink' for NSS modules 94 | # to work. 95 | default = []; 96 | example = ["/"]; 97 | description = "List of directories to be symlinked in /run/current-system/sw."; 98 | }; 99 | 100 | extraOutputsToInstall = mkOption { 101 | type = types.listOf types.str; 102 | default = [ ]; 103 | example = [ "doc" "info" "devdoc" ]; 104 | description = "List of additional package outputs to be symlinked into /run/current-system/sw."; 105 | }; 106 | 107 | extraSetup = mkOption { 108 | type = types.lines; 109 | default = ""; 110 | description = "Shell fragments to be run after the system environment has been created. This should only be used for things that need to modify the internals of the environment, e.g. generating MIME caches. The environment being built can be accessed at $out."; 111 | }; 112 | 113 | }; 114 | 115 | system = { 116 | 117 | path = mkOption { 118 | internal = true; 119 | description = '' 120 | The packages you want in the boot environment. 121 | ''; 122 | }; 123 | 124 | }; 125 | 126 | }; 127 | 128 | config = { 129 | 130 | environment.systemPackages = requiredPackages ++ config.environment.defaultPackages; 131 | 132 | environment.pathsToLink = 133 | [ "/bin" 134 | "/etc/xdg" 135 | "/etc/gtk-2.0" 136 | "/etc/gtk-3.0" 137 | "/lib" # FIXME: remove and update debug-info.nix 138 | "/sbin" 139 | "/share/emacs" 140 | "/share/hunspell" 141 | "/share/nano" 142 | "/share/org" 143 | "/share/themes" 144 | "/share/vim-plugins" 145 | "/share/vulkan" 146 | "/share/kservices5" 147 | "/share/kservicetypes5" 148 | "/share/kxmlgui5" 149 | "/share/systemd" 150 | ]; 151 | 152 | system.path = pkgs.buildEnv { 153 | name = "system-path"; 154 | paths = config.environment.systemPackages; 155 | inherit (config.environment) pathsToLink extraOutputsToInstall; 156 | ignoreCollisions = true; 157 | # !!! Hacky, should modularise. 158 | # outputs TODO: note that the tools will often not be linked by default 159 | postBuild = 160 | '' 161 | # Remove wrapped binaries, they shouldn't be accessible via PATH. 162 | find $out/bin -maxdepth 1 -name ".*-wrapped" -type l -delete 163 | 164 | if [ -x $out/bin/glib-compile-schemas -a -w $out/share/glib-2.0/schemas ]; then 165 | $out/bin/glib-compile-schemas $out/share/glib-2.0/schemas 166 | fi 167 | 168 | ${config.environment.extraSetup} 169 | ''; 170 | }; 171 | 172 | }; 173 | } 174 | --------------------------------------------------------------------------------