nix: update patches and wip stuffs
authorJulien Moutinho <julm@sourcephile.fr>
Thu, 31 Dec 2020 00:08:16 +0000 (01:08 +0100)
committerJulien Moutinho <julm@sourcephile.fr>
Thu, 31 Dec 2020 00:17:15 +0000 (01:17 +0100)
18 files changed:
flake.lock
flake.nix
gitolite
machines/losurdo.nix
machines/losurdo/fileSystems.nix
machines/losurdo/networking/openvpn/riseup.nix
machines/losurdo/networking/tor.nix
machines/losurdo/nginx/sourcephile.fr/losurdo.nix
machines/losurdo/transmission.nix
machines/losurdo/users.nix
nixos/modules.nix
nixos/modules/services/networking/netns.nix
nixos/modules/services/networking/openvpn.nix
nixos/modules/services/security/tor.nix
nixpkgs/overlays.nix
nixpkgs/patches.nix
nixpkgs/patches.sh
nixpkgs/patches/apparmor.diff

index 37c537c33d2255b2b69b609cf0f287722a18fcf4..22ff96cd2b5dfc170b22dd96b87c245ccf564f80 100644 (file)
@@ -2,11 +2,11 @@
   "nodes": {
     "flake-utils": {
       "locked": {
-        "lastModified": 1601282935,
-        "narHash": "sha256-WQAFV6sGGQxrRs3a+/Yj9xUYvhTpukQJIcMbIi7LCJ4=",
+        "lastModified": 1605370193,
+        "narHash": "sha256-YyMTf3URDL/otKdKgtoMChu4vfVL3vCMkRqpGifhUn0=",
         "owner": "numtide",
         "repo": "flake-utils",
-        "rev": "588973065fce51f4763287f0fda87a174d78bf48",
+        "rev": "5021eac20303a61fafe17224c087f5519baed54d",
         "type": "github"
       },
       "original": {
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1606359166,
-        "narHash": "sha256-YOX9LLDmiqMdKKiep8faf50j9vZDgrAmHhBprnDxfU4=",
+        "lastModified": 1608904631,
+        "narHash": "sha256-0rjcorKEXJ9dL2gRsKWVthv1MAdRgHTlyx2qtJ6LMlk=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "8585991bfb629edda1e42c191bef935d9d70d690",
+        "rev": "6b342809b1b66dce758364f763b64c6a1a9e6211",
         "type": "github"
       },
       "original": {
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "8585991bfb629edda1e42c191bef935d9d70d690",
+        "rev": "6b342809b1b66dce758364f763b64c6a1a9e6211",
         "type": "github"
       }
     },
     "pass": {
       "flake": false,
       "locked": {
-        "narHash": "sha256-F3SgXxJ7Z6TBeDTSvpkL8Cq9d3kCxCrCpKnj3BO7IA8=",
+        "narHash": "sha256-xkU8OQFkckhnFGxL4JypxZYD6DPQrP3UF3ttDRQcesk=",
         "path": "./pass",
         "type": "path"
       },
@@ -55,7 +55,7 @@
     "secrets": {
       "flake": false,
       "locked": {
-        "narHash": "sha256-WSd10VYivtR46NXRlsH7adY0iDThmbX9iPRRkXWWz4k=",
+        "narHash": "sha256-xE3TWiTCRfxZ+6uRYjemt3Q18X8xheFrygrheLbiYOQ=",
         "path": "./sec",
         "type": "path"
       },
index 3a8bc7100a3d58e1b2c938e90effd26c64aa09f4..9d21ce2171175b7d794b015cbf732280e44e8986 100644 (file)
--- a/flake.nix
+++ b/flake.nix
@@ -1,6 +1,6 @@
 {
 # Pin down nixpkgs from github, instead of using global, system or user registries.
-inputs.nixpkgs.url = "github:NixOS/nixpkgs/8585991bfb629edda1e42c191bef935d9d70d690";
+inputs.nixpkgs.url = "github:NixOS/nixpkgs/6b342809b1b66dce758364f763b64c6a1a9e6211";
 #inputs.nixpkgs.url = "flake:nixpkgs";
 inputs.flake-utils.url = "github:numtide/flake-utils";
 inputs.shell = { type = "path"; path = "./shell"; flake = false; };
@@ -14,6 +14,7 @@ outputs = inputs: let
     #nixpkgs/patches/zerobin.diff
     #nixpkgs/patches/gitolite.diff
     #nixpkgs/patches/tor.diff
+    #nixpkgs/patches/freeciv.diff
     #nixpkgs/patches/fix-ld-nix.diff
     #nixpkgs/patches/fix-ld-nix-apparmor.diff
   ];
@@ -119,25 +120,7 @@ outputs = inputs: let
       # Example: nix run .#losurdo.sendkeys
       "sendkeys" = {
         type = "app";
-        program = (pkgs.writeShellScript "sendkeys" ''
-          set -eux
-          set -o pipefail
-          ssh '${target}' \
-            gpg-connect-agent --no-autostart --homedir /var/lib/gnupg "'keyinfo --list'" /bye 2>&1 |
-          grep -qx -e "gpg-connect-agent: no gpg-agent running in this session" \
-                   -e "S KEYINFO ${keygrip} . . . 1 .*" || {
-            # Send the GnuPG root key
-            gpg --decrypt '${config.security.gnupg.store}/root/key.pass.gpg' |
-            gpg --batch --pinentry-mode loopback --passphrase-fd 0 --export-secret-subkeys @root@${machineName} |
-            ssh '${target}' \
-              gpg --no-autostart --homedir /var/lib/gnupg --no-autostart --batch --pinentry-mode loopback --import
-
-            # Send the GnuPG root key's passphrase
-            gpg --decrypt '${config.security.gnupg.store}/root/key.pass.gpg' |
-            ssh '${target}' \
-              gpg-preset-passphrase --homedir /var/lib/gnupg --preset ${keygrip}
-          }
-        '').outPath;
+        program = config.security.gnupg.agent.sendKeys + "/bin/gnupg-agent-sendKeys";
       };
     }) inputs.self.nixosConfigurations;}
   );
