summary refs log tree commit diff
path: root/nixos/modules/services/misc/rmfakecloud.nix
blob: fe522653c216f00e8ed9e4025dddc58f85c5c278 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.rmfakecloud;
  serviceDataDir = "/var/lib/rmfakecloud";

in {
  options = {
    services.rmfakecloud = {
      enable = mkEnableOption "rmfakecloud remarkable self-hosted cloud";

      package = mkOption {
        type = types.package;
        default = pkgs.rmfakecloud;
        defaultText = literalExpression "pkgs.rmfakecloud";
        description = ''
          rmfakecloud package to use.

          The default does not include the web user interface.
        '';
      };

      storageUrl = mkOption {
        type = types.str;
        example = "https://local.appspot.com";
        description = ''
          URL used by the tablet to access the rmfakecloud service.
        '';
      };

      port = mkOption {
        type = types.port;
        default = 3000;
        description = ''
          Listening port number.
        '';
      };

      logLevel = mkOption {
        type = types.enum [ "info" "debug" "warn" "error" ];
        default = "info";
        description = ''
          Logging level.
        '';
      };

      extraSettings = mkOption {
        type = with types; attrsOf str;
        default = { };
        example = { DATADIR = "/custom/path/for/rmfakecloud/data"; };
        description = ''
          Extra settings in the form of a set of key-value pairs.
          For tokens and secrets, use `environmentFile` instead.

          Available settings are listed on
          https://ddvk.github.io/rmfakecloud/install/configuration/.
        '';
      };

      environmentFile = mkOption {
        type = with types; nullOr path;
        default = null;
        example = "/etc/secrets/rmfakecloud.env";
        description = ''
          Path to an environment file loaded for the rmfakecloud service.

          This can be used to securely store tokens and secrets outside of the
          world-readable Nix store. Since this file is read by systemd, it may
          have permission 0400 and be owned by root.
        '';
      };
    };
  };

  config = mkIf cfg.enable {
    systemd.services.rmfakecloud = {
      description = "rmfakecloud remarkable self-hosted cloud";

      environment = {
        STORAGE_URL = cfg.storageUrl;
        PORT = toString cfg.port;
        LOGLEVEL = cfg.logLevel;
      } // cfg.extraSettings;

      preStart = ''
        # Generate the secret key used to sign client session tokens.
        # Replacing it invalidates the previously established sessions.
        if [ -z "$JWT_SECRET_KEY" ] && [ ! -f jwt_secret_key ]; then
          (umask 077; touch jwt_secret_key)
          cat /dev/urandom | tr -cd '[:alnum:]' | head -c 48 >> jwt_secret_key
        fi
      '';

      script = ''
        if [ -z "$JWT_SECRET_KEY" ]; then
          export JWT_SECRET_KEY="$(cat jwt_secret_key)"
        fi

        ${cfg.package}/bin/rmfakecloud
      '';

      wantedBy = [ "multi-user.target" ];
      wants = [ "network-online.target" ];
      after = [ "network-online.target" ];

      serviceConfig = {
        Type = "simple";
        Restart = "always";

        EnvironmentFile =
          mkIf (cfg.environmentFile != null) cfg.environmentFile;

        AmbientCapabilities =
          mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];

        DynamicUser = true;
        PrivateDevices = true;
        ProtectHome = true;
        ProtectKernelTunables = true;
        ProtectKernelModules = true;
        ProtectControlGroups = true;
        CapabilityBoundingSet = [ "" ];
        DevicePolicy = "closed";
        LockPersonality = true;
        MemoryDenyWriteExecute = true;
        ProtectClock = true;
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectProc = "invisible";
        ProcSubset = "pid";
        RemoveIPC = true;
        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
        RestrictNamespaces = true;
        RestrictRealtime = true;
        RestrictSUIDSGID = true;
        SystemCallArchitectures = "native";
        WorkingDirectory = serviceDataDir;
        StateDirectory = baseNameOf serviceDataDir;
        UMask = 0027;
      };
    };
  };

  meta.maintainers = with maintainers; [ pacien ];
}