summaryrefslogtreecommitdiffstats
path: root/nixos/modules/services/web-apps/stump.nix
blob: 8dbf1dfcb7daf36a2047df5781fa49d5d69c515f (plain)
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
{
  lib,
  config,
  pkgs,
  ...
}:

let
  cfg = config.services.stump;

  inherit (lib)
    types
    mkIf
    mkOption
    mkEnableOption
    ;

  secret = types.nullOr (
    types.str
    // {
      # We don't want users to be able to pass a path literal here but
      # it should look like a path.
      check = it: lib.isString it && lib.types.path.check it;
    }
  );
in
{
  options.services.stump = {
    enable = mkEnableOption "Stump";
    package = lib.mkPackageOption pkgs "stump" { };

    configLocation = mkOption {
      type = types.path;
      default = "/var/lib/stump";
      description = "Directory used to store the database and configuration files. If it is not the default, the directory has to be created manually such that the stump user is able to read and write to it.";
    };

    environment = mkOption {
      type = types.attrsOf types.str;
      default = { };
      example = {
        STUMP_VERBOSITY = "2";
      };
      description = ''
        Extra configuration environment variables. Refer to the [documentation](https://www.stumpapp.dev/docs/guides/configuration/server-config) for options.
      '';
    };

    environmentFile = mkOption {
      type = secret;
      example = "/run/secrets/stump";
      default = null;
      description = ''
        Path of a file with extra environment variables to be loaded from disk.
        This file is not added to the nix store, so it can be used to pass secrets to stump.
        Refer to the [documentation](https://www.stumpapp.dev/docs/guides/configuration/server-config) for options.
      '';
    };

    secretFiles = mkOption {
      type = types.attrsOf secret;
      example = {
        STUMP_OIDC_CLIENT_SECRET = "/run/secrets/stump_client_secret";
      };
      default = { };
      description = ''
        Attribute set containing paths to files to add to the environment of stump.
        The files are not added to the nix store, so they can be used to pass secrets to stump.
        Refer to the [documentation](https://www.stumpapp.dev/docs/guides/configuration/server-config) for options.
      '';
    };

    ip = mkOption {
      type = types.str;
      default = "127.0.0.1";
      description = "The IP address that Stump will listen on.";
    };
    port = mkOption {
      type = types.port;
      default = 10001;
      description = "The port that Stump will listen on.";
    };
    openFirewall = mkOption {
      type = types.bool;
      default = false;
      description = "Whether to open the Stump port in the firewall";
    };
    user = mkOption {
      type = types.str;
      default = "stump";
      description = "The user Stump should run as.";
    };
    group = mkOption {
      type = types.str;
      default = "stump";
      description = "The group stump should run as.";
    };
  };

  config = mkIf cfg.enable {
    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];

    services.stump.environment = {
      STUMP_IP = cfg.ip;
      STUMP_PORT = toString cfg.port;
      STUMP_CONFIG_DIR = cfg.configLocation;
    };

    systemd.services.stump = {
      description = "Stump (A free and open source comics, manga and digital book server with OPDS support)";
      requires = [ "network-online.target" ];
      after = [ "network-online.target" ];
      wantedBy = [ "multi-user.target" ];
      environment = cfg.environment;
      serviceConfig = {
        Type = "simple";
        Restart = "on-failure";
        RestartSec = 3;

        ExecStart =
          if cfg.secretFiles == { } then
            "${lib.getExe cfg.package}"
          else
            pkgs.writeShellScript "stump-env" ''
              ${lib.strings.concatStringsSep "\n" (
                lib.attrsets.mapAttrsToList (key: path: "export ${key}=$(< \"${path}\")") cfg.secretFiles
              )}
              ${lib.getExe cfg.package}
            '';
        EnvironmentFile = cfg.environmentFile;
        StateDirectory = "stump";
        User = cfg.user;
        Group = cfg.group;

        # Hardening
        CapabilityBoundingSet = "";
        NoNewPrivileges = true;
        PrivateUsers = true;
        PrivateTmp = true;
        PrivateDevices = true;
        PrivateMounts = true;
        ProtectClock = true;
        ProtectControlGroups = true;
        ProtectHome = true;
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        RestrictAddressFamilies = [
          "AF_INET"
          "AF_INET6"
          "AF_UNIX"
          "AF_NETLINK" # is used to determine local ip
        ];
        RestrictNamespaces = true;
        RestrictRealtime = true;
        RestrictSUIDSGID = true;
      };
    };

    users.users = mkIf (cfg.user == "stump") {
      stump = {
        name = "stump";
        group = cfg.group;
        isSystemUser = true;
      };
    };
    users.groups = mkIf (cfg.group == "stump") { stump = { }; };

    meta.maintainers = with lib.maintainers; [ jvanbruegge ];
  };
}