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