From 48d6d6f7a208cfb985d0953b0ea73950683087bd Mon Sep 17 00:00:00 2001 From: beatzaplenty Date: Mon, 27 Jul 2026 05:27:56 +1000 Subject: [PATCH] fix(lxc): auto-derive privileged from NFS fileSystems, not hostname list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the hardcoded hostname check (docker, pxe-boot) with a check on config.fileSystems: any lxc-* host whose NixOS config declares an NFS fileSystem entry is automatically made privileged. The script already reads proxmoxLXC.privileged dynamically via flake_target_lxc_privileged, so no logic change is needed there — only the comment is updated to describe the new derivation. Result: lxc-docker and lxc-pxe-boot (the two with NFS mounts) evaluate as privileged=true; lxc-nix-cache, lxc-minimal, lxc-server, lxc-tailscale-router, lxc-tor-relay evaluate as privileged=false. Any future lxc-* host that declares an NFS mount gets the correct privilege level for free without a separate manual edit. Co-Authored-By: Claude Sonnet 4.6 --- modules/platforms/lxc.nix | 35 ++++++++++------------ scripts/proxmox/create-proxmox-resource.sh | 22 +++++++------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/modules/platforms/lxc.nix b/modules/platforms/lxc.nix index fb73b4f..0eb0f42 100644 --- a/modules/platforms/lxc.nix +++ b/modules/platforms/lxc.nix @@ -63,25 +63,22 @@ in # back to decide `pct create`'s --unprivileged flag, so the two stay # in sync). # - # lxc-docker and lxc-pxe-boot are the exceptions: the kernel's NFS client - # doesn't set FS_USERNS_MOUNT, so mounting NFS from inside *any* non-init - # user namespace -- which is exactly what an unprivileged container's - # UID-mapped root runs in -- is rejected at the VFS layer with EPERM, - # no matter what Proxmox's own `mount=nfs;nfs4` container feature - # allows at the AppArmor layer (confirmed live: TCP to the NFS server - # succeeds, the server's export table matches the container's IP, and - # `mount.nfs: Operation not permitted` still fires immediately with no - # corresponding denial anywhere in the server's logs -- a kernel-level - # rejection, not a network or export-permission one). Keying off - # hostName rather than something build-type-specific because - # modules/build-types/{docker,pxe-boot}.nix are also composed for - # linode-docker/proxmox-docker/proxmox-pxe-boot, which don't import - # proxmox-lxc.nix at all -- setting this option there would break their - # eval with "option does not exist" regardless of any mkIf guard, since - # mkIf only makes a value conditional, not whether the option needs to - # exist somewhere. - privileged = config.networking.hostName == "docker" - || config.networking.hostName == "pxe-boot"; + # Any lxc-* host with an NFS fileSystem must be privileged: the kernel's + # NFS client doesn't set FS_USERNS_MOUNT, so mounting NFS from inside + # *any* non-init user namespace -- which is exactly what an unprivileged + # container's UID-mapped root runs in -- is rejected at the VFS layer + # with EPERM, no matter what Proxmox's own `mount=nfs;nfs4` container + # feature allows at the AppArmor layer (confirmed live: TCP to the NFS + # server succeeds, the server's export table matches the container's IP, + # and `mount.nfs: Operation not permitted` still fires immediately with + # no corresponding denial anywhere in the server's logs -- a kernel-level + # rejection, not a network or export-permission one). Deriving this from + # fileSystems rather than a per-host override keeps it self-consistent: + # any new lxc-* host that declares an NFS mount automatically gets the + # privilege level it needs without a separate manual flag. + privileged = builtins.any + (fs: fs.fsType == "nfs" || fs.fsType == "nfs4") + (builtins.attrValues config.fileSystems); }; boot.loader = { diff --git a/scripts/proxmox/create-proxmox-resource.sh b/scripts/proxmox/create-proxmox-resource.sh index e0b5374..918085f 100755 --- a/scripts/proxmox/create-proxmox-resource.sh +++ b/scripts/proxmox/create-proxmox-resource.sh @@ -867,17 +867,17 @@ if [[ "$type" == "lxc" ]]; then local_swap="${swap:-$memory}" # --unprivileged: read back from modules/platforms/lxc.nix's own # proxmoxLXC.privileged (via flake_target_lxc_privileged) rather than - # hardcoded, since that's no longer the same for every lxc-* target -- - # lxc-docker sets it true so the container's NFS mounts work at all (the - # kernel's NFS client can't mount from inside any unprivileged - # container's user namespace, no matter what AppArmor allows -- see that - # option's own comment). The NixOS config inside the image bakes in - # cgroup/capability/mount expectations matching whichever value it was - # built with, so this must stay in sync with it -- `pct create`'s own - # CLI default for this flag is privileged (unlike the web UI, which - # defaults its checkbox the other way), so leaving it unset would create - # a privileged container running a NixOS config that assumes - # unprivileged for every target except lxc-docker, a real mismatch. + # hardcoded. lxc.nix derives this automatically: any lxc-* host whose + # config.fileSystems has an NFS entry gets privileged=true, because the + # kernel's NFS client (FS_USERNS_MOUNT not set) rejects NFS mounts from + # inside any non-init user namespace -- exactly what an unprivileged + # container's UID-mapped root lives in -- with EPERM at the VFS layer, + # regardless of AppArmor (see lxc.nix's own comment). The NixOS config + # bakes in cgroup/capability/mount expectations matching whichever value + # it was built with, so this must stay in sync -- `pct create`'s CLI + # default is privileged (unlike the web UI, which defaults the other + # way), so leaving it unset would create a privileged container running + # a NixOS config that assumes unprivileged, a real mismatch. privileged_eval="$(flake_target_lxc_privileged "$repo_root" "$flake_target")" unprivileged_flag=1 [[ "$privileged_eval" == "true" ]] && unprivileged_flag=0