1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.rmfakecloud;
9 serviceDataDir = "/var/lib/rmfakecloud";
10
11in
12{
13 options = {
14 services.rmfakecloud = {
15 enable = lib.mkEnableOption "rmfakecloud remarkable self-hosted cloud";
16
17 package = lib.mkPackageOption pkgs "rmfakecloud" { };
18
19 storageUrl = lib.mkOption {
20 type = lib.types.str;
21 example = "https://local.appspot.com";
22 description = ''
23 URL used by the tablet to access the rmfakecloud service.
24 '';
25 };
26
27 port = lib.mkOption {
28 type = lib.types.port;
29 default = 3000;
30 description = ''
31 Listening port number.
32 '';
33 };
34
35 logLevel = lib.mkOption {
36 type = lib.types.enum [
37 "info"
38 "debug"
39 "warn"
40 "error"
41 ];
42 default = "info";
43 description = ''
44 Logging level.
45 '';
46 };
47
48 extraSettings = lib.mkOption {
49 type = with lib.types; attrsOf str;
50 default = { };
51 example = {
52 DATADIR = "/custom/path/for/rmfakecloud/data";
53 };
54 description = ''
55 Extra settings in the form of a set of key-value pairs.
56 For tokens and secrets, use `environmentFile` instead.
57
58 Available settings are listed on
59 https://ddvk.github.io/rmfakecloud/install/configuration/.
60 '';
61 };
62
63 environmentFile = lib.mkOption {
64 type = with lib.types; nullOr path;
65 default = null;
66 example = "/etc/secrets/rmfakecloud.env";
67 description = ''
68 Path to an environment file loaded for the rmfakecloud service.
69
70 This can be used to securely store tokens and secrets outside of the
71 world-readable Nix store. Since this file is read by systemd, it may
72 have permission 0400 and be owned by root.
73 '';
74 };
75 };
76 };
77
78 config = lib.mkIf cfg.enable {
79 systemd.services.rmfakecloud = {
80 description = "rmfakecloud remarkable self-hosted cloud";
81
82 environment = {
83 STORAGE_URL = cfg.storageUrl;
84 PORT = toString cfg.port;
85 LOGLEVEL = cfg.logLevel;
86 } // cfg.extraSettings;
87
88 preStart = ''
89 # Generate the secret key used to sign client session tokens.
90 # Replacing it invalidates the previously established sessions.
91 if [ -z "$JWT_SECRET_KEY" ] && [ ! -f jwt_secret_key ]; then
92 (umask 077; touch jwt_secret_key)
93 cat /dev/urandom | tr -cd '[:alnum:]' | head -c 48 >> jwt_secret_key
94 fi
95 '';
96
97 script = ''
98 if [ -z "$JWT_SECRET_KEY" ]; then
99 export JWT_SECRET_KEY="$(cat jwt_secret_key)"
100 fi
101
102 ${cfg.package}/bin/rmfakecloud
103 '';
104
105 wantedBy = [ "multi-user.target" ];
106 wants = [ "network-online.target" ];
107 after = [ "network-online.target" ];
108
109 serviceConfig = {
110 Type = "simple";
111 Restart = "always";
112
113 EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
114
115 AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
116
117 DynamicUser = true;
118 PrivateDevices = true;
119 ProtectHome = true;
120 ProtectKernelTunables = true;
121 ProtectKernelModules = true;
122 ProtectControlGroups = true;
123 CapabilityBoundingSet = [ "" ];
124 DevicePolicy = "closed";
125 LockPersonality = true;
126 MemoryDenyWriteExecute = true;
127 ProtectClock = true;
128 ProtectHostname = true;
129 ProtectKernelLogs = true;
130 ProtectProc = "invisible";
131 ProcSubset = "pid";
132 RemoveIPC = true;
133 RestrictAddressFamilies = [
134 "AF_INET"
135 "AF_INET6"
136 ];
137 RestrictNamespaces = true;
138 RestrictRealtime = true;
139 RestrictSUIDSGID = true;
140 SystemCallArchitectures = "native";
141 WorkingDirectory = serviceDataDir;
142 StateDirectory = baseNameOf serviceDataDir;
143 UMask = "0027";
144 };
145 };
146 };
147
148 meta.maintainers = with lib.maintainers; [ euxane ];
149}