fix(lxc): auto-derive privileged from NFS fileSystems, not hostname list

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 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 05:27:56 +10:00
co-authored by Claude Sonnet 4.6
parent cf0d62696f
commit 48d6d6f7a2
2 changed files with 27 additions and 30 deletions
+16 -19
View File
@@ -63,25 +63,22 @@ in
# back to decide `pct create`'s --unprivileged flag, so the two stay # back to decide `pct create`'s --unprivileged flag, so the two stay
# in sync). # in sync).
# #
# lxc-docker and lxc-pxe-boot are the exceptions: the kernel's NFS client # Any lxc-* host with an NFS fileSystem must be privileged: the kernel's
# doesn't set FS_USERNS_MOUNT, so mounting NFS from inside *any* non-init # NFS client doesn't set FS_USERNS_MOUNT, so mounting NFS from inside
# user namespace -- which is exactly what an unprivileged container's # *any* non-init user namespace -- which is exactly what an unprivileged
# UID-mapped root runs in -- is rejected at the VFS layer with EPERM, # container's UID-mapped root runs in -- is rejected at the VFS layer
# no matter what Proxmox's own `mount=nfs;nfs4` container feature # with EPERM, no matter what Proxmox's own `mount=nfs;nfs4` container
# allows at the AppArmor layer (confirmed live: TCP to the NFS server # feature allows at the AppArmor layer (confirmed live: TCP to the NFS
# succeeds, the server's export table matches the container's IP, and # server succeeds, the server's export table matches the container's IP,
# `mount.nfs: Operation not permitted` still fires immediately with no # and `mount.nfs: Operation not permitted` still fires immediately with
# corresponding denial anywhere in the server's logs -- a kernel-level # no corresponding denial anywhere in the server's logs -- a kernel-level
# rejection, not a network or export-permission one). Keying off # rejection, not a network or export-permission one). Deriving this from
# hostName rather than something build-type-specific because # fileSystems rather than a per-host override keeps it self-consistent:
# modules/build-types/{docker,pxe-boot}.nix are also composed for # any new lxc-* host that declares an NFS mount automatically gets the
# linode-docker/proxmox-docker/proxmox-pxe-boot, which don't import # privilege level it needs without a separate manual flag.
# proxmox-lxc.nix at all -- setting this option there would break their privileged = builtins.any
# eval with "option does not exist" regardless of any mkIf guard, since (fs: fs.fsType == "nfs" || fs.fsType == "nfs4")
# mkIf only makes a value conditional, not whether the option needs to (builtins.attrValues config.fileSystems);
# exist somewhere.
privileged = config.networking.hostName == "docker"
|| config.networking.hostName == "pxe-boot";
}; };
boot.loader = { boot.loader = {
+11 -11
View File
@@ -867,17 +867,17 @@ if [[ "$type" == "lxc" ]]; then
local_swap="${swap:-$memory}" local_swap="${swap:-$memory}"
# --unprivileged: read back from modules/platforms/lxc.nix's own # --unprivileged: read back from modules/platforms/lxc.nix's own
# proxmoxLXC.privileged (via flake_target_lxc_privileged) rather than # proxmoxLXC.privileged (via flake_target_lxc_privileged) rather than
# hardcoded, since that's no longer the same for every lxc-* target -- # hardcoded. lxc.nix derives this automatically: any lxc-* host whose
# lxc-docker sets it true so the container's NFS mounts work at all (the # config.fileSystems has an NFS entry gets privileged=true, because the
# kernel's NFS client can't mount from inside any unprivileged # kernel's NFS client (FS_USERNS_MOUNT not set) rejects NFS mounts from
# container's user namespace, no matter what AppArmor allows -- see that # inside any non-init user namespace -- exactly what an unprivileged
# option's own comment). The NixOS config inside the image bakes in # container's UID-mapped root lives in -- with EPERM at the VFS layer,
# cgroup/capability/mount expectations matching whichever value it was # regardless of AppArmor (see lxc.nix's own comment). The NixOS config
# built with, so this must stay in sync with it -- `pct create`'s own # bakes in cgroup/capability/mount expectations matching whichever value
# CLI default for this flag is privileged (unlike the web UI, which # it was built with, so this must stay in sync -- `pct create`'s CLI
# defaults its checkbox the other way), so leaving it unset would create # default is privileged (unlike the web UI, which defaults the other
# a privileged container running a NixOS config that assumes # way), so leaving it unset would create a privileged container running
# unprivileged for every target except lxc-docker, a real mismatch. # a NixOS config that assumes unprivileged, a real mismatch.
privileged_eval="$(flake_target_lxc_privileged "$repo_root" "$flake_target")" privileged_eval="$(flake_target_lxc_privileged "$repo_root" "$flake_target")"
unprivileged_flag=1 unprivileged_flag=1
[[ "$privileged_eval" == "true" ]] && unprivileged_flag=0 [[ "$privileged_eval" == "true" ]] && unprivileged_flag=0