{ config, lib, pkgs, ... }: # External hard-drive storage module. # # Each host sets: # homey.storage.device = "/dev/disk/by-id/usb-WD_..."; (by-id is stable across reboots) # homey.storage.mountPoint = "/mnt/data"; (default) # # All service data lives under //, so the whole # dataset can be browsed, backed up, or restored with plain filesystem tools. # # Directory layout under mountPoint: # openldap/ # etc-ldap-slapd.d/ ← /etc/ldap/slapd.d in container # var-lib-ldap/ ← /var/lib/ldap in container # authelia/ # config/ ← /config in container (sqlite db etc.) # gitea/ # data/ ← /data in container # nextcloud/ # html/ ← /var/www/html in container # db/ ← /var/lib/postgresql/data in postgres container # jellyfin/ # config/ # media/ # movies/ # tvshows/ # general/ # complete/ # transmission/ # config/ # uptime-kuma/ ← /app/data in uptime-kuma container (SQLite DB, config) # ntfy/ # auth.db ← user/token auth database # cache.db ← message cache # attachments/ ← file attachments # restic-cache/ ← restic local cache (not the backup destination) let cfg = config.homey.storage; in { options.homey.storage = { device = lib.mkOption { type = lib.types.str; example = "/dev/disk/by-id/usb-WD_Elements_12345-0:0"; description = '' Block device for the external hard drive. Use /dev/disk/by-id/ paths for stable identification across reboots. Leave empty to skip automount (useful during initial setup). ''; default = ""; }; mountPoint = lib.mkOption { type = lib.types.str; default = "/mnt/data"; description = "Where the external HD is mounted. All service data lives here."; }; fsType = lib.mkOption { type = lib.types.str; default = "ext4"; description = "Filesystem type of the external drive."; }; extraDirs = lib.mkOption { type = lib.types.listOf (lib.types.submodule { options = { path = lib.mkOption { type = lib.types.str; description = "Path relative to mountPoint (e.g. \"gitea/data\")."; }; mode = lib.mkOption { type = lib.types.str; default = "0750"; }; user = lib.mkOption { type = lib.types.str; default = "root"; }; group = lib.mkOption { type = lib.types.str; default = "root"; }; }; }); default = []; description = "Per-service directories to create under mountPoint. Each service module contributes its own entries."; }; }; config = lib.mkIf (cfg.device != "") { # Mount the external drive fileSystems."${cfg.mountPoint}" = { device = cfg.device; fsType = cfg.fsType; options = [ "defaults" "nofail" # Don't block boot if drive is absent "noatime" # Better performance / less SD wear "x-systemd.automount" "x-systemd.idle-timeout=0" ]; }; # Mount point root + shared infrastructure dirs (restic cache, shared media). # Per-service dirs are contributed via homey.storage.extraDirs by each # service module, so adding a new service only requires editing that module. systemd.tmpfiles.rules = [ "d ${cfg.mountPoint} 0755 root root -" # Shared media directories used by both Jellyfin and Transmission. "d ${cfg.mountPoint}/media 0755 root root -" "d ${cfg.mountPoint}/media/movies 0755 root root -" "d ${cfg.mountPoint}/media/tvshows 0755 root root -" "d ${cfg.mountPoint}/media/general 0755 root root -" "d ${cfg.mountPoint}/media/complete 0755 root root -" "d ${cfg.mountPoint}/restic-cache 0700 root root -" ] ++ (map (d: "d ${cfg.mountPoint}/${d.path} ${d.mode} ${d.user} ${d.group} -") config.homey.storage.extraDirs); }; }