{ config, pkgs, ... }: { ######################################## # Samba File Sharing ######################################## services.samba = { enable = true; openFirewall = true; # Enable nmbd for NetBIOS name resolution and browsing (legacy Windows/macOS) nmbd.enable = true; # Enable WSD discovery (Windows) and mDNS/NetBIOS advertisement improvements via wsdd + Avahi. # "smb encrypt" set to desired to avoid discovery issues with some clients while still allowing encryption. settings = { global = { workgroup = "WORKGROUP"; "server string" = "NixFiles"; "netbios name" = "fileserver"; security = "user"; "map to guest" = "bad user"; "unix extensions" = "no"; # macOS optimizations "vfs objects" = "catia fruit streams_xattr"; "fruit:aapl" = "yes"; "fruit:metadata" = "stream"; "fruit:resource" = "stream"; "fruit:model" = "MacSamba"; "fruit:advertise_fullsync" = "yes"; "ea support" = "yes"; # Time Machine support "fruit:time machine" = "yes"; "fruit:time machine max size" = "500G"; # Prefer encryption but do not require it for basic browsing/guest visibility "smb encrypt" = "desired"; # Better macOS discovery "min protocol" = "SMB2"; "max protocol" = "SMB3"; "server role" = "standalone server"; # Allow Apple extended attributes & full sync advertisement already configured above. }; daten = { path = "/data/daten"; browseable = "yes"; "read only" = "no"; "valid users" = "danlin"; "fruit:time machine" = "no"; }; daten-share = { path = "/data/daten/share"; browseable = "yes"; "read only" = "no"; "guest ok" = "yes"; # Gastzugriff erlaubt public = "yes"; # Alias für guest ok # Removed "valid users" to allow true guest access; danlin can still write via group permissions "fruit:time machine" = "no"; }; timemachine = { path = "/data/backup/timemachine"; browseable = "yes"; "read only" = "no"; "valid users" = "danlin"; "fruit:time machine" = "yes"; "fruit:time machine max size" = "500G"; }; }; }; ######################################## # Network Discovery Services (Avahi + WSD) ######################################## # Avahi for macOS Finder discovery (mDNS) and general _smb._tcp advertising services.avahi = { enable = true; nssmdns4 = true; # Provide .local resolution openFirewall = true; publish = { enable = true; userServices = true; }; # Explicit SMB service advertisement for macOS Finder extraServiceFiles = { smb = '' %h _smb._tcp 445 _device-info._tcp 0 model=TimeCapsule8,119 _adisk._tcp 9 sys=waMa=0,adVF=0x100 dk0=adVN=timemachine,adVF=0x82 ''; }; }; # Windows network neighborhood discovery via Web Services for Devices (WSD) # NixOS does not provide a `services.wsdd` option in this release, so define a systemd unit. systemd.services.wsdd = { description = "Web Services Discovery daemon for Samba (wsdd)"; wantedBy = [ "multi-user.target" ]; after = [ "network-online.target" "samba.service" ]; wants = [ "network-online.target" ]; serviceConfig = { Type = "simple"; ExecStart = "${pkgs.wsdd}/bin/wsdd --hostname fileserver --workgroup WORKGROUP --interface ens18"; Restart = "on-failure"; RestartSec = 5; }; }; ######################################## # Directory Structure ######################################## systemd.tmpfiles.rules = [ "d /data 0775 root root -" "d /data/daten 0775 danlin users -" "d /data/daten/share 0775 danlin users -" # Gitea (runs as git user inside container, uid 1000) "d /data/gitea 0755 root root -" # DJ-Struktur "d /data/daten/DJing 0775 danlin users -" "d /data/daten/DJing/Music 0775 danlin users -" "d /data/daten/DJing/Data 0775 danlin users -" "d /data/daten/DJing/Inbox 0775 danlin users -" # Time Machine backup "d /data/backup 0775 root root -" "d /data/backup/timemachine 0775 danlin users -" # dj-beets project "d /opt/dj-beets 0755 root root -" ]; ######################################## # Virtualization & Containers ######################################## # Virtualization virtualisation.podman.enable = true; virtualisation.oci-containers.backend = "podman"; virtualisation.oci-containers.containers.filebrowser = { image = "gtstef/filebrowser:stable"; autoStart = true; ports = [ "8080:80" ]; volumes = [ "/data:/srv" ]; environment = { TZ = "Europe/Berlin"; FILEBROWSER_ADMIN_PASSWORD = "CHANGE_ME"; }; }; virtualisation.oci-containers.containers.gitea = { image = "gitea/gitea:1.25"; autoStart = true; ports = [ "3000:3000" "2222:2222" ]; volumes = [ "/data/gitea:/data" ]; environment = { USER_UID = "1000"; USER_GID = "1000"; TZ = "Europe/Berlin"; GITEA__server__DOMAIN = "git.home.lindenfelser.de"; GITEA__server__ROOT_URL = "https://git.home.lindenfelser.de/"; GITEA__server__SSH_DOMAIN = "git.home.lindenfelser.de"; GITEA__server__SSH_PORT = "2222"; GITEA__server__SSH_LISTEN_PORT = "2222"; GITEA__server__START_SSH_SERVER = "true"; }; }; virtualisation.oci-containers.containers.dj-beets-cli = { image = "dj-beets:latest"; autoStart = false; # Run manually or via systemd service volumes = [ "/data/daten/DJing:/data/daten/DJing" "/opt/dj-beets/config.yaml:/etc/beets/config.yaml:ro" "/opt/dj-beets/beatport_token.json:/opt/dj-beets/beatport_token.json" ]; environment = { BEETS_CONFIG = "/etc/beets/config.yaml"; }; cmd = [ "tail" "-f" "/dev/null" ]; # Keep container alive }; virtualisation.oci-containers.containers.dj-beets-web = { image = "dj-beets:latest"; autoStart = true; ports = [ "8337:8337" ]; volumes = [ "/data/daten/DJing:/data/daten/DJing" "/opt/dj-beets/config.yaml:/etc/beets/config.yaml:ro" "/opt/dj-beets/beatport_token.json:/opt/dj-beets/beatport_token.json" ]; environment = { BEETS_CONFIG = "/etc/beets/config.yaml"; }; cmd = [ "beet" "web" ]; }; ######################################## # Backup Services ######################################## systemd.services.rclone-backup = { description = "Backup /data to Google Drive via rclone"; serviceConfig = { Type = "oneshot"; ExecStart = '' ${pkgs.rclone}/bin/rclone sync /data gdrive:backup-daten \ --fast-list \ --drive-stop-on-upload-limit \ --log-file=/var/log/rclone-backup.log \ --log-level=INFO ''; User = "root"; }; }; systemd.timers.rclone-backup = { wantedBy = [ "timers.target" ]; timerConfig = { OnCalendar = "03:00"; Persistent = true; }; }; ######################################## # System Packages ######################################## environment.systemPackages = [ pkgs.git pkgs.tmux pkgs.htop pkgs.podman-compose # Beets CLI wrapper (pkgs.writeShellScriptBin "beet" '' exec ${pkgs.podman}/bin/podman run --rm -it \ -v /data/daten/DJing:/data/daten/DJing \ -v /opt/dj-beets/config.yaml:/etc/beets/config.yaml:ro \ -v /opt/dj-beets/beatport_token.json:/opt/dj-beets/beatport_token.json \ -e BEETS_CONFIG=/etc/beets/config.yaml \ -u $(id -u):$(id -g) \ dj-beets:latest \ beet "$@" '') ]; ######################################## # DJ-Beets Services ######################################## # Systemd service to build dj-beets image on deploy systemd.services.dj-beets-build = { description = "Build dj-beets Docker image"; wantedBy = [ "multi-user.target" ]; before = [ "podman-dj-beets-web.service" ]; after = [ "podman.service" ]; wants = [ "podman.service" ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ExecStart = pkgs.writeShellScript "build-dj-beets" '' set -euo pipefail if [ ! -f /opt/dj-beets/Dockerfile ]; then echo "ERROR: Dockerfile not found at /opt/dj-beets" >&2 echo "Deploy with 'make deploy-fileserver' first" >&2 exit 1 fi cd /opt/dj-beets ${pkgs.podman}/bin/podman build -t dj-beets:latest . echo "dj-beets image built successfully" ''; User = "root"; }; }; # Auto-import service using Docker container systemd.services.dj-beets-autoimport = { description = "Auto-import music into beets library via Docker"; after = [ "dj-beets-build.service" ]; wants = [ "dj-beets-build.service" ]; serviceConfig = { Type = "oneshot"; ExecStart = pkgs.writeShellScript "beets-autoimport" '' set -euo pipefail INBOX="/data/daten/DJing/Inbox" LOG="/data/daten/DJing/Data/BeetsAutoImport.log" # Check if inbox has files if ! find "$INBOX" -mindepth 1 -maxdepth 1 -type f -print -quit | grep -q .; then echo "[$(date)] No files in Inbox, skipping" >> "$LOG" exit 0 fi echo "[$(date)] Starting beets import" >> "$LOG" ${pkgs.podman}/bin/podman run --rm \ -v /data/daten/DJing:/data/daten/DJing \ -v /opt/dj-beets/config.yaml:/etc/beets/config.yaml:ro \ -v /opt/dj-beets/beatport_token.json:/opt/dj-beets/beatport_token.json \ -e BEETS_CONFIG=/etc/beets/config.yaml \ localhost/dj-beets:latest \ beet import -q /data/daten/DJing/Inbox >> "$LOG" 2>&1 ''; # Use root so the service can access the root Podman image storage User = "root"; Group = "root"; }; }; systemd.timers.dj-beets-autoimport = { wantedBy = [ "timers.target" ]; timerConfig = { OnCalendar = "*:0/10"; # Every 10 minutes Persistent = true; }; }; ######################################## # Firewall ######################################## networking.firewall.enable = true; networking.firewall.allowedTCPPorts = [ 22 139 445 8080 8337 5357 3000 2222 ]; # 5357 WSD networking.firewall.allowedUDPPorts = [ 137 138 3702 5353 ]; # NetBIOS + WSD (3702) + mDNS (5353) }