diff --git a/flake.lock b/flake.lock index 94e9c2d3..e37c4d78 100644 --- a/flake.lock +++ b/flake.lock @@ -583,10 +583,10 @@ "mysecrets": { "flake": false, "locked": { - "lastModified": 1773850740, - "narHash": "sha256-izDf+wgii0KGAfNZ3/RFt2HJgUY/s+u8qKyKvNi0uGc=", + "lastModified": 1773927262, + "narHash": "sha256-nxx7jAiHGTYU6hXdjHYjdR1SJNgFcVPokiFlR8MZwTc=", "ref": "refs/heads/main", - "rev": "86de5313787257806723f03dccabd52bb7501ff3", + "rev": "c388ca33beb5c099bf4603e6ab25a2e94af00b80", "shallow": true, "type": "git", "url": "ssh://git@github.com/ryan4yin/nix-secrets.git" diff --git a/hosts/idols-ai/netdev-mount.nix b/hosts/idols-ai/netdev-mount.nix index 9518d246..d0f30c2b 100644 --- a/hosts/idols-ai/netdev-mount.nix +++ b/hosts/idols-ai/netdev-mount.nix @@ -1,38 +1,25 @@ { config, - myvars, ... }: { - # supported file systems, so we can mount any removable disks with these filesystems - boot.supportedFilesystems = [ - # "cifs" - "davfs" - ]; - - # mount a smb/cifs share - # fileSystems."/home/${myvars.username}/SMB-Downloads" = { - # device = "//windows-server-nas/Downloads"; - # fsType = "cifs"; - # options = [ - # # https://www.freedesktop.org/software/systemd/man/latest/systemd.mount.html - # "nofail,_netdev" - # "uid=1000,gid=100,dir_mode=0755,file_mode=0755" - # "vers=3.0,credentials=${config.age.secrets.smb-credentials.path}" - # ]; - # }; + # enable davfs2 driver for webdav + services.davfs2.enable = true; # mount a webdav share # https://wiki.archlinux.org/title/Davfs2 - # fileSystems."/home/${myvars.username}/webdav-downloads" = { - # device = "https://webdav.writefor.fun/Downloads"; - # fsType = "davfs"; - # options = [ - # # https://www.freedesktop.org/software/systemd/man/latest/systemd.mount.html - # "nofail,_netdev" - # "uid=1000,gid=100,dir_mode=0755,file_mode=0755" - # ]; - # }; + fileSystems."/mnt/fileshare" = { + device = "https://webdav.writefor.fun/"; + fsType = "davfs"; + options = [ + # https://www.freedesktop.org/software/systemd/man/latest/systemd.mount.html + "nofail,_netdev" + "uid=1000,gid=100,dir_mode=0750,file_mode=0750" + ]; + }; # davfs2 reads its credentials from /etc/davfs2/secrets - # environment.etc."davfs2/secrets".source = config.age.secrets."davfs-secrets".path; + environment.etc."davfs2/secrets" = { + source = config.age.secrets."davfs-secrets".path; + mode = "0600"; + }; } diff --git a/hosts/idols-aquamarine/disko-fs.nix b/hosts/idols-aquamarine/disko-fs.nix index 837b9dce..724ec30d 100644 --- a/hosts/idols-aquamarine/disko-fs.nix +++ b/hosts/idols-aquamarine/disko-fs.nix @@ -7,6 +7,30 @@ let unlockDisk = "data-encrypted"; in { + # Bind-mount a dedicated backing directory (/data-ro) onto /data as read-only. + # Using a separate source instead of a self-bind avoids the duplicate mount + # entries that a self-bind (device == mountpoint) would produce in lsblk. + # Disk subvolumes (/data/apps, /data/fileshare, …) are mounted on top by + # systemd automatically (path-hierarchy ordering). If any subvolume fails + # (nofail), its subdirectory falls back to this read-only layer and ALL + # writes — including root — are rejected with EROFS. + fileSystems."/data" = { + device = "/data-ro"; + fsType = "none"; + options = [ + "bind" + "ro" + ]; + }; + + # Pre-create the backing directory and subvolume mountpoints on the root + # filesystem. activation runs before sysinit.target (before all mount + # units), and writes to /data-ro (not the ro-mounted /data), so this is + # safe to re-run on nixos-rebuild switch. + system.activationScripts.data-ro-backing.text = '' + mkdir -p /data-ro/fileshare /data-ro/apps /data-ro/backups /data-ro/apps-snapshots + ''; + fileSystems."/data/fileshare/public".depends = [ "/data/fileshare" ]; # By adding this crypttab entry, the disk will be unlocked by systemd-cryptsetup@xxx.service at boot time. diff --git a/hosts/idols-aquamarine/sftpgo.nix b/hosts/idols-aquamarine/sftpgo.nix index 2067c795..127114b5 100644 --- a/hosts/idols-aquamarine/sftpgo.nix +++ b/hosts/idols-aquamarine/sftpgo.nix @@ -5,12 +5,21 @@ let in { # Read SFTPGO_DEFAULT_ADMIN_USERNAME and SFTPGO_DEFAULT_ADMIN_PASSWORD from a file - systemd.services.sftpgo.serviceConfig.EnvironmentFile = config.age.secrets."sftpgo.env".path; + systemd.services.sftpgo.serviceConfig = { + EnvironmentFile = config.age.secrets."sftpgo.env".path; + }; + + # Join the shared fileshare group (defined globally in user-group.nix) so + # sftpgo can read/write files created by transmission, and vice versa. + users.users.${user}.extraGroups = [ "fileshare" ]; # Create Directories # https://www.freedesktop.org/software/systemd/man/latest/tmpfiles.d.html#Type + # Mode 2775: setgid ensures new files/dirs inherit the 'fileshare' group + # regardless of the creating process's primary group. systemd.tmpfiles.rules = [ - "d ${dataDir} 0755 ${user} ${user}" + "d ${dataDir} 0755 ${user} ${user} -" + "d /data/fileshare/public 2775 root fileshare -" ]; services.sftpgo = { diff --git a/hosts/idols-aquamarine/transmission.nix b/hosts/idols-aquamarine/transmission.nix index f8897709..ba38533a 100644 --- a/hosts/idols-aquamarine/transmission.nix +++ b/hosts/idols-aquamarine/transmission.nix @@ -9,6 +9,17 @@ let name = "transmission"; in { + # Join the shared fileshare group so transmission can read/write files + # created by sftpgo, and vice versa (via setgid directories). + users.users.${name}.extraGroups = [ "fileshare" ]; + + # Set up transmission's home dir with setgid + fileshare group ownership. + # The setgid bit (2) causes all files created here to inherit the group + # 'fileshare', regardless of which service creates them. + systemd.tmpfiles.rules = [ + "d ${dataDir} 2775 ${name} fileshare -" + ]; + # the headless Transmission BitTorrent daemon # https://github.com/NixOS/nixpkgs/blob/nixos-25.11/nixos/modules/services/torrent/transmission.nix # https://wiki.archlinux.org/title/transmission @@ -18,7 +29,8 @@ in user = name; group = name; home = dataDir; - downloadDirPermissions = "0770"; + # 2770: setgid preserves fileshare group on download/incomplete dirs. + downloadDirPermissions = "2770"; # Whether to enable tweaking of kernel parameters to open many more connections at the same time. # Note that you may also want to increase peer-limit-global. diff --git a/modules/nixos/base/user-group.nix b/modules/nixos/base/user-group.nix index 3ecbc83c..23cc2efa 100644 --- a/modules/nixos/base/user-group.nix +++ b/modules/nixos/base/user-group.nix @@ -19,6 +19,9 @@ plugdev = { }; # misc uinput = { }; + # shared group for services that read/write the same data directory + # (e.g. sftpgo + transmission on aquamarine) + fileshare = { }; }; users.users."${myvars.username}" = { @@ -36,6 +39,7 @@ "wireshark" "adbusers" # android debugging "libvirtd" # virt-viewer / qemu + "fileshare" ]; }; diff --git a/secrets/nixos.nix b/secrets/nixos.nix index 305ee11a..5d92d7fe 100644 --- a/secrets/nixos.nix +++ b/secrets/nixos.nix @@ -99,19 +99,11 @@ in } // noaccess; - # --------------------------------------------- - # only root can read this file. - # --------------------------------------------- - - "wg-business.conf" = { - file = "${mysecrets}/wg-business.conf.age"; - } - // high_security; - # Used only by NixOS Modules - # smb-credentials is referenced in /etc/fstab, by ../hosts/ai/cifs-mount.nix - "smb-credentials" = { - file = "${mysecrets}/smb-credentials.age"; + + # referenced in /etc/fstab to mount davfs volume + "davfs-secrets" = { + file = "${mysecrets}/davfs-secrets.age"; } // high_security; @@ -138,11 +130,6 @@ in # place secrets in /etc/ environment.etc = { - # wireguard config used with `wg-quick up wg-business` - "wireguard/wg-business.conf" = { - source = config.age.secrets."wg-business.conf".path; - }; - "agenix/rclone.conf" = { source = config.age.secrets."rclone.conf".path; };