index c2d13541f7183c7adb74728a3a0a67d40a66ab37..118cbca1a3915213d9c08df8c2542f95a366e25c 160000 (submodule)
--- a/gitolite
+++ b/gitolite
@@ -1 +1 @@
-Subproject commit c2d13541f7183c7adb74728a3a0a67d40a66ab37
+Subproject commit 118cbca1a3915213d9c08df8c2542f95a366e25c
index 7482c1370e3da235aa4d4b5e12fe3d518375d2d9..1c5fb727aea62c1abac108238a6c2f70c8d778c5 100644 (file)
@@ -4,6 +4,7 @@
 { inputs, ... }:
 {
 system = "x86_64-linux";
+#config.allowUnfree = true;
 extraArgs = {
   wireguard = rec {
     wg-intra = {
@@ -21,6 +22,7 @@ extraArgs = {
 modules = [
   ../nixos/defaults.nix
   losurdo/acme.nix
+  #losurdo/apc.nix
   losurdo/debug.nix
   losurdo/fail2ban.nix
   losurdo/fileSystems.nix
index b9874a1741e43df96237627dd8b7061681459e5f..2f5aaeea48527a9bf97509be0419e5c818851d6f 100644 (file)
@@ -62,6 +62,7 @@ fileSystems."/mnt/key" =
 services.udev.packages = [ pkgs.android-udev-rules ];
 environment.systemPackages = [
   pkgs.go-mtpfs
+  pkgs.ntfs3g
 ];
 
 }
index 9b8dc153d53cc56f71b9c30cd2eb6348aebc6c71..b39aa7f69c40699a1378cc346fab72b9852adf2a 100644 (file)
@@ -1,18 +1,14 @@
 { pkgs, lib, config, ... }:
 let
   ns = "riseup";
-  dev = "ov-${ns}";
   inherit (config.services) openvpn;
+  inherit (config.security) gnupg;
 in
 {
 networking.nftables.ruleset = ''
   #add rule inet filter fw2net tcp dport {443,1194} counter accept comment "OpenVPN"
   add rule inet filter fw2net udp dport 1194 counter accept comment "OpenVPN"
 '';
-systemd.services."openvpn-${ns}" = {
-  bindsTo = [ "netns-${ns}.service" ];
-  requires = [ "netns-${ns}.service" ];
-};
 services.netns.namespaces.riseup = {
   nftables = lib.mkBefore ''
     table inet filter {
@@ -42,157 +38,45 @@ services.netns.namespaces.riseup = {
     }
   '';
 };
-services.openvpn.servers = {
-  "${ns}" = {
-    /*
-      cert ${riseup/client.pem}
-      key ${riseup/client.pem}
-      remote 37.218.241.7 1194 tcp4
-      remote 37.218.241.106 443 tcp4
-      remote 163.172.126.44 443 tcp4
-      remote 198.252.153.28 443 tcp4
-      remote 199.58.81.143 443 tcp4
-      remote 199.58.81.145 443 tcp4
-      remote 212.83.143.67 443 tcp4
-      remote 212.83.144.12 443 tcp4
-      remote 212.83.146.228 443 tcp4
-      remote 212.83.165.160 443 tcp4
-      remote 212.83.182.127 443 tcp4
-      remote 212.129.62.247 443 tcp4
-      ca ${riseup/cacert.pem}
-    */
-    config = ''
-      verb 3
-      ca ${riseup/RiseupCA.pem}
-      client
-      dev ov-${ns}
-      dev-type tun
-      persist-tun
-      nobind
-      # Useless to setup the interface
-      # because moving it to ${ns} will reset it
-      ifconfig-noexec
-      route-noexec
-      persist-key
-      auth-user-pass /root/riseup.auth
-      tls-client
-      remote-cert-tls server
-      remote 198.252.153.226 1194 udp
-      reneg-sec 0
-      script-security 2
-      up-restart
-    '';
-    up = let dev = "ov-${ns}"; in ''
-      set -eux
-      PATH=${lib.makeBinPath [pkgs.iproute]}
-      ip link set dev "${dev}" up netns "${ns}" mtu "$tun_mtu"
-      ip netns exec "${ns}" ${pkgs.writeShellScript "route-up.sh" ''
-        set -eux
-        PATH=${lib.makeBinPath [pkgs.iproute pkgs.coreutils]}
-
-        ip link set dev lo up
-
-        mkdir -p /etc/netns/"${ns}"
-        foreign_opt_domains=
-        process_foreign_option () {
-          case "$1:$2" in
-            dhcp-option:DNS) echo "nameserver $3" >>/etc/netns/"${ns}"/resolv.conf ;;
-            dhcp-option:DOMAIN) foreign_opt_domains="$foreign_opt_domains $3" ;;
-          esac
-        }
-        if test ! -e /etc/netns/"${ns}"/resolv.conf; then
-          # add DNS settings if given in foreign options
-          i=1
-          while
-            eval opt=\"\''${foreign_option_$i-}\"
-            [ -n "$opt" ]
-          do
-            process_foreign_option $opt
-            i=$(( i + 1 ))
-          done
-          for d in $foreign_opt_domains; do
-            printf '%s\n' "domain $1" "search $*" \
-              >>/etc/netns/"${ns}"/resolv.conf
-          done
-        fi
-
-        netmask4="''${ifconfig_netmask:-30}"
-        netbits6="''${ifconfig_ipv6_netbits:-112}"
-        if [ -n "''${ifconfig_local-}" ]; then
-          if [ -n "''${ifconfig_remote-}" ]; then
-            ip -4 addr replace \
-              local "$ifconfig_local" \
-              peer "$ifconfig_remote/$netmask4" \
-              ''${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"} \
-              dev "${dev}"
-          else
-            ip -4 addr replace \
-              local "$ifconfig_local/$netmask4" \
-              ''${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"} \
-              dev "${dev}"
-          fi
-        fi
-        if [ -n "''${ifconfig_ipv6_local-}" ]; then
-          if [ -n "''${ifconfig_ipv6_remote-}" ]; then
-            ip -6 addr replace \
-              local "$ifconfig_ipv6_local" \
-              peer "$ifconfig_ipv6_remote/$netbits6" \
-              dev "${dev}"
-          else
-            ip -6 addr replace \
-              local "$ifconfig_ipv6_local/$netbits6" \
-              dev "${dev}"
-          fi
-        fi
-      ''}
-    '';
-    routeUp = ''
-      set -eux
-      PATH=${lib.makeBinPath [pkgs.iproute]}
-      ${pkgs.coreutils}/bin/env
-      ip netns exec "${ns}" ${pkgs.writeShellScript "route-up.sh" ''
-        set -eux
-        PATH=${lib.makeBinPath [pkgs.iproute]}
-        i=1
-        while
-          eval net=\"\''${route_network_$i-}\"
-          eval mask=\"\''${route_netmask_$i-}\"
-          eval gw=\"\''${route_gateway_$i-}\"
-          eval mtr=\"\''${route_metric_$i-}\"
-          [ -n "$net" ]
-        do
-          ip -4 route replace "$net/$mask" via "$gw" ''${mtr:+metric "$mtr"}
-          i=$(( i + 1 ))
-        done
-
-        if [ -n "''${route_vpn_gateway-}" ]; then
-          ip -4 route replace default via "$route_vpn_gateway"
-        fi
-
-        i=1
-        while
-          # There doesn't seem to be $route_ipv6_metric_<n>
-          # according to the manpage.
-          eval net=\"\''${route_ipv6_network_$i-}\"
-          eval gw=\"\''${route_ipv6_gateway_$i-}\"
-          [ -n "$net" ]
-        do
-          ip -6 route replace  "$net"  via "$gw"  metric 100
-          i=$(( i + 1 ))
-        done
-
-        # There's no $route_vpn_gateway for IPv6. It's not
-        # documented if OpenVPN includes default route in
-        # $route_ipv6_*. Set default route to remote VPN
-        # endpoint address if there is one. Use higher metric
-        # than $route_ipv6_* routes to give preference to a
-        # possible default route in them.
-        if [ -n "''${ifconfig_ipv6_remote-}" ]; then
-          ip -6 route replace default \
-            via "$ifconfig_ipv6_remote" metric 200
-        fi
-      ''}
-    '';
+security.gnupg.secrets."openvpn/riseup/auth-user-pass" = {
+  systemdConfig.before = [ "openvpn-riseup.service" ];
+  systemdConfig.wantedBy = [ "openvpn-riseup.service" ];
+};
+services.openvpn.servers.${ns} = {
+  /*
+    cert ${riseup/client.pem}
+    key ${riseup/client.pem}
+    remote 37.218.241.7 1194 tcp4
+    remote 37.218.241.106 443 tcp4
+    remote 163.172.126.44 443 tcp4
+    remote 198.252.153.28 443 tcp4
+    remote 199.58.81.143 443 tcp4
+    remote 199.58.81.145 443 tcp4
+    remote 212.83.143.67 443 tcp4
+    remote 212.83.144.12 443 tcp4
+    remote 212.83.146.228 443 tcp4
+    remote 212.83.165.160 443 tcp4
+    remote 212.83.182.127 443 tcp4
+    remote 212.129.62.247 443 tcp4
+    ca ${riseup/cacert.pem}
+  */
+  netns = ns;
+  settings = {
+    verb = 3;
+    auth-user-pass = gnupg.secrets."openvpn/riseup/auth-user-pass".path;
+    ca = riseup/RiseupCA.pem;
+    client = true;
+    dev = "ov-${ns}";
+    dev-type = "tun";
+    persist-tun = true;
+    nobind = true;
+    persist-key = true;
+    tls-client = true;
+    remote-cert-tls = "server";
+    remote = "198.252.153.226 1194 udp";
+    reneg-sec = 0;
+    script-security = 2;
+    up-restart = true;
   };
 };
 }
index e7793e47916e3996c972d58a74c924b7808926f9..b77d51d7309181432af4105dd5ce53b2911b3c30 100644 (file)
@@ -18,10 +18,9 @@ networking.nftables.ruleset = ''
   add rule inet filter net2fw tcp dport {${lib.concatMapStringsSep "," toString ports}} counter accept comment "Tor"
 '';
 #security.gnupg.secrets."tor/auth/julm" = {};
-security.gnupg.secrets."tor/onion/${onion}/hs_ed25519_secret_key" = {};
-systemd.services.tor = {
-  after    = [ gnupg.secrets."tor/onion/${onion}/hs_ed25519_secret_key".service ];
-  requires = [ gnupg.secrets."tor/onion/${onion}/hs_ed25519_secret_key".service ];
+security.gnupg.secrets."tor/onion/${onion}/hs_ed25519_secret_key" = {
+  systemdConfig.before = [ "tor.service" ];
+  systemdConfig.requiredBy = [ "tor.service" ];
 };
 services.tor = {
   enable = true;
index 7b53befe3570cd5f8cd329d98a4d60db16223b55..ef20b66cbe027a2526580e85ee1981ba2cb42bf3 100644 (file)
@@ -26,8 +26,14 @@ services.tor = {
     ];
   };
 };
-security.gnupg.secrets."tor/onion/${onion}/hs_ed25519_secret_key" = {};
-security.gnupg.secrets."tor/auth/julm" = {};
+security.gnupg.secrets."tor/onion/${onion}/hs_ed25519_secret_key" = {
+  systemdConfig.before = [ "tor.service" ];
+  systemdConfig.wantedBy = [ "tor.service" ];
+};
+security.gnupg.secrets."tor/auth/julm" = {
+  systemdConfig.before = [ "tor.service" ];
+  systemdConfig.wantedBy = [ "tor.service" ];
+};
 services.nginx = {
   virtualHosts."${srv}.${domain}" = {
     serverAliases = [ domain "${onion}.onion" ];
index 01756771ba9146d94a2327eaf4e557409967812d..34b67445d7d3ffcfe968e889d3d0e76ce9b63bab 100644 (file)
@@ -17,15 +17,17 @@ services.netns.namespaces.${netns}.nftables = ''
 #users.groups.keys.members = [ transmission.user ];
 security.gnupg.secrets."transmission/settings.json" = {
   user = transmission.user;
+  systemdConfig.before = [ "transmission.service" ];
+  systemdConfig.wantedBy = [ "transmission.service" ];
 };
 systemd.services.transmission = {
   after = [
-    gnupg.secrets."transmission/settings.json".service
     "netns-${netns}.service"
+    "zfs.target"
   ];
   requires = [
-    gnupg.secrets."transmission/settings.json".service
     "netns-${netns}.service"
+    "zfs.target"
   ];
   serviceConfig.NetworkNamespacePath = "/var/run/netns/${netns}";
 };
index 3cf81d08d1ce61d9bed07c4402c3aa00d9800da7..bb1153956a83bc057e0c59c7613a83bdc75b252d 100644 (file)
@@ -20,6 +20,10 @@ users = {
         users."julm".openssh.authorizedKeys.keys;
       hashedPassword = "!";
     };
+    gnupg = {
+      openssh.authorizedKeys.keys =
+        users."root".openssh.authorizedKeys.keys;
+    };
   };
   groups = {
     adbusers.members = [
@@ -34,6 +38,9 @@ users = {
     wheel.members = [
       users."julm".name
     ];
+    gpg-agent.members = [
+      users."julm".name
+    ];
   };
 };
 
index 148b8eed4d138fbdb5962aafc7610f9f794181f4..f8331bd282f40c16da34c786b93a00465a743d06 100644 (file)
@@ -5,13 +5,14 @@
 imports = [
   #modules/services/databases/openldap.nix
   #modules/services/mail/public-inbox.nix
-  #modules/services/security/tor.nix
+  modules/services/security/tor.nix
   #modules/services/backup/syncoid.nix
   #modules/services/torrent/transmission.nix
   #modules/security/gnupg.nix
   #modules/services/networking/biboumi.nix
   #modules/services/networking/croc.nix
   modules/services/networking/netns.nix
+  #modules/security/gnupg.nix
   modules/services/networking/openvpn.nix
   #/home/julm/src/nix/nixpkgs/.git-worktree/transmission/nixos/modules/services/torrent/transmission.nix
   #/home/julm/src/nix/nixpkgs/nixos/modules/services/torrent/transmission.nix
@@ -22,8 +23,9 @@ imports = [
 disabledModules = [
   #"tasks/network-interfaces.nix"
   "services/mail/mlmmj.nix"
+  #"security/gnupg.nix"
   #"services/mail/public-inbox.nix"
-  #"services/security/tor.nix"
+  "services/security/tor.nix"
   #"services/backup/syncoid.nix"
   #"security/gnupg.nix"
   #"services/networking/biboumi.nix"
index 95a6ee4a4461d2671635a8d36a139178d4bce215..3769bfbb4fb441051cb9d58c0805690efe40ef12 100644 (file)
@@ -13,6 +13,7 @@ in
 options.services.netns = {
   namespaces = mkOption {
     description = "netns namespaces to create";
+    default = {};
     type = types.attrsOf (types.submodule {
       options = {
         nftables = mkOption {
@@ -36,7 +37,6 @@ options.services.netns = {
         };
       };
     });
-    default = {};
   };
 };
 config = {
index 84cac8af1cc8c846b5d5a8f894b65f67d7a6af30..07be18eb9eafc02e586eab3d86ad8fdf156f91cd 100644 (file)
@@ -8,60 +8,192 @@ let
 
   inherit (pkgs) openvpn;
 
+  PATH = name: makeBinPath (getAttr "openvpn-${name}" config.systemd.services).path;
+
   makeOpenVPNJob = cfg: name:
     let
 
-      path = makeBinPath (getAttr "openvpn-${name}" config.systemd.services).path;
-
-      upScript = ''
-        export PATH=${path}
-
-        # For convenience in client scripts, extract the remote domain
-        # name and name server.
-        for var in ''${!foreign_option_*}; do
-          x=(''${!var})
-          if [ "''${x[0]}" = dhcp-option ]; then
-            if [ "''${x[1]}" = DOMAIN ]; then domain="''${x[2]}"
-            elif [ "''${x[1]}" = DNS ]; then nameserver="''${x[2]}"
-            fi
-          fi
-        done
-
-        ${cfg.up}
-        ${optionalString cfg.updateResolvConf
-           "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
-      '';
-
-      downScript = ''
-        export PATH=${path}
-        ${optionalString cfg.updateResolvConf
-           "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
-        ${cfg.down}
-      '';
-
-      configFile = pkgs.writeText "openvpn-config-${name}"
-        ''
-          errors-to-stderr
-          ${optionalString (cfg.up != "" || cfg.down != "" || cfg.updateResolvConf) "script-security 2"}
-          ${cfg.config}
-          ${optionalString (cfg.up != "" || cfg.updateResolvConf)
-              "up ${pkgs.writeShellScript "openvpn-${name}-up" upScript}"}
-          ${optionalString (cfg.routeUp != "")
-              "route-up ${pkgs.writeShellScript "openvpn-${name}-up" cfg.routeUp}"}
-          ${optionalString (cfg.down != "" || cfg.updateResolvConf)
-              "down ${pkgs.writeShellScript "openvpn-${name}-down" downScript}"}
-          ${optionalString (cfg.authUserPass != null)
-              "auth-user-pass ${pkgs.writeText "openvpn-credentials-${name}" ''
-                ${cfg.authUserPass.username}
-                ${cfg.authUserPass.password}
-              ''}"}
-        '';
+      configFile = pkgs.writeText "openvpn-config-${name}" (
+        generators.toKeyValue {
+          mkKeyValue = key: value:
+            if hasAttr key scripts
+            then "${key} " + pkgs.writeShellScript "openvpn-${name}-${key}" (scripts.${key} value)
+            else if builtins.isBool value
+            then optionalString value key
+            else if builtins.isPath value
+            then "${key} ${toString value}"
+            else "${key} ${generators.mkValueStringDefault {} value}";
+          listsAsDuplicateKeys = true;
+        } cfg.settings
+      );
+
+      scripts = {
+        up = script:
+          let init = ''
+            export PATH=${PATH name}
+
+            # For convenience in client scripts, extract the remote domain
+            # name and name server.
+            for var in ''${!foreign_option_*}; do
+              x=(''${!var})
+              if [ "''${x[0]}" = dhcp-option ]; then
+                if [ "''${x[1]}" = DOMAIN ]; then domain="''${x[2]}"
+                elif [ "''${x[1]}" = DNS ]; then nameserver="''${x[2]}"
+                fi
+              fi
+            done
+
+            ${optionalString cfg.updateResolvConf
+               "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
+          ''; in
+          if cfg.netns == null
+          then ''
+            ${init}
+            ${script}
+          ''
+          else ''
+            export PATH=${PATH name}
+            set -eux
+            ip link set dev '${cfg.settings.dev}' up netns '${cfg.netns}' mtu "$tun_mtu"
+            ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-up-netns.sh" ''
+              ${init}
+              set -eux
+              export PATH=${PATH name}
+
+              ip link set dev lo up
+
+              mkdir -p /etc/netns/'${cfg.netns}'
+              foreign_opt_domains=
+              process_foreign_option () {
+                case "$1:$2" in
+                  dhcp-option:DNS) echo "nameserver $3" >>/etc/netns/'${cfg.netns}'/resolv.conf ;;
+                  dhcp-option:DOMAIN) foreign_opt_domains="$foreign_opt_domains $3" ;;
+                esac
+              }
+              if test ! -e /etc/netns/'${cfg.netns}'/resolv.conf; then
+                # add DNS settings if given in foreign options
+                i=1
+                while
+                  eval opt=\"\''${foreign_option_$i-}\"
+                  [ -n "$opt" ]
+                do
+                  process_foreign_option $opt
+                  i=$(( i + 1 ))
+                done
+                for d in $foreign_opt_domains; do
+                  printf '%s\n' "domain $1" "search $*" \
+                    >>/etc/netns/'${cfg.netns}'/resolv.conf
+                done
+              fi
+
+              netmask4="''${ifconfig_netmask:-30}"
+              netbits6="''${ifconfig_ipv6_netbits:-112}"
+              if [ -n "''${ifconfig_local-}" ]; then
+                if [ -n "''${ifconfig_remote-}" ]; then
+                  ip -4 addr replace \
+                    local "$ifconfig_local" \
+                    peer "$ifconfig_remote/$netmask4" \
+                    ''${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"} \
+                    dev '${cfg.settings.dev}'
+                else
+                  ip -4 addr replace \
+                    local "$ifconfig_local/$netmask4" \
+                    ''${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"} \
+                    dev '${cfg.settings.dev}'
+                fi
+              fi
+              if [ -n "''${ifconfig_ipv6_local-}" ]; then
+                if [ -n "''${ifconfig_ipv6_remote-}" ]; then
+                  ip -6 addr replace \
+                    local "$ifconfig_ipv6_local" \
+                    peer "$ifconfig_ipv6_remote/$netbits6" \
+                    dev '${cfg.settings.dev}'
+                else
+                  ip -6 addr replace \
+                    local "$ifconfig_ipv6_local/$netbits6" \
+                    dev '${cfg.settings.dev}'
+                fi
+              fi
+              set +eux
+              ${script}
+            ''}
+          '';
+        route-up = script:
+          if cfg.netns == null
+          then script
+          else ''
+            export PATH=${PATH name}
+            set -eux
+            ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-route-up-netns" ''
+              export PATH=${PATH name}
+              set -eux
+              i=1
+              while
+                eval net=\"\''${route_network_$i-}\"
+                eval mask=\"\''${route_netmask_$i-}\"
+                eval gw=\"\''${route_gateway_$i-}\"
+                eval mtr=\"\''${route_metric_$i-}\"
+                [ -n "$net" ]
+              do
+                ip -4 route replace "$net/$mask" via "$gw" ''${mtr:+metric "$mtr"}
+                i=$(( i + 1 ))
+              done
+
+              if [ -n "''${route_vpn_gateway-}" ]; then
+                ip -4 route replace default via "$route_vpn_gateway"
+              fi
+
+              i=1
+              while
+                # There doesn't seem to be $route_ipv6_metric_<n>
+                # according to the manpage.
+                eval net=\"\''${route_ipv6_network_$i-}\"
+                eval gw=\"\''${route_ipv6_gateway_$i-}\"
+                [ -n "$net" ]
+              do
+                ip -6 route replace  "$net"  via "$gw"  metric 100
+                i=$(( i + 1 ))
+              done
+
+              # There's no $route_vpn_gateway for IPv6. It's not
+              # documented if OpenVPN includes default route in
+              # $route_ipv6_*. Set default route to remote VPN
+              # endpoint address if there is one. Use higher metric
+              # than $route_ipv6_* routes to give preference to a
+              # possible default route in them.
+              if [ -n "''${ifconfig_ipv6_remote-}" ]; then
+                ip -6 route replace default \
+                  via "$ifconfig_ipv6_remote" metric 200
+              fi
+              ${script}
+            ''}
+          '';
+        down = script:
+          let init = ''
+            export PATH=${PATH name}
+            ${optionalString cfg.updateResolvConf
+               "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
+          ''; in
+          if cfg.netns == null
+          then ''
+            ${init}
+            ${script}
+          ''
+          else ''
+            ip netns exec '${cfg.netns}' ${pkgs.writeShellScript "openvpn-${name}-down-netns.sh" ''
+              ${init}
+              ${script}
+            ''}
+          '';
+      };
 
     in {
       description = "OpenVPN instance ‘${name}’";
 
       wantedBy = optional cfg.autoStart "multi-user.target";
       after = [ "network.target" ];
+      bindsTo = optional (cfg.netns != null) "netns-${cfg.netns}.service";
+      requires = optional (cfg.netns != null) "netns-${cfg.netns}.service";
 
       path = [ pkgs.iptables pkgs.iproute pkgs.nettools ];
 
@@ -69,7 +201,6 @@ let
       serviceConfig.Restart = "always";
       serviceConfig.Type = "notify";
     };
-
 in
 
 {
@@ -87,30 +218,30 @@ in
       example = literalExample ''
         {
           server = {
-            config = '''
+            settings = {
               # Simplest server configuration: https://community.openvpn.net/openvpn/wiki/StaticKeyMiniHowto
               # server :
-              dev tun
-              ifconfig 10.8.0.1 10.8.0.2
-              secret /root/static.key
-            ''';
-            up = "ip route add ...";
-            down = "ip route del ...";
+              dev = "tun";
+              ifconfig = "10.8.0.1 10.8.0.2";
+              secret = "/root/static.key";
+              up = "ip route add ...";
+              down = "ip route del ...";
+            };
           };
 
           client = {
-            config = '''
-              client
-              remote vpn.example.org
-              dev tun
-              proto tcp-client
-              port 8080
-              ca /root/.vpn/ca.crt
-              cert /root/.vpn/alice.crt
-              key /root/.vpn/alice.key
-            ''';
-            up = "echo nameserver $nameserver | ''${pkgs.openresolv}/sbin/resolvconf -m 0 -a $dev";
-            down = "''${pkgs.openresolv}/sbin/resolvconf -d $dev";
+            settings = {
+              client = true;
+              remote = "vpn.example.org";
+              dev = "tun";
+              proto = "tcp-client";
+              port = 8080;
+              ca = "/root/.vpn/ca.crt";
+              cert = "/root/.vpn/alice.crt";
+              key = "/root/.vpn/alice.key";
+              up = "echo nameserver $nameserver | ''${pkgs.openresolv}/sbin/resolvconf -m 0 -a $dev";
+              down = "''${pkgs.openresolv}/sbin/resolvconf -d $dev";
+            };
           };
         }
       '';
@@ -124,51 +255,67 @@ in
         attribute name.
       '';
 
-      type = with types; attrsOf (submodule {
+      type = with types; attrsOf (submodule ({name, config, ...}: {
 
         options = {
-
-          config = mkOption {
-            type = types.lines;
+          settings = mkOption {
             description = ''
               Configuration of this OpenVPN instance.  See
               <citerefentry><refentrytitle>openvpn</refentrytitle><manvolnum>8</manvolnum></citerefentry>
               for details.
 
               To import an external config file, use the following definition:
-              <literal>config = "config /path/to/config.ovpn"</literal>
-            '';
-          };
-
-          up = mkOption {
-            default = "";
-            type = types.lines;
-            description = ''
-              Shell commands executed when the instance is starting.
-            '';
-          };
-
-          routeUp = mkOption {
-            default = "";
-            type = types.lines;
-            description = ''
-            '';
-          };
-
-          down = mkOption {
-            default = "";
-            type = types.lines;
-            description = ''
-              Shell commands executed when the instance is shutting down.
-            '';
-          };
-
-          nftables = mkOption {
-            default = "";
-            type = types.lines;
-            description = ''
-              Nftables rules
+              <literal>config = /path/to/config.ovpn;</literal>
             '';
+            default = {};
+            type = types.submodule {
+              freeformType = with types; attrsOf (nullOr (oneOf [path str bool int]));
+              options.dev = mkOption {
+                default = null;
+                type = types.str;
+                description = ''
+                  Shell commands executed when the instance is starting.
+                '';
+              };
+              options.down = mkOption {
+                default = "";
+                type = types.lines;
+                description = ''
+                  Shell commands executed when the instance is shutting down.
+                '';
+              };
+              options.errors-to-stderr = mkOption {
+                default = true;
+                type = types.bool;
+                description = ''
+                  Output errors to stderr instead of stdout
+                  unless log output is redirected by one of the <code>--log</code> options.
+                '';
+              };
+              options.route-up = mkOption {
+                default = "";
+                type = types.lines;
+                description = ''
+                  Run command after routes are added.
+                '';
+              };
+              options.up = mkOption {
+                default = "";
+                type = types.lines;
+                description = ''
+                  Shell commands executed when the instance is starting.
+                '';
+              };
+              options.script-security = mkOption {
+                default = 1;
+                type = types.enum [1 2 3];
+                description = ''
+                  1 — (Default) Only call built-in executables such as ifconfig, ip, route, or netsh.
+                  2 — Allow calling of built-in executables and user-defined scripts.
+                  3 — Allow passwords to be passed to scripts via environmental variables (potentially unsafe).
+                '';
+              };
+            };
           };
 
           autoStart = mkOption {
@@ -210,9 +357,34 @@ in
               };
             });
           };
+
+          netns = mkOption {
+            default = true;
+            type = with types; nullOr str;
+            description = "Network namespace.";
+          };
         };
 
-      });
+        config.settings = mkMerge
+          [ (mkIf (config.netns != null) {
+              # Useless to setup the interface
+              # because moving it to the netns will reset it
+              ifconfig-noexec = true;
+              route-noexec = true;
+              script-security = 2;
+            })
+            (mkIf (config.authUserPass != null) {
+              auth-user-pass = pkgs.writeText "openvpn-auth-user-pass-${name}" ''
+                ${config.authUserPass.username}
+                ${config.authUserPass.password}
+              '';
+            })
+            (mkIf config.updateResolvConf {
+              script-security = 2;
+            })
+          ];
+
+      }));
 
     };
 
index 175707d64e78c3d14e5cafb7e261590bf2c59f70..2f89d6bc1dfe8b5630521ff815d9dca55746a02a 100644 (file)
@@ -98,7 +98,7 @@ let
     "IsolateSOCKSAuth"
     "KeepAliveIsolateSOCKSAuth"
   ];
-  optionSOCKSPort = let
+  optionSOCKSPort = doConfig: let
     flags = [
       "CacheDNS" "CacheIPv4DNS" "CacheIPv6DNS" "GroupWritable" "IPv6Traffic"
       "NoDNSRequest" "NoIPv4Traffic" "NoOnionTraffic" "OnionTrafficOnly"
@@ -114,7 +114,7 @@ let
           flags = optionFlags;
           SessionGroup = mkOption { type = nullOr int; default = null; };
         } // genAttrs flags (name: mkOption { type = types.bool; default = false; });
-        config = {
+        config = mkIf doConfig { # Only add flags in SOCKSPort to avoid duplicates
           flags = filter (name: config.${name} == true) flags ++
                   optional (config.SessionGroup != null) "SessionGroup=${toString config.SessionGroup}";
         };
@@ -194,6 +194,17 @@ let
 in
 {
   imports = [
+    (mkRenamedOptionModule [ "services" "tor" "client" "dns" "automapHostsSuffixes" ] [ "services" "tor" "settings" "AutomapHostsSuffixes" ])
+    (mkRemovedOptionModule [ "services" "tor" "client" "dns" "isolationOptions" ] "Use services.tor.settings.DNSPort instead.")
+    (mkRemovedOptionModule [ "services" "tor" "client" "dns" "listenAddress" ] "Use services.tor.settings.DNSPort instead.")
+    (mkRemovedOptionModule [ "services" "tor" "client" "privoxy" "enable" ] "Use services.privoxy.enable and services.privoxy.enableTor instead.")
+    (mkRemovedOptionModule [ "services" "tor" "client" "socksIsolationOptions" ] "Use services.tor.settings.SOCKSPort")
+    (mkRenamedOptionModule [ "services" "tor" "client" "socksPolicy" ] [ "services" "tor" "settings" "SocksPolicy" ])
+    (mkRemovedOptionModule [ "services" "tor" "client" "transparentProxy" "isolationOptions" ] "Use services.tor.settings.TransPort instead.")
+    (mkRemovedOptionModule [ "services" "tor" "client" "transparentProxy" "listenAddress" ] "Use services.tor.settings.TransPort instead.")
+    (mkRenamedOptionModule [ "services" "tor" "controlPort" ] [ "services" "tor" "settings" "ControlPort" ])
+    (mkRemovedOptionModule [ "services" "tor" "extraConfig" ] "Plese use services.tor.settings instead.")
+    (mkRenamedOptionModule [ "services" "tor" "hiddenServices" ] [ "services" "tor" "relay" "onionServices" ])
     (mkRenamedOptionModule [ "services" "tor" "relay" "accountingMax" ] [ "services" "tor" "settings" "AccountingMax" ])
     (mkRenamedOptionModule [ "services" "tor" "relay" "accountingStart" ] [ "services" "tor" "settings" "AccountingStart" ])
     (mkRenamedOptionModule [ "services" "tor" "relay" "address" ] [ "services" "tor" "settings" "Address" ])
@@ -207,18 +218,6 @@ in
     (mkRenamedOptionModule [ "services" "tor" "relay" "nickname" ] [ "services" "tor" "settings" "Nickname" ])
     (mkRenamedOptionModule [ "services" "tor" "relay" "port" ] [ "services" "tor" "settings" "ORPort" ])
     (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "settings" "ORPort" ])
-    (mkRemovedOptionModule [ "services" "tor" "extraConfig" ] "Plese use services.tor.settings instead.")
-    (mkRenamedOptionModule [ "services" "tor" "controlPort" ] [ "services" "tor" "settings" "ControlPort" ])
-    (mkRenamedOptionModule [ "services" "tor" "client" "socksPolicy" ] [ "services" "tor" "settings" "SocksPolicy" ])
-    (mkRemovedOptionModule [ "services" "tor" "client" "socksIsolationOptions" ] "Use services.tor.settings.SOCKSPort")
-    (mkRemovedOptionModule [ "services" "tor" "client" "listenAddress" ] "Use services.tor.settings.SOCKSPort instead.")
-    (mkRemovedOptionModule [ "services" "tor" "client" "listenAddressFaster" ] "Use services.tor.settings.SOCKSPort instead.")
-    (mkRemovedOptionModule [ "services" "tor" "client" "transparentProxy" "listenAddress" ] "Use services.tor.settings.TransPort instead.")
-    (mkRemovedOptionModule [ "services" "tor" "client" "transparentProxy" "isolationOptions" ] "Use services.tor.settings.TransPort instead.")
-    (mkRemovedOptionModule [ "services" "tor" "client" "dns" "listenAddress" ] "Use services.tor.settings.DNSPort instead.")
-    (mkRemovedOptionModule [ "services" "tor" "client" "dns" "isolationOptions" ] "Use services.tor.settings.DNSPort instead.")
-    (mkRenamedOptionModule [ "services" "tor" "client" "dns" "automapHostsSuffixes" ] [ "services" "tor" "settings" "AutomapHostsSuffixes" ])
-    (mkRenamedOptionModule [ "services" "tor" "hiddenServices" ] [ "services" "tor" "relay" "onionServices" ])
   ];
 
   options = {
@@ -251,17 +250,15 @@ in
         transparentProxy.enable = mkEnableOption "transparent proxy";
         dns.enable = mkEnableOption "DNS resolver";
 
-        privoxy.enable = lib.mkEnableOption ''Privoxy system,
-          to use Tor's faster port, suitable for HTTP.
-
-          To have anonymity, protocols need to be scrubbed of identifying
-          information, and this can be accomplished for HTTP by Privoxy.
-
-          Privoxy can also be useful for KDE torification. In this setup
-          SOCKS proxy is set to the default Tor port (9050), providing maximum
-          circuit isolation where possible; and HTTP proxy to Privoxy
-          to route HTTP traffic is set over a faster, but less isolated port (9063).
-        '';
+        socksListenAddress = mkOption {
+          type = optionSOCKSPort false;
+          default = {addr = "127.0.0.1"; port = 9050; IsolateDestAddr = true;};
+          example = {addr = "192.168.0.1"; port = 9090; IsolateDestAddr = true;};
+          description = ''
+            Bind to this address to listen for connections from
+            Socks-speaking applications.
+          '';
+        };
 
         onionServices = mkOption {
           description = descriptionGeneric "HiddenServiceDir";
@@ -276,7 +273,7 @@ in
               description = ''
                 Clients' authorizations for a v3 hidden service,
                 as a list of files containing each one private key, in the format:
-                <screen>descriptor:x25519:<base32-private-key></screen>
+                <screen>descriptor:x25519:&lt;base32-private-key&gt;</screen>
               '' + descriptionGeneric "_client_authorization";
               type = with types; listOf path;
               default = [];
@@ -492,7 +489,7 @@ in
               description = ''
                 Authorized clients for a v3 hidden service,
                 as a list of public key, in the format:
-                <screen>descriptor:x25519:<base32-public-key></screen>
+                <screen>descriptor:x25519:&lt;base32-public-key&gt;</screen>
               '' + descriptionGeneric "_client_authorization";
               type = with types; listOf str;
               default = [];
@@ -527,7 +524,7 @@ in
             options.settings = mkOption {
               description = ''
                 Settings of the onion service.
-              '' ++ descriptionGeneric "_hidden_service_options";
+              '' + descriptionGeneric "_hidden_service_options";
               default = {};
               type = types.submodule {
                 freeformType = with types;
@@ -797,7 +794,7 @@ in
             description = descriptionGeneric "SOCKSPort";
             default = if cfg.settings.HiddenServiceNonAnonymousMode == true then [{port = 0;}] else [];
             example = [{port = 9090;}];
-            type = types.listOf optionSOCKSPort;
+            type = types.listOf (optionSOCKSPort true);
           };
           options.TestingTorNetwork = optionBool "TestingTorNetwork";
           options.TransPort = optionIsolablePorts "TransPort";
@@ -897,10 +894,7 @@ in
         PublishServerDescriptor = mkForce false;
       })
       (mkIf cfg.client.enable (
-        { SOCKSPort = [
-            { addr = "127.0.0.1"; port = 9050; IsolateDestAddr = true; }
-            { addr = "127.0.0.1"; port = 9063; }
-          ];
+        { SOCKSPort = [ cfg.client.socksListenAddress ];
         } // optionalAttrs cfg.client.transparentProxy.enable {
           TransPort = [{ addr = "127.0.0.1"; port = 9040; }];
         } // optionalAttrs cfg.client.dns.enable {
@@ -1037,17 +1031,6 @@ in
     };
 
     environment.systemPackages = [ cfg.package ];
-
-    services.privoxy = mkIf (cfg.client.enable && cfg.client.privoxy.enable) {
-      enable = true;
-      extraConfig = ''
-        forward-socks5t / ${mkValueString "" { addr = "127.0.0.1"; port = 9063; }} .
-        toggle 1
-        enable-remote-toggle 0
-        enable-edit-actions 0
-        enable-remote-http-toggle 0
-      '';
-    };
   };
 
   meta.maintainers = with lib.maintainers; [ julm ];
index 470c161348586518ddb4051982ad95d5ea7b0bea..27e8e935e8d90b36a31bc652601bc31c1b2a44a7 100644 (file)
@@ -7,4 +7,6 @@ map import
   overlays/swaplist.nix
   overlays/notmuch.nix
   #overlays/zerobin.nix
+] ++ [
+  #(self: super: { biboumi = super.callPackage pkgs/biboumi {}; })
 ]
index 7a2f37cfe551ff027dd312be954f1888f17dbee9..3ba262c487be57170636dc92d1aff9e2a063ed11 100644 (file)
@@ -28,7 +28,7 @@
 {
   meta.description = "nixos/security.gnupg: provisioning GnuPG-protected secrets through the Nix store";
   url = "https://github.com/NixOS/nixpkgs/pull/93659.diff";
-  sha256 = "sha256-IQWwpsHaOFAbUg+sOu7OF4iOX+EozKp4ln7QTxf7Y2Q=";
+  sha256 = "sha256-eKriMbvToXkfnuApS4CEAzYGmHudBujMy9CP8C5MMzI=";
 }
 {
   meta.description = "nixos/croc: init";
   sha256 = "sha256-lRKe8WnGPc8ojaD9W8ZS+NVMhIoGOCOj9njhqQhzaCM=";
 }
 */
+/*
 {
   meta.description = "nixos/tor: improve type-checking and hardening";
   url = "https://github.com/NixOS/nixpkgs/pull/97740.diff";
   sha256 = "sha256-RBSVsEm/EkuSDy/01jAmDZhlq6pTvUGd3yPDPW1bXJM=";
 }
+*/
 {
   meta.description = "nixos/syncoid: split in multiple systemd services and harden them";
   url = "https://github.com/NixOS/nixpkgs/pull/98455.diff";
   url = "https://github.com/NixOS/nixpkgs/pull/98734.diff";
   sha256 = "sha256-mEFvzR0ymwR7V3bJBnr9fR3GxA1jDfXZXQzo+jc5Nxc=";
 }
+/*
 {
   meta.description = "apparmor: try again to fix and improve";
   url = "https://github.com/NixOS/nixpkgs/pull/101071.diff";
-  sha256 = "sha256-HCoCyCHXoSFo2XxPPqMcXLzj3BayrkhDzOZXixYg420=";
+  sha256 = "sha256-UrZVDTS15VaQ16xl0jhTypyDED6yx72hYHqFc99oPj0";
 }
+*/
 {
   meta.description = "Update public-inbox to 1.6.0 and add systemd services";
   url = "https://github.com/NixOS/nixpkgs/pull/104457.diff";
 {
   meta.description = "Add freeciv service";
   url = "https://github.com/NixOS/nixpkgs/pull/104460.diff";
-  sha256 = "sha256-O5WYGtGcDbGb/OXccwy8IWeqigA0E4abP4/plOhUFUw=";
+  sha256 = "sha256-BB+f5+eXo3rDsK2xH9QBU8u1/XGRBXeNZKaulIwZ20o=";
+}
+{
+  meta.description = "biboumi: 8.5 -> 9.0";
+  url = "https://github.com/NixOS/nixpkgs/pull/106765.diff";
+  sha256 = "sha256-U5vrgyVIa6Ro3tQamNfgaq9RYRMCOPry1Mc9GKTC6Gw=";
 }
 ]
index 91f9d2b40a89491236a047583f7a68fd3221a4a4..f2237fc38043023ace05426df18ccd3fdfced0c1 100755 (executable)
@@ -18,6 +18,7 @@ for pr in \
   101071 \
   104457 \
   104460 \
+  106765 \
   ; do
   PR=${pr%_}
   test $PR = $pr || echo '/*'
index cc0f039eec5b1b532e327e02638eec346511f168..ab4dacc51764e0c43912522cd468e71afde93bb6 100644 (file)
-diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
-index e9b5834dab4..779924a65a8 100644
---- a/nixos/modules/services/torrent/transmission.nix
-+++ b/nixos/modules/services/torrent/transmission.nix
-@@ -7,15 +7,20 @@ let
-   inherit (config.environment) etc;
-   apparmor = config.security.apparmor;
-   rootDir = "/run/transmission";
--  homeDir = "/var/lib/transmission";
-   settingsDir = ".config/transmission-daemon";
-   downloadsDir = "Downloads";
-   incompleteDir = ".incomplete";
-   watchDir = "watchdir";
--  # TODO: switch to configGen.json once RFC0042 is implemented
--  settingsFile = pkgs.writeText "settings.json" (builtins.toJSON cfg.settings);
-+  settingsFormat = pkgs.formats.json {};
-+  settingsFile = settingsFormat.generate "settings.json" cfg.settings;
+diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix
+index 0fa804c1f9c..dc12a12b139 100644
+--- a/maintainers/maintainer-list.nix
++++ b/maintainers/maintainer-list.nix
+@@ -4455,7 +4455,7 @@
+     name = "Julien Dehos";
+   };
+   julm = {
+-    email = "julm+nix@sourcephile.fr";
++    email = "julm+nixpkgs@sourcephile.fr";
+     github = "ju1m";
+     githubId = 21160136;
+     name = "Julien Moutinho";
+diff --git a/nixos/doc/manual/release-notes/rl-2009.xml b/nixos/doc/manual/release-notes/rl-2009.xml
+index 3da8080958e..55f913cb3d4 100644
+--- a/nixos/doc/manual/release-notes/rl-2009.xml
++++ b/nixos/doc/manual/release-notes/rl-2009.xml
+@@ -1495,6 +1495,24 @@ services.transmission.settings.rpc-bind-address = "0.0.0.0";
+      to get the previous behavior of listening on all network interfaces.
+     </para>
+    </listitem>
++   <listitem>
++    <para>
++     The <literal>security.apparmor</literal> module,
++     for the <link xlink:href="https://gitlab.com/apparmor/apparmor/-/wikis/Documentation">AppArmor</link>
++     Mandatory Access Control system,
++     has been substantialy improved along with related tools,
++     so that module maintainers can now more easily write AppArmor profiles for NixOS.
++     The most notable change on the user-side is the new option <xref linkend="opt-security.apparmor.policies"/>,
++     replacing the previous <literal>profiles</literal> option
++     to provide a way to disable a profile
++     and to select whether to confine in enforce mode (default)
++     or in complain mode (see <literal>journalctl -b --grep apparmor</literal>).
++     Before enabling this module, either directly
++     or by importing <literal>&lt;nixpkgs/nixos/modules/profiles/hardened.nix&gt;</literal>,
++     please be sure to read the documentation of <link linkend="opt-security.apparmor.enable">security.apparmor.enable</link>,
++     and especially the part about <xref linkend="opt-security.apparmor.killUnconfinedConfinables"/>.
++    </para>
++   </listitem>
+    <listitem>
+     <para>
+      With this release <literal>systemd-networkd</literal> (when enabled through <xref linkend="opt-networking.useNetworkd"/>)
+diff --git a/nixos/modules/config/fonts/fontconfig.nix b/nixos/modules/config/fonts/fontconfig.nix
+index 5b681ca5946..97607134bb1 100644
+--- a/nixos/modules/config/fonts/fontconfig.nix
++++ b/nixos/modules/config/fonts/fontconfig.nix
+@@ -448,6 +448,40 @@ in
+     (mkIf cfg.enable {
+       environment.systemPackages    = [ pkgs.fontconfig ];
+       environment.etc.fonts.source  = "${fontconfigEtc}/etc/fonts/";
++      security.apparmor.includes."abstractions/fonts" = ''
++        # fonts.conf
++        r ${pkg.out}/etc/fonts/fonts.conf,
++
++        # fontconfig default config files
++        r ${pkg.out}/etc/fonts/conf.d/*.conf,
++
++        # 00-nixos-cache.conf
++        r ${cacheConf},
++
++        # 10-nixos-rendering.conf
++        r ${renderConf},
++
++        # 50-user.conf
++        ${optionalString cfg.includeUserConf ''
++        r ${pkg.out}/etc/fonts/conf.d.bak/50-user.conf,
++        ''}
++
++        # local.conf (indirect priority 51)
++        ${optionalString (cfg.localConf != "") ''
++        r ${localConf},
++        ''}
++
++        # 52-nixos-default-fonts.conf
++        r ${defaultFontsConf},
++
++        # 53-no-bitmaps.conf
++        r ${rejectBitmaps},
++
++        ${optionalString (!cfg.allowType1) ''
++        # 53-nixos-reject-type1.conf
++        r ${rejectType1},
++        ''}
++      '';
+     })
+     (mkIf cfg.enable {
+       fonts.fontconfig.confPackages = [ confPkg ];
+diff --git a/nixos/modules/config/malloc.nix b/nixos/modules/config/malloc.nix
+index a3eb55d8a42..fc35993b5a8 100644
+--- a/nixos/modules/config/malloc.nix
++++ b/nixos/modules/config/malloc.nix
+@@ -87,5 +87,12 @@ in
+     environment.etc."ld-nix.so.preload".text = ''
+       ${providerLibPath}
+     '';
++    security.apparmor.includes = {
++      "abstractions/base" = ''
++        r /etc/ld-nix.so.preload,
++        r ${config.environment.etc."ld-nix.so.preload".source},
++        mr ${providerLibPath},
++      '';
++    };
+   };
+ }
+diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
+index 3b67a857493..73af364f40a 100644
+--- a/nixos/modules/module-list.nix
++++ b/nixos/modules/module-list.nix
+@@ -190,7 +190,6 @@
+   ./rename.nix
+   ./security/acme.nix
+   ./security/apparmor.nix
+-  ./security/apparmor-suid.nix
+   ./security/audit.nix
+   ./security/auditd.nix
+   ./security/ca.nix
+diff --git a/nixos/modules/security/apparmor-suid.nix b/nixos/modules/security/apparmor-suid.nix
+deleted file mode 100644
+index 6c479e070e2..00000000000
+--- a/nixos/modules/security/apparmor-suid.nix
++++ /dev/null
+@@ -1,49 +0,0 @@
+-{ config, lib, pkgs, ... }:
+-let
+-  cfg = config.security.apparmor;
+-in
+-with lib;
+-{
+-  imports = [
+-    (mkRenamedOptionModule [ "security" "virtualization" "flushL1DataCache" ] [ "security" "virtualisation" "flushL1DataCache" ])
+-  ];
+-
+-  options.security.apparmor.confineSUIDApplications = mkOption {
+-    type = types.bool;
+-    default = true;
+-    description = ''
+-      Install AppArmor profiles for commonly-used SUID application
+-      to mitigate potential privilege escalation attacks due to bugs
+-      in such applications.
+-
+-      Currently available profiles: ping
+-    '';
+-  };
+-
+-  config = mkIf (cfg.confineSUIDApplications) {
+-    security.apparmor.profiles = [ (pkgs.writeText "ping" ''
+-      #include <tunables/global>
+-      /run/wrappers/bin/ping {
+-        #include <abstractions/base>
+-        #include <abstractions/consoles>
+-        #include <abstractions/nameservice>
+-
+-        capability net_raw,
+-        capability setuid,
+-        network inet raw,
+-
+-        ${pkgs.stdenv.cc.libc.out}/lib/*.so mr,
+-        ${pkgs.libcap.lib}/lib/libcap.so* mr,
+-        ${pkgs.attr.out}/lib/libattr.so* mr,
+-
+-        ${pkgs.iputils}/bin/ping mixr,
+-
+-        #/etc/modules.conf r,
+-
+-        ## Site-specific additions and overrides. See local/README for details.
+-        ##include <local/bin.ping>
+-      }
+-    '') ];
+-  };
+-
+-}
+diff --git a/nixos/modules/security/apparmor.nix b/nixos/modules/security/apparmor.nix
+index cfc65b347bc..dfa695b81bb 100644
+--- a/nixos/modules/security/apparmor.nix
++++ b/nixos/modules/security/apparmor.nix
+@@ -1,59 +1,207 @@
+ { config, lib, pkgs, ... }:
+ let
+-  inherit (lib) mkIf mkOption types concatMapStrings;
++  inherit (builtins) attrNames head map match readFile;
++  inherit (lib) types;
++  inherit (config.environment) etc;
+   cfg = config.security.apparmor;
++  mkDisableOption = name: lib.mkEnableOption name // {
++    default = true;
++    example = false;
++  };
++  enabledPolicies = lib.filterAttrs (n: p: p.enable) cfg.policies;
  in
  {
+-   options = {
+-     security.apparmor = {
+-       enable = mkOption {
+-         type = types.bool;
+-         default = false;
+-         description = "Enable the AppArmor Mandatory Access Control system.";
+-       };
+-       profiles = mkOption {
+-         type = types.listOf types.path;
+-         default = [];
+-         description = "List of files containing AppArmor profiles.";
+-       };
+-       packages = mkOption {
+-         type = types.listOf types.package;
+-         default = [];
+-         description = "List of packages to be added to apparmor's include path";
+-       };
+-     };
+-   };
 +  imports = [
-+    (mkRenamedOptionModule ["services" "transmission" "port"]
-+                           ["services" "transmission" "settings" "rpc-port"])
-+    (mkAliasOptionModule ["services" "transmission" "openFirewall"]
-+                         ["services" "transmission" "openPeerPorts"])
++    (lib.mkRenamedOptionModule [ "security" "virtualization" "flushL1DataCache" ] [ "security" "virtualisation" "flushL1DataCache" ])
++    (lib.mkRemovedOptionModule [ "security" "apparmor" "confineSUIDApplications" ] "Please use the new options: `security.apparmor.policies.<policy>.enable'.")
++    (lib.mkRemovedOptionModule [ "security" "apparmor" "profiles" ] "Please use the new option: `security.apparmor.policies'.")
++    apparmor/includes.nix
++    apparmor/profiles.nix
 +  ];
-   options = {
-     services.transmission = {
-       enable = mkEnableOption ''the headless Transmission BitTorrent daemon.
-@@ -24,48 +29,141 @@ in
-         transmission-remote, the WebUI (http://127.0.0.1:9091/ by default),
-         or other clients like stig or tremc.
--        Torrents are downloaded to ${homeDir}/${downloadsDir} by default and are
-+        Torrents are downloaded to <xref linkend="opt-services.transmission.home"/>/${downloadsDir} by default and are
-         accessible to users in the "transmission" group'';
--      settings = mkOption rec {
--        # TODO: switch to types.config.json as prescribed by RFC0042 once it's implemented
--        type = types.attrs;
--        apply = recursiveUpdate default;
--        default =
--          {
--            download-dir = "${cfg.home}/${downloadsDir}";
--            incomplete-dir = "${cfg.home}/${incompleteDir}";
--            incomplete-dir-enabled = true;
--            watch-dir = "${cfg.home}/${watchDir}";
--            watch-dir-enabled = false;
--            message-level = 1;
--            peer-port = 51413;
--            peer-port-random-high = 65535;
--            peer-port-random-low = 49152;
--            peer-port-random-on-start = false;
--            rpc-bind-address = "127.0.0.1";
--            rpc-port = 9091;
--            script-torrent-done-enabled = false;
--            script-torrent-done-filename = "";
--            umask = 2; # 0o002 in decimal as expected by Transmission
--            utp-enabled = true;
--          };
--        example =
--          {
--            download-dir = "/srv/torrents/";
--            incomplete-dir = "/srv/torrents/.incomplete/";
--            incomplete-dir-enabled = true;
--            rpc-whitelist = "127.0.0.1,192.168.*.*";
--          };
-+      settings = mkOption {
-         description = ''
--          Attribute set whose fields overwrites fields in
-+          Settings whose options overwrite fields in
-           <literal>.config/transmission-daemon/settings.json</literal>
--          (each time the service starts). String values must be quoted, integer and
--          boolean values must not.
-+          (each time the service starts).
-           See <link xlink:href="https://github.com/transmission/transmission/wiki/Editing-Configuration-Files">Transmission's Wiki</link>
--          for documentation.
-+          for documentation of settings not explicitely covered by this module.
-         '';
-+        default = {};
-+        type = types.submodule {
-+          freeformType = settingsFormat.type;
-+          options.download-dir = mkOption {
-+            type = types.path;
-+            default = "${cfg.home}/${downloadsDir}";
-+            description = "Directory where to download torrents.";
-+          };
-+          options.incomplete-dir = mkOption {
-+            type = types.path;
-+            default = "${cfg.home}/${incompleteDir}";
-+            description = ''
-+              When enabled with
-+              services.transmission.home
-+              <xref linkend="opt-services.transmission.settings.incomplete-dir-enabled"/>,
-+              new torrents will download the files to this directory.
-+              When complete, the files will be moved to download-dir
-+              <xref linkend="opt-services.transmission.settings.download-dir"/>.
-+            '';
-+          };
-+          options.incomplete-dir-enabled = mkOption {
-+            type = types.bool;
-+            default = true;
-+            description = "";
-+          };
-+          options.message-level = mkOption {
-+            type = types.ints.between 0 2;
-+            default = 2;
-+            description = "Set verbosity of transmission messages.";
-+          };
-+          options.peer-port = mkOption {
-+            type = types.port;
-+            default = 51413;
-+            description = "The peer port to listen for incoming connections.";
-+          };
-+          options.peer-port-random-high = mkOption {
-+            type = types.port;
-+            default = 65535;
-+            description = ''
-+              The maximum peer port to listen to for incoming connections
-+              when <xref linkend="opt-services.transmission.settings.peer-port-random-on-start"/> is enabled.
-+            '';
-+          };
-+          options.peer-port-random-low = mkOption {
-+            type = types.port;
-+            default = 65535;
-+            description = ''
-+              The minimal peer port to listen to for incoming connections
-+              when <xref linkend="opt-services.transmission.settings.peer-port-random-on-start"/> is enabled.
-+            '';
-+          };
-+          options.peer-port-random-on-start = mkOption {
-+            type = types.bool;
-+            default = false;
-+            description = "Randomize the peer port.";
-+          };
-+          options.rpc-bind-address = mkOption {
-+            type = types.str;
-+            default = "127.0.0.1";
-+            example = "0.0.0.0";
-+            description = ''
-+              Where to listen for RPC connections.
-+              Use \"0.0.0.0\" to listen on all interfaces.
-+            '';
-+          };
-+          options.rpc-port = mkOption {
-+            type = types.port;
-+            default = 9091;
-+            description = "The RPC port to listen to.";
-+          };
-+          options.script-torrent-done-enabled = mkOption {
-+            type = types.bool;
-+            default = false;
-+            description = ''
-+              Whether to run
-+              <xref linkend="opt-services.transmission.settings.script-torrent-done-filename"/>
-+              at torrent completion.
-+            '';
-+          };
-+          options.script-torrent-done-filename = mkOption {
-+            type = types.nullOr types.path;
-+            default = null;
-+            description = "Executable to be run at torrent completion.";
-+          };
-+          options.umask = mkOption {
-+            type = types.int;
-+            default = 2;
-+            description = ''
-+              Sets transmission's file mode creation mask.
-+              See the umask(2) manpage for more information.
-+              Users who want their saved torrents to be world-writable
-+              may want to set this value to 0.
-+              Bear in mind that the json markup language only accepts numbers in base 10,
-+              so the standard umask(2) octal notation "022" is written in settings.json as 18.
-+            '';
-+          };
-+          options.utp-enabled = mkOption {
-+            type = types.bool;
-+            default = true;
-+            description = ''
-+              Whether to enable <link xlink:href="http://en.wikipedia.org/wiki/Micro_Transport_Protocol">Micro Transport Protocol (µTP)</link>.
-+            '';
-+          };
-+          options.watch-dir = mkOption {
-+            type = types.path;
-+            default = "${cfg.home}/${watchDir}";
-+            description = "Watch a directory for torrent files and add them to transmission.";
-+          };
-+          options.watch-dir-enabled = mkOption {
-+            type = types.bool;
-+            default = false;
-+            description = ''Whether to enable the
-+              <xref linkend="opt-services.transmission.settings.watch-dir"/>.
-+            '';
-+          };
-+          options.trash-original-torrent-files = mkOption {
-+            type = types.bool;
-+            default = false;
-+            description = ''Whether to delete torrents added from the
-+              <xref linkend="opt-services.transmission.settings.watch-dir"/>.
-+            '';
+-   config = mkIf cfg.enable {
+-     environment.systemPackages = [ pkgs.apparmor-utils ];
++  options = {
++    security.apparmor = {
++      enable = lib.mkEnableOption ''the AppArmor Mandatory Access Control system.
+-     boot.kernelParams = [ "apparmor=1" "security=apparmor" ];
++        If you're enabling this module on a running system,
++        note that a reboot will be required to activate AppArmor in the kernel.
+-     systemd.services.apparmor = let
+-       paths = concatMapStrings (s: " -I ${s}/etc/apparmor.d")
+-         ([ pkgs.apparmor-profiles ] ++ cfg.packages);
+-     in {
+-       after = [ "local-fs.target" ];
+-       before = [ "sysinit.target" ];
+-       wantedBy = [ "multi-user.target" ];
+-       unitConfig = {
+-         DefaultDependencies = "no";
+-       };
+-       serviceConfig = {
+-         Type = "oneshot";
+-         RemainAfterExit = "yes";
+-         ExecStart = map (p:
+-           ''${pkgs.apparmor-parser}/bin/apparmor_parser -rKv ${paths} "${p}"''
+-         ) cfg.profiles;
+-         ExecStop = map (p:
+-           ''${pkgs.apparmor-parser}/bin/apparmor_parser -Rv "${p}"''
+-         ) cfg.profiles;
+-         ExecReload = map (p:
+-           ''${pkgs.apparmor-parser}/bin/apparmor_parser --reload ${paths} "${p}"''
+-         ) cfg.profiles;
+-       };
+-     };
+-   };
++        Also, beware that enabling this module will by default
++        try to kill unconfined but confinable running processes,
++        in order to obtain a confinement matching what is declared in the NixOS configuration.
++        This will happen when upgrading to a NixOS revision
++        introducing an AppArmor profile for the executable of a running process.
++        This is because enabling an AppArmor profile for an executable
++        can only confine new or already confined processes of that executable,
++        but leaves already running processes unconfined.
++        Set <link linkend="opt-security.apparmor.killUnconfinedConfinables">killUnconfinedConfinables</link>
++        to <literal>false</literal> if you prefer to leave those processes running'';
++      policies = lib.mkOption {
++        description = ''
++          AppArmor policies.
++        '';
++        type = types.attrsOf (types.submodule ({ name, config, ... }: {
++          options = {
++            enable = mkDisableOption "loading of the profile into the kernel";
++            enforce = mkDisableOption "enforcing of the policy or only complain in the logs";
++            profile = lib.mkOption {
++              description = "The policy of the profile.";
++              type = types.lines;
++              apply = pkgs.writeText name;
++            };
 +          };
-+        };
++        }));
++        default = {};
++      };
++      includes = lib.mkOption {
++        type = types.attrsOf types.lines;
++        default = {};
++        description = ''
++          List of paths to be added to AppArmor's searched paths
++          when resolving <literal>include</literal> directives.
++        '';
++        apply = lib.mapAttrs pkgs.writeText;
++      };
++      packages = lib.mkOption {
++        type = types.listOf types.package;
++        default = [];
++        description = "List of packages to be added to AppArmor's include path";
++      };
++      enableCache = lib.mkEnableOption ''caching of AppArmor policies
++        in <literal>/var/cache/apparmor/</literal>.
++
++        Beware that AppArmor policies almost always contain Nix store paths,
++        and thus produce at each change of these paths
++        a new cached version accumulating in the cache'';
++      killUnconfinedConfinables = mkDisableOption ''killing of processes
++        which have an AppArmor profile enabled
++        (in <link linkend="opt-security.apparmor.policies">policies</link>)
++        but are not confined (because AppArmor can only confine new processes).
++        Beware that due to a current limitation of AppArmor,
++        only profiles with exact paths (and no name) can enable such kills'';
++    };
++  };
++
++  config = lib.mkIf cfg.enable {
++    assertions = map (policy:
++      { assertion = match ".*/.*" policy == null;
++        message = "`security.apparmor.policies.\"${policy}\"' must not contain a slash.";
++        # Because, for instance, aa-remove-unknown uses profiles_names_list() in rc.apparmor.functions
++        # which does not recurse into sub-directories.
++      }
++    ) (attrNames cfg.policies);
++
++    environment.systemPackages = [
++      pkgs.apparmor-utils
++      pkgs.apparmor-bin-utils
++    ];
++    environment.etc."apparmor.d".source = pkgs.linkFarm "apparmor.d" (
++      # It's important to put only enabledPolicies here and not all cfg.policies
++      # because aa-remove-unknown reads profiles from all /etc/apparmor.d/*
++      lib.mapAttrsToList (name: p: {inherit name; path=p.profile;}) enabledPolicies ++
++      lib.mapAttrsToList (name: path: {inherit name path;}) cfg.includes
++    );
++    environment.etc."apparmor/parser.conf".text = ''
++      ${if cfg.enableCache then "write-cache" else "skip-cache"}
++      cache-loc /var/cache/apparmor
++      Include /etc/apparmor.d
++    '' +
++    lib.concatMapStrings (p: "Include ${p}/etc/apparmor.d\n") cfg.packages;
++    # For aa-logprof
++    environment.etc."apparmor/apparmor.conf".text = ''
++    '';
++    # For aa-logprof
++    environment.etc."apparmor/severity.db".source = pkgs.apparmor-utils + "/etc/apparmor/severity.db";
++    environment.etc."apparmor/logprof.conf".source = pkgs.runCommand "logprof.conf" {
++      header = ''
++        [settings]
++          # /etc/apparmor.d/ is read-only on NixOS
++          profiledir = /var/cache/apparmor/logprof
++          inactive_profiledir = /etc/apparmor.d/disable
++          # Use: journalctl -b --since today --grep audit: | aa-logprof
++          logfiles = /dev/stdin
++
++          parser = ${pkgs.apparmor-parser}/bin/apparmor_parser
++          ldd = ${pkgs.glibc.bin}/bin/ldd
++          logger = ${pkgs.utillinux}/bin/logger
++
++          # customize how file ownership permissions are presented
++          # 0 - off
++          # 1 - default of what ever mode the log reported
++          # 2 - force the new permissions to be user
++          # 3 - force all perms on the rule to be user
++          default_owner_prompt = 1
++
++          custom_includes = /etc/apparmor.d ${lib.concatMapStringsSep " " (p: "${p}/etc/apparmor.d") cfg.packages}
++
++        [qualifiers]
++          ${pkgs.runtimeShell} = icnu
++          ${pkgs.bashInteractive}/bin/sh = icnu
++          ${pkgs.bashInteractive}/bin/bash = icnu
++      '';
++      footer = "${pkgs.apparmor-utils}/etc/apparmor/logprof.conf";
++      passAsFile = [ "header" ];
++    } ''
++      cp $headerPath $out
++      sed '1,/\[qualifiers\]/d' $footer >> $out
++    '';
++
++    boot.kernelParams = [ "apparmor=1" "security=apparmor" ];
++
++    systemd.services.apparmor = {
++      after = [
++        "local-fs.target"
++        "systemd-journald-audit.socket"
++      ];
++      before = [ "sysinit.target" ];
++      wantedBy = [ "multi-user.target" ];
++      unitConfig = {
++        Description="Load AppArmor policies";
++        DefaultDependencies = "no";
++        ConditionSecurity = "apparmor";
++      };
++      # Reloading instead of restarting enables to load new AppArmor profiles
++      # without necessarily restarting all services which have Requires=apparmor.service
++      reloadIfChanged = true;
++      restartTriggers = [
++        etc."apparmor/parser.conf".source
++        etc."apparmor.d".source
++      ];
++      serviceConfig = let
++        killUnconfinedConfinables = pkgs.writeShellScript "apparmor-kill" ''
++          set -eu
++          ${pkgs.apparmor-bin-utils}/bin/aa-status --json |
++          ${pkgs.jq}/bin/jq --raw-output '.processes | .[] | .[] | select (.status == "unconfined") | .pid' |
++          xargs --verbose --no-run-if-empty --delimiter='\n' \
++          kill
++        '';
++        commonOpts = p: "--verbose --show-cache ${lib.optionalString (!p.enforce) "--complain "}${p.profile}";
++        in {
++        Type = "oneshot";
++        RemainAfterExit = "yes";
++        ExecStartPre = "${pkgs.apparmor-utils}/bin/aa-teardown";
++        ExecStart = lib.mapAttrsToList (n: p: "${pkgs.apparmor-parser}/bin/apparmor_parser --add ${commonOpts p}") enabledPolicies;
++        ExecStartPost = lib.optional cfg.killUnconfinedConfinables killUnconfinedConfinables;
++        ExecReload =
++          # Add or replace into the kernel profiles in enabledPolicies
++          # (because AppArmor can do that without stopping the processes already confined).
++          lib.mapAttrsToList (n: p: "${pkgs.apparmor-parser}/bin/apparmor_parser --replace ${commonOpts p}") enabledPolicies ++
++          # Remove from the kernel any profile whose name is not
++          # one of the names within the content of the profiles in enabledPolicies
++          # (indirectly read from /etc/apparmor.d/*, without recursing into sub-directory).
++          # Note that this does not remove profiles dynamically generated by libvirt.
++          [ "${pkgs.apparmor-utils}/bin/aa-remove-unknown" ] ++
++          # Optionaly kill the processes which are unconfined but now have a profile loaded
++          # (because AppArmor can only start to confine new processes).
++          lib.optional cfg.killUnconfinedConfinables killUnconfinedConfinables;
++        ExecStop = "${pkgs.apparmor-utils}/bin/aa-teardown";
++        CacheDirectory = [ "apparmor" "apparmor/logprof" ];
++        CacheDirectoryMode = "0700";
++      };
++    };
++  };
++
++  meta.maintainers = with lib.maintainers; [ julm ];
+ }
+diff --git a/nixos/modules/security/apparmor/includes.nix b/nixos/modules/security/apparmor/includes.nix
+new file mode 100644
+index 00000000000..498d7e77650
+--- /dev/null
++++ b/nixos/modules/security/apparmor/includes.nix
+@@ -0,0 +1,301 @@
++{ config, lib, pkgs, ... }:
++let
++  inherit (builtins) attrNames hasAttr isAttrs;
++  inherit (lib) getLib;
++  inherit (config.environment) etc;
++  etcRule = arg:
++    let go = {path ? null, mode ? "r", trail ? ""}:
++      lib.optionalString (hasAttr path etc)
++        "${mode} ${config.environment.etc.${path}.source}${trail},";
++    in if isAttrs arg
++    then go arg
++    else go {path=arg;};
++in
++{
++# FIXME: most of the etcRule calls below have been
++# written systematically by converting from apparmor-profiles's profiles
++# without testing nor deep understanding of their uses,
++# and thus may need more rules or can have less rules;
++# this remains to be determined case by case,
++# some may even be completely useless.
++config.security.apparmor.includes = {
++  # This one is included by <tunables/global>
++  # which is usualy included before any profile.
++  "abstractions/tunables/alias" = ''
++    alias /bin -> /run/current-system/sw/bin,
++    alias /lib/modules -> /run/current-system/kernel/lib/modules,
++    alias /sbin -> /run/current-system/sw/sbin,
++    alias /usr -> /run/current-system/sw,
++  '';
++  "abstractions/audio" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/audio"
++    ${etcRule "asound.conf"}
++    ${etcRule "esound/esd.conf"}
++    ${etcRule "libao.conf"}
++    ${etcRule {path="pulse"; trail="/";}}
++    ${etcRule {path="pulse"; trail="/**";}}
++    ${etcRule {path="sound"; trail="/";}}
++    ${etcRule {path="sound"; trail="/**";}}
++    ${etcRule {path="alsa/conf.d"; trail="/";}}
++    ${etcRule {path="alsa/conf.d"; trail="/*";}}
++    ${etcRule "openal/alsoft.conf"}
++    ${etcRule "wildmidi/wildmidi.conf"}
++  '';
++  "abstractions/authentication" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/authentication"
++    # Defined in security.pam
++    include <abstractions/pam>
++    ${etcRule "nologin"}
++    ${etcRule "securetty"}
++    ${etcRule {path="security"; trail="/*";}}
++    ${etcRule "shadow"}
++    ${etcRule "gshadow"}
++    ${etcRule "pwdb.conf"}
++    ${etcRule "default/passwd"}
++    ${etcRule "login.defs"}
++  '';
++  "abstractions/base" = ''
++     include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/base"
++     r ${pkgs.stdenv.cc.libc}/share/locale/**,
++     r ${pkgs.stdenv.cc.libc}/share/locale.alias,
++     ${lib.optionalString (pkgs.glibcLocales != null) "r ${pkgs.glibcLocales}/lib/locale/locale-archive,"}
++     ${etcRule "localtime"}
++     r ${pkgs.tzdata}/share/zoneinfo/**,
++     r ${pkgs.stdenv.cc.libc}/share/i18n/**,
++  '';
++  "abstractions/bash" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/bash"
++    # system-wide bash configuration
++    ${etcRule "profile.dos"}
++    ${etcRule "profile"}
++    ${etcRule "profile.d"}
++    ${etcRule {path="profile.d"; trail="/*";}}
++    ${etcRule "bashrc"}
++    ${etcRule "bash.bashrc"}
++    ${etcRule "bash.bashrc.local"}
++    ${etcRule "bash_completion"}
++    ${etcRule "bash_completion.d"}
++    ${etcRule {path="bash_completion.d"; trail="/*";}}
++    # bash relies on system-wide readline configuration
++    ${etcRule "inputrc"}
++    # bash inspects filesystems at startup
++    # and /etc/mtab is linked to /proc/mounts
++    @{PROC}/mounts
++
++    # run out of /etc/bash.bashrc
++    ${etcRule "DIR_COLORS"}
++  '';
++  "abstractions/cups-client" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/cpus-client"
++    ${etcRule "cups/cups-client.conf"}
++  '';
++  "abstractions/consoles" = ''
++     include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/consoles"
++  '';
++  "abstractions/dbus-session-strict" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/dbus-session-strict"
++    ${etcRule "machine-id"}
++  '';
++  "abstractions/dconf" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/dconf"
++    ${etcRule {path="dconf"; trail="/**";}}
++  '';
++  "abstractions/dri-common" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/dri-common"
++    ${etcRule "drirc"}
++  '';
++  # The config.fonts.fontconfig NixOS module adds many files to /etc/fonts/
++  # by symlinking them but without exporting them outside of its NixOS module,
++  # those are therefore added there to this "abstractions/fonts".
++  "abstractions/fonts" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/fonts"
++    ${etcRule {path="fonts"; trail="/**";}}
++  '';
++  "abstractions/gnome" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/gnome"
++    ${etcRule {path="gnome"; trail="/gtkrc*";}}
++    ${etcRule {path="gtk"; trail="/*";}}
++    ${etcRule {path="gtk-2.0"; trail="/*";}}
++    ${etcRule {path="gtk-3.0"; trail="/*";}}
++    ${etcRule "orbitrc"}
++    include <abstractions/fonts>
++    ${etcRule {path="pango"; trail="/*";}}
++    ${etcRule {path="/etc/gnome-vfs-2.0"; trail="/modules/";}}
++    ${etcRule {path="/etc/gnome-vfs-2.0"; trail="/modules/*";}}
++    ${etcRule "papersize"}
++    ${etcRule {path="cups"; trail="/lpoptions";}}
++    ${etcRule {path="gnome"; trail="/defaults.list";}}
++    ${etcRule {path="xdg"; trail="/{,*-}mimeapps.list";}}
++    ${etcRule "xdg/mimeapps.list"}
++  '';
++  "abstractions/kde" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/kde"
++    ${etcRule {path="qt3"; trail="/kstylerc";}}
++    ${etcRule {path="qt3"; trail="/qt_plugins_3.3rc";}}
++    ${etcRule {path="qt3"; trail="/qtrc";}}
++    ${etcRule "kderc"}
++    ${etcRule {path="kde3"; trail="/*";}}
++    ${etcRule "kde4rc"}
++    ${etcRule {path="xdg"; trail="/kdeglobals";}}
++    ${etcRule {path="xdg"; trail="/Trolltech.conf";}}
++  '';
++  "abstractions/kerberosclient" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/kerberosclient"
++    ${etcRule {path="krb5.keytab"; mode="rk";}}
++    ${etcRule "krb5.conf"}
++    ${etcRule "krb5.conf.d"}
++    ${etcRule {path="krb5.conf.d"; trail="/*";}}
++
++    # config files found via strings on libs
++    ${etcRule "krb.conf"}
++    ${etcRule "krb.realms"}
++    ${etcRule "srvtab"}
++  '';
++  "abstractions/ldapclient" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/ldapclient"
++    ${etcRule "ldap.conf"}
++    ${etcRule "ldap.secret"}
++    ${etcRule {path="openldap"; trail="/*";}}
++    ${etcRule {path="openldap"; trail="/cacerts/*";}}
++    ${etcRule {path="sasl2"; trail="/*";}}
++  '';
++  "abstractions/likewise" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/likewise"
++  '';
++  "abstractions/mdns" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/mdns"
++     ${etcRule "nss_mdns.conf"}
++  '';
++  "abstractions/nameservice" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/nameservice"
++
++    # Many programs wish to perform nameservice-like operations, such as
++    # looking up users by name or id, groups by name or id, hosts by name
++    # or IP, etc. These operations may be performed through files, dns,
++    # NIS, NIS+, LDAP, hesiod, wins, etc. Allow them all here.
++    ${etcRule "group"}
++    ${etcRule "host.conf"}
++    ${etcRule "hosts"}
++    ${etcRule "nsswitch.conf"}
++    ${etcRule "gai.conf"}
++    ${etcRule "passwd"}
++    ${etcRule "protocols"}
++
++    # libtirpc (used for NIS/YP login) needs this
++    ${etcRule "netconfig"}
++
++    ${etcRule "resolv.conf"}
++
++    ${etcRule {path="samba"; trail="/lmhosts";}}
++    ${etcRule "services"}
++
++    ${etcRule "default/nss"}
++
++    # libnl-3-200 via libnss-gw-name
++    ${etcRule {path="libnl"; trail="/classid";}}
++    ${etcRule {path="libnl-3"; trail="/classid";}}
++
++    mr ${getLib pkgs.nss}/lib/libnss_*.so*,
++    mr ${getLib pkgs.nss}/lib64/libnss_*.so*,
++  '';
++  "abstractions/nis" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/nis"
++  '';
++  "abstractions/nvidia" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/nvidia"
++    ${etcRule "vdpau_wrapper.cfg"}
++  '';
++  "abstractions/opencl-common" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/opencl-common"
++    ${etcRule {path="OpenCL"; trail="/**";}}
++  '';
++  "abstractions/opencl-mesa" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/opencl-mesa"
++    ${etcRule "default/drirc"}
++  '';
++  "abstractions/openssl" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/openssl"
++    ${etcRule {path="ssl"; trail="/openssl.cnf";}}
++  '';
++  "abstractions/p11-kit" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/p11-kit"
++    ${etcRule {path="pkcs11"; trail="/";}}
++    ${etcRule {path="pkcs11"; trail="/pkcs11.conf";}}
++    ${etcRule {path="pkcs11"; trail="/modules/";}}
++    ${etcRule {path="pkcs11"; trail="/modules/*";}}
++  '';
++  "abstractions/perl" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/perl"
++    ${etcRule {path="perl"; trail="/**";}}
++  '';
++  "abstractions/php" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/php"
++    ${etcRule {path="php"; trail="/**/";}}
++    ${etcRule {path="php5"; trail="/**/";}}
++    ${etcRule {path="php7"; trail="/**/";}}
++    ${etcRule {path="php"; trail="/**.ini";}}
++    ${etcRule {path="php5"; trail="/**.ini";}}
++    ${etcRule {path="php7"; trail="/**.ini";}}
++  '';
++  "abstractions/postfix-common" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/postfix-common"
++    ${etcRule "mailname"}
++    ${etcRule {path="postfix"; trail="/*.cf";}}
++    ${etcRule "postfix/main.cf"}
++    ${etcRule "postfix/master.cf"}
++  '';
++  "abstractions/python" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/python"
++  '';
++  "abstractions/qt5" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/qt5"
++    ${etcRule {path="xdg"; trail="/QtProject/qtlogging.ini";}}
++    ${etcRule {path="xdg/QtProject"; trail="/qtlogging.ini";}}
++    ${etcRule "xdg/QtProject/qtlogging.ini"}
++  '';
++  "abstractions/samba" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/samba"
++    ${etcRule {path="samba"; trail="/*";}}
++  '';
++  "abstractions/ssl_certs" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/ssl_certs"
++    ${etcRule "ssl/certs/ca-certificates.crt"}
++    ${etcRule "ssl/certs/ca-bundle.crt"}
++    ${etcRule "pki/tls/certs/ca-bundle.crt"}
++
++    ${etcRule {path="ssl/trust"; trail="/";}}
++    ${etcRule {path="ssl/trust"; trail="/*";}}
++    ${etcRule {path="ssl/trust/anchors"; trail="/";}}
++    ${etcRule {path="ssl/trust/anchors"; trail="/**";}}
++    ${etcRule {path="pki/trust"; trail="/";}}
++    ${etcRule {path="pki/trust"; trail="/*";}}
++    ${etcRule {path="pki/trust/anchors"; trail="/";}}
++    ${etcRule {path="pki/trust/anchors"; trail="/**";}}
++
++    # security.acme NixOS module
++    r /var/lib/acme/*/cert.pem,
++    r /var/lib/acme/*/chain.pem,
++    r /var/lib/acme/*/fullchain.pem,
++  '';
++  "abstractions/ssl_keys" = ''
++    # security.acme NixOS module
++    r /var/lib/acme/*/full.pem,
++    r /var/lib/acme/*/key.pem,
++  '';
++  "abstractions/vulkan" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/vulkan"
++    ${etcRule {path="vulkan/icd.d"; trail="/";}}
++    ${etcRule {path="vulkan/icd.d"; trail="/*.json";}}
++  '';
++  "abstractions/winbind" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/winbind"
++    ${etcRule {path="samba"; trail="/smb.conf";}}
++    ${etcRule {path="samba"; trail="/dhcp.conf";}}
++  '';
++  "abstractions/X" = ''
++    include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/X"
++    ${etcRule {path="X11/cursors"; trail="/";}}
++    ${etcRule {path="X11/cursors"; trail="/**";}}
++  '';
++};
++}
+diff --git a/nixos/modules/security/apparmor/profiles.nix b/nixos/modules/security/apparmor/profiles.nix
+new file mode 100644
+index 00000000000..8eb630b5a48
+--- /dev/null
++++ b/nixos/modules/security/apparmor/profiles.nix
+@@ -0,0 +1,11 @@
++{ config, lib, pkgs, ... }:
++let apparmor = config.security.apparmor; in
++{
++config.security.apparmor.packages = [ pkgs.apparmor-profiles ];
++config.security.apparmor.policies."bin.ping".profile = lib.mkIf apparmor.policies."bin.ping".enable ''
++  include "${pkgs.iputils.apparmor}/bin.ping"
++  include "${pkgs.inetutils.apparmor}/bin.ping"
++  # Note that including those two profiles in the same profile
++  # would not work if the second one were to re-include <tunables/global>.
++'';
++}
+diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
+index a428103eaa9..114a448857f 100644
+--- a/nixos/modules/security/pam.nix
++++ b/nixos/modules/security/pam.nix
+@@ -895,6 +895,61 @@ in
+         runuser-l = { rootOK = true; unixAuth = false; };
        };
  
-       downloadDirPermissions = mkOption {
-@@ -74,31 +172,22 @@ in
-         example = "775";
-         description = ''
-           The permissions set by <literal>systemd.activationScripts.transmission-daemon</literal>
--          on the directories <link linkend="opt-services.transmission.settings">settings.download-dir</link>
--          and <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link>.
-+          on the directories <xref linkend="opt-services.transmission.settings.download-dir"/>
-+          and <xref linkend="opt-services.transmission.settings.incomplete-dir"/>.
-           Note that you may also want to change
--          <link linkend="opt-services.transmission.settings">settings.umask</link>.
--        '';
--      };
--
--      port = mkOption {
--        type = types.port;
--        description = ''
--          TCP port number to run the RPC/web interface.
--
--          If instead you want to change the peer port,
--          use <link linkend="opt-services.transmission.settings">settings.peer-port</link>
--          or <link linkend="opt-services.transmission.settings">settings.peer-port-random-on-start</link>.
-+          <xref linkend="opt-services.transmission.settings.umask"/>.
-         '';
-       };
++    security.apparmor.includes."abstractions/pam" = let
++      isEnabled = test: fold or false (map test (attrValues config.security.pam.services));
++      in ''
++      ${lib.concatMapStringsSep "\n"
++         (name: "r ${config.environment.etc."pam.d/${name}".source},")
++         (attrNames config.security.pam.services)}
++      mr ${getLib pkgs.pam}/lib/security/pam_filter/*,
++      mr ${getLib pkgs.pam}/lib/security/pam_*.so,
++      r ${getLib pkgs.pam}/lib/security/,
++      ${optionalString use_ldap
++        "mr ${pam_ldap}/lib/security/pam_ldap.so,"}
++      ${optionalString config.services.sssd.enable
++        "mr ${pkgs.sssd}/lib/security/pam_sss.so,"}
++      ${optionalString config.krb5.enable ''
++        mr ${pam_krb5}/lib/security/pam_krb5.so,
++        mr ${pam_ccreds}/lib/security/pam_ccreds.so,
++      ''}
++      ${optionalString (isEnabled (cfg: cfg.googleOsLoginAccountVerification)) ''
++        mr ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so,
++        mr ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_admin.so,
++      ''}
++      ${optionalString (isEnabled (cfg: cfg.googleOsLoginAuthentication))
++        "mr ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so,"}
++      ${optionalString (config.security.pam.enableSSHAgentAuth && isEnabled (cfg: cfg.sshAgentAuth))
++        "mr ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so,"}
++      ${optionalString (isEnabled (cfg: cfg.fprintAuth))
++        "mr ${pkgs.fprintd}/lib/security/pam_fprintd.so,"}
++      ${optionalString (isEnabled (cfg: cfg.u2fAuth))
++        "mr ${pkgs.pam_u2f}/lib/security/pam_u2f.so,"}
++      ${optionalString (isEnabled (cfg: cfg.usbAuth))
++        "mr ${pkgs.pam_usb}/lib/security/pam_usb.so,"}
++      ${optionalString (isEnabled (cfg: cfg.oathAuth))
++        "mr ${pkgs.oathToolkit}/lib/security/pam_oath.so,"}
++      ${optionalString (isEnabled (cfg: cfg.yubicoAuth))
++        "mr ${pkgs.yubico-pam}/lib/security/pam_yubico.so,"}
++      ${optionalString (isEnabled (cfg: cfg.duoSecurity.enable))
++        "mr ${pkgs.duo-unix}/lib/security/pam_duo.so,"}
++      ${optionalString (isEnabled (cfg: cfg.otpwAuth))
++        "mr ${pkgs.otpw}/lib/security/pam_otpw.so,"}
++      ${optionalString config.security.pam.enableEcryptfs
++        "mr ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so,"}
++      ${optionalString (isEnabled (cfg: cfg.pamMount))
++        "mr ${pkgs.pam_mount}/lib/security/pam_mount.so,"}
++      ${optionalString (isEnabled (cfg: cfg.enableGnomeKeyring))
++        "mr ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so,"}
++      ${optionalString (isEnabled (cfg: cfg.startSession))
++        "mr ${pkgs.systemd}/lib/security/pam_systemd.so,"}
++      ${optionalString (isEnabled (cfg: cfg.enableAppArmor) && config.security.apparmor.enable)
++        "mr ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so,"}
++      ${optionalString (isEnabled (cfg: cfg.enableKwallet))
++        "mr ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so,"}
++      ${optionalString config.virtualisation.lxc.lxcfs.enable
++        "mr ${pkgs.lxc}/lib/security/pam_cgfs.so"}
++    '';
++
+   };
  
-       home = mkOption {
-         type = types.path;
--        default = homeDir;
-+        default = "/var/lib/transmission";
-         description = ''
-           The directory where Transmission will create <literal>${settingsDir}</literal>.
--          as well as <literal>${downloadsDir}/</literal> unless <link linkend="opt-services.transmission.settings">settings.download-dir</link> is changed,
--          and <literal>${incompleteDir}/</literal> unless <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link> is changed.
-+          as well as <literal>${downloadsDir}/</literal> unless
-+          <xref linkend="opt-services.transmission.settings.download-dir"/> is changed,
-+          and <literal>${incompleteDir}/</literal> unless
-+          <xref linkend="opt-services.transmission.settings.incomplete-dir"/> is changed.
-         '';
-       };
+ }
+diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
+index de6213714ac..4467b909e45 100644
+--- a/nixos/modules/security/wrappers/default.nix
++++ b/nixos/modules/security/wrappers/default.nix
+@@ -179,6 +179,14 @@ in
+       export PATH="${wrapperDir}:$PATH"
+     '';
  
-@@ -119,19 +208,22 @@ in
-         description = ''
-           Path to a JSON file to be merged with the settings.
-           Useful to merge a file which is better kept out of the Nix store
--          because it contains sensible data like <link linkend="opt-services.transmission.settings">settings.rpc-password</link>.
-+          because it contains sensible data like
-+          <xref linkend="opt-services.transmission.settings.rpc-password"/>.
-         '';
-         default = "/dev/null";
-         example = "/var/lib/secrets/transmission/settings.json";
-       };
++    security.apparmor.includes."nixos/security.wrappers" = ''
++      include "${pkgs.apparmorRulesFromClosure { name="security.wrappers"; } [
++        securityWrapper
++        pkgs.stdenv.cc.cc
++        pkgs.stdenv.cc.libc
++      ]}"
++    '';
++
+     ###### setcap activation script
+     system.activationScripts.wrappers =
+       lib.stringAfter [ "specialfs" "users" ]
+diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
+index 7bec073e26f..e9b5834dab4 100644
+--- a/nixos/modules/services/torrent/transmission.nix
++++ b/nixos/modules/services/torrent/transmission.nix
+@@ -5,7 +5,7 @@ with lib;
+ let
+   cfg = config.services.transmission;
+   inherit (config.environment) etc;
+-  apparmor = config.security.apparmor.enable;
++  apparmor = config.security.apparmor;
+   rootDir = "/run/transmission";
+   homeDir = "/var/lib/transmission";
+   settingsDir = ".config/transmission-daemon";
+@@ -184,8 +184,8 @@ in
  
--      openFirewall = mkEnableOption "opening of the peer port(s) in the firewall";
-+      openPeerPorts = mkEnableOption "opening of the peer port(s) in the firewall";
-+
-+      openRPCPort = mkEnableOption "opening of the RPC port in the firewall";
-       performanceNetParameters = mkEnableOption ''tweaking of kernel parameters
-         to open many more connections at the same time.
-         Note that you may also want to increase
--        <link linkend="opt-services.transmission.settings">settings.peer-limit-global</link>.
-+        <xref linkend="opt-services.transmission.settings.peer-limit-global"/>.
-         And be aware that these settings are quite aggressive
-         and might not suite your regular desktop use.
-         For instance, SSH sessions may time out more easily'';
-@@ -152,36 +244,10 @@ in
-       install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.download-dir}'
-       '' + optionalString cfg.settings.incomplete-dir-enabled ''
-       install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.incomplete-dir}'
-+      '' + optionalString cfg.settings.watch-dir-enabled ''
-+      install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.watch-dir}'
-       '';
--    assertions = [
--      { assertion = builtins.match "^/.*" cfg.home != null;
--        message = "`services.transmission.home' must be an absolute path.";
--      }
--      { assertion = types.path.check cfg.settings.download-dir;
--        message = "`services.transmission.settings.download-dir' must be an absolute path.";
--      }
--      { assertion = types.path.check cfg.settings.incomplete-dir;
--        message = "`services.transmission.settings.incomplete-dir' must be an absolute path.";
--      }
--      { assertion = types.path.check cfg.settings.watch-dir;
--        message = "`services.transmission.settings.watch-dir' must be an absolute path.";
--      }
--      { assertion = cfg.settings.script-torrent-done-filename == "" || types.path.check cfg.settings.script-torrent-done-filename;
--        message = "`services.transmission.settings.script-torrent-done-filename' must be an absolute path.";
--      }
--      { assertion = types.port.check cfg.settings.rpc-port;
--        message = "${toString cfg.settings.rpc-port} is not a valid port number for `services.transmission.settings.rpc-port`.";
--      }
--      # In case both port and settings.rpc-port are explicitely defined: they must be the same.
--      { assertion = !options.services.transmission.port.isDefined || cfg.port == cfg.settings.rpc-port;
--        message = "`services.transmission.port' is not equal to `services.transmission.settings.rpc-port'";
--      }
--    ];
--
--    services.transmission.settings =
--      optionalAttrs options.services.transmission.port.isDefined { rpc-port = cfg.port; };
--
      systemd.services.transmission = {
        description = "Transmission BitTorrent Service";
-       after = [ "network.target" ] ++ optional apparmor.enable "apparmor.service";
-@@ -226,11 +292,9 @@ in
-             cfg.settings.download-dir
-           ] ++
-           optional cfg.settings.incomplete-dir-enabled
--            cfg.settings.incomplete-dir
--          ++
--          optional cfg.settings.watch-dir-enabled
--            cfg.settings.watch-dir
--          ;
-+            cfg.settings.incomplete-dir ++
-+          optional (cfg.settings.watch-dir-enabled && cfg.settings.trash-original-torrent-files)
-+            cfg.settings.watch-dir;
-         BindReadOnlyPaths = [
-           # No confinement done of /nix/store here like in systemd-confinement.nix,
-           # an AppArmor profile is provided to get a confinement based upon paths and rights.
-@@ -239,8 +303,10 @@ in
-           "/run"
-           ] ++
-           optional (cfg.settings.script-torrent-done-enabled &&
--                    cfg.settings.script-torrent-done-filename != "")
--            cfg.settings.script-torrent-done-filename;
-+                    cfg.settings.script-torrent-done-filename != null)
-+            cfg.settings.script-torrent-done-filename ++
-+          optional (cfg.settings.watch-dir-enabled && !cfg.settings.trash-original-torrent-files)
-+            cfg.settings.watch-dir;
-         # The following options are only for optimizing:
-         # systemd-analyze security transmission
-         AmbientCapabilities = "";
-@@ -307,25 +373,28 @@ in
-       };
-     });
--    networking.firewall = mkIf cfg.openFirewall (
--      if cfg.settings.peer-port-random-on-start
--      then
--        { allowedTCPPortRanges =
--            [ { from = cfg.settings.peer-port-random-low;
--                to   = cfg.settings.peer-port-random-high;
--              }
--            ];
--          allowedUDPPortRanges =
--            [ { from = cfg.settings.peer-port-random-low;
--                to   = cfg.settings.peer-port-random-high;
--              }
--            ];
--        }
--      else
--        { allowedTCPPorts = [ cfg.settings.peer-port ];
--          allowedUDPPorts = [ cfg.settings.peer-port ];
--        }
--    );
-+    networking.firewall = mkMerge [
-+      (mkIf cfg.openPeerPorts (
-+        if cfg.settings.peer-port-random-on-start
-+        then
-+          { allowedTCPPortRanges =
-+              [ { from = cfg.settings.peer-port-random-low;
-+                  to   = cfg.settings.peer-port-random-high;
-+                }
-+              ];
-+            allowedUDPPortRanges =
-+              [ { from = cfg.settings.peer-port-random-low;
-+                  to   = cfg.settings.peer-port-random-high;
-+                }
-+              ];
-+          }
-+        else
-+          { allowedTCPPorts = [ cfg.settings.peer-port ];
-+            allowedUDPPorts = [ cfg.settings.peer-port ];
-+          }
-+      ))
-+      (mkIf cfg.openRPCPort { allowedTCPPorts = [ cfg.settings.rpc-port ]; })
-+    ];
+-      after = [ "network.target" ] ++ optional apparmor "apparmor.service";
+-      requires = optional apparmor "apparmor.service";
++      after = [ "network.target" ] ++ optional apparmor.enable "apparmor.service";
++      requires = optional apparmor.enable "apparmor.service";
+       wantedBy = [ "multi-user.target" ];
+       environment.CURL_CA_BUNDLE = etc."ssl/certs/ca-certificates.crt".source;
  
-     boot.kernel.sysctl = mkMerge [
-       # Transmission uses a single UDP socket in order to implement multiple uTP sockets,
-@@ -340,74 +409,57 @@ in
-         # Increase the number of available source (local) TCP and UDP ports to 49151.
-         # Usual default is 32768 60999, ie. 28231 ports.
-         # Find out your current usage with: ss -s
--        "net.ipv4.ip_local_port_range" = "16384 65535";
-+        "net.ipv4.ip_local_port_range" = mkDefault "16384 65535";
-         # Timeout faster generic TCP states.
-         # Usual default is 600.
-         # Find out your current usage with: watch -n 1 netstat -nptuo
--        "net.netfilter.nf_conntrack_generic_timeout" = 60;
-+        "net.netfilter.nf_conntrack_generic_timeout" = mkDefault 60;
-         # Timeout faster established but inactive connections.
-         # Usual default is 432000.
--        "net.netfilter.nf_conntrack_tcp_timeout_established" = 600;
-+        "net.netfilter.nf_conntrack_tcp_timeout_established" = mkDefault 600;
-         # Clear immediately TCP states after timeout.
-         # Usual default is 120.
--        "net.netfilter.nf_conntrack_tcp_timeout_time_wait" = 1;
-+        "net.netfilter.nf_conntrack_tcp_timeout_time_wait" = mkDefault 1;
-         # Increase the number of trackable connections.
-         # Usual default is 262144.
-         # Find out your current usage with: conntrack -C
--        "net.netfilter.nf_conntrack_max" = 1048576;
-+        "net.netfilter.nf_conntrack_max" = mkDefault 1048576;
+@@ -358,62 +358,26 @@ in
        })
      ];
  
-     security.apparmor.policies."bin.transmission-daemon".profile = ''
--        include <tunables/global>
--        ${pkgs.transmission}/bin/transmission-daemon {
--          include <abstractions/base>
--          include <abstractions/nameservice>
--          include <abstractions/ssl_certs>
--          include "${pkgs.apparmorRulesFromClosure
--            { name = "transmission-daemon"; }
--            [ pkgs.transmission ]}"
--          include <local/bin.transmission-daemon>
--
--          r @{PROC}/sys/kernel/random/uuid,
--          r @{PROC}/sys/vm/overcommit_memory,
--          r @{PROC}/@{pid}/environ,
--          r @{PROC}/@{pid}/mounts,
--          rwk /tmp/tr_session_id_*,
--          r /run/systemd/resolve/stub-resolv.conf,
+-    security.apparmor.profiles = mkIf apparmor [
+-      (pkgs.writeText "apparmor-transmission-daemon" ''
++    security.apparmor.policies."bin.transmission-daemon".profile = ''
+         include <tunables/global>
 -
--          r ${pkgs.openssl.out}/etc/**,
--          r ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE},
+         ${pkgs.transmission}/bin/transmission-daemon {
+           include <abstractions/base>
+           include <abstractions/nameservice>
 -
--          owner rw ${cfg.home}/${settingsDir}/**,
--          rw ${cfg.settings.download-dir}/**,
--          ${optionalString cfg.settings.incomplete-dir-enabled ''
--            rw ${cfg.settings.incomplete-dir}/**,
+-          # NOTE: https://github.com/NixOS/nixpkgs/pull/93457
+-          # will remove the need for these by fixing <abstractions/base>
+-          r ${etc."hosts".source},
+-          r /etc/ld-nix.so.preload,
+-          ${lib.optionalString (builtins.hasAttr "ld-nix.so.preload" etc) ''
+-            r ${etc."ld-nix.so.preload".source},
+-            ${concatMapStrings (p: optionalString (p != "") ("mr ${p},\n"))
+-              (splitString "\n" config.environment.etc."ld-nix.so.preload".text)}
 -          ''}
--          ${optionalString cfg.settings.watch-dir-enabled ''
--            rw ${cfg.settings.watch-dir}/**,
--          ''}
--          profile dirs {
--            rw ${cfg.settings.download-dir}/**,
--            ${optionalString cfg.settings.incomplete-dir-enabled ''
--              rw ${cfg.settings.incomplete-dir}/**,
--            ''}
--            ${optionalString cfg.settings.watch-dir-enabled ''
--              rw ${cfg.settings.watch-dir}/**,
--            ''}
--          }
+-          r ${etc."ssl/certs/ca-certificates.crt".source},
+-          r ${pkgs.tzdata}/share/zoneinfo/**,
+-          r ${pkgs.stdenv.cc.libc}/share/i18n/**,
+-          r ${pkgs.stdenv.cc.libc}/share/locale/**,
 -
--          ${optionalString (cfg.settings.script-torrent-done-enabled &&
--                            cfg.settings.script-torrent-done-filename != "") ''
--            # Stack transmission_directories profile on top of
--            # any existing profile for script-torrent-done-filename
--            # FIXME: to be tested as I'm not sure it works well with NoNewPrivileges=
--            # https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
--            px ${cfg.settings.script-torrent-done-filename} -> &@{dirs},
--          ''}
--        }
-+      include "${pkgs.transmission.apparmor}/bin.transmission-daemon"
+-          mr ${getLib pkgs.stdenv.cc.cc}/lib/*.so*,
+-          mr ${getLib pkgs.stdenv.cc.libc}/lib/*.so*,
+-          mr ${getLib pkgs.attr}/lib/libattr*.so*,
+-          mr ${getLib pkgs.c-ares}/lib/libcares*.so*,
+-          mr ${getLib pkgs.curl}/lib/libcurl*.so*,
+-          mr ${getLib pkgs.keyutils}/lib/libkeyutils*.so*,
+-          mr ${getLib pkgs.libcap}/lib/libcap*.so*,
+-          mr ${getLib pkgs.libevent}/lib/libevent*.so*,
+-          mr ${getLib pkgs.libgcrypt}/lib/libgcrypt*.so*,
+-          mr ${getLib pkgs.libgpgerror}/lib/libgpg-error*.so*,
+-          mr ${getLib pkgs.libkrb5}/lib/lib*.so*,
+-          mr ${getLib pkgs.libssh2}/lib/libssh2*.so*,
+-          mr ${getLib pkgs.lz4}/lib/liblz4*.so*,
+-          mr ${getLib pkgs.nghttp2}/lib/libnghttp2*.so*,
+-          mr ${getLib pkgs.openssl}/lib/libcrypto*.so*,
+-          mr ${getLib pkgs.openssl}/lib/libssl*.so*,
+-          mr ${getLib pkgs.systemd}/lib/libsystemd*.so*,
+-          mr ${getLib pkgs.util-linuxMinimal.out}/lib/libblkid.so*,
+-          mr ${getLib pkgs.util-linuxMinimal.out}/lib/libmount.so*,
+-          mr ${getLib pkgs.util-linuxMinimal.out}/lib/libuuid.so*,
+-          mr ${getLib pkgs.xz}/lib/liblzma*.so*,
+-          mr ${getLib pkgs.zlib}/lib/libz*.so*,
++          include <abstractions/ssl_certs>
++          include "${pkgs.apparmorRulesFromClosure
++            { name = "transmission-daemon"; }
++            [ pkgs.transmission ]}"
++          include <local/bin.transmission-daemon>
+           r @{PROC}/sys/kernel/random/uuid,
+           r @{PROC}/sys/vm/overcommit_memory,
+-          # @{pid} is not a kernel variable yet but a regexp
+-          #r @{PROC}/@{pid}/environ,
++          r @{PROC}/@{pid}/environ,
+           r @{PROC}/@{pid}/mounts,
+           rwk /tmp/tr_session_id_*,
+           r /run/systemd/resolve/stub-resolv.conf,
+           r ${pkgs.openssl.out}/etc/**,
+           r ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE},
+-          r ${pkgs.transmission}/share/transmission/**,
+           owner rw ${cfg.home}/${settingsDir}/**,
+           rw ${cfg.settings.download-dir}/**,
+@@ -441,12 +405,9 @@ in
+             # https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
+             px ${cfg.settings.script-torrent-done-filename} -> &@{dirs},
+           ''}
+-
+-          # FIXME: enable customizing using https://github.com/NixOS/nixpkgs/pull/93457
+-          # include <local/transmission-daemon>
+         }
+-      '')
+-    ];
 +    '';
-+    security.apparmor.includes."local/bin.transmission-daemon" = ''
-+      r ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE},
-+
-+      owner rw ${cfg.home}/${settingsDir}/**,
-+      rw ${cfg.settings.download-dir}/**,
-+      ${optionalString cfg.settings.incomplete-dir-enabled ''
-+        rw ${cfg.settings.incomplete-dir}/**,
-+      ''}
-+      ${optionalString cfg.settings.watch-dir-enabled ''
-+        r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
-+      ''}
-+      profile dirs {
-+        rw ${cfg.settings.download-dir}/**,
-+        ${optionalString cfg.settings.incomplete-dir-enabled ''
-+          rw ${cfg.settings.incomplete-dir}/**,
-+        ''}
-+        ${optionalString cfg.settings.watch-dir-enabled ''
-+          r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
-+        ''}
-+      }
-+
-+      ${optionalString (cfg.settings.script-torrent-done-enabled &&
-+                        cfg.settings.script-torrent-done-filename != null) ''
-+        # Stack transmission_directories profile on top of
-+        # any existing profile for script-torrent-done-filename
-+        # FIXME: to be tested as I'm not sure it works well with NoNewPrivileges=
-+        # https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
-+        px ${cfg.settings.script-torrent-done-filename} -> &@{dirs},
-+      ''}
-     '';
--    security.apparmor.includes."local/bin.transmission-daemon" = "";
++    security.apparmor.includes."local/bin.transmission-daemon" = "";
    };
  
    meta.maintainers = with lib.maintainers; [ julm ];
-diff --git a/pkgs/applications/networking/p2p/transmission/default.nix b/pkgs/applications/networking/p2p/transmission/default.nix
-index ab4fc0908ba..858b09c9aaa 100644
---- a/pkgs/applications/networking/p2p/transmission/default.nix
-+++ b/pkgs/applications/networking/p2p/transmission/default.nix
-@@ -20,6 +20,7 @@
- , enableSystemd ? stdenv.isLinux
- , enableDaemon ? true
- , enableCli ? true
-+, apparmorRulesFromClosure
+diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
+index afb9c540416..d4ad32a300b 100644
+--- a/nixos/modules/tasks/network-interfaces.nix
++++ b/nixos/modules/tasks/network-interfaces.nix
+@@ -1093,6 +1093,21 @@ in
+     } else {
+       ping.source = "${pkgs.iputils.out}/bin/ping";
+     };
++    security.apparmor.policies."bin.ping".profile = lib.mkIf config.security.apparmor.policies."bin.ping".enable (lib.mkAfter ''
++      /run/wrappers/bin/ping {
++        include <abstractions/base>
++        include <nixos/security.wrappers>
++        rpx /run/wrappers/wrappers.*/ping,
++      }
++      /run/wrappers/wrappers.*/ping {
++        include <abstractions/base>
++        include <nixos/security.wrappers>
++        r /run/wrappers/wrappers.*/ping.real,
++        mrpx ${config.security.wrappers.ping.source},
++        capability net_raw,
++        capability setpcap,
++      }
++    '');
+     # Set the host and domain names in the activation script.  Don't
+     # clear it if it's not configured in the NixOS configuration,
+diff --git a/nixos/modules/virtualisation/lxc.nix b/nixos/modules/virtualisation/lxc.nix
+index f484d5ee59a..0f8b22a45df 100644
+--- a/nixos/modules/virtualisation/lxc.nix
++++ b/nixos/modules/virtualisation/lxc.nix
+@@ -74,9 +74,13 @@ in
+     systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
+     security.apparmor.packages = [ pkgs.lxc ];
+-    security.apparmor.profiles = [
+-      "${pkgs.lxc}/etc/apparmor.d/lxc-containers"
+-      "${pkgs.lxc}/etc/apparmor.d/usr.bin.lxc-start"
+-    ];
++    security.apparmor.policies = {
++      "bin.lxc-start".profile = ''
++        include ${pkgs.lxc}/etc/apparmor.d/usr.bin.lxc-start
++      '';
++      "lxc-containers".profile = ''
++        include ${pkgs.lxc}/etc/apparmor.d/lxc-containers
++      '';
++    };
+   };
+ }
+diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
+index 3958fc2c1d7..876956f654b 100644
+--- a/nixos/modules/virtualisation/lxd.nix
++++ b/nixos/modules/virtualisation/lxd.nix
+@@ -93,11 +93,15 @@ in
+     security.apparmor = {
+       enable = true;
+-      profiles = [
+-        "${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start"
+-        "${cfg.lxcPackage}/etc/apparmor.d/lxc-containers"
+-      ];
+       packages = [ cfg.lxcPackage ];
++      policies = {
++        "bin.lxc-start".profile = ''
++          include ${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start
++        '';
++        "lxc-containers".profile = ''
++          include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers
++        '';
++      };
+     };
+     systemd.services.lxd = {
+diff --git a/pkgs/os-specific/linux/apparmor/default.nix b/pkgs/os-specific/linux/apparmor/default.nix
+index 3ce310acf23..fc776ff2001 100644
+--- a/pkgs/os-specific/linux/apparmor/default.nix
++++ b/pkgs/os-specific/linux/apparmor/default.nix
+@@ -10,11 +10,18 @@
+ , pam
+ , libnotify
+ , buildPackages
++, coreutils
++, gnugrep
++, gnused
++, kmod
++, writeShellScript
++, closureInfo
++, runCommand
  }:
  
  let
-@@ -37,6 +38,8 @@ in stdenv.mkDerivation {
-     fetchSubmodules = true;
+-  apparmor-series = "2.13";
+-  apparmor-patchver = "6";
++  apparmor-series = "3.0";
++  apparmor-patchver = "1";
+   apparmor-version = apparmor-series + "." + apparmor-patchver;
+   apparmor-meta = component: with stdenv.lib; {
+@@ -26,10 +33,16 @@ let
    };
  
-+  outputs = [ "out" "apparmor" ];
+   apparmor-sources = fetchurl {
+-    url = "https://launchpad.net/apparmor/${apparmor-series}/${apparmor-version}/+download/apparmor-${apparmor-version}.tar.gz";
+-    sha256 = "13xshy7905d9q9n8d8i0jmdi9m36wr525g4wlsp8k21n7yvvh9j4";
++    url = "https://launchpad.net/apparmor/${apparmor-series}/${apparmor-series}.${apparmor-patchver}/+download/apparmor-${apparmor-version}.tar.gz";
++    sha256 = "096zbg3v7b51x7f1ly61mzd3iy9alad6sd4lam98j2d6v5ragbcg";
+   };
++  aa-teardown = writeShellScript "aa-teardown" ''
++    PATH="${lib.makeBinPath [coreutils gnused gnugrep]}:$PATH"
++    . ${apparmor-parser}/lib/apparmor/rc.apparmor.functions
++    remove_profiles
++  '';
++
+   prePatchCommon = ''
+     chmod a+x ./common/list_capabilities.sh ./common/list_af_names.sh
+     patchShebangs ./common/list_capabilities.sh ./common/list_af_names.sh
+@@ -45,12 +58,6 @@ let
+       name = "0003-Added-missing-typedef-definitions-on-parser.patch";
+       sha256 = "0yyaqz8jlmn1bm37arggprqz0njb4lhjni2d9c8qfqj0kll0bam0";
+     })
+-    (fetchpatch {
+-      url = "https://git.alpinelinux.org/aports/plain/testing/apparmor/0007-Do-not-build-install-vim-file-with-utils-package.patch?id=74b8427cc21f04e32030d047ae92caa618105b53";
+-      name = "0007-Do-not-build-install-vim-file-with-utils-package.patch";
+-      sha256 = "1m4dx901biqgnr4w4wz8a2z9r9dxyw7wv6m6mqglqwf2lxinqmp4";
+-    })
+-    # (alpine patches {1,4,5,6,8} are needed for apparmor 2.11, but not 2.12)
+     ];
+   # Set to `true` after the next FIXME gets fixed or this gets some
+@@ -121,7 +128,11 @@ let
+       libapparmor.python
+     ];
+-    prePatch = prePatchCommon + ''
++    prePatch = prePatchCommon +
++      # Do not build vim file
++      stdenv.lib.optionalString stdenv.hostPlatform.isMusl ''
++        sed -i ./utils/Makefile -e "/\<vim\>/d"
++      '' + ''
+       substituteInPlace ./utils/apparmor/easyprof.py --replace "/sbin/apparmor_parser" "${apparmor-parser}/bin/apparmor_parser"
+       substituteInPlace ./utils/apparmor/aa.py --replace "/sbin/apparmor_parser" "${apparmor-parser}/bin/apparmor_parser"
+       substituteInPlace ./utils/logprof.conf --replace "/sbin/apparmor_parser" "${apparmor-parser}/bin/apparmor_parser"
+@@ -132,7 +143,8 @@ let
+     installFlags = [ "DESTDIR=$(out)" "BINDIR=$(out)/bin" "VIM_INSTALL_PATH=$(out)/share" "PYPREFIX=" ];
+     postInstall = ''
+-      for prog in aa-audit aa-autodep aa-cleanprof aa-complain aa-disable aa-enforce aa-genprof aa-logprof aa-mergeprof aa-status aa-unconfined ; do
++      sed -i $out/bin/aa-unconfined -e "/my_env\['PATH'\]/d"
++      for prog in aa-audit aa-autodep aa-cleanprof aa-complain aa-disable aa-enforce aa-genprof aa-logprof aa-mergeprof aa-unconfined ; do
+         wrapProgram $out/bin/$prog --prefix PYTHONPATH : "$out/lib/${python.libPrefix}/site-packages:$PYTHONPATH"
+       done
+@@ -140,6 +152,13 @@ let
+       # aa-notify checks its name and does not work named ".aa-notify-wrapped"
+       mv $out/bin/aa-notify $out/bin/aa-notify-wrapped
+       makeWrapper ${perl}/bin/perl $out/bin/aa-notify --set PERL5LIB ${libapparmor}/${perl.libPrefix} --add-flags $out/bin/aa-notify-wrapped
++
++      substituteInPlace $out/bin/aa-remove-unknown \
++       --replace "/lib/apparmor/rc.apparmor.functions" "${apparmor-parser}/lib/apparmor/rc.apparmor.functions"
++      wrapProgram $out/bin/aa-remove-unknown \
++       --prefix PATH : ${lib.makeBinPath [gawk]}
 +
-   cmakeFlags =
-     let
-       mkFlag = opt: if opt then "ON" else "OFF";
-@@ -72,6 +75,30 @@ in stdenv.mkDerivation {
++      ln -s ${aa-teardown} $out/bin/aa-teardown
+     '';
+     inherit doCheck;
+@@ -167,7 +186,7 @@ let
+     prePatch = prePatchCommon;
+     postPatch = "cd ./binutils";
+     makeFlags = [ "LANGS=" "USE_SYSTEM=1" ];
+-    installFlags = [ "DESTDIR=$(out)" "BINDIR=$(out)/bin" ];
++    installFlags = [ "DESTDIR=$(out)" "BINDIR=$(out)/bin" "SBINDIR=$(out)/bin" ];
+     inherit doCheck;
  
-   NIX_LDFLAGS = lib.optionalString stdenv.isDarwin "-framework CoreFoundation";
+@@ -188,6 +207,9 @@ let
+       substituteInPlace ./parser/Makefile --replace "/usr/include/linux/capability.h" "${linuxHeaders}/include/linux/capability.h"
+       ## techdoc.pdf still doesn't build ...
+       substituteInPlace ./parser/Makefile --replace "manpages htmlmanpages pdf" "manpages htmlmanpages"
++      substituteInPlace parser/rc.apparmor.functions \
++       --replace "/sbin/apparmor_parser" "$out/bin/apparmor_parser"
++      sed -i parser/rc.apparmor.functions -e '2i . ${./fix-rc.apparmor.functions.sh}'
+     '';
+     inherit patches;
+     postPatch = "cd ./parser";
+@@ -249,8 +271,35 @@ let
+     meta = apparmor-meta "kernel patches";
+   };
  
++  # Generate generic AppArmor rules in a file,
++  # from the closure of given rootPaths.
++  # To be included in an AppArmor profile like so:
++  # include "$(apparmorRulesFromClosure {} [pkgs.hello]}"
++  apparmorRulesFromClosure =
++    { # The store path of the derivation is given in $path
++      additionalRules ? []
++      # TODO: factorize here some other common paths
++      # that may emerge from use cases.
++    , baseRules ? [
++        "r $path"
++        "r $path/etc/**"
++        "r $path/share/**"
++        # Note that not all libraries are prefixed with "lib",
++        # eg. glibc-2.30/lib/ld-2.30.so
++        "mr $path/lib/**.so*"
++        # eg. glibc-2.30/lib/gconv/gconv-modules
++        "r $path/lib/**"
++      ]
++    , name ? ""
++    }: rootPaths: runCommand
++      ( "apparmor-closure-rules"
++      + stdenv.lib.optionalString (name != "") "-${name}") {} ''
++    touch $out
++    while read -r path
++    do printf >>$out "%s,\n" ${lib.concatMapStringsSep " " (x: "\"${x}\"") (baseRules ++ additionalRules)}
++    done <${closureInfo {inherit rootPaths;}}/store-paths
++  '';
+ in
+-
+ {
+   inherit
+     libapparmor
+@@ -259,5 +308,6 @@ in
+     apparmor-parser
+     apparmor-pam
+     apparmor-profiles
+-    apparmor-kernel-patches;
++    apparmor-kernel-patches
++    apparmorRulesFromClosure;
+ }
+diff --git a/pkgs/os-specific/linux/apparmor/fix-rc.apparmor.functions.sh b/pkgs/os-specific/linux/apparmor/fix-rc.apparmor.functions.sh
+new file mode 100644
+index 00000000000..ebc1baaa92d
+--- /dev/null
++++ b/pkgs/os-specific/linux/apparmor/fix-rc.apparmor.functions.sh
+@@ -0,0 +1,32 @@
++aa_action() {
++  STRING=$1
++  shift
++  $*
++  rc=$?
++  if [ $rc -eq 0 ] ; then
++    aa_log_success_msg $"$STRING "
++  else
++    aa_log_failure_msg $"$STRING "
++  fi
++  return $rc
++}
++
++aa_log_success_msg() {
++   [ -n "$1" ] && echo -n $1
++   echo ": done."
++}
++
++aa_log_warning_msg() {
++   [ -n "$1" ] && echo -n $1
++   echo ": Warning."
++}
++
++aa_log_failure_msg() {
++   [ -n "$1" ] && echo -n $1
++   echo ": Failed."
++}
++
++aa_log_skipped_msg() {
++   [ -n "$1" ] && echo -n $1
++   echo ": Skipped."
++}
+diff --git a/pkgs/os-specific/linux/iputils/default.nix b/pkgs/os-specific/linux/iputils/default.nix
+index 3bb653ebcf7..96134a16635 100644
+--- a/pkgs/os-specific/linux/iputils/default.nix
++++ b/pkgs/os-specific/linux/iputils/default.nix
+@@ -1,6 +1,7 @@
+ { stdenv, fetchFromGitHub, fetchpatch
+ , meson, ninja, pkgconfig, gettext, libxslt, docbook_xsl_ns
+ , libcap, libidn2
++, apparmorRulesFromClosure
+ }:
+ with stdenv.lib;
+@@ -30,6 +31,8 @@ in stdenv.mkDerivation rec {
+     })
+   ];
++  outputs = ["out" "apparmor"];
++
+   mesonFlags = [
+     "-DBUILD_RARPD=true"
+     "-DBUILD_TRACEROUTE6=true"
+@@ -44,6 +47,25 @@ in stdenv.mkDerivation rec {
+   nativeBuildInputs = [ meson ninja pkgconfig gettext libxslt.bin docbook_xsl_ns ];
+   buildInputs = [ libcap ]
+     ++ optional (!stdenv.hostPlatform.isMusl) libidn2;
 +  postInstall = ''
-+    install -D /dev/stdin $apparmor/bin.transmission-daemon <<EOF
++    install -D /dev/stdin $apparmor/bin.ping <<EOF
 +    include <tunables/global>
-+    $out/bin/transmission-daemon {
++    $out/bin/ping {
 +      include <abstractions/base>
++      include <abstractions/consoles>
 +      include <abstractions/nameservice>
-+      include <abstractions/ssl_certs>
-+      include "${apparmorRulesFromClosure { name = "transmission-daemon"; } ([
-+        curl libevent openssl pcre zlib
-+      ] ++ lib.optionals enableSystemd [ systemd ]
-+        ++ lib.optionals stdenv.isLinux [ inotify-tools ]
-+      )}"
-+      r @{PROC}/sys/kernel/random/uuid,
-+      r @{PROC}/sys/vm/overcommit_memory,
++      include "${apparmorRulesFromClosure { name = "ping"; }
++       ([libcap] ++ optional (!stdenv.hostPlatform.isMusl) libidn2)}"
++      include <local/bin.ping>
++      capability net_raw,
++      network inet raw,
++      network inet6 raw,
++      mr $out/bin/ping,
++      r $out/share/locale/**,
 +      r @{PROC}/@{pid}/environ,
-+      r @{PROC}/@{pid}/mounts,
-+      rwk /tmp/tr_session_id_*,
-+      r /run/systemd/resolve/stub-resolv.conf,
++    }
++    EOF
++  '';
+   meta = {
+     description = "A set of small useful utilities for Linux networking";
+diff --git a/pkgs/tools/networking/inetutils/default.nix b/pkgs/tools/networking/inetutils/default.nix
+index 1290ec2bdb1..d88f2697085 100644
+--- a/pkgs/tools/networking/inetutils/default.nix
++++ b/pkgs/tools/networking/inetutils/default.nix
+@@ -1,4 +1,6 @@
+-{ stdenv, lib, fetchurl, ncurses, perl, help2man }:
++{ stdenv, lib, fetchurl, ncurses, perl, help2man
++, apparmorRulesFromClosure
++}:
+ stdenv.mkDerivation rec {
+   name = "inetutils-1.9.4";
+@@ -8,6 +10,8 @@ stdenv.mkDerivation rec {
+     sha256 = "05n65k4ixl85dc6rxc51b1b732gnmm8xnqi424dy9f1nz7ppb3xy";
+   };
++  outputs = ["out" "apparmor"];
 +
-+      include <local/bin.transmission-daemon>
+   patches = [
+     ./whois-Update-Canadian-TLD-server.patch
+     ./service-name.patch
+@@ -41,6 +45,22 @@ stdenv.mkDerivation rec {
+   installFlags = [ "SUIDMODE=" ];
++  postInstall = ''
++    install -D /dev/stdin $apparmor/bin.ping <<EOF
++    $out/bin/ping {
++      include <abstractions/base>
++      include <abstractions/consoles>
++      include <abstractions/nameservice>
++      include "${apparmorRulesFromClosure { name = "ping"; } [stdenv.cc.libc]}"
++      include <local/bin.ping>
++      capability net_raw,
++      network inet raw,
++      network inet6 raw,
++      mr $out/bin/ping,
 +    }
 +    EOF
 +  '';
 +
-   meta = {
-     description = "A fast, easy and free BitTorrent client";
-     longDescription = ''
+   meta = with lib; {
+     description = "Collection of common network programs";
+diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
+index 32bba34a798..4306fc7662e 100644
+--- a/pkgs/top-level/all-packages.nix
++++ b/pkgs/top-level/all-packages.nix
+@@ -17979,7 +17979,7 @@ in
+   inherit (callPackages ../os-specific/linux/apparmor { python = python3; })
+     libapparmor apparmor-utils apparmor-bin-utils apparmor-parser apparmor-pam
+-    apparmor-profiles apparmor-kernel-patches;
++    apparmor-profiles apparmor-kernel-patches apparmorRulesFromClosure;
+   atop = callPackage ../os-specific/linux/atop { };