{ config, lib, pkgs, ... }: with lib; let cfg = config.services.restic.server; in { meta.maintainers = [ maintainers.bachp ]; options.services.restic.server = { enable = mkEnableOption "Restic REST Server"; listenAddress = mkOption { default = "8000"; example = "127.0.0.1:8080"; type = types.str; description = "Listen on a specific IP address and port or unix socket."; }; dataDir = mkOption { default = "/var/lib/restic"; type = types.path; description = "The directory for storing the restic repository."; }; appendOnly = mkOption { default = false; type = types.bool; description = '' Enable append only mode. This mode allows creation of new backups but prevents deletion and modification of existing backups. This can be useful when backing up systems that have a potential of being hacked. ''; }; privateRepos = mkOption { default = false; type = types.bool; description = '' Enable private repos. Grants access only when a subdirectory with the same name as the user is specified in the repository URL. ''; }; prometheus = mkOption { default = false; type = types.bool; description = "Enable Prometheus metrics at /metrics."; }; extraFlags = mkOption { type = types.listOf types.str; default = []; description = '' Extra commandline options to pass to Restic REST server. ''; }; package = mkPackageOption pkgs "restic-rest-server" { }; }; config = mkIf cfg.enable { assertions = [{ assertion = lib.substring 0 1 cfg.listenAddress != ":"; message = "The restic-rest-server now uses systemd socket activation, which expects only the Port number: services.restic.server.listenAddress = \"${lib.substring 1 6 cfg.listenAddress}\";"; }]; systemd.services.restic-rest-server = { description = "Restic REST Server"; after = [ "network.target" "restic-rest-server.socket" ]; requires = [ "restic-rest-server.socket" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = '' ${cfg.package}/bin/rest-server \ --path ${cfg.dataDir} \ ${optionalString cfg.appendOnly "--append-only"} \ ${optionalString cfg.privateRepos "--private-repos"} \ ${optionalString cfg.prometheus "--prometheus"} \ ${escapeShellArgs cfg.extraFlags} \ ''; Type = "simple"; User = "restic"; Group = "restic"; # Security hardening CapabilityBoundingSet = ""; LockPersonality = true; MemoryDenyWriteExecute = true; NoNewPrivileges = true; PrivateNetwork = true; PrivateTmp = true; PrivateUsers = true; ProtectClock = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectProc = "invisible"; ProtectSystem = "strict"; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectControlGroups = true; PrivateDevices = true; ReadWritePaths = [ cfg.dataDir ]; RemoveIPC = true; RestrictAddressFamilies = "none"; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; SystemCallFilter = "@system-service"; UMask = 027; }; }; systemd.sockets.restic-rest-server = { listenStreams = [ cfg.listenAddress ]; wantedBy = [ "sockets.target" ]; }; systemd.tmpfiles.rules = mkIf cfg.privateRepos [ "f ${cfg.dataDir}/.htpasswd 0700 restic restic -" ]; users.users.restic = { group = "restic"; home = cfg.dataDir; createHome = true; uid = config.ids.uids.restic; }; users.groups.restic.gid = config.ids.uids.restic; }; }