# Opt-in services & integrations — off by default, one toggle each, enabled # in system.nix (see the commented examples in the downstream template). # These are system-side: their own `nomarchy.services.*` namespace, distinct # from the distro's core `nomarchy.system.*` toggles. `username` (from # specialArgs) is read lazily, only where a service needs it. { config, lib, pkgs, ... }@args: let cfg = config.nomarchy.services; in { options.nomarchy.services = { tailscale.enable = lib.mkEnableOption '' Tailscale, the mesh VPN — ships the daemon; authenticate once with `sudo tailscale up`''; syncthing.enable = lib.mkEnableOption '' Syncthing continuous file sync, running as the login user — add folders and devices in the web UI at http://127.0.0.1:8384''; podman.enable = lib.mkEnableOption '' Podman for rootless containers, with `docker` aliased to it and DNS on the default network; the login user gets a subuid/subgid range''; flatpak.enable = lib.mkEnableOption '' Flatpak, with the Flathub remote added system-wide — install apps with `flatpak install flathub `''; pika.enable = lib.mkEnableOption '' Pika Backup, a friendly GUI for scheduled Borg backups (the app; configure repositories and schedules inside it)''; steam.enable = lib.mkEnableOption '' Steam (Valve's game store/launcher) via programs.steam — which wires up the 32-bit graphics libraries, controller udev rules and Remote-Play firewall ports a bare package can't. Unfree''; libvirt.enable = lib.mkEnableOption '' libvirt/KVM virtualisation with the virt-manager GUI — runs the libvirtd daemon and adds the login user to the `libvirtd` group, so VMs can be managed without root''; obs.enable = lib.mkEnableOption '' OBS Studio for screen recording and streaming, plus a v4l2loopback virtual camera — so an OBS scene can be selected as a webcam in Zoom, Teams or a browser (screen capture itself already works over the desktop's PipeWire portal)''; docker.enable = lib.mkEnableOption '' Docker (rootful) with the login user in the `docker` group. Mutually exclusive with the podman toggle's docker-compat (both provide the `docker` command) — enable only one''; kdeconnect.enable = lib.mkEnableOption '' KDE Connect for phone integration — shared clipboard, notifications and file transfer with a paired phone; opens the firewall ports it needs''; gamemode.enable = lib.mkEnableOption '' Feral GameMode — a daemon games ask to apply a performance CPU governor and IO/nice priorities while running (via `gamemoderun`)''; adb.enable = lib.mkEnableOption '' the Android platform tools (adb/fastboot) for flashing/debugging phones. systemd ≥258 applies the device uaccess udev rules automatically, so the tools alone are enough — no group dance''; wireshark.enable = lib.mkEnableOption '' Wireshark (the Qt GUI) for packet capture, with the login user in the `wireshark` group so capture works without root''; ollama.enable = lib.mkEnableOption '' Ollama, a local LLM runtime serving an API on 127.0.0.1:11434 (pair it with the lmstudio/alpaca GUIs). CPU by default — set `services.ollama.acceleration` natively for GPU offload''; printing.enable = lib.mkEnableOption '' CUPS printing with Avahi/mDNS, so network printers are auto-discovered (add vendor drivers via `services.printing.drivers`); the menu's System ▸ Printers entry opens the system-config-printer GUI''; openrgb.enable = lib.mkEnableOption '' the OpenRGB daemon and GUI for controlling RGB lighting on peripherals and (where supported) motherboards — installs the udev rules for device access. Some motherboard controllers also need `hardware.i2c.enable`''; restic = { enable = lib.mkEnableOption '' a scheduled (daily) restic backup — headless, complementing the Pika GUI. Backs up `paths` with sane retention (7 daily / 4 weekly / 6 monthly); needs a `repository` and a `passwordFile` (below). List and restore with the generated `restic-nomarchy` wrapper (repo + password baked in), e.g. `restic-nomarchy snapshots`''; repository = lib.mkOption { type = lib.types.str; default = ""; example = "/mnt/backup/restic"; description = '' The restic repository as a path or URL — e.g. `/mnt/backup/restic`, `sftp:user@host:/srv/restic`, `b2:bucket:/path`. Required when `restic.enable` is set. ''; }; passwordFile = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "/etc/nomarchy/restic-password"; description = '' Path to a file holding the repository password. Give an absolute path that lives ON THE MACHINE — not a `./file` in the flake, which would copy the secret into the world-readable Nix store. Required when `restic.enable` is set. ''; }; paths = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "/home" ]; description = "Paths to include in the backup."; }; }; }; config = lib.mkMerge [ (lib.mkIf cfg.tailscale.enable { services.tailscale.enable = true; }) (lib.mkIf cfg.syncthing.enable { services.syncthing = { enable = true; user = args.username; # Manage folders/devices in the GUI; don't let the (empty) declarative # config wipe them on a rebuild. overrideDevices = false; overrideFolders = false; guiAddress = "127.0.0.1:8384"; }; }) (lib.mkIf cfg.podman.enable { virtualisation.podman = { enable = true; dockerCompat = lib.mkDefault true; # `docker` → podman (off if you run real docker) defaultNetwork.settings.dns_enabled = true; # DNS for rootless containers }; # Rootless podman needs a subuid/subgid range for the user. users.users.${args.username}.autoSubUidGidRange = lib.mkDefault true; }) (lib.mkIf cfg.flatpak.enable { services.flatpak.enable = true; # Flatpak ships no remotes; add Flathub system-wide. Idempotent # (--if-not-exists), and it reads the .flatpakrepo over the network, so # it waits for connectivity and simply retries next boot if offline. systemd.services.nomarchy-flathub = { description = "Add the Flathub Flatpak remote"; wantedBy = [ "multi-user.target" ]; wants = [ "network-online.target" ]; after = [ "network-online.target" ]; path = [ pkgs.flatpak ]; serviceConfig.Type = "oneshot"; script = '' flatpak remote-add --system --if-not-exists flathub \ https://dl.flathub.org/repo/flathub.flatpakrepo ''; }; }) (lib.mkIf cfg.pika.enable { environment.systemPackages = [ pkgs.pika-backup ]; }) (lib.mkIf cfg.steam.enable { # The Steam package alone misses the system bits a game needs: the # 32-bit graphics stack, controller udev rules, Remote-Play ports. programs.steam.enable = true; }) (lib.mkIf cfg.libvirt.enable { virtualisation.libvirtd.enable = true; programs.virt-manager.enable = true; # Group membership lets the user reach the system libvirtd socket # (manage VMs) without sudo. Merges with the user's other extraGroups. users.users.${args.username}.extraGroups = [ "libvirtd" ]; }) (lib.mkIf cfg.obs.enable { environment.systemPackages = [ pkgs.obs-studio ]; # Virtual camera: OBS streams a scene into a v4l2 loopback device that # Zoom/Teams/browsers then pick up as a webcam. exclusive_caps=1 is the # bit that makes those apps actually list the device. boot.extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ]; boot.kernelModules = [ "v4l2loopback" ]; boot.extraModprobeConfig = '' options v4l2loopback devices=1 video_nr=10 card_label="OBS Virtual Camera" exclusive_caps=1 ''; }) (lib.mkIf cfg.docker.enable { virtualisation.docker.enable = true; users.users.${args.username}.extraGroups = [ "docker" ]; # Podman's docker-compat and real Docker both claim the `docker` # command — guard against enabling both (unless you turn that off). assertions = [{ assertion = !(cfg.podman.enable && config.virtualisation.podman.dockerCompat); message = '' nomarchy.services: docker and podman both provide the `docker` command. Enable only one, or set `virtualisation.podman.dockerCompat = false`. ''; }]; }) (lib.mkIf cfg.kdeconnect.enable { programs.kdeconnect.enable = true; # installs the app and opens its ports }) (lib.mkIf cfg.gamemode.enable { programs.gamemode.enable = true; }) (lib.mkIf cfg.adb.enable { # `programs.adb` was removed — systemd ≥258 handles the device uaccess # udev rules, so the tools package is all that's needed now. environment.systemPackages = [ pkgs.android-tools ]; }) (lib.mkIf cfg.wireshark.enable { programs.wireshark = { enable = true; package = pkgs.wireshark; # the Qt GUI (the module defaults to the CLI) }; users.users.${args.username}.extraGroups = [ "wireshark" ]; }) (lib.mkIf cfg.ollama.enable { services.ollama.enable = true; }) (lib.mkIf cfg.printing.enable { services.printing.enable = true; # mDNS so network printers show up without manual setup. services.avahi = { enable = true; nssmdns4 = true; openFirewall = true; }; # The CUPS admin GUI — the menu's System ▸ Printers entry execs it # (self-gated on this binary), so it ships with the printing service. environment.systemPackages = [ pkgs.system-config-printer ]; }) (lib.mkIf cfg.openrgb.enable { services.hardware.openrgb.enable = true; }) (lib.mkIf cfg.restic.enable { assertions = [ { assertion = cfg.restic.repository != ""; message = "nomarchy.services.restic.repository must be set when restic is enabled."; } { assertion = cfg.restic.passwordFile != null; message = "nomarchy.services.restic.passwordFile must be set when restic is enabled."; } ]; services.restic.backups.nomarchy = { repository = cfg.restic.repository; passwordFile = cfg.restic.passwordFile; paths = cfg.restic.paths; initialize = true; # create the repo on first run if it doesn't exist extraBackupArgs = [ "--exclude-caches" ]; pruneOpts = [ "--keep-daily 7" "--keep-weekly 4" "--keep-monthly 6" ]; timerConfig = { OnCalendar = "daily"; Persistent = true; # catch up a missed run after downtime }; }; }) ]; }