1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.restic.server;
9in
10{
11 meta.maintainers = [ lib.maintainers.bachp ];
12
13 options.services.restic.server = {
14 enable = lib.mkEnableOption "Restic REST Server";
15
16 listenAddress = lib.mkOption {
17 default = "8000";
18 example = "127.0.0.1:8080";
19 type = lib.types.str;
20 description = "Listen on a specific IP address and port or unix socket.";
21 };
22
23 dataDir = lib.mkOption {
24 default = "/var/lib/restic";
25 type = lib.types.path;
26 description = "The directory for storing the restic repository.";
27 };
28
29 appendOnly = lib.mkOption {
30 default = false;
31 type = lib.types.bool;
32 description = ''
33 Enable append only mode.
34 This mode allows creation of new backups but prevents deletion and modification of existing backups.
35 This can be useful when backing up systems that have a potential of being hacked.
36 '';
37 };
38
39 htpasswd-file = lib.mkOption {
40 default = null;
41 type = lib.types.nullOr lib.types.path;
42 description = "The path to the servers .htpasswd file. Defaults to `\${dataDir}/.htpasswd`.";
43 };
44
45 privateRepos = lib.mkOption {
46 default = false;
47 type = lib.types.bool;
48 description = ''
49 Enable private repos.
50 Grants access only when a subdirectory with the same name as the user is specified in the repository URL.
51 '';
52 };
53
54 prometheus = lib.mkOption {
55 default = false;
56 type = lib.types.bool;
57 description = "Enable Prometheus metrics at /metrics.";
58 };
59
60 extraFlags = lib.mkOption {
61 type = lib.types.listOf lib.types.str;
62 default = [ ];
63 description = ''
64 Extra commandline options to pass to Restic REST server.
65 '';
66 };
67
68 package = lib.mkPackageOption pkgs "restic-rest-server" { };
69 };
70
71 config = lib.mkIf cfg.enable {
72 assertions = [
73 {
74 assertion = lib.substring 0 1 cfg.listenAddress != ":";
75 message = "The restic-rest-server now uses systemd socket activation, which expects only the Port number: services.restic.server.listenAddress = \"${
76 lib.substring 1 6 cfg.listenAddress
77 }\";";
78 }
79 ];
80
81 systemd.services.restic-rest-server = {
82 description = "Restic REST Server";
83 after = [
84 "network.target"
85 "restic-rest-server.socket"
86 ];
87 requires = [ "restic-rest-server.socket" ];
88 wantedBy = [ "multi-user.target" ];
89 serviceConfig = {
90 ExecStart = ''
91 ${cfg.package}/bin/rest-server \
92 --path ${cfg.dataDir} \
93 ${lib.optionalString (cfg.htpasswd-file != null) "--htpasswd-file ${cfg.htpasswd-file}"} \
94 ${lib.optionalString cfg.appendOnly "--append-only"} \
95 ${lib.optionalString cfg.privateRepos "--private-repos"} \
96 ${lib.optionalString cfg.prometheus "--prometheus"} \
97 ${lib.escapeShellArgs cfg.extraFlags} \
98 '';
99 Type = "simple";
100 User = "restic";
101 Group = "restic";
102
103 # Security hardening
104 CapabilityBoundingSet = "";
105 LockPersonality = true;
106 MemoryDenyWriteExecute = true;
107 NoNewPrivileges = true;
108 PrivateNetwork = true;
109 PrivateTmp = true;
110 PrivateUsers = true;
111 ProtectClock = true;
112 ProtectHome = true;
113 ProtectHostname = true;
114 ProtectKernelLogs = true;
115 ProtectProc = "invisible";
116 ProtectSystem = "strict";
117 ProtectKernelTunables = true;
118 ProtectKernelModules = true;
119 ProtectControlGroups = true;
120 PrivateDevices = true;
121 ReadWritePaths = [ cfg.dataDir ];
122 ReadOnlyPaths = lib.optional (cfg.htpasswd-file != null) cfg.htpasswd-file;
123 RemoveIPC = true;
124 RestrictAddressFamilies = "none";
125 RestrictNamespaces = true;
126 RestrictRealtime = true;
127 RestrictSUIDSGID = true;
128 SystemCallArchitectures = "native";
129 SystemCallFilter = "@system-service";
130 UMask = 27;
131 };
132 };
133
134 systemd.sockets.restic-rest-server = {
135 listenStreams = [ cfg.listenAddress ];
136 wantedBy = [ "sockets.target" ];
137 };
138
139 systemd.tmpfiles.rules = lib.mkIf cfg.privateRepos [
140 "f ${cfg.dataDir}/.htpasswd 0700 restic restic -"
141 ];
142
143 users.users.restic = {
144 group = "restic";
145 home = cfg.dataDir;
146 createHome = true;
147 uid = config.ids.uids.restic;
148 };
149
150 users.groups.restic.gid = config.ids.uids.restic;
151 };
152}