├── .envrc ├── .gitignore ├── LICENSE ├── README.md ├── all-tests-hooked.nix ├── default.nix ├── failed ├── make-test-python-hooked.nix ├── nix-codemod ├── .envrc ├── Cargo.lock ├── Cargo.toml ├── README.md ├── default.nix ├── shell.nix └── src │ ├── commands │ ├── edit_systemd_service.rs │ ├── find_all_tests.rs │ ├── is_test_well_formed.rs │ ├── list_systemd_services.rs │ ├── mod.rs │ └── print_systemd_service_config.rs │ ├── edit.rs │ ├── main.rs │ └── walkers.rs ├── printInfo.nix ├── run.oil └── shell.nix /.envrc: -------------------------------------------------------------------------------- 1 | use nix 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .direnv 2 | result 3 | output 4 | options.json 5 | nix-codemod/target 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Julien Marquet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hardening Systemd Services 2 | 3 | This project is supported by the NLnet foundation and the NGI Assure fund. 4 | 5 | The goal is to add security configuration to as many services as possible in NixOS. 6 | 7 | Idea : 8 | > Let's say a service is well configured (works) if all of its tests pass, 9 | > then we can find the best possible configuration by simply trying every possibility 10 | > and finding the most secure combination that still works. 11 | 12 | Strategy & Technical Overview: 13 | * Find all systemd services 14 | * Then for each service, find all the tests that activate it 15 | * First find all the modules by evaluating `nixpkgs/nixos/modules/module-list` 16 | * Then, parse all of these modules to find what systemd services it declares 17 | * This is done by `./run.oil discover-systemd-services ...` 18 | * `./run.oil test-deps ...` finds which service each test activates 19 | * `./run.oil collect-tests ...` will use the output of `./run.oil test-deps` and 20 | `./run.oil discover-systemd-services` to create a file that show what test 21 | correspond to each service. 22 | * Add hooks on these services that enable us to choose 23 | the hardening options of these service when we run tests 24 | * Once again, we use `module-list` and `./run.oil discover-systemd-services` to find 25 | what are the services and where they are defined 26 | * Add hooks on these services: we create a new argument called `systemdPassthru` 27 | that will be passed when we'll call tests 28 | * This is the job of `./run.oil hook-modules ...` 29 | * Test each service against each possible configuration! 30 | * With `./run.oil run-specific-tests`, which takes the data we gathered previously 31 | in input, along with the name of a service and a JSON file that specifies which hardening 32 | options to test the service against. 33 | 34 | How to run (while waiting for some better docs): 35 | * `nix-shell` 36 | * `mkdir output` 37 | * Clone nixpkgs 38 | * `cd output` 39 | * `git clone ` 40 | * We want `nixpkgs` in `./output/nixpkgs` (that's what the rest of these instructions assume) 41 | * `./run.oil discover-systemd-services ./output/nixpkgs ./output/systemd-services` 42 | * Look for service definitions in modules, save the result. 43 | * `./run.oil test-deps ./output/nixpkgs output/test-deps` 44 | * Find the systemd services each test activates 45 | * Takes a while because it requires evaluating each test (not *running* 46 | but *evaluating* to compute the resulting configuration -- no download, 47 | but takes a while). 48 | * `./run.oil collect-tests ./output/systemd-services ./output/test-deps ./output/tests` 49 | * Creates the file that associate each systemd service with its origin file, 50 | the hardening options that should be tested, and all the tests that should be run 51 | to check the service still works properly with the sandboxing options. 52 | * `./run.oil hook-modules ./output/nixpkgs ./output/tests` 53 | * Adds a `systemdPassthru` to the modules (and to some test-related files): 54 | we can now select the hardening options of every single service! 55 | (Well, to be precide, each service that we detected and that is well-behaved... 56 | from my tests, this covers 60%-70% of all modules.) 57 | * Test a service! 58 | * Write custom hardening options to `./options.json`, e.g. `{ "PrivateDevices": true }` 59 | * `./run.oil dry-run-specific-tests ./output/tests ./output/nixpkgs ./output/malformed-tests ./options.json hydra-server` 60 | * This will `hydra-server`'s tests! 61 | * Try some options (edit `./options.json`): 62 | * `{ "PrivateDevices": true }`: Still works! 63 | * `{ "PrivateNetwork": true }`: Breaks! 64 | 65 | Miscellaneous: 66 | * We get some extra information on nixpkgs for free: 67 | the output of `./run.oil discover-systemd-services` shows two interesting metrics 68 | * For each (detectable) systemd service, the module it is defined in 69 | * For each (detectable) systed service, the list of the tests that activate it 70 | * This shows "how well" a service is tested (or "how fundamental" it is: 71 | for instance, `nginx` gets tested *a lot* !) 72 | * This also show the services that are *never* tested 73 | * Which hardening options are already configured for each service 74 | * There is a helper command `./run.oil find-malformed-tests` that lists all the tests 75 | that don't support our `systemdPassthru` hack. In the future, it would be great to also 76 | modify these tests so they work with this framework, but for now let's focus on running 77 | the tests that already work 78 | * There is another development helper, `dry-run-all-tests`, that checks wether all the tests 79 | actually evaluate. This helps finding some ticky parts of nixpkgs that are harder to 80 | hook with the `systemdPassthru` method. 81 | * Currently, this tool only targets boolean configuration options, not all of 82 | what `systemd-analyze` checks, e.g. not seccomp filters 83 | We target: 84 | - PrivateDevices 85 | - PrivateMounts 86 | - PrivateNetwork 87 | - PrivateTmp 88 | - PrivateUsers 89 | - ProtectControlGroups 90 | - ProtectKernelModules 91 | - ProtectKernelTunables 92 | - ProtectKernelLogs 93 | - ProtectClock 94 | - ProtectHostname 95 | - LockPersonality 96 | - MemoryDenyWriteExecute 97 | - NoNewPrivileges 98 | - RestrictRealtime 99 | - RestrictSUIDSGID 100 | 101 | todo: use patch instead of cp to add systemdPassthru 102 | 103 | -------------------------------------------------------------------------------- /all-tests-hooked.nix: -------------------------------------------------------------------------------- 1 | { systemdPassthru, system, pkgs, callTest }: 2 | # The return value of this function will be an attrset with arbitrary depth and 3 | # the `anything` returned by callTest at its test leafs. 4 | # The tests not supported by `system` will be replaced with `{}`, so that 5 | # `passthru.tests` can contain links to those without breaking on architectures 6 | # where said tests are unsupported. 7 | # Example callTest that just extracts the derivation from the test: 8 | # callTest = t: t.test; 9 | 10 | with pkgs.lib; 11 | 12 | let 13 | discoverTests = val: 14 | if !isAttrs val then val 15 | else if hasAttr "test" val then callTest val 16 | else mapAttrs (n: s: discoverTests s) val; 17 | handleTest = path: args: 18 | discoverTests (import path ({ inherit systemdPassthru system pkgs; } // args)); 19 | handleTestOn = systems: path: args: 20 | if elem system systems then handleTest path args 21 | else {}; 22 | 23 | nixosLib = import ../lib { 24 | # Experimental features need testing too, but there's no point in warning 25 | # about it, so we enable the feature flag. 26 | featureFlags.minimalModules = {}; 27 | }; 28 | evalMinimalConfig = module: nixosLib.evalModules { modules = [ module ]; }; 29 | in 30 | { 31 | _3proxy = handleTest ./3proxy.nix {}; 32 | acme = handleTest ./acme.nix {}; 33 | adguardhome = handleTest ./adguardhome.nix {}; 34 | aesmd = handleTest ./aesmd.nix {}; 35 | agate = handleTest ./web-servers/agate.nix {}; 36 | agda = handleTest ./agda.nix {}; 37 | airsonic = handleTest ./airsonic.nix {}; 38 | allTerminfo = handleTest ./all-terminfo.nix {}; 39 | amazon-init-shell = handleTest ./amazon-init-shell.nix {}; 40 | apfs = handleTest ./apfs.nix {}; 41 | apparmor = handleTest ./apparmor.nix {}; 42 | atd = handleTest ./atd.nix {}; 43 | atop = handleTest ./atop.nix {}; 44 | avahi = handleTest ./avahi.nix {}; 45 | avahi-with-resolved = handleTest ./avahi.nix { networkd = true; }; 46 | babeld = handleTest ./babeld.nix {}; 47 | bazarr = handleTest ./bazarr.nix {}; 48 | bcachefs = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bcachefs.nix {}; 49 | beanstalkd = handleTest ./beanstalkd.nix {}; 50 | bees = handleTest ./bees.nix {}; 51 | bind = handleTest ./bind.nix {}; 52 | bird = handleTest ./bird.nix {}; 53 | bitcoind = handleTest ./bitcoind.nix {}; 54 | bittorrent = handleTest ./bittorrent.nix {}; 55 | blockbook-frontend = handleTest ./blockbook-frontend.nix {}; 56 | blocky = handleTest ./blocky.nix {}; 57 | boot = handleTestOn ["x86_64-linux" "aarch64-linux"] ./boot.nix {}; 58 | boot-stage1 = handleTest ./boot-stage1.nix {}; 59 | borgbackup = handleTest ./borgbackup.nix {}; 60 | botamusique = handleTest ./botamusique.nix {}; 61 | bpf = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bpf.nix {}; 62 | breitbandmessung = handleTest ./breitbandmessung.nix {}; 63 | brscan5 = handleTest ./brscan5.nix {}; 64 | btrbk = handleTest ./btrbk.nix {}; 65 | buildbot = handleTest ./buildbot.nix {}; 66 | buildkite-agents = handleTest ./buildkite-agents.nix {}; 67 | caddy = handleTest ./caddy.nix {}; 68 | cadvisor = handleTestOn ["x86_64-linux"] ./cadvisor.nix {}; 69 | cage = handleTest ./cage.nix {}; 70 | cagebreak = handleTest ./cagebreak.nix {}; 71 | calibre-web = handleTest ./calibre-web.nix {}; 72 | cassandra_2_1 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_2_1; }; 73 | cassandra_2_2 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_2_2; }; 74 | cassandra_3_0 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_0; }; 75 | cassandra_3_11 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_11; }; 76 | ceph-multi-node = handleTestOn ["x86_64-linux"] ./ceph-multi-node.nix {}; 77 | ceph-single-node = handleTestOn ["x86_64-linux"] ./ceph-single-node.nix {}; 78 | ceph-single-node-bluestore = handleTestOn ["x86_64-linux"] ./ceph-single-node-bluestore.nix {}; 79 | certmgr = handleTest ./certmgr.nix {}; 80 | cfssl = handleTestOn ["x86_64-linux"] ./cfssl.nix {}; 81 | charliecloud = handleTest ./charliecloud.nix {}; 82 | chromium = (handleTestOn ["x86_64-linux"] ./chromium.nix {}).stable or {}; 83 | cjdns = handleTest ./cjdns.nix {}; 84 | clickhouse = handleTest ./clickhouse.nix {}; 85 | cloud-init = handleTest ./cloud-init.nix {}; 86 | cntr = handleTest ./cntr.nix {}; 87 | cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {}; 88 | collectd = handleTest ./collectd.nix {}; 89 | consul = handleTest ./consul.nix {}; 90 | containers-bridge = handleTest ./containers-bridge.nix {}; 91 | containers-custom-pkgs.nix = handleTest ./containers-custom-pkgs.nix {}; 92 | containers-ephemeral = handleTest ./containers-ephemeral.nix {}; 93 | containers-extra_veth = handleTest ./containers-extra_veth.nix {}; 94 | containers-hosts = handleTest ./containers-hosts.nix {}; 95 | containers-imperative = handleTest ./containers-imperative.nix {}; 96 | containers-ip = handleTest ./containers-ip.nix {}; 97 | containers-macvlans = handleTest ./containers-macvlans.nix {}; 98 | containers-names = handleTest ./containers-names.nix {}; 99 | containers-nested = handleTest ./containers-nested.nix {}; 100 | containers-physical_interfaces = handleTest ./containers-physical_interfaces.nix {}; 101 | containers-portforward = handleTest ./containers-portforward.nix {}; 102 | containers-reloadable = handleTest ./containers-reloadable.nix {}; 103 | containers-restart_networking = handleTest ./containers-restart_networking.nix {}; 104 | containers-tmpfs = handleTest ./containers-tmpfs.nix {}; 105 | convos = handleTest ./convos.nix {}; 106 | corerad = handleTest ./corerad.nix {}; 107 | coturn = handleTest ./coturn.nix {}; 108 | couchdb = handleTest ./couchdb.nix {}; 109 | cri-o = handleTestOn ["x86_64-linux"] ./cri-o.nix {}; 110 | custom-ca = handleTest ./custom-ca.nix {}; 111 | croc = handleTest ./croc.nix {}; 112 | cryptpad = handleTest ./cryptpad.nix {}; 113 | deluge = handleTest ./deluge.nix {}; 114 | dendrite = handleTest ./dendrite.nix {}; 115 | dex-oidc = handleTest ./dex-oidc.nix {}; 116 | dhparams = handleTest ./dhparams.nix {}; 117 | disable-installer-tools = handleTest ./disable-installer-tools.nix {}; 118 | discourse = handleTest ./discourse.nix {}; 119 | dnscrypt-proxy2 = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy2.nix {}; 120 | dnscrypt-wrapper = handleTestOn ["x86_64-linux"] ./dnscrypt-wrapper {}; 121 | dnsdist = handleTest ./dnsdist.nix {}; 122 | doas = handleTest ./doas.nix {}; 123 | docker = handleTestOn ["x86_64-linux"] ./docker.nix {}; 124 | docker-rootless = handleTestOn ["x86_64-linux"] ./docker-rootless.nix {}; 125 | docker-edge = handleTestOn ["x86_64-linux"] ./docker-edge.nix {}; 126 | docker-registry = handleTest ./docker-registry.nix {}; 127 | docker-tools = handleTestOn ["x86_64-linux"] ./docker-tools.nix {}; 128 | docker-tools-cross = handleTestOn ["x86_64-linux" "aarch64-linux"] ./docker-tools-cross.nix {}; 129 | docker-tools-overlay = handleTestOn ["x86_64-linux"] ./docker-tools-overlay.nix {}; 130 | documize = handleTest ./documize.nix {}; 131 | doh-proxy-rust = handleTest ./doh-proxy-rust.nix {}; 132 | dokuwiki = handleTest ./dokuwiki.nix {}; 133 | domination = handleTest ./domination.nix {}; 134 | dovecot = handleTest ./dovecot.nix {}; 135 | drbd = handleTest ./drbd.nix {}; 136 | earlyoom = handleTestOn ["x86_64-linux"] ./earlyoom.nix {}; 137 | ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {}; 138 | ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {}; 139 | ecryptfs = handleTest ./ecryptfs.nix {}; 140 | ejabberd = handleTest ./xmpp/ejabberd.nix {}; 141 | elk = handleTestOn ["x86_64-linux"] ./elk.nix {}; 142 | emacs-daemon = handleTest ./emacs-daemon.nix {}; 143 | engelsystem = handleTest ./engelsystem.nix {}; 144 | enlightenment = handleTest ./enlightenment.nix {}; 145 | env = handleTest ./env.nix {}; 146 | envoy = handleTest ./envoy.nix {}; 147 | ergo = handleTest ./ergo.nix {}; 148 | ergochat = handleTest ./ergochat.nix {}; 149 | etc = pkgs.callPackage ../modules/system/etc/test.nix { inherit evalMinimalConfig; }; 150 | etcd = handleTestOn ["x86_64-linux"] ./etcd.nix {}; 151 | etcd-cluster = handleTestOn ["x86_64-linux"] ./etcd-cluster.nix {}; 152 | etebase-server = handleTest ./etebase-server.nix {}; 153 | etesync-dav = handleTest ./etesync-dav.nix {}; 154 | fancontrol = handleTest ./fancontrol.nix {}; 155 | fcitx = handleTest ./fcitx {}; 156 | fenics = handleTest ./fenics.nix {}; 157 | ferm = handleTest ./ferm.nix {}; 158 | firefox = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox; }; 159 | firefox-esr = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr; }; # used in `tested` job 160 | firefox-esr-91 = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr-91; }; 161 | firejail = handleTest ./firejail.nix {}; 162 | firewall = handleTest ./firewall.nix {}; 163 | fish = handleTest ./fish.nix {}; 164 | flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {}; 165 | fluentd = handleTest ./fluentd.nix {}; 166 | fluidd = handleTest ./fluidd.nix {}; 167 | fontconfig-default-fonts = handleTest ./fontconfig-default-fonts.nix {}; 168 | freeswitch = handleTest ./freeswitch.nix {}; 169 | frr = handleTest ./frr.nix {}; 170 | fsck = handleTest ./fsck.nix {}; 171 | ft2-clone = handleTest ./ft2-clone.nix {}; 172 | gerrit = handleTest ./gerrit.nix {}; 173 | geth = handleTest ./geth.nix {}; 174 | ghostunnel = handleTest ./ghostunnel.nix {}; 175 | gitdaemon = handleTest ./gitdaemon.nix {}; 176 | gitea = handleTest ./gitea.nix {}; 177 | gitlab = handleTest ./gitlab.nix {}; 178 | gitolite = handleTest ./gitolite.nix {}; 179 | gitolite-fcgiwrap = handleTest ./gitolite-fcgiwrap.nix {}; 180 | glusterfs = handleTest ./glusterfs.nix {}; 181 | gnome = handleTest ./gnome.nix {}; 182 | gnome-xorg = handleTest ./gnome-xorg.nix {}; 183 | go-neb = handleTest ./go-neb.nix {}; 184 | gobgpd = handleTest ./gobgpd.nix {}; 185 | gocd-agent = handleTest ./gocd-agent.nix {}; 186 | gocd-server = handleTest ./gocd-server.nix {}; 187 | google-oslogin = handleTest ./google-oslogin {}; 188 | gotify-server = handleTest ./gotify-server.nix {}; 189 | grafana = handleTest ./grafana.nix {}; 190 | graphite = handleTest ./graphite.nix {}; 191 | graylog = handleTest ./graylog.nix {}; 192 | grocy = handleTest ./grocy.nix {}; 193 | grub = handleTest ./grub.nix {}; 194 | gvisor = handleTest ./gvisor.nix {}; 195 | hadoop = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop; }; 196 | hadoop_3_2 = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop_3_2; }; 197 | hadoop2 = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop2; }; 198 | haka = handleTest ./haka.nix {}; 199 | haste-server = handleTest ./haste-server.nix {}; 200 | haproxy = handleTest ./haproxy.nix {}; 201 | hardened = handleTest ./hardened.nix {}; 202 | hbase1 = handleTest ./hbase.nix { package=pkgs.hbase1; }; 203 | hbase2 = handleTest ./hbase.nix { package=pkgs.hbase2; }; 204 | hbase3 = handleTest ./hbase.nix { package=pkgs.hbase3; }; 205 | hedgedoc = handleTest ./hedgedoc.nix {}; 206 | herbstluftwm = handleTest ./herbstluftwm.nix {}; 207 | installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {}); 208 | invidious = handleTest ./invidious.nix {}; 209 | oci-containers = handleTestOn ["x86_64-linux"] ./oci-containers.nix {}; 210 | odoo = handleTest ./odoo.nix {}; 211 | # 9pnet_virtio used to mount /nix partition doesn't support 212 | # hibernation. This test happens to work on x86_64-linux but 213 | # not on other platforms. 214 | hibernate = handleTestOn ["x86_64-linux"] ./hibernate.nix {}; 215 | hibernate-systemd-stage-1 = handleTestOn ["x86_64-linux"] ./hibernate.nix { systemdStage1 = true; }; 216 | hitch = handleTest ./hitch {}; 217 | hledger-web = handleTest ./hledger-web.nix {}; 218 | hocker-fetchdocker = handleTest ./hocker-fetchdocker {}; 219 | hockeypuck = handleTest ./hockeypuck.nix { }; 220 | home-assistant = handleTest ./home-assistant.nix {}; 221 | hostname = handleTest ./hostname.nix {}; 222 | hound = handleTest ./hound.nix {}; 223 | hub = handleTest ./git/hub.nix {}; 224 | hydra = handleTest ./hydra {}; 225 | i3wm = handleTest ./i3wm.nix {}; 226 | icingaweb2 = handleTest ./icingaweb2.nix {}; 227 | iftop = handleTest ./iftop.nix {}; 228 | ihatemoney = handleTest ./ihatemoney {}; 229 | incron = handleTest ./incron.nix {}; 230 | influxdb = handleTest ./influxdb.nix {}; 231 | initrd-network-openvpn = handleTest ./initrd-network-openvpn {}; 232 | initrd-network-ssh = handleTest ./initrd-network-ssh {}; 233 | initrdNetwork = handleTest ./initrd-network.nix {}; 234 | initrd-secrets = handleTest ./initrd-secrets.nix {}; 235 | input-remapper = handleTest ./input-remapper.nix {}; 236 | inspircd = handleTest ./inspircd.nix {}; 237 | installer = handleTest ./installer.nix {}; 238 | installer-systemd-stage-1 = handleTest ./installer-systemd-stage-1.nix {}; 239 | invoiceplane = handleTest ./invoiceplane.nix {}; 240 | iodine = handleTest ./iodine.nix {}; 241 | ipfs = handleTest ./ipfs.nix {}; 242 | ipv6 = handleTest ./ipv6.nix {}; 243 | iscsi-multipath-root = handleTest ./iscsi-multipath-root.nix {}; 244 | iscsi-root = handleTest ./iscsi-root.nix {}; 245 | isso = handleTest ./isso.nix {}; 246 | jackett = handleTest ./jackett.nix {}; 247 | jellyfin = handleTest ./jellyfin.nix {}; 248 | jenkins = handleTest ./jenkins.nix {}; 249 | jenkins-cli = handleTest ./jenkins-cli.nix {}; 250 | jibri = handleTest ./jibri.nix {}; 251 | jirafeau = handleTest ./jirafeau.nix {}; 252 | jitsi-meet = handleTest ./jitsi-meet.nix {}; 253 | k3s-single-node = handleTest ./k3s-single-node.nix {}; 254 | k3s-single-node-docker = handleTest ./k3s-single-node-docker.nix {}; 255 | kafka = handleTest ./kafka.nix {}; 256 | kanidm = handleTest ./kanidm.nix {}; 257 | kbd-setfont-decompress = handleTest ./kbd-setfont-decompress.nix {}; 258 | kbd-update-search-paths-patch = handleTest ./kbd-update-search-paths-patch.nix {}; 259 | kea = handleTest ./kea.nix {}; 260 | keepalived = handleTest ./keepalived.nix {}; 261 | keepassxc = handleTest ./keepassxc.nix {}; 262 | kerberos = handleTest ./kerberos/default.nix {}; 263 | kernel-generic = handleTest ./kernel-generic.nix {}; 264 | kernel-latest-ath-user-regd = handleTest ./kernel-latest-ath-user-regd.nix {}; 265 | kexec = handleTest ./kexec.nix {}; 266 | keycloak = discoverTests (import ./keycloak.nix); 267 | keymap = handleTest ./keymap.nix {}; 268 | knot = handleTest ./knot.nix {}; 269 | krb5 = discoverTests (import ./krb5 {}); 270 | ksm = handleTest ./ksm.nix {}; 271 | kubernetes = handleTestOn ["x86_64-linux"] ./kubernetes {}; 272 | latestKernel.login = handleTest ./login.nix { latestKernel = true; }; 273 | leaps = handleTest ./leaps.nix {}; 274 | libinput = handleTest ./libinput.nix {}; 275 | libreddit = handleTest ./libreddit.nix {}; 276 | libresprite = handleTest ./libresprite.nix {}; 277 | libreswan = handleTest ./libreswan.nix {}; 278 | lidarr = handleTest ./lidarr.nix {}; 279 | lightdm = handleTest ./lightdm.nix {}; 280 | limesurvey = handleTest ./limesurvey.nix {}; 281 | litestream = handleTest ./litestream.nix {}; 282 | locate = handleTest ./locate.nix {}; 283 | login = handleTest ./login.nix {}; 284 | logrotate = handleTest ./logrotate.nix {}; 285 | loki = handleTest ./loki.nix {}; 286 | lvm2 = handleTest ./lvm2 {}; 287 | lxd = handleTest ./lxd.nix {}; 288 | lxd-nftables = handleTest ./lxd-nftables.nix {}; 289 | lxd-image-server = handleTest ./lxd-image-server.nix {}; 290 | #logstash = handleTest ./logstash.nix {}; 291 | lorri = handleTest ./lorri/default.nix {}; 292 | maddy = handleTest ./maddy.nix {}; 293 | maestral = handleTest ./maestral.nix {}; 294 | magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {}; 295 | magnetico = handleTest ./magnetico.nix {}; 296 | mailcatcher = handleTest ./mailcatcher.nix {}; 297 | mailhog = handleTest ./mailhog.nix {}; 298 | man = handleTest ./man.nix {}; 299 | mariadb-galera = handleTest ./mysql/mariadb-galera.nix {}; 300 | mastodon = handleTestOn ["x86_64-linux" "i686-linux" "aarch64-linux"] ./web-apps/mastodon.nix {}; 301 | matomo = handleTest ./matomo.nix {}; 302 | matrix-appservice-irc = handleTest ./matrix-appservice-irc.nix {}; 303 | matrix-conduit = handleTest ./matrix-conduit.nix {}; 304 | matrix-synapse = handleTest ./matrix-synapse.nix {}; 305 | mattermost = handleTest ./mattermost.nix {}; 306 | mediatomb = handleTest ./mediatomb.nix {}; 307 | mediawiki = handleTest ./mediawiki.nix {}; 308 | meilisearch = handleTest ./meilisearch.nix {}; 309 | memcached = handleTest ./memcached.nix {}; 310 | metabase = handleTest ./metabase.nix {}; 311 | minecraft = handleTest ./minecraft.nix {}; 312 | minecraft-server = handleTest ./minecraft-server.nix {}; 313 | minidlna = handleTest ./minidlna.nix {}; 314 | miniflux = handleTest ./miniflux.nix {}; 315 | minio = handleTest ./minio.nix {}; 316 | misc = handleTest ./misc.nix {}; 317 | mjolnir = handleTest ./matrix/mjolnir.nix {}; 318 | mod_perl = handleTest ./mod_perl.nix {}; 319 | molly-brown = handleTest ./molly-brown.nix {}; 320 | mongodb = handleTest ./mongodb.nix {}; 321 | moodle = handleTest ./moodle.nix {}; 322 | moonraker = handleTest ./moonraker.nix {}; 323 | morty = handleTest ./morty.nix {}; 324 | mosquitto = handleTest ./mosquitto.nix {}; 325 | moosefs = handleTest ./moosefs.nix {}; 326 | mpd = handleTest ./mpd.nix {}; 327 | mpv = handleTest ./mpv.nix {}; 328 | mtp = handleTest ./mtp.nix {}; 329 | mumble = handleTest ./mumble.nix {}; 330 | musescore = handleTest ./musescore.nix {}; 331 | munin = handleTest ./munin.nix {}; 332 | mutableUsers = handleTest ./mutable-users.nix {}; 333 | mxisd = handleTest ./mxisd.nix {}; 334 | mysql = handleTest ./mysql/mysql.nix {}; 335 | mysql-autobackup = handleTest ./mysql/mysql-autobackup.nix {}; 336 | mysql-backup = handleTest ./mysql/mysql-backup.nix {}; 337 | mysql-replication = handleTest ./mysql/mysql-replication.nix {}; 338 | n8n = handleTest ./n8n.nix {}; 339 | nagios = handleTest ./nagios.nix {}; 340 | nar-serve = handleTest ./nar-serve.nix {}; 341 | nat.firewall = handleTest ./nat.nix { withFirewall = true; }; 342 | nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; }; 343 | nat.standalone = handleTest ./nat.nix { withFirewall = false; }; 344 | nats = handleTest ./nats.nix {}; 345 | navidrome = handleTest ./navidrome.nix {}; 346 | nbd = handleTest ./nbd.nix {}; 347 | ncdns = handleTest ./ncdns.nix {}; 348 | ndppd = handleTest ./ndppd.nix {}; 349 | nebula = handleTest ./nebula.nix {}; 350 | neo4j = handleTest ./neo4j.nix {}; 351 | netdata = handleTest ./netdata.nix {}; 352 | networking.networkd = handleTest ./networking.nix { networkd = true; }; 353 | networking.scripted = handleTest ./networking.nix { networkd = false; }; 354 | specialisation = handleTest ./specialisation.nix {}; 355 | netbox = handleTest ./web-apps/netbox.nix {}; 356 | # TODO: put in networking.nix after the test becomes more complete 357 | networkingProxy = handleTest ./networking-proxy.nix {}; 358 | nextcloud = handleTest ./nextcloud {}; 359 | nexus = handleTest ./nexus.nix {}; 360 | # TODO: Test nfsv3 + Kerberos 361 | nfs3 = handleTest ./nfs { version = 3; }; 362 | nfs4 = handleTest ./nfs { version = 4; }; 363 | nghttpx = handleTest ./nghttpx.nix {}; 364 | nginx = handleTest ./nginx.nix {}; 365 | nginx-auth = handleTest ./nginx-auth.nix {}; 366 | nginx-etag = handleTest ./nginx-etag.nix {}; 367 | nginx-modsecurity = handleTest ./nginx-modsecurity.nix {}; 368 | nginx-pubhtml = handleTest ./nginx-pubhtml.nix {}; 369 | nginx-sandbox = handleTestOn ["x86_64-linux"] ./nginx-sandbox.nix {}; 370 | nginx-sso = handleTest ./nginx-sso.nix {}; 371 | nginx-variants = handleTest ./nginx-variants.nix {}; 372 | nifi = handleTestOn ["x86_64-linux"] ./web-apps/nifi.nix {}; 373 | nitter = handleTest ./nitter.nix {}; 374 | nix-ld = handleTest ./nix-ld.nix {}; 375 | nix-serve = handleTest ./nix-serve.nix {}; 376 | nix-serve-ssh = handleTest ./nix-serve-ssh.nix {}; 377 | nixops = handleTest ./nixops/default.nix {}; 378 | nixos-generate-config = handleTest ./nixos-generate-config.nix {}; 379 | nixpkgs = pkgs.callPackage ../modules/misc/nixpkgs/test.nix { inherit evalMinimalConfig; }; 380 | node-red = handleTest ./node-red.nix {}; 381 | nomad = handleTest ./nomad.nix {}; 382 | noto-fonts = handleTest ./noto-fonts.nix {}; 383 | novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {}; 384 | nsd = handleTest ./nsd.nix {}; 385 | nzbget = handleTest ./nzbget.nix {}; 386 | nzbhydra2 = handleTest ./nzbhydra2.nix {}; 387 | oh-my-zsh = handleTest ./oh-my-zsh.nix {}; 388 | ombi = handleTest ./ombi.nix {}; 389 | openarena = handleTest ./openarena.nix {}; 390 | openldap = handleTest ./openldap.nix {}; 391 | openresty-lua = handleTest ./openresty-lua.nix {}; 392 | opensmtpd = handleTest ./opensmtpd.nix {}; 393 | opensmtpd-rspamd = handleTest ./opensmtpd-rspamd.nix {}; 394 | openssh = handleTest ./openssh.nix {}; 395 | openstack-image-metadata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).metadata or {}; 396 | openstack-image-userdata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).userdata or {}; 397 | opentabletdriver = handleTest ./opentabletdriver.nix {}; 398 | owncast = handleTest ./owncast.nix {}; 399 | image-contents = handleTest ./image-contents.nix {}; 400 | orangefs = handleTest ./orangefs.nix {}; 401 | os-prober = handleTestOn ["x86_64-linux"] ./os-prober.nix {}; 402 | osrm-backend = handleTest ./osrm-backend.nix {}; 403 | overlayfs = handleTest ./overlayfs.nix {}; 404 | pacemaker = handleTest ./pacemaker.nix {}; 405 | packagekit = handleTest ./packagekit.nix {}; 406 | pam-file-contents = handleTest ./pam/pam-file-contents.nix {}; 407 | pam-oath-login = handleTest ./pam/pam-oath-login.nix {}; 408 | pam-u2f = handleTest ./pam/pam-u2f.nix {}; 409 | pam-ussh = handleTest ./pam/pam-ussh.nix {}; 410 | pantalaimon = handleTest ./matrix/pantalaimon.nix {}; 411 | pantheon = handleTest ./pantheon.nix {}; 412 | paperless = handleTest ./paperless.nix {}; 413 | parsedmarc = handleTest ./parsedmarc {}; 414 | pdns-recursor = handleTest ./pdns-recursor.nix {}; 415 | peerflix = handleTest ./peerflix.nix {}; 416 | peertube = handleTestOn ["x86_64-linux"] ./web-apps/peertube.nix {}; 417 | pgadmin4 = handleTest ./pgadmin4.nix {}; 418 | pgadmin4-standalone = handleTest ./pgadmin4-standalone.nix {}; 419 | pgjwt = handleTest ./pgjwt.nix {}; 420 | pgmanage = handleTest ./pgmanage.nix {}; 421 | php = handleTest ./php {}; 422 | php74 = handleTest ./php { php = pkgs.php74; }; 423 | php80 = handleTest ./php { php = pkgs.php80; }; 424 | php81 = handleTest ./php { php = pkgs.php81; }; 425 | pict-rs = handleTest ./pict-rs.nix {}; 426 | pinnwand = handleTest ./pinnwand.nix {}; 427 | plasma5 = handleTest ./plasma5.nix {}; 428 | plasma5-systemd-start = handleTest ./plasma5-systemd-start.nix {}; 429 | plausible = handleTest ./plausible.nix {}; 430 | pleroma = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./pleroma.nix {}; 431 | plikd = handleTest ./plikd.nix {}; 432 | plotinus = handleTest ./plotinus.nix {}; 433 | podgrab = handleTest ./podgrab.nix {}; 434 | podman = handleTestOn ["x86_64-linux"] ./podman/default.nix {}; 435 | podman-dnsname = handleTestOn ["x86_64-linux"] ./podman/dnsname.nix {}; 436 | podman-tls-ghostunnel = handleTestOn ["x86_64-linux"] ./podman/tls-ghostunnel.nix {}; 437 | pomerium = handleTestOn ["x86_64-linux"] ./pomerium.nix {}; 438 | postfix = handleTest ./postfix.nix {}; 439 | postfix-raise-smtpd-tls-security-level = handleTest ./postfix-raise-smtpd-tls-security-level.nix {}; 440 | postfixadmin = handleTest ./postfixadmin.nix {}; 441 | postgis = handleTest ./postgis.nix {}; 442 | postgresql = handleTest ./postgresql.nix {}; 443 | postgresql-wal-receiver = handleTest ./postgresql-wal-receiver.nix {}; 444 | powerdns = handleTest ./powerdns.nix {}; 445 | powerdns-admin = handleTest ./powerdns-admin.nix {}; 446 | power-profiles-daemon = handleTest ./power-profiles-daemon.nix {}; 447 | pppd = handleTest ./pppd.nix {}; 448 | predictable-interface-names = handleTest ./predictable-interface-names.nix {}; 449 | printing = handleTest ./printing.nix {}; 450 | privacyidea = handleTest ./privacyidea.nix {}; 451 | privoxy = handleTest ./privoxy.nix {}; 452 | prometheus = handleTest ./prometheus.nix {}; 453 | prometheus-exporters = handleTest ./prometheus-exporters.nix {}; 454 | prosody = handleTest ./xmpp/prosody.nix {}; 455 | prosody-mysql = handleTest ./xmpp/prosody-mysql.nix {}; 456 | proxy = handleTest ./proxy.nix {}; 457 | prowlarr = handleTest ./prowlarr.nix {}; 458 | pt2-clone = handleTest ./pt2-clone.nix {}; 459 | pulseaudio = discoverTests (import ./pulseaudio.nix); 460 | qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {}; 461 | quorum = handleTest ./quorum.nix {}; 462 | rabbitmq = handleTest ./rabbitmq.nix {}; 463 | radarr = handleTest ./radarr.nix {}; 464 | radicale = handleTest ./radicale.nix {}; 465 | rasdaemon = handleTest ./rasdaemon.nix {}; 466 | redis = handleTest ./redis.nix {}; 467 | redmine = handleTest ./redmine.nix {}; 468 | resolv = handleTest ./resolv.nix {}; 469 | restartByActivationScript = handleTest ./restart-by-activation-script.nix {}; 470 | restic = handleTest ./restic.nix {}; 471 | retroarch = handleTest ./retroarch.nix {}; 472 | riak = handleTest ./riak.nix {}; 473 | robustirc-bridge = handleTest ./robustirc-bridge.nix {}; 474 | roundcube = handleTest ./roundcube.nix {}; 475 | rspamd = handleTest ./rspamd.nix {}; 476 | rss2email = handleTest ./rss2email.nix {}; 477 | rstudio-server = handleTest ./rstudio-server.nix {}; 478 | rsyncd = handleTest ./rsyncd.nix {}; 479 | rsyslogd = handleTest ./rsyslogd.nix {}; 480 | rxe = handleTest ./rxe.nix {}; 481 | sabnzbd = handleTest ./sabnzbd.nix {}; 482 | samba = handleTest ./samba.nix {}; 483 | samba-wsdd = handleTest ./samba-wsdd.nix {}; 484 | sanoid = handleTest ./sanoid.nix {}; 485 | sddm = handleTest ./sddm.nix {}; 486 | seafile = handleTest ./seafile.nix {}; 487 | searx = handleTest ./searx.nix {}; 488 | service-runner = handleTest ./service-runner.nix {}; 489 | sfxr-qt = handleTest ./sfxr-qt.nix {}; 490 | shadow = handleTest ./shadow.nix {}; 491 | shadowsocks = handleTest ./shadowsocks {}; 492 | shattered-pixel-dungeon = handleTest ./shattered-pixel-dungeon.nix {}; 493 | shiori = handleTest ./shiori.nix {}; 494 | signal-desktop = handleTest ./signal-desktop.nix {}; 495 | simple = handleTest ./simple.nix {}; 496 | slurm = handleTest ./slurm.nix {}; 497 | smokeping = handleTest ./smokeping.nix {}; 498 | snapcast = handleTest ./snapcast.nix {}; 499 | snapper = handleTest ./snapper.nix {}; 500 | soapui = handleTest ./soapui.nix {}; 501 | sogo = handleTest ./sogo.nix {}; 502 | solanum = handleTest ./solanum.nix {}; 503 | solr = handleTest ./solr.nix {}; 504 | sonarr = handleTest ./sonarr.nix {}; 505 | sourcehut = handleTest ./sourcehut.nix {}; 506 | spacecookie = handleTest ./spacecookie.nix {}; 507 | spark = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./spark {}; 508 | sslh = handleTest ./sslh.nix {}; 509 | sssd = handleTestOn ["x86_64-linux"] ./sssd.nix {}; 510 | sssd-ldap = handleTestOn ["x86_64-linux"] ./sssd-ldap.nix {}; 511 | starship = handleTest ./starship.nix {}; 512 | step-ca = handleTestOn ["x86_64-linux"] ./step-ca.nix {}; 513 | strongswan-swanctl = handleTest ./strongswan-swanctl.nix {}; 514 | sudo = handleTest ./sudo.nix {}; 515 | sway = handleTest ./sway.nix {}; 516 | switchTest = handleTest ./switch-test.nix {}; 517 | sympa = handleTest ./sympa.nix {}; 518 | syncthing = handleTest ./syncthing.nix {}; 519 | syncthing-init = handleTest ./syncthing-init.nix {}; 520 | syncthing-relay = handleTest ./syncthing-relay.nix {}; 521 | systemd = handleTest ./systemd.nix {}; 522 | systemd-analyze = handleTest ./systemd-analyze.nix {}; 523 | systemd-binfmt = handleTestOn ["x86_64-linux"] ./systemd-binfmt.nix {}; 524 | systemd-boot = handleTest ./systemd-boot.nix {}; 525 | systemd-confinement = handleTest ./systemd-confinement.nix {}; 526 | systemd-cryptenroll = handleTest ./systemd-cryptenroll.nix {}; 527 | systemd-escaping = handleTest ./systemd-escaping.nix {}; 528 | systemd-initrd-btrfs-raid = handleTest ./systemd-initrd-btrfs-raid.nix {}; 529 | systemd-initrd-luks-keyfile = handleTest ./systemd-initrd-luks-keyfile.nix {}; 530 | systemd-initrd-luks-password = handleTest ./systemd-initrd-luks-password.nix {}; 531 | systemd-initrd-shutdown = handleTest ./systemd-shutdown.nix { systemdStage1 = true; }; 532 | systemd-initrd-simple = handleTest ./systemd-initrd-simple.nix {}; 533 | systemd-initrd-swraid = handleTest ./systemd-initrd-swraid.nix {}; 534 | systemd-journal = handleTest ./systemd-journal.nix {}; 535 | systemd-machinectl = handleTest ./systemd-machinectl.nix {}; 536 | systemd-networkd = handleTest ./systemd-networkd.nix {}; 537 | systemd-networkd-dhcpserver = handleTest ./systemd-networkd-dhcpserver.nix {}; 538 | systemd-networkd-dhcpserver-static-leases = handleTest ./systemd-networkd-dhcpserver-static-leases.nix {}; 539 | systemd-networkd-ipv6-prefix-delegation = handleTest ./systemd-networkd-ipv6-prefix-delegation.nix {}; 540 | systemd-networkd-vrf = handleTest ./systemd-networkd-vrf.nix {}; 541 | systemd-nspawn = handleTest ./systemd-nspawn.nix {}; 542 | systemd-shutdown = handleTest ./systemd-shutdown.nix {}; 543 | systemd-timesyncd = handleTest ./systemd-timesyncd.nix {}; 544 | systemd-misc = handleTest ./systemd-misc.nix {}; 545 | taskserver = handleTest ./taskserver.nix {}; 546 | teeworlds = handleTest ./teeworlds.nix {}; 547 | telegraf = handleTest ./telegraf.nix {}; 548 | teleport = handleTest ./teleport.nix {}; 549 | thelounge = handleTest ./thelounge.nix {}; 550 | terminal-emulators = handleTest ./terminal-emulators.nix {}; 551 | tiddlywiki = handleTest ./tiddlywiki.nix {}; 552 | tigervnc = handleTest ./tigervnc.nix {}; 553 | timezone = handleTest ./timezone.nix {}; 554 | tinc = handleTest ./tinc {}; 555 | tinydns = handleTest ./tinydns.nix {}; 556 | tinywl = handleTest ./tinywl.nix {}; 557 | tomcat = handleTest ./tomcat.nix {}; 558 | tor = handleTest ./tor.nix {}; 559 | # traefik test relies on docker-containers 560 | traefik = handleTestOn ["x86_64-linux"] ./traefik.nix {}; 561 | trafficserver = handleTest ./trafficserver.nix {}; 562 | transmission = handleTest ./transmission.nix {}; 563 | trezord = handleTest ./trezord.nix {}; 564 | trickster = handleTest ./trickster.nix {}; 565 | trilium-server = handleTestOn ["x86_64-linux"] ./trilium-server.nix {}; 566 | tsm-client-gui = handleTest ./tsm-client-gui.nix {}; 567 | txredisapi = handleTest ./txredisapi.nix {}; 568 | tuptime = handleTest ./tuptime.nix {}; 569 | turbovnc-headless-server = handleTest ./turbovnc-headless-server.nix {}; 570 | tuxguitar = handleTest ./tuxguitar.nix {}; 571 | ucarp = handleTest ./ucarp.nix {}; 572 | udisks2 = handleTest ./udisks2.nix {}; 573 | unbound = handleTest ./unbound.nix {}; 574 | unifi = handleTest ./unifi.nix {}; 575 | unit-php = handleTest ./web-servers/unit-php.nix {}; 576 | upnp = handleTest ./upnp.nix {}; 577 | usbguard = handleTest ./usbguard.nix {}; 578 | user-activation-scripts = handleTest ./user-activation-scripts.nix {}; 579 | uwsgi = handleTest ./uwsgi.nix {}; 580 | v2ray = handleTest ./v2ray.nix {}; 581 | vault = handleTest ./vault.nix {}; 582 | vault-postgresql = handleTest ./vault-postgresql.nix {}; 583 | vaultwarden = handleTest ./vaultwarden.nix {}; 584 | vector = handleTest ./vector.nix {}; 585 | vengi-tools = handleTest ./vengi-tools.nix {}; 586 | victoriametrics = handleTest ./victoriametrics.nix {}; 587 | vikunja = handleTest ./vikunja.nix {}; 588 | virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {}; 589 | vscodium = discoverTests (import ./vscodium.nix); 590 | vsftpd = handleTest ./vsftpd.nix {}; 591 | wasabibackend = handleTest ./wasabibackend.nix {}; 592 | wiki-js = handleTest ./wiki-js.nix {}; 593 | wine = handleTest ./wine.nix {}; 594 | wireguard = handleTest ./wireguard {}; 595 | without-nix = handleTest ./without-nix.nix {}; 596 | wmderland = handleTest ./wmderland.nix {}; 597 | wpa_supplicant = handleTest ./wpa_supplicant.nix {}; 598 | wordpress = handleTest ./wordpress.nix {}; 599 | xandikos = handleTest ./xandikos.nix {}; 600 | xautolock = handleTest ./xautolock.nix {}; 601 | xfce = handleTest ./xfce.nix {}; 602 | xmonad = handleTest ./xmonad.nix {}; 603 | xmonad-xdg-autostart = handleTest ./xmonad-xdg-autostart.nix {}; 604 | xrdp = handleTest ./xrdp.nix {}; 605 | xss-lock = handleTest ./xss-lock.nix {}; 606 | xterm = handleTest ./xterm.nix {}; 607 | xxh = handleTest ./xxh.nix {}; 608 | yabar = handleTest ./yabar.nix {}; 609 | yggdrasil = handleTest ./yggdrasil.nix {}; 610 | zammad = handleTest ./zammad.nix {}; 611 | zfs = handleTest ./zfs.nix {}; 612 | zigbee2mqtt = handleTest ./zigbee2mqtt.nix {}; 613 | zoneminder = handleTest ./zoneminder.nix {}; 614 | zookeeper = handleTest ./zookeeper.nix {}; 615 | zrepl = handleTest ./zrepl.nix {}; 616 | zsh-history = handleTest ./zsh-history.nix {}; 617 | } 618 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {}, ... }: 2 | let 3 | nix-codemod = import ./nix-codemod { inherit pkgs; }; 4 | in { 5 | shell = pkgs.mkShell { 6 | buildInputs = with pkgs; [ oil jq nix-codemod ]; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /failed: -------------------------------------------------------------------------------- 1 | 2 | 3proxy 3 | ModemManager 4 | NetworkManager 5 | NetworkManager-dispatcher 6 | NetworkManager-wait-online 7 | -------------------------------------------------------------------------------- /make-test-python-hooked.nix: -------------------------------------------------------------------------------- 1 | f: { 2 | systemdPassthru, 3 | system ? builtins.currentSystem, 4 | pkgs ? import ../.. { inherit system; }, 5 | ... 6 | } @ args: 7 | 8 | with import ../lib/testing-python.nix { inherit system pkgs; specialArgs = { inherit systemdPassthru; }; }; 9 | 10 | makeTest (if pkgs.lib.isFunction f then f (args // { inherit pkgs; inherit (pkgs) lib; }) else f) 11 | -------------------------------------------------------------------------------- /nix-codemod/.envrc: -------------------------------------------------------------------------------- 1 | use nix 2 | -------------------------------------------------------------------------------- /nix-codemod/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ansi_term" 7 | version = "0.12.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 10 | dependencies = [ 11 | "winapi", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.1.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.3.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 36 | 37 | [[package]] 38 | name = "cbitset" 39 | version = "0.2.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "29b6ad25ae296159fb0da12b970b2fe179b234584d7cd294c891e2bbb284466b" 42 | dependencies = [ 43 | "num-traits", 44 | ] 45 | 46 | [[package]] 47 | name = "clap" 48 | version = "3.1.14" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "535434c063ced786eb04aaf529308092c5ab60889e8fe24275d15de07b01fa97" 51 | dependencies = [ 52 | "atty", 53 | "bitflags", 54 | "clap_derive", 55 | "clap_lex", 56 | "indexmap", 57 | "lazy_static", 58 | "strsim", 59 | "termcolor", 60 | "textwrap", 61 | ] 62 | 63 | [[package]] 64 | name = "clap_derive" 65 | version = "3.1.7" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" 68 | dependencies = [ 69 | "heck", 70 | "proc-macro-error", 71 | "proc-macro2", 72 | "quote", 73 | "syn", 74 | ] 75 | 76 | [[package]] 77 | name = "clap_lex" 78 | version = "0.2.0" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" 81 | dependencies = [ 82 | "os_str_bytes", 83 | ] 84 | 85 | [[package]] 86 | name = "countme" 87 | version = "2.0.4" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58" 90 | 91 | [[package]] 92 | name = "ctor" 93 | version = "0.1.22" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" 96 | dependencies = [ 97 | "quote", 98 | "syn", 99 | ] 100 | 101 | [[package]] 102 | name = "diff" 103 | version = "0.1.12" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" 106 | 107 | [[package]] 108 | name = "hashbrown" 109 | version = "0.9.1" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 112 | 113 | [[package]] 114 | name = "hashbrown" 115 | version = "0.11.2" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 118 | 119 | [[package]] 120 | name = "heck" 121 | version = "0.4.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 124 | 125 | [[package]] 126 | name = "hermit-abi" 127 | version = "0.1.19" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 130 | dependencies = [ 131 | "libc", 132 | ] 133 | 134 | [[package]] 135 | name = "indexmap" 136 | version = "1.8.1" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" 139 | dependencies = [ 140 | "autocfg", 141 | "hashbrown 0.11.2", 142 | ] 143 | 144 | [[package]] 145 | name = "itoa" 146 | version = "1.0.1" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 149 | 150 | [[package]] 151 | name = "lazy_static" 152 | version = "1.4.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 155 | 156 | [[package]] 157 | name = "libc" 158 | version = "0.2.125" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" 161 | 162 | [[package]] 163 | name = "memoffset" 164 | version = "0.6.5" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 167 | dependencies = [ 168 | "autocfg", 169 | ] 170 | 171 | [[package]] 172 | name = "nix-codemod" 173 | version = "0.1.0" 174 | dependencies = [ 175 | "clap", 176 | "pretty_assertions", 177 | "rnix", 178 | "serde", 179 | "serde_json", 180 | ] 181 | 182 | [[package]] 183 | name = "num-traits" 184 | version = "0.2.14" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 187 | dependencies = [ 188 | "autocfg", 189 | ] 190 | 191 | [[package]] 192 | name = "os_str_bytes" 193 | version = "6.0.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 196 | 197 | [[package]] 198 | name = "output_vt100" 199 | version = "0.1.3" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" 202 | dependencies = [ 203 | "winapi", 204 | ] 205 | 206 | [[package]] 207 | name = "pretty_assertions" 208 | version = "1.2.1" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" 211 | dependencies = [ 212 | "ansi_term", 213 | "ctor", 214 | "diff", 215 | "output_vt100", 216 | ] 217 | 218 | [[package]] 219 | name = "proc-macro-error" 220 | version = "1.0.4" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 223 | dependencies = [ 224 | "proc-macro-error-attr", 225 | "proc-macro2", 226 | "quote", 227 | "syn", 228 | "version_check", 229 | ] 230 | 231 | [[package]] 232 | name = "proc-macro-error-attr" 233 | version = "1.0.4" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 236 | dependencies = [ 237 | "proc-macro2", 238 | "quote", 239 | "version_check", 240 | ] 241 | 242 | [[package]] 243 | name = "proc-macro2" 244 | version = "1.0.37" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" 247 | dependencies = [ 248 | "unicode-xid", 249 | ] 250 | 251 | [[package]] 252 | name = "quote" 253 | version = "1.0.18" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" 256 | dependencies = [ 257 | "proc-macro2", 258 | ] 259 | 260 | [[package]] 261 | name = "rnix" 262 | version = "0.10.1" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "065c5eac8e8f7e7b0e7227bdc46e2d8dd7d69fff7a9a2734e21f686bbcb9b2c9" 265 | dependencies = [ 266 | "cbitset", 267 | "rowan", 268 | "smol_str", 269 | ] 270 | 271 | [[package]] 272 | name = "rowan" 273 | version = "0.12.6" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "a1b36e449f3702f3b0c821411db1cbdf30fb451726a9456dce5dabcd44420043" 276 | dependencies = [ 277 | "countme", 278 | "hashbrown 0.9.1", 279 | "memoffset", 280 | "rustc-hash", 281 | "text-size", 282 | ] 283 | 284 | [[package]] 285 | name = "rustc-hash" 286 | version = "1.1.0" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 289 | 290 | [[package]] 291 | name = "ryu" 292 | version = "1.0.9" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 295 | 296 | [[package]] 297 | name = "serde" 298 | version = "1.0.136" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" 301 | dependencies = [ 302 | "serde_derive", 303 | ] 304 | 305 | [[package]] 306 | name = "serde_derive" 307 | version = "1.0.136" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" 310 | dependencies = [ 311 | "proc-macro2", 312 | "quote", 313 | "syn", 314 | ] 315 | 316 | [[package]] 317 | name = "serde_json" 318 | version = "1.0.81" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" 321 | dependencies = [ 322 | "itoa", 323 | "ryu", 324 | "serde", 325 | ] 326 | 327 | [[package]] 328 | name = "smol_str" 329 | version = "0.1.23" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "7475118a28b7e3a2e157ce0131ba8c5526ea96e90ee601d9f6bb2e286a35ab44" 332 | dependencies = [ 333 | "serde", 334 | ] 335 | 336 | [[package]] 337 | name = "strsim" 338 | version = "0.10.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 341 | 342 | [[package]] 343 | name = "syn" 344 | version = "1.0.92" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" 347 | dependencies = [ 348 | "proc-macro2", 349 | "quote", 350 | "unicode-xid", 351 | ] 352 | 353 | [[package]] 354 | name = "termcolor" 355 | version = "1.1.3" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 358 | dependencies = [ 359 | "winapi-util", 360 | ] 361 | 362 | [[package]] 363 | name = "text-size" 364 | version = "1.1.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a" 367 | 368 | [[package]] 369 | name = "textwrap" 370 | version = "0.15.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 373 | 374 | [[package]] 375 | name = "unicode-xid" 376 | version = "0.2.2" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 379 | 380 | [[package]] 381 | name = "version_check" 382 | version = "0.9.4" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 385 | 386 | [[package]] 387 | name = "winapi" 388 | version = "0.3.9" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 391 | dependencies = [ 392 | "winapi-i686-pc-windows-gnu", 393 | "winapi-x86_64-pc-windows-gnu", 394 | ] 395 | 396 | [[package]] 397 | name = "winapi-i686-pc-windows-gnu" 398 | version = "0.4.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 401 | 402 | [[package]] 403 | name = "winapi-util" 404 | version = "0.1.5" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 407 | dependencies = [ 408 | "winapi", 409 | ] 410 | 411 | [[package]] 412 | name = "winapi-x86_64-pc-windows-gnu" 413 | version = "0.4.0" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 416 | -------------------------------------------------------------------------------- /nix-codemod/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nix-codemod" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rnix = "0.10.1" 10 | serde = { version = "^1.0.100", features = [ "derive" ] } 11 | serde_json = "1.0.81" 12 | clap = { version = "3.1.14", features = [ "derive" ] } 13 | pretty_assertions = "^1.2.1" 14 | -------------------------------------------------------------------------------- /nix-codemod/README.md: -------------------------------------------------------------------------------- 1 | # nix-codemod 2 | 3 | A specific tool to edit nixpkgs, modify systemd services, place hooks on their config and rewrite whole modules. 4 | 5 | ### List Systemd Services 6 | 7 | ### Edit Systemd Service Config 8 | 9 | ### Place Hooks in Service Config 10 | 11 | ```nix 12 | { pkgs, ...}: 13 | { 14 | options = ...; 15 | config.systemd.services.myservice.serviceConfig = { 16 | Foo = "Bar"; 17 | }; 18 | } 19 | ``` 20 | becomes 21 | ```nix 22 | { pkgs, systemdPassthru ...}: 23 | { 24 | options = ...; 25 | config.systemd.services.myservice.serviceConfig = { 26 | Foo = "Bar"; 27 | PrivateDevices = systemdPassthru.myservice.PrivateDevices; 28 | }; 29 | } 30 | ``` 31 | 32 | ### Collect All the Test Definitions From `all-tests` 33 | 34 | ## Check Some Test is "Well-Formed" 35 | 36 | This means that this tests will accept the `systemdPassthru` parameter 37 | 38 | -------------------------------------------------------------------------------- /nix-codemod/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} }: 2 | pkgs.rustPlatform.buildRustPackage rec { 3 | name = "nix-codemod"; 4 | src = ./.; 5 | cargoLock = { 6 | lockFile = ./Cargo.lock; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /nix-codemod/shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {}}: 2 | pkgs.mkShell { 3 | nativeBuildInputs = with pkgs; [ rustc cargo oil ]; 4 | } 5 | -------------------------------------------------------------------------------- /nix-codemod/src/commands/edit_systemd_service.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::fs; 3 | use std::error::Error; 4 | use std::iter; 5 | 6 | use serde_json; 7 | 8 | use rnix::types::*; 9 | use rnix::SyntaxNode; 10 | 11 | use crate::walkers::*; 12 | use crate::edit::*; 13 | 14 | fn find_service_decl(root: Root, service: &str) -> Result> { 15 | let x = root.inner().and_then(Lambda::cast).ok_or("root isn't a function")?; 16 | let x = go_right_value(x.body().ok_or("parse error")?)?; 17 | decl_value( 18 | &[ "config".to_string(), 19 | "systemd".to_string(), 20 | "services".to_string(), 21 | service.to_string()], 22 | x)?.ok_or(format!("config.systemd.services.{} is not declared", service).into()) 23 | } 24 | 25 | fn modify_attribute_set(n: SyntaxNode, replacements: &[(String, String)]) -> Result, Box> { 26 | let n = match ParsedType::try_from(n)? { 27 | ParsedType::AttrSet(o) => o, 28 | _ => panic!("expected an attribute set") 29 | }; 30 | 31 | let to_remove = n.entries().map(|e| { 32 | let keys = e.key().ok_or("parse error")?.path() 33 | .map(parse_ident) 34 | .collect::, Box>>()?; 35 | 36 | for (k, _) in replacements.iter() { 37 | if keys == [Some(k.to_string())] { 38 | return Ok(Some(e)) 39 | } 40 | } 41 | 42 | Ok(None) 43 | }).collect::, Box>>()?.into_iter().filter_map(|o| o); 44 | 45 | 46 | let mut edits = Vec::new(); 47 | 48 | for n in to_remove { 49 | edits.push(remove_node(&n.node())); 50 | } 51 | 52 | let indent = guess_indent(n.node())?.unwrap_or(0); 53 | let lines = replacements.iter() 54 | .map(|(k, v)| format!("{} = {};", k, v)) 55 | .collect::>(); 56 | edits.push(insert_at_set_end(n.node(), &lines, indent)?); 57 | 58 | Ok(edits) 59 | } 60 | 61 | fn merge_decls( 62 | n: &SyntaxNode, 63 | prefix: &[String], 64 | entries: &[(Vec, DeclKV)], 65 | replacements: &[(String, String)] 66 | ) -> Result, Box> { 67 | let indent = guess_indent(n)?.unwrap_or(0); 68 | let lines = iter::once(format!("{} = {{", prefix.join("."))) 69 | .chain(entries.iter().filter_map(|(key, DeclKV { value, .. })| { 70 | for (k, _) in replacements { 71 | if key == &[k.to_string()] { 72 | return None 73 | } 74 | } 75 | Some(format!(" {} = {};", key.join("."), value)) 76 | })) 77 | .chain(replacements.iter() 78 | .map(|(k, v)| format!(" {} = {};", k, v))) 79 | .chain(iter::once("};".to_string())) 80 | .collect::>(); 81 | let edits = entries.iter() 82 | .map(|(_, DeclKV { node, .. })| remove_node(&node)) 83 | .chain(iter::once(insert_at_set_end(n, &lines, indent)?)) 84 | .collect::>(); 85 | 86 | Ok(edits) 87 | } 88 | 89 | fn add_attribute_decl(n: &SyntaxNode, prefix: &[String], replacements: &[(String, String)]) -> Result, Box> { 90 | let inherited: Vec = AttrSet::cast(n.clone()).ok_or("parse error")?.inherits() 91 | .map(|inherit| inherit.idents().map(|ident| ident.as_str().to_string())) 92 | .flatten() 93 | .collect(); 94 | 95 | if inherited.contains(&"serviceConfig".to_string()) { 96 | Err("can't replace an attribute that is inherited")? 97 | } 98 | 99 | let indent = guess_indent(n)?.unwrap_or(0); 100 | let lines = iter::once(format!("{}serviceConfig = {{", 101 | prefix.iter().map(|k| format!("{}.", k)).collect::())) 102 | .chain(replacements.iter() 103 | .map(|(k, v)| format!(" {} = {};", k, v)) 104 | .collect::>()) 105 | .chain(iter::once("};".to_string())) 106 | .collect::>(); 107 | let edit = insert_at_set_end(n, &lines, indent)?; 108 | 109 | Ok(vec![edit]) 110 | } 111 | 112 | pub fn edit_systemd_service(module: &str, service: &str, options: &str, verbose: bool) -> Result<(), Box> { 113 | let content = fs::read_to_string(&module)?; 114 | let ast = rnix::parse(&content).as_result()?; 115 | 116 | let decl = find_service_decl(ast.root(), &service)?; 117 | let cfg = decl.clone().project("serviceConfig")?; 118 | 119 | let options: Vec<(String, String)> = { 120 | let content = fs::read_to_string(&options)?; 121 | serde_json::from_str(&content)? 122 | }; 123 | 124 | let edits = match cfg { 125 | Some(DeclValue::Node(n)) => { 126 | if verbose { 127 | println!("modify entries in already declared {}.serviceConfig", service); 128 | } 129 | 130 | modify_attribute_set(n.value, &options)? 131 | }, 132 | Some(DeclValue::PartialAttr { node, prefix, entries }) => { 133 | if verbose { 134 | println!("merge declarations in {} = {{ ... }}", prefix.join(".")); 135 | } 136 | 137 | merge_decls(&node, &prefix, &entries, &options)? 138 | }, 139 | None => { 140 | if verbose { 141 | println!("add {}.serviceConfig", 142 | decl.prefix().iter().map(|k| format!("{}.", k)).collect::()); 143 | } 144 | 145 | add_attribute_decl(&decl.value(), decl.prefix(), &options)? 146 | }, 147 | }; 148 | 149 | let mut text = content.clone(); 150 | 151 | if verbose { 152 | println!("{}", text); 153 | } 154 | 155 | apply_edits(edits, &mut text); 156 | 157 | print!("{}", text); 158 | 159 | Ok(()) 160 | } 161 | 162 | fn add_passthru_arg(root: Root) -> Result, Box> { 163 | let n = root.inner().and_then(Lambda::cast).ok_or("root isn't a function")?; 164 | let n = n.arg().and_then(Pattern::cast).ok_or("root function's argument isn't a pattern")?; 165 | 166 | let already_defined = n.entries() 167 | .filter_map(|x| x.name()) 168 | .any(|ident| ident.as_str() == "systemdPassthru"); 169 | 170 | if !already_defined { 171 | Ok(vec!(insert_at_pattern_start(n.node(), " systemdPassthru,".to_string())?)) 172 | } else { 173 | Ok(vec!()) 174 | } 175 | } 176 | 177 | fn systemd_hooks_edits(root: Root, service: &str, option_names: &[String]) -> Result, Box> { 178 | let mut edits = vec!(); 179 | 180 | edits.append(&mut add_passthru_arg(root.clone())?); 181 | 182 | let decl = find_service_decl(root, &service)?; 183 | let cfg = decl.clone().project("serviceConfig")?; 184 | 185 | fn maybe_quote(name: &str) -> String { 186 | if name.chars().all(|c| c.is_alphanumeric()) { 187 | name.to_string() 188 | } else { 189 | format!("\"{}\"", name) 190 | } 191 | } 192 | 193 | let options: Vec<(String, String)> = option_names.iter() 194 | .map(|name| (name.clone(), format!("systemdPassthru.{}.{}", maybe_quote(service), name))) 195 | .collect(); 196 | 197 | edits.append(&mut match cfg { 198 | Some(DeclValue::Node(n)) => 199 | modify_attribute_set(n.value, &options)?, 200 | Some(DeclValue::PartialAttr { node, prefix, entries }) => 201 | merge_decls(&node, &prefix, &entries, &options)?, 202 | None => 203 | add_attribute_decl(&decl.value(), decl.prefix(), &options)?, 204 | }); 205 | 206 | Ok(edits) 207 | } 208 | 209 | pub fn insert_systemd_hooks(module: &str, service: &str, option_names: &str) -> Result<(), Box> { 210 | let content = fs::read_to_string(&module)?; 211 | let ast = rnix::parse(&content).as_result()?; 212 | 213 | let option_names: Vec = { 214 | let content = fs::read_to_string(&option_names)?; 215 | serde_json::from_str(&content)? 216 | }; 217 | 218 | let edits = systemd_hooks_edits(ast.root(), service, &option_names)?; 219 | 220 | let mut text = content.clone(); 221 | apply_edits(edits, &mut text); 222 | 223 | print!("{}", text); 224 | 225 | Ok(()) 226 | } 227 | 228 | #[cfg(test)] 229 | mod edit_tests { 230 | use pretty_assertions::assert_eq; 231 | 232 | use super::*; 233 | 234 | fn test_case(input: &str, output: &str) { 235 | let options: &[(String, String)] = &[ 236 | ("a".to_string(), "false".to_string()), 237 | ("c".to_string(), "true".to_string()), 238 | ]; 239 | 240 | let ast = rnix::parse(input).as_result().unwrap(); 241 | let decl = find_service_decl(ast.root(), "codemod").unwrap(); 242 | let cfg = decl.clone().project("serviceConfig").unwrap(); 243 | 244 | let edits = match cfg { 245 | Some(DeclValue::Node(n)) => 246 | modify_attribute_set(n.value, &options).unwrap(), 247 | Some(DeclValue::PartialAttr { node, prefix, entries }) => 248 | merge_decls(&node, &prefix, &entries, &options).unwrap(), 249 | None => 250 | add_attribute_decl(&decl.value(), decl.prefix(), &options).unwrap(), 251 | }; 252 | 253 | let mut text = input.to_string(); 254 | apply_edits(edits, &mut text); 255 | 256 | assert_eq!(text, output); 257 | } 258 | 259 | #[test] 260 | fn test_add_entry() { 261 | test_case(" 262 | {}: { 263 | config.systemd.services.codemod = { 264 | u = true; 265 | }; 266 | } 267 | ", " 268 | {}: { 269 | config.systemd.services.codemod = { 270 | u = true; 271 | serviceConfig = { 272 | a = false; 273 | c = true; 274 | }; 275 | }; 276 | } 277 | "); 278 | } 279 | 280 | #[test] 281 | fn test_deep_merge_entries() { 282 | test_case(" 283 | {}: { 284 | config.systemd.services.codemod.u = true; 285 | config.systemd.services.codemod.serviceConfig.a = true; 286 | config.systemd.services.codemod.serviceConfig.b = true; 287 | } 288 | ", " 289 | {}: { 290 | config.systemd.services.codemod.u = true; 291 | config.systemd.services.codemod.serviceConfig = { 292 | b = true; 293 | a = false; 294 | c = true; 295 | }; 296 | } 297 | "); 298 | } 299 | 300 | #[test] 301 | fn test_merge_add() { 302 | test_case(" 303 | {}: { 304 | config.systemd.services.codemod.a = true; 305 | config.systemd.services.codemod.b = true; 306 | } 307 | ", " 308 | {}: { 309 | config.systemd.services.codemod.a = true; 310 | config.systemd.services.codemod.b = true; 311 | config.systemd.services.codemod.serviceConfig = { 312 | a = false; 313 | c = true; 314 | }; 315 | } 316 | "); 317 | } 318 | 319 | #[test] 320 | fn test_merge_entries() { 321 | test_case(" 322 | {}: { 323 | config.systemd.services.codemod = { 324 | serviceConfig.a = true; 325 | serviceConfig.b = true; 326 | }; 327 | } 328 | ", " 329 | {}: { 330 | config.systemd.services.codemod = { 331 | serviceConfig = { 332 | b = true; 333 | a = false; 334 | c = true; 335 | }; 336 | }; 337 | } 338 | "); 339 | } 340 | 341 | #[test] 342 | fn test_modify_entries() { 343 | test_case(" 344 | {}: 345 | { 346 | config.systemd.services.codemod.serviceConfig = { 347 | a = true; 348 | b = true; 349 | }; 350 | } 351 | ", " 352 | {}: 353 | { 354 | config.systemd.services.codemod.serviceConfig = { 355 | b = true; 356 | a = false; 357 | c = true; 358 | }; 359 | } 360 | "); 361 | } 362 | } 363 | 364 | #[cfg(test)] 365 | mod hooks_test { 366 | use pretty_assertions::assert_eq; 367 | 368 | use super::*; 369 | 370 | fn base_test_case(service: &str, input: &str, output: &str) { 371 | let option_names = &["a".to_string(), "c".to_string()]; 372 | let ast = rnix::parse(input).as_result().unwrap(); 373 | 374 | let edits = systemd_hooks_edits(ast.root(), service, option_names).unwrap(); 375 | let mut text = input.to_string(); 376 | apply_edits(edits, &mut text); 377 | 378 | assert_eq!(text, output); 379 | } 380 | 381 | fn test_case(input: &str, output: &str) { 382 | base_test_case("codemod", input, output) 383 | } 384 | 385 | #[test] 386 | fn test_systemd_hooks() { 387 | test_case(" 388 | { pkgs, ... }: { 389 | config.systemd.services.codemod.serviceConfig = { 390 | a = true; 391 | b = true; 392 | }; 393 | } 394 | ", " 395 | { systemdPassthru, pkgs, ... }: { 396 | config.systemd.services.codemod.serviceConfig = { 397 | b = true; 398 | a = systemdPassthru.codemod.a; 399 | c = systemdPassthru.codemod.c; 400 | }; 401 | } 402 | "); 403 | } 404 | 405 | #[test] 406 | fn test_hook_idempotent() { 407 | test_case(" 408 | { systemdPassthru, pkgs, ... }: { 409 | config.systemd.services.codemod.serviceConfig = { 410 | b = true; 411 | a = systemdPassthru.codemod.a; 412 | c = systemdPassthru.codemod.c; 413 | }; 414 | } 415 | ", " 416 | { systemdPassthru, pkgs, ... }: { 417 | config.systemd.services.codemod.serviceConfig = { 418 | b = true; 419 | a = systemdPassthru.codemod.a; 420 | c = systemdPassthru.codemod.c; 421 | }; 422 | } 423 | "); 424 | } 425 | 426 | #[test] 427 | fn test_non_alphanumeric() { 428 | base_test_case("codemod@", " 429 | { systemdPassthru, pkgs, ... }: { 430 | config.systemd.services.\"codemod@\".serviceConfig = { 431 | b = true; 432 | a = systemdPassthru.codemod.a; 433 | c = systemdPassthru.codemod.c; 434 | }; 435 | } 436 | ", " 437 | { systemdPassthru, pkgs, ... }: { 438 | config.systemd.services.\"codemod@\".serviceConfig = { 439 | b = true; 440 | a = systemdPassthru.\"codemod@\".a; 441 | c = systemdPassthru.\"codemod@\".c; 442 | }; 443 | } 444 | "); 445 | } 446 | } 447 | 448 | -------------------------------------------------------------------------------- /nix-codemod/src/commands/find_all_tests.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::fs; 3 | use std::error::Error; 4 | 5 | use serde::Serialize; 6 | 7 | use serde_json; 8 | 9 | use rnix::types::*; 10 | use rnix::SyntaxNode; 11 | 12 | use crate::walkers::*; 13 | 14 | fn find_test_file(n: SyntaxNode) -> Result> { 15 | let app_ext = Apply::cast(n).ok_or("unexpected file structure")?; 16 | let app = Apply::cast(app_ext.lambda().ok_or("parse error")?).ok_or("unexpected file structure")?; 17 | 18 | if let Ok(f) = parse_ident_select(app.lambda().ok_or("parse error")?) { 19 | if f == [ "handleTest" ] { 20 | expect_relative_path(app.value().ok_or("parse error")?) 21 | } else { 22 | Err("unexpected file structure")? 23 | } 24 | } else { 25 | let app_inner = Apply::cast(app.lambda().ok_or("parse error")?).ok_or("unexpected file structure")?; 26 | let f = parse_ident_select(app_inner.lambda().ok_or("parse error")?)?; 27 | if f == [ "handleTestOn" ] { 28 | expect_relative_path(app.value().ok_or("parse error")?) 29 | } else { 30 | Err("unexpected file structure")? 31 | } 32 | } 33 | } 34 | 35 | #[derive(Serialize)] 36 | struct Output { 37 | paths: Vec<(String, String)>, 38 | failures: Vec, 39 | } 40 | 41 | pub fn find_all_tests(all_tests: &str) -> Result<(), Box> { 42 | let content = fs::read_to_string(&all_tests)?; 43 | let ast = rnix::parse(&content).as_result()?; 44 | 45 | let root_fn = ast.root().inner().and_then(Lambda::cast).ok_or("root isn't a function")?; 46 | let val = go_right_value(root_fn.body().ok_or("parse error")?)?; 47 | let attrs = AttrSet::cast(val).ok_or("unexpected file structure")?; 48 | 49 | let nodes = attrs.entries() 50 | .map(|entry| -> Result<_, Box> { 51 | Ok((entry.key().ok_or("parse error")?, 52 | entry.value().ok_or("parse error")?)) } ) 53 | .collect::, Box>>()?; 54 | 55 | let mut paths: Vec<(String, String)> = vec!(); 56 | let mut failures: Vec = vec!(); 57 | 58 | for (k, v) in nodes.into_iter() { 59 | if let Ok(f) = find_test_file(v) { 60 | paths.push((format!("{}", k.node()), f)); 61 | } else { 62 | failures.push(format!("{}", k.node())); 63 | } 64 | } 65 | 66 | println!("{}", serde_json::to_string(&Output { paths, failures })?); 67 | 68 | Ok(()) 69 | } 70 | 71 | -------------------------------------------------------------------------------- /nix-codemod/src/commands/is_test_well_formed.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::fs; 3 | use std::error::Error; 4 | 5 | use rnix::types::*; 6 | 7 | use crate::walkers::*; 8 | 9 | pub fn is_test_well_formed(test: &str) -> Result<(), Box> { 10 | let content = fs::read_to_string(&test)?; 11 | let ast = rnix::parse(&content).as_result()?; 12 | 13 | let val = go_right_value(ast.root().inner().ok_or("parse error")?)?; 14 | 15 | if let Some(app_ext) = Apply::cast(val) { 16 | if let Some(app) = Apply::cast(app_ext.lambda().ok_or("parse error")?) { 17 | if let Ok(p) = expect_relative_path(app.value().ok_or("parse error")?) { 18 | if p == "./make-test-python.nix" || p == "../make-test-python.nix" { 19 | println!("true"); 20 | 21 | return Ok(()) 22 | } 23 | } 24 | } 25 | } 26 | 27 | println!("false"); 28 | 29 | Ok(()) 30 | } 31 | 32 | -------------------------------------------------------------------------------- /nix-codemod/src/commands/list_systemd_services.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::fs; 3 | use std::error::Error; 4 | use std::iter; 5 | 6 | use clap::Parser; 7 | use clap::Subcommand; 8 | 9 | use serde_json; 10 | 11 | use rnix::types::*; 12 | use rnix::SyntaxNode; 13 | 14 | use crate::walkers::*; 15 | use crate::edit::*; 16 | 17 | fn find_systemd_services(root: Root) -> Result, Box> { 18 | let x = root.inner().and_then(Lambda::cast).ok_or("root isn't a function")?; 19 | let x = go_right_value(x.body().ok_or("parse error")?)?; 20 | let x = decl_value( 21 | &[ "config".to_string(), 22 | "systemd".to_string(), 23 | "services".to_string(), ], 24 | x)?; 25 | 26 | match x { 27 | Some(DeclValue::Node(_)) => Err("Couldn't reduce")?, 28 | Some(DeclValue::PartialAttr { entries, .. }) => { 29 | let mut keys = entries.iter() 30 | .map(|(attr_name, _)| attr_name.first().unwrap().clone()) 31 | .collect::>(); 32 | keys.sort(); 33 | keys.dedup(); 34 | Ok(keys) 35 | }, 36 | None => Ok(vec!()) 37 | } 38 | } 39 | 40 | fn find_service_decl(root: Root, service: &str) -> Result> { 41 | let x = root.inner().and_then(Lambda::cast).ok_or("root isn't a function")?; 42 | let x = go_right_value(x.body().ok_or("parse error")?)?; 43 | decl_value( 44 | &[ "config".to_string(), 45 | "systemd".to_string(), 46 | "services".to_string(), 47 | service.to_string()], 48 | x)?.ok_or(format!("config.systemd.services.{} is not declared", service).into()) 49 | } 50 | 51 | pub fn list_systemd_services(module: &str, verbose: bool) -> Result<(), Box> { 52 | let content = fs::read_to_string(&module)?; 53 | let ast = rnix::parse(&content).as_result()?; 54 | 55 | let declared_services: Vec = find_systemd_services(ast.root())? 56 | .into_iter() 57 | .filter_map(|name| -> Option { 58 | let decl = find_service_decl(ast.root(), &name).ok()?; 59 | let cfg = decl.clone().project("serviceConfig").ok()?; 60 | match cfg { 61 | Some(cfg) => if cfg.entries().ok()?.is_some() { Some(name) } else { None }, 62 | None => { 63 | let inherited: Vec = AttrSet::cast(decl.value().clone()).ok_or("parse error").ok()?.inherits() 64 | .map(|inherit| inherit.idents().map(|ident| ident.as_str().to_string())) 65 | .flatten() 66 | .collect(); 67 | 68 | if inherited.contains(&"serviceConfig".to_string()) { 69 | None 70 | } else { 71 | Some(name) 72 | } 73 | } 74 | } 75 | }) 76 | .collect(); 77 | 78 | if verbose { 79 | if declared_services.len() == 0 { 80 | println!("No systemd service"); 81 | return Ok(()) 82 | } 83 | 84 | println!("This file declares"); 85 | for service in declared_services.iter() { 86 | println!(" * {}", service); 87 | } 88 | } else { 89 | println!("{}", serde_json::to_string(&declared_services)?); 90 | } 91 | 92 | Ok(()) 93 | } 94 | 95 | -------------------------------------------------------------------------------- /nix-codemod/src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | mod list_systemd_services; 3 | mod print_systemd_service_config; 4 | mod edit_systemd_service; 5 | mod find_all_tests; 6 | mod is_test_well_formed; 7 | 8 | pub use list_systemd_services::*; 9 | pub use print_systemd_service_config::*; 10 | pub use edit_systemd_service::*; 11 | pub use find_all_tests::*; 12 | pub use is_test_well_formed::*; 13 | 14 | -------------------------------------------------------------------------------- /nix-codemod/src/commands/print_systemd_service_config.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::fs; 3 | use std::error::Error; 4 | 5 | use serde_json; 6 | 7 | use rnix::types::*; 8 | use rnix::SyntaxNode; 9 | 10 | use crate::walkers::*; 11 | 12 | static bool_options: &'static [&'static str] = &[ 13 | "PrivateDevices", 14 | "PrivateMounts", 15 | "PrivateNetwork", 16 | "PrivateTmp", 17 | "PrivateUsers", 18 | "ProtectControlGroups", 19 | "ProtectKernelModules", 20 | "ProtectKernelTunables", 21 | "ProtectKernelLogs", 22 | "ProtectClock", 23 | "ProtectHostname", 24 | "LockPersonality", 25 | "MemoryDenyWriteExecute", 26 | "NoNewPrivileges", 27 | //"Delegate", -- inverted, so not here! 28 | "RestrictRealtime", 29 | "RestrictSUIDSGID", 30 | ]; 31 | 32 | fn find_service_decl(root: Root, service: &str) -> Result> { 33 | let x = root.inner().and_then(Lambda::cast).ok_or("root isn't a function")?; 34 | let x = go_right_value(x.body().ok_or("parse error")?)?; 35 | decl_value( 36 | &[ "config".to_string(), 37 | "systemd".to_string(), 38 | "services".to_string(), 39 | service.to_string()], 40 | x)?.ok_or(format!("config.systemd.services.{} is not declared", service).into()) 41 | } 42 | 43 | pub fn print_systemd_service_config( 44 | module: &str, 45 | service: &str, 46 | verbose: bool 47 | ) -> Result<(), Box> { 48 | let content = fs::read_to_string(&module)?; 49 | let ast = rnix::parse(&content).as_result()?; 50 | 51 | let decl = find_service_decl(ast.root(), &service)?; 52 | let cfg = decl.project("serviceConfig")?; 53 | 54 | let entries = if let Some(entries) = cfg.map(DeclValue::entries).transpose()?.unwrap_or(Some(vec!())) { 55 | entries 56 | } else { 57 | Err("serviceConfig is not an attribute set")? 58 | }; 59 | 60 | let configured: Vec = entries.iter() 61 | .filter_map(|(key, _)| if key.len() == 1 { Some(key.first().unwrap().clone()) } else { None }) 62 | .collect(); 63 | 64 | if verbose { 65 | for (key, DeclKV { value, .. }) in entries.into_iter() { 66 | print!(" * {} = ", key.join(".")); 67 | 68 | match parse_cfg_value(value)? { 69 | CfgValue::Str(s) => { 70 | println!("{}", s); 71 | }, 72 | CfgValue::Bool(b) => { 73 | println!("{}", b); 74 | }, 75 | CfgValue::List(elems) => { 76 | println!("[ {} ]", elems.join(" ")); 77 | }, 78 | CfgValue::NotReduced => { 79 | println!(""); 80 | }, 81 | } 82 | } 83 | 84 | println!(); 85 | } 86 | 87 | let blank_options: Vec<&str> = bool_options.iter() 88 | .filter(|opt| !configured.contains(&opt.to_string())) 89 | .map(|s| *s) 90 | .collect(); 91 | 92 | println!("{}", serde_json::to_string(&blank_options)?); 93 | 94 | Ok(()) 95 | } 96 | 97 | -------------------------------------------------------------------------------- /nix-codemod/src/edit.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::error::Error; 3 | 4 | //use rnix::types::*; 5 | use rnix::SyntaxNode; 6 | use rnix::SyntaxToken; 7 | use rnix::SyntaxKind; 8 | use rnix::NodeOrToken; 9 | 10 | pub struct Edit { 11 | pub start: usize, 12 | pub end: usize, 13 | pub replace: String, 14 | } 15 | 16 | pub fn apply_edits(mut edits: Vec, text: &mut String) { 17 | // essentially https://github.com/rust-lang/rust-analyzer/blob/master/crates/text-edit/src/lib.rs 18 | edits.sort_by_key(|e| (e.start, e.end)); 19 | if !Iterator::zip(edits.iter(), edits.iter().skip(1)).all(|(l, r)| l.start <= r.end) { 20 | panic!("Overlapping edits, this is a bug!") 21 | } 22 | 23 | for e in edits.into_iter().rev() { 24 | text.replace_range(e.start..e.end, &e.replace); 25 | } 26 | } 27 | 28 | /// Returns the end of the range to delete when deleting 29 | /// the token `n`; and wether this range ends at the end 30 | /// of a line. 31 | pub fn find_span_end(n: &SyntaxNode) -> (usize, bool) { 32 | let mut end: usize = n.text_range().end().into(); 33 | let mut node: NodeOrToken = NodeOrToken::Node(n.clone()); 34 | 35 | let is_line_end = loop { 36 | if let Some(n) = node.next_sibling_or_token() { 37 | node = n; 38 | } 39 | 40 | let s = match node.kind() { 41 | SyntaxKind::TOKEN_WHITESPACE => { 42 | match &node { 43 | NodeOrToken::Token(token) => token.text(), 44 | _ => unreachable!() 45 | } 46 | }, 47 | _ => break false 48 | }; 49 | 50 | match s.find('\n') { 51 | Some(i) => { end += i; break true }, 52 | None => end = node.text_range().end().into() 53 | } 54 | }; 55 | 56 | (end, is_line_end) 57 | } 58 | 59 | pub fn find_span_start(n: &SyntaxNode) -> usize { 60 | let mut start: usize = n.text_range().start().into(); 61 | let mut node: NodeOrToken = NodeOrToken::Node(n.clone()); 62 | 63 | loop { 64 | if let Some(n) = node.prev_sibling_or_token() { 65 | node = n; 66 | } 67 | 68 | let s = match node.kind() { 69 | SyntaxKind::TOKEN_WHITESPACE => { 70 | match &node { 71 | NodeOrToken::Token(token) => token.text(), 72 | _ => unreachable!() 73 | } 74 | }, 75 | _ => break 76 | }; 77 | 78 | match s.rfind('\n') { 79 | Some(i) => { 80 | start = node.text_range().start().into(); 81 | start += i; 82 | break 83 | }, 84 | None => start = node.text_range().start().into() 85 | } 86 | } 87 | 88 | start 89 | } 90 | 91 | pub fn replace_node(n: &SyntaxNode, replace: String) -> Edit { 92 | let range = n.text_range(); 93 | 94 | Edit { 95 | start: range.start().into(), 96 | end: range.end().into(), 97 | replace, 98 | } 99 | } 100 | 101 | pub fn remove_node(n: &SyntaxNode) -> Edit { 102 | let range = n.text_range(); 103 | 104 | let (end, is_line_end) = find_span_end(n); 105 | let start = if is_line_end { 106 | find_span_start(n) 107 | } else { range.start().into() }; 108 | 109 | Edit { 110 | start, 111 | end, 112 | replace: "".to_string(), 113 | } 114 | } 115 | 116 | /// Expects an `AttrSet` node 117 | pub fn guess_indent(n: &SyntaxNode) -> Result, Box> { 118 | let n = { 119 | let mut n = n.first_child_or_token().ok_or("parse error")?; 120 | loop { 121 | if n.kind() == SyntaxKind::TOKEN_REC { 122 | n = n.next_sibling_or_token().ok_or("parse error")? 123 | } else if n.kind() == SyntaxKind::TOKEN_WHITESPACE { 124 | n = n.next_sibling_or_token().ok_or("parse error")? 125 | } else { 126 | break 127 | } 128 | } 129 | n 130 | }; 131 | 132 | if n.kind() != SyntaxKind::TOKEN_CURLY_B_OPEN { Err("parse error")? } 133 | 134 | if let Some(n) = n.next_sibling_or_token() { 135 | if n.kind() == SyntaxKind::TOKEN_WHITESPACE { 136 | if let NodeOrToken::Token(token) = n { 137 | if let Some(i) = token.text().rfind("\n") { 138 | //TODO: handle spaces & tabs ? 139 | let end: usize = token.text_range().end().into(); 140 | let start: usize = token.text_range().start().into(); 141 | return Ok(Some(end - (start + i + 1))) 142 | } 143 | } 144 | } 145 | } 146 | 147 | Ok(None) 148 | } 149 | 150 | pub fn insert_at_set_end(n: &SyntaxNode, lines: &[String], indent: usize) -> Result> { 151 | let n = n.last_child_or_token().ok_or("parse error")?; 152 | if n.kind() != SyntaxKind::TOKEN_CURLY_B_CLOSE { Err("parse error")? } 153 | 154 | let mut spot: usize = n.text_range().start().into(); 155 | 156 | if let Some(n) = n.prev_sibling_or_token() { 157 | if n.kind() == SyntaxKind::TOKEN_WHITESPACE { 158 | if let NodeOrToken::Token(token) = n { 159 | spot = token.text_range().start().into(); 160 | if let Some(i) = token.text().rfind("\n") { spot += i } 161 | } 162 | } 163 | } 164 | 165 | let replace = lines.iter() 166 | .map(|s| format!("\n{:indent$}{}", "", s, indent=indent)) 167 | .collect::(); 168 | 169 | Ok(Edit { start: spot, end: spot, replace }) 170 | } 171 | 172 | pub fn insert_at_pattern_start(n: &SyntaxNode, text: String) -> Result> { 173 | let n = n.first_child_or_token().ok_or("parse error")?; 174 | if n.kind() != SyntaxKind::TOKEN_CURLY_B_OPEN { Err("parse error")? } 175 | 176 | Ok(Edit { 177 | start: n.text_range().end().into(), 178 | end: n.text_range().end().into(), 179 | replace: text, 180 | }) 181 | } 182 | 183 | -------------------------------------------------------------------------------- /nix-codemod/src/main.rs: -------------------------------------------------------------------------------- 1 | 2 | mod walkers; 3 | mod edit; 4 | mod commands; 5 | 6 | use std::error::Error; 7 | 8 | use clap::Parser; 9 | use clap::Subcommand; 10 | 11 | use commands::*; 12 | 13 | #[derive(Parser)] 14 | struct Cli { 15 | #[clap(subcommand)] 16 | command: Command 17 | } 18 | 19 | #[derive(Subcommand)] 20 | enum Command { 21 | ListSystemdServices { 22 | module: String, 23 | #[clap(short, long)] 24 | verbose: bool, 25 | }, 26 | PrintSystemdServiceConfig { 27 | module: String, 28 | service: String, 29 | #[clap(short, long)] 30 | verbose: bool, 31 | }, 32 | EditSystemdService { 33 | module: String, 34 | service: String, 35 | options: String, 36 | #[clap(short, long)] 37 | verbose: bool, 38 | }, 39 | InsertSystemdHooks { 40 | module: String, 41 | service: String, 42 | option_names: String, 43 | }, 44 | FindAllTests { 45 | all_tests: String, 46 | }, 47 | IsTestWellFormed { 48 | test: String, 49 | }, 50 | } 51 | 52 | fn main() -> Result<(), Box> { 53 | let cli = Cli::parse(); 54 | 55 | match cli.command { 56 | Command::ListSystemdServices { module, verbose } => 57 | list_systemd_services(&module, verbose)?, 58 | Command::PrintSystemdServiceConfig { module, service, verbose } => 59 | print_systemd_service_config(&module, &service, verbose)?, 60 | Command::EditSystemdService { module, service, options, verbose } => 61 | edit_systemd_service(&module, &service, &options, verbose)?, 62 | Command::InsertSystemdHooks { module, service, option_names } => 63 | insert_systemd_hooks(&module, &service, &option_names)?, 64 | Command::FindAllTests { all_tests } => 65 | find_all_tests(&all_tests)?, 66 | Command::IsTestWellFormed { test } => 67 | is_test_well_formed(&test)?, 68 | } 69 | 70 | Ok(()) 71 | } 72 | 73 | -------------------------------------------------------------------------------- /nix-codemod/src/walkers.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::error::Error; 3 | 4 | use rnix::types::*; 5 | use rnix::value; 6 | use rnix::SyntaxNode; 7 | use rnix::StrPart; 8 | 9 | pub fn expect_relative_path(n: SyntaxNode) -> Result> { 10 | let v = Value::cast(n) 11 | .ok_or("unexpected file structure")? 12 | .to_value()?; 13 | if let value::Value::Path(value::Anchor::Relative, s) = v { 14 | Ok(s) 15 | } else { 16 | Err("expected a relative path")? 17 | } 18 | } 19 | 20 | pub fn parse_ident(n: SyntaxNode) -> Result, Box> { 21 | match ParsedType::try_from(n)? { 22 | ParsedType::Ident(n) => { 23 | Ok(Some(n.as_str().to_string())) 24 | }, 25 | _ => Ok(None) 26 | } 27 | } 28 | 29 | fn extract_simple_string(n: Str) -> Option { 30 | if n.parts().len() == 1 { 31 | if let Some(StrPart::Literal(s)) = n.parts().first() { 32 | Some(s.clone()) 33 | } else { 34 | panic!("Isn't that case unreachable?") 35 | } 36 | } else { 37 | None 38 | } 39 | } 40 | 41 | pub enum CfgValue { 42 | Str(String), 43 | Bool(bool), 44 | List(Vec), 45 | NotReduced, 46 | } 47 | 48 | pub fn parse_cfg_value(n: SyntaxNode) -> Result> { 49 | match ParsedType::try_from(n)? { 50 | ParsedType::Str(n) => { 51 | if let Some(s) = extract_simple_string(n) { 52 | Ok(CfgValue::Str(s.clone())) 53 | } else { 54 | Ok(CfgValue::NotReduced) 55 | } 56 | }, 57 | ParsedType::Ident(n) => { 58 | match n.as_str() { 59 | "true" => Ok(CfgValue::Bool(true)), 60 | "false" => Ok(CfgValue::Bool(false)), 61 | _ => Ok(CfgValue::NotReduced) 62 | } 63 | }, 64 | ParsedType::List(n) => { 65 | let elems = n.items() 66 | .map(|n| extract_simple_string(Str::cast(n)?)) 67 | .collect::>>(); 68 | match elems { 69 | Some(elems) => Ok(CfgValue::List(elems)), 70 | None => Ok(CfgValue::NotReduced) 71 | } 72 | }, 73 | _ => Ok(CfgValue::NotReduced) 74 | } 75 | } 76 | 77 | pub fn parse_ident_select(n: SyntaxNode) -> Result, Box> { 78 | struct Visitor { 79 | idents: Vec 80 | } 81 | 82 | impl Visitor { 83 | fn visit(&mut self, n: SyntaxNode) -> Result<(), Box> { 84 | match ParsedType::try_from(n)? { 85 | ParsedType::Ident(n) => { 86 | self.idents.push(n.as_str().to_string()); 87 | Ok(()) 88 | }, 89 | ParsedType::Select(n) => { 90 | self.idents.push( 91 | parse_ident(n.index().ok_or("parse error")?)? 92 | .ok_or("expected an identifier")?); 93 | self.visit(n.set().ok_or("parse error")?) 94 | }, 95 | _ => Err("parse error")? 96 | } 97 | } 98 | } 99 | 100 | let mut v = Visitor { idents: vec!() }; 101 | v.visit(n)?; 102 | v.idents.reverse(); 103 | 104 | Ok(v.idents) 105 | } 106 | 107 | pub fn go_right_value(n: SyntaxNode) -> Result> { 108 | match ParsedType::try_from(n) { 109 | Ok(ParsedType::LetIn(n)) => { 110 | go_right_value(n.body().ok_or("parse error")?) 111 | }, 112 | Ok(ParsedType::With(n)) => { 113 | go_right_value(n.body().ok_or("parse error")?) 114 | }, 115 | Ok(ParsedType::Apply(n)) => { 116 | if let Ok(p) = parse_ident_select(n.lambda().ok_or("parse error")?) { 117 | if p == ["mkMerge"] { 118 | todo!("handle mkMerge") 119 | } else { 120 | Ok(n.node().clone()) 121 | } 122 | } else { 123 | match ParsedType::try_from(n.lambda().ok_or("parse error")?)? { 124 | ParsedType::Apply(n1) => { 125 | if let Ok(p) = parse_ident_select(n1.lambda().ok_or("parse error")?) { 126 | if p == ["mkIf"] { 127 | n.value().ok_or("parse error".into()) 128 | } else { 129 | Ok(n.node().clone()) 130 | } 131 | } else { 132 | Ok(n.node().clone()) 133 | } 134 | }, 135 | _ => Ok(n.node().clone()) 136 | } 137 | } 138 | }, 139 | Ok(n) => Ok(n.node().clone()), 140 | _ => Err("parse error")? 141 | } 142 | } 143 | 144 | #[derive(Clone)] 145 | pub struct DeclKV { 146 | /// The KeyValue node that declared this entry 147 | pub node: SyntaxNode, 148 | /// The name of the node in the attribute set it originates from 149 | pub key: Vec, 150 | pub value: SyntaxNode, 151 | } 152 | 153 | pub fn attrset_entries(n: SyntaxNode) -> Result>, Box> { 154 | match ParsedType::try_from(n)? { 155 | ParsedType::AttrSet(n) => n.entries() 156 | .map(|entry| { 157 | let keys = entry.key().ok_or("parse error")?.path().map(|n| -> Result> { 158 | match ParsedType::try_from(n)? { 159 | ParsedType::Ident(n) => { 160 | Ok(n.as_str().to_string()) 161 | }, 162 | ParsedType::Str(n) => { 163 | if n.parts().len() == 1 { 164 | if let Some(StrPart::Literal(s)) = n.parts().first() { 165 | Ok(s.clone()) 166 | } else { 167 | panic!("Isn't that case unreachable?") 168 | } 169 | } else { 170 | todo!() 171 | } 172 | }, 173 | _ => Err("Unexpected node type when unrolling keys")? 174 | } 175 | }).collect::, _>>()?; 176 | Ok(DeclKV { 177 | node: entry.node().clone(), 178 | key: keys, 179 | value: entry.value().ok_or("parse error")?, 180 | }) 181 | }) 182 | .collect::, _>>().map(Some), 183 | _ => Ok(None) 184 | } 185 | } 186 | 187 | #[derive(Clone)] 188 | pub enum DeclValue { 189 | Node(DeclKV), 190 | PartialAttr { 191 | node: SyntaxNode, 192 | prefix: Vec, 193 | entries: Vec<(Vec, DeclKV)> 194 | }, 195 | } 196 | 197 | impl DeclValue { 198 | pub fn value(&self) -> &SyntaxNode { 199 | match self { 200 | DeclValue::Node(kv) => &kv.value, 201 | DeclValue::PartialAttr { node, .. } => &node, 202 | } 203 | } 204 | 205 | pub fn prefix(&self) -> &[String] { 206 | match self { 207 | DeclValue::Node(_) => &[], 208 | DeclValue::PartialAttr { prefix, .. } => &prefix, 209 | } 210 | } 211 | 212 | pub fn entries(self) -> Result, DeclKV)>>, Box> { 213 | match self { 214 | DeclValue::Node(n) => Ok(attrset_entries(n.value)? 215 | .map(|entries| entries.into_iter().map(|entry| (entry.key.clone(), entry)).collect())), 216 | DeclValue::PartialAttr { entries, .. } => Ok(Some(entries)), 217 | } 218 | } 219 | 220 | pub fn project(self, p: &str) -> Result, Box> { 221 | match self { 222 | DeclValue::Node(n) => { 223 | let n = go_right_value(n.value)?; 224 | decl_value(&[p.to_string()], n) 225 | }, 226 | DeclValue::PartialAttr { node, mut prefix, entries } => { 227 | let mut v: Vec<_> = entries.into_iter() 228 | .filter(|(attr_name, _)| if let Some(q) = attr_name.first() { p == q } else { false }) 229 | .map(|(mut attr_name, DeclKV { node, key, value })| { 230 | attr_name.remove(0); 231 | (attr_name, DeclKV { node, key, value }) 232 | }).collect(); 233 | if v.is_empty() { 234 | Ok(None) 235 | } else if v.len() == 1 && v.first().unwrap().0.len() == 0 { 236 | let (_, kv) = v.remove(0); 237 | Ok(Some(DeclValue::Node(kv))) 238 | } else { 239 | prefix.push(p.to_string()); 240 | Ok(Some(DeclValue::PartialAttr { node, prefix, entries: v })) 241 | } 242 | } 243 | } 244 | } 245 | } 246 | 247 | pub fn decl_value(path: &[String], n: SyntaxNode) -> Result, Box> { 248 | struct ValHolder { 249 | val: Option 250 | } 251 | 252 | impl ValHolder { 253 | fn merge(&mut self, v: DeclValue) -> Result<(), Box> { 254 | match (&mut self.val, v) { 255 | (None, v) => self.val = Some(v), 256 | (Some(DeclValue::PartialAttr { entries, .. }), 257 | DeclValue::PartialAttr { entries: mut entries2, .. }) => { 258 | entries.append(&mut entries2); 259 | }, 260 | _ => Err("defined multiple times")?, 261 | } 262 | Ok(()) 263 | } 264 | } 265 | 266 | let mut holder = ValHolder { val: None }; 267 | 268 | for kv in attrset_entries(n.clone())?.ok_or("couldn't reduce")? { 269 | // Check the paths match 270 | if !Iterator::zip(path.iter(), kv.key.iter()).all(|(p, q)| p == q) { 271 | continue 272 | } 273 | 274 | match Ord::cmp(&path.len(), &kv.key.len()) { 275 | std::cmp::Ordering::Less => { 276 | holder.merge(DeclValue::PartialAttr { 277 | node: n.clone(), 278 | prefix: path.into(), 279 | entries: vec![( 280 | kv.key[path.len()..].into(), 281 | kv 282 | )], 283 | })? 284 | }, 285 | std::cmp::Ordering::Equal => { 286 | holder.merge(DeclValue::Node(kv))? 287 | }, 288 | std::cmp::Ordering::Greater => { 289 | let x = go_right_value(kv.value)?; 290 | let x = decl_value(&path[kv.key.len()..], x)?; 291 | if let Some(x) = x { holder.merge(x)? } 292 | }, 293 | } 294 | } 295 | 296 | Ok(holder.val) 297 | } 298 | 299 | -------------------------------------------------------------------------------- /printInfo.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {}, lib ? pkgs.lib }: 2 | let 3 | mytests = import { system=builtins.currentSystem; inherit pkgs; callTest = lib.id; }; 4 | modules = import ; 5 | in rec { 6 | allTests = lib.attrNames mytests; 7 | testDeps = test: 8 | let 9 | mytest = mytests.${test}; 10 | findDeps = node: 11 | let f = k: v: 12 | let v' = node.config.systemd.services.${k}; 13 | in (v'.visible or true) && (v'.enable.visible or true) && (v.enable or false); 14 | in builtins.attrNames (lib.filterAttrs f node.config.systemd.services); 15 | discoverTests = f: 16 | let go = val: 17 | if !lib.isAttrs val then [ ] 18 | else if lib.hasAttr "test" val then f val 19 | else lib.concatMap lib.id (lib.mapAttrsToList (lib.const go) val); 20 | in x: lib.unique (go x); 21 | in discoverTests (mytest: lib.concatMap lib.id (lib.attrValues (lib.mapAttrs (lib.const findDeps) mytest.nodes))) mytest; 22 | allServices = builtins.attrNames 23 | (lib.evalModules { 24 | modules = modules ++ [( { ... }: { 25 | _module.check = false; 26 | nixpkgs.system = "x86_64-linux"; 27 | nixpkgs.config.allowBroken = true; 28 | } )]; 29 | }).options.services; 30 | printServiceConfig = name: 31 | (import { 32 | modules = [ ({ ... }: { 33 | #_module.check = false; 34 | services.${name}.enable = true; 35 | })]; 36 | }).config.systemd.services.${name}.serviceConfig; 37 | collectJobs = x: 38 | let go = acc: prefix: x: lib.foldr (name: acc: 39 | let 40 | path = "${prefix}${name}."; 41 | val = x."${name}"; 42 | in if lib.isDerivation val 43 | then [ { name = path; value = val; } ] ++ acc 44 | else if lib.isAttrs val 45 | then go acc path val 46 | else acc) acc (lib.attrNames x); 47 | in builtins.listToAttrs (go [] "." x); 48 | mkSystemdPassthru = collectedTests: 49 | let tests = builtins.fromJSON (builtins.readFile collectedTests); 50 | in lib.mapAttrs (_: value: builtins.listToAttrs (map (name: lib.nameValuePair name false) value.fields)) tests; 51 | mkOverrideOptions = defaultPassthru: service: override: 52 | defaultPassthru // { "${service}" = defaultPassthru."${service}" // override; }; 53 | mkHookedTests = nixpkgs: systemdPassthru: 54 | let allTests = import "${toString nixpkgs}/nixos/tests/all-tests.nix" { 55 | inherit systemdPassthru; 56 | system = builtins.currentSystem; 57 | pkgs = import (toString nixpkgs) { system = builtins.currentSystem; }; 58 | callTest = t: lib.hydraJob t.test; 59 | }; 60 | in allTests; 61 | mkOverridableTests = collectedTests: nixpkgs: excluded: 62 | let 63 | tests = builtins.fromJSON (builtins.readFile collectedTests); 64 | defaultPassthru = mkSystemdPassthru collectedTests; 65 | in lib.mapAttrs (name: info: override: 66 | let passthru = mkOverrideOptions defaultPassthru name override; 67 | in lib.mapAttrsToList (name: value: lib.nameValuePair name value) (collectJobs (builtins.listToAttrs (map ( 68 | test: lib.nameValuePair test (mkHookedTests nixpkgs passthru)."${test}" 69 | ) (lib.filter (name: lib.all (excl: excl != name) excluded) info.tests)))) 70 | ) tests; 71 | } 72 | -------------------------------------------------------------------------------- /run.oil: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env oil 2 | 3 | proc discover-systemd-services(nixpkgs, output) { 4 | m = $(nix-instantiate --eval -E "$nixpkgs/nixos/modules/module-list.nix") 5 | dir = $(nix-instantiate --eval -E "$nixpkgs/nixos/modules") 6 | modules = split($(cat $m | grep '^ *\./services' | awk -v prepend=$dir '{sub(/^ *\.\//,""); printf prepend; printf "/"; print}')) 7 | 8 | const n = len(modules) 9 | var i = 0 10 | var k = 0 11 | var success = 0 12 | var targets = [] 13 | for module in @modules { 14 | setvar i = i + 1 15 | write -n "$i/$n... " 16 | if ! nix-codemod list-systemd-services $module 2>/dev/null | json read serviceNames 2> /dev/null { 17 | write "✗" 18 | } else { 19 | write "✓" 20 | setvar success = success + 1 21 | setvar k = k + len(serviceNames) 22 | for service in @serviceNames { 23 | nix-codemod print-systemd-service-config $module $service | json read blank 24 | setvar targets = targets + [{ module: $module, service: $service, blank: blank }] 25 | } 26 | } 27 | } 28 | 29 | json write (targets) > $output 30 | 31 | write "$success ($(($success*100/$n))%) succeeded" 32 | write "$k services discovered" 33 | } 34 | 35 | proc test-deps(nixpkgs, output) { 36 | nix-instantiate --eval -E "(import ./printInfo.nix { pkgs = import $nixpkgs {}; }).allTests" --json | json read testNames 37 | const n = len(testNames) 38 | var i = 0 39 | var assoc = [] 40 | for test in @testNames { 41 | write -n "$test ($[i + 1]/$n)... " 42 | if ! nix-instantiate --eval -E "(import ./printInfo.nix { pkgs = import $nixpkgs {}; }).testDeps \"$test\"" --json 2> /dev/null | json read deps 2> /dev/null { 43 | write "Couldn't evaluate" 44 | } else { 45 | setvar assoc = assoc + [{ test: $test, deps: deps }] 46 | write "$[len(deps)] systemd services found" 47 | } 48 | setvar i = i + 1 49 | } 50 | 51 | json write (assoc) > $output 52 | 53 | exit 0 54 | } 55 | 56 | proc find-malformed-tests(nixpkgs, output) { 57 | nix-codemod find-all-tests $nixpkgs/nixos/tests/all-tests.nix | json read testFiles 58 | const paths = testFiles['paths'] 59 | const failures = testFiles['failures'] 60 | 61 | const args = '{ system = builtins.currentSystem; pkgs = import {}; systemdPassthru = {}; }' 62 | 63 | var failed = [] 64 | setvar failed = failures + failed 65 | 66 | var i = 0 67 | var f = 0 68 | while (i < len(paths)) { 69 | var test = paths[i][0] 70 | var path = "$nixpkgs/nixos/tests/$[paths[i][1]]" 71 | setvar i = i + 1 72 | if test -f $path { 73 | nix-codemod is-test-well-formed $path | read status 74 | if (status === "false") { 75 | # check if it accepts "systemdPassthru" 76 | if ! nix-instantiate --eval -E "(import $path) $args" > /dev/null 2> /dev/null { 77 | setvar f = f + 1 78 | write "***** $test ($path)" 79 | head -n 10 $path 80 | write "" 81 | 82 | setvar failed = [ $test ] + failed 83 | } 84 | } 85 | } 86 | } 87 | 88 | json write (failed) > $output 89 | 90 | write "$f/$[len(paths)] failed" 91 | } 92 | 93 | proc collect-tests(systemdServices, testDeps, output) { 94 | cat $systemdServices | json read services 95 | cat $testDeps | jq '[.[].test]' | json read testNames 96 | cat $output | json read deps 97 | 98 | var assoc = {} 99 | 100 | var i = 0 101 | while (i < len(services)) { 102 | var service = services[i] 103 | setvar i = i + 1 104 | write -n "$i/$[len(services)]... " 105 | var testedby = [] 106 | 107 | ... cat $testDeps 108 | | jq --arg service $[service["service"]] '[.[] | select(.deps[] == $service) | .test]' 109 | | json read testedby 110 | ; 111 | 112 | setvar assoc[service["service"]] = { module: service["module"], fields: service["blank"], tests: testedby } 113 | 114 | write -n "$[service['service']] ($[service['module']]) is tested by" 115 | 116 | for test in @testedby { 117 | write -n " $test" 118 | } 119 | 120 | write "" 121 | } 122 | 123 | json write (assoc) > $output 124 | } 125 | 126 | proc hook-modules(nixpkgs, collectedtests) { 127 | cat $collectedtests | json read tests 128 | cat $collectedtests | jq 'keys' | json read keys 129 | mktemp -d | read tmp 130 | trap "rm -rf -- $tmp" EXIT 131 | 132 | cp ./all-tests-hooked.nix $nixpkgs/nixos/tests/all-tests.nix 133 | cp ./make-test-python-hooked.nix $nixpkgs/nixos/tests/make-test-python.nix 134 | 135 | var i = 0 136 | while (i < len(keys)) { 137 | var service = keys[i] 138 | setvar i = i + 1 139 | var info = tests[service] 140 | var module = info["module"] 141 | 142 | json write (info['fields']) > $tmp/fields.json 143 | 144 | if ! nix-codemod insert-systemd-hooks $module $service $tmp/fields.json > $tmp/val { 145 | write "failed @ $module" 146 | } else { 147 | cat $tmp/val > $module 148 | } 149 | 150 | write -n $'\r' 151 | write -n "$i/$[len(keys)]" 152 | } 153 | } 154 | 155 | proc run-specific-tests(tests, nixpkgs, malformed, opts, service, dryRun) { 156 | # nix-instantiate --eval --strict -E "builtins.attrNames (((import ./printInfo.nix {}).mkOverridableTests ./output/tests ./output/nixpkgs).nginx { PrivateNetwork = true; })" --show-trace 157 | 158 | const excluded = "(builtins.fromJSON (builtins.readFile excluded))" 159 | const info = "((import ./printInfo.nix {}).mkOverridableTests $tests $nixpkgs $excluded).\"\${service}\"" 160 | const options = "(builtins.fromJSON (builtins.readFile options))" 161 | ... nix-instantiate --eval --json 162 | --arg service "\"$service\"" 163 | --arg options $opts 164 | --arg excluded $malformed 165 | -E "{ service, options, excluded }: builtins.length ($info $options)" 166 | --show-trace 167 | | json read n 168 | ; 169 | 170 | var extraOpts = [] 171 | if ($dryRun === "true") { 172 | setvar extraOpts = [ "--dry-run" ] 173 | } 174 | 175 | var i = 0 176 | while (i < n) { 177 | write "$i/$n" 178 | ... nix-build @extraOpts 179 | --arg service "\"$service\"" 180 | --arg options $opts 181 | --arg excluded $malformed 182 | --arg i $i 183 | -E "{ service, options, i, excluded }: (builtins.elemAt ($info $options) i).value" 184 | ; 185 | setvar i = i + 1; 186 | } 187 | } 188 | 189 | proc dry-run-all-tests(collectedtests, nixpkgs, malformed, opts) { 190 | cat $collectedtests | jq 'keys' | json read keys 191 | 192 | write "" > failed 193 | write 'writing every failure to "failed"' 194 | 195 | var i = 0 196 | while (i < len(keys)) { 197 | var service = keys[i] 198 | setvar i = i + 1 199 | write -n $'\r' 200 | write -n "$i/$[len(keys)]" 201 | if ! try run-specific-tests $collectedtests $nixpkgs $opts $malformed $service true 2> /dev/null { 202 | write -n $'\r' 203 | write -n " " 204 | write -n $'\r' 205 | write $service 206 | write $service >> failed 207 | } 208 | } 209 | } 210 | 211 | usage () { 212 | echo "Usage" 213 | write "$0 discover-systemd-services " 214 | write " For each NixOS service, find the systemd services it declares" 215 | write "$0 test-deps " 216 | write " Computes for each test the systemd services it depends on," 217 | write " and *writes* the result in " 218 | write "$0 find-malformed-tests " 219 | write " Parses , and check which tests can support" 220 | write " the 'systemdPassthru' hooks" 221 | write "$0 collect-tests " 222 | write " For each systemd service, computes all the tests that depend on it," 223 | write " and *writes* the result in " 224 | write "$0 hook-modules " 225 | write " Adds hooks to every module in the given nixpkgs source folder" 226 | write "$0 run-specific-test " 227 | write "$0 dry-run-all-tests " 228 | 229 | # nix-build -E "(((import ./printInfo.nix {}).mkOverridableTests ./output/tests ./output/nixpkgs).hydra-server { PrivateNetwork = false; }).hydra.hydra-unstable.test" 230 | 231 | exit 1 232 | } 233 | 234 | if (len(ARGV) === 0) { 235 | usage 236 | } 237 | 238 | case $[ARGV[0]] in 239 | "discover-systemd-services") 240 | if (len(ARGV) === 3) { 241 | discover-systemd-services $[ARGV[1]] $[ARGV[2]] 242 | } else { 243 | usage 244 | } 245 | ;; 246 | "test-deps") 247 | if (len(ARGV) === 3) { 248 | test-deps $[ARGV[1]] $[ARGV[2]] 249 | } else { 250 | usage 251 | } 252 | ;; 253 | "find-malformed-tests") 254 | if (len(ARGV) === 3) { 255 | find-malformed-tests $[ARGV[1]] $[ARGV[2]] 256 | } else { 257 | usage 258 | } 259 | ;; 260 | "collect-tests") 261 | if (len(ARGV) === 4) { 262 | collect-tests $[ARGV[1]] $[ARGV[2]] $[ARGV[3]] 263 | } else { 264 | usage 265 | } 266 | ;; 267 | "hook-modules") 268 | if (len(ARGV) === 3) { 269 | hook-modules $[ARGV[1]] $[ARGV[2]] 270 | } else { 271 | usage 272 | } 273 | ;; 274 | "dry-run-specific-tests") 275 | if (len(ARGV) === 6) { 276 | run-specific-tests $[ARGV[1]] $[ARGV[2]] $[ARGV[3]] $[ARGV[4]] $[ARGV[5]] "true" 277 | } else { 278 | usage 279 | } 280 | ;; 281 | "run-specific-tests") 282 | if (len(ARGV) === 6) { 283 | run-specific-tests $[ARGV[1]] $[ARGV[2]] $[ARGV[3]] $[ARGV[4]] $[ARGV[5]] "false" 284 | } else { 285 | usage 286 | } 287 | ;; 288 | "dry-run-all-tests") 289 | if (len(ARGV) === 5) { 290 | dry-run-all-tests $[ARGV[1]] $[ARGV[2]] $[ARGV[3]] $[ARGV[5]] 291 | } else { 292 | usage 293 | } 294 | ;; 295 | *) 296 | usage 297 | ;; 298 | esac 299 | 300 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import ./default.nix {}).shell 2 | --------------------------------------------------------------------------------