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