feat(ai): add webdav mount (#253)

feat(aquamarine): add group for filesharing, protect /data on subvolume mount failures
This commit is contained in:
Ryan Yin
2026-03-19 22:25:48 +08:00
committed by GitHub
parent 94e4598681
commit b143a89443
7 changed files with 74 additions and 51 deletions

6
flake.lock generated
View File

@@ -583,10 +583,10 @@
"mysecrets": { "mysecrets": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1773850740, "lastModified": 1773927262,
"narHash": "sha256-izDf+wgii0KGAfNZ3/RFt2HJgUY/s+u8qKyKvNi0uGc=", "narHash": "sha256-nxx7jAiHGTYU6hXdjHYjdR1SJNgFcVPokiFlR8MZwTc=",
"ref": "refs/heads/main", "ref": "refs/heads/main",
"rev": "86de5313787257806723f03dccabd52bb7501ff3", "rev": "c388ca33beb5c099bf4603e6ab25a2e94af00b80",
"shallow": true, "shallow": true,
"type": "git", "type": "git",
"url": "ssh://git@github.com/ryan4yin/nix-secrets.git" "url": "ssh://git@github.com/ryan4yin/nix-secrets.git"

View File

@@ -1,38 +1,25 @@
{ {
config, config,
myvars,
... ...
}: }:
{ {
# supported file systems, so we can mount any removable disks with these filesystems # enable davfs2 driver for webdav
boot.supportedFilesystems = [ services.davfs2.enable = true;
# "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}"
# ];
# };
# mount a webdav share # mount a webdav share
# https://wiki.archlinux.org/title/Davfs2 # https://wiki.archlinux.org/title/Davfs2
# fileSystems."/home/${myvars.username}/webdav-downloads" = { fileSystems."/mnt/fileshare" = {
# device = "https://webdav.writefor.fun/Downloads"; device = "https://webdav.writefor.fun/";
# fsType = "davfs"; fsType = "davfs";
# options = [ options = [
# # https://www.freedesktop.org/software/systemd/man/latest/systemd.mount.html # https://www.freedesktop.org/software/systemd/man/latest/systemd.mount.html
# "nofail,_netdev" "nofail,_netdev"
# "uid=1000,gid=100,dir_mode=0755,file_mode=0755" "uid=1000,gid=100,dir_mode=0750,file_mode=0750"
# ]; ];
# }; };
# davfs2 reads its credentials from /etc/davfs2/secrets # 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";
};
} }

View File

@@ -7,6 +7,30 @@ let
unlockDisk = "data-encrypted"; unlockDisk = "data-encrypted";
in 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" ]; 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. # By adding this crypttab entry, the disk will be unlocked by systemd-cryptsetup@xxx.service at boot time.

View File

@@ -5,12 +5,21 @@ let
in in
{ {
# Read SFTPGO_DEFAULT_ADMIN_USERNAME and SFTPGO_DEFAULT_ADMIN_PASSWORD from a file # 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 # Create Directories
# https://www.freedesktop.org/software/systemd/man/latest/tmpfiles.d.html#Type # 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 = [ systemd.tmpfiles.rules = [
"d ${dataDir} 0755 ${user} ${user}" "d ${dataDir} 0755 ${user} ${user} -"
"d /data/fileshare/public 2775 root fileshare -"
]; ];
services.sftpgo = { services.sftpgo = {

View File

@@ -9,6 +9,17 @@ let
name = "transmission"; name = "transmission";
in 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 # the headless Transmission BitTorrent daemon
# https://github.com/NixOS/nixpkgs/blob/nixos-25.11/nixos/modules/services/torrent/transmission.nix # https://github.com/NixOS/nixpkgs/blob/nixos-25.11/nixos/modules/services/torrent/transmission.nix
# https://wiki.archlinux.org/title/transmission # https://wiki.archlinux.org/title/transmission
@@ -18,7 +29,8 @@ in
user = name; user = name;
group = name; group = name;
home = dataDir; 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. # 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. # Note that you may also want to increase peer-limit-global.

View File

@@ -19,6 +19,9 @@
plugdev = { }; plugdev = { };
# misc # misc
uinput = { }; uinput = { };
# shared group for services that read/write the same data directory
# (e.g. sftpgo + transmission on aquamarine)
fileshare = { };
}; };
users.users."${myvars.username}" = { users.users."${myvars.username}" = {
@@ -36,6 +39,7 @@
"wireshark" "wireshark"
"adbusers" # android debugging "adbusers" # android debugging
"libvirtd" # virt-viewer / qemu "libvirtd" # virt-viewer / qemu
"fileshare"
]; ];
}; };

View File

@@ -99,19 +99,11 @@ in
} }
// noaccess; // noaccess;
# ---------------------------------------------
# only root can read this file.
# ---------------------------------------------
"wg-business.conf" = {
file = "${mysecrets}/wg-business.conf.age";
}
// high_security;
# Used only by NixOS Modules # Used only by NixOS Modules
# smb-credentials is referenced in /etc/fstab, by ../hosts/ai/cifs-mount.nix
"smb-credentials" = { # referenced in /etc/fstab to mount davfs volume
file = "${mysecrets}/smb-credentials.age"; "davfs-secrets" = {
file = "${mysecrets}/davfs-secrets.age";
} }
// high_security; // high_security;
@@ -138,11 +130,6 @@ in
# place secrets in /etc/ # place secrets in /etc/
environment.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" = { "agenix/rclone.conf" = {
source = config.age.secrets."rclone.conf".path; source = config.age.secrets."rclone.conf".path;
}; };