1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.zipline;
9in
10{
11 meta.maintainers = with lib.maintainers; [ defelo ];
12
13 options.services.zipline = {
14 enable = lib.mkEnableOption "Zipline";
15
16 package = lib.mkPackageOption pkgs "zipline" { };
17
18 settings = lib.mkOption {
19 description = ''
20 Configuration of Zipline. See <https://zipline.diced.sh/docs/config> for more information.
21 '';
22 default = { };
23 example = {
24 DATABASE_URL = "postgres://postgres:postgres@postgres/postgres";
25 CORE_SECRET = "changethis";
26 CORE_HOSTNAME = "0.0.0.0";
27 CORE_PORT = "3000";
28 DATASOURCE_TYPE = "local";
29 DATASOURCE_LOCAL_DIRECTORY = "/var/lib/zipline/uploads";
30 };
31
32 type = lib.types.submodule {
33 freeformType =
34 with lib.types;
35 attrsOf (oneOf [
36 str
37 int
38 ]);
39
40 options = {
41 CORE_HOSTNAME = lib.mkOption {
42 type = lib.types.str;
43 description = "The hostname to listen on.";
44 default = "127.0.0.1";
45 example = "0.0.0.0";
46 };
47
48 CORE_PORT = lib.mkOption {
49 type = lib.types.port;
50 description = "The port to listen on.";
51 default = 3000;
52 example = 8000;
53 };
54 };
55 };
56 };
57
58 environmentFiles = lib.mkOption {
59 type = lib.types.listOf lib.types.path;
60 default = [ ];
61 example = [ "/run/secrets/zipline.env" ];
62 description = ''
63 Files to load environment variables from (in addition to [](#opt-services.zipline.settings)). This is useful to avoid putting secrets into the nix store. See <https://zipline.diced.sh/docs/config> for more information.
64 '';
65 };
66
67 database.createLocally = lib.mkOption {
68 type = lib.types.bool;
69 default = true;
70 description = ''
71 Whether to enable and configure a local PostgreSQL database server.
72 '';
73 };
74 };
75
76 config = lib.mkIf cfg.enable {
77 services.zipline.settings = {
78 DATABASE_URL = lib.mkIf cfg.database.createLocally "postgresql://zipline@localhost/zipline?host=/run/postgresql";
79 DATASOURCE_TYPE = lib.mkDefault "local";
80 DATASOURCE_LOCAL_DIRECTORY = lib.mkDefault "/var/lib/zipline/uploads"; # created automatically by zipline
81 };
82
83 services.postgresql = lib.mkIf cfg.database.createLocally {
84 enable = true;
85 ensureUsers = lib.singleton {
86 name = "zipline";
87 ensureDBOwnership = true;
88 };
89 ensureDatabases = [ "zipline" ];
90 };
91
92 systemd.services.zipline = {
93 wantedBy = [ "multi-user.target" ];
94
95 wants = [ "network-online.target" ];
96 after = [ "network-online.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service";
97 requires = lib.optional cfg.database.createLocally "postgresql.service";
98
99 environment = lib.mapAttrs (_: value: toString value) cfg.settings;
100
101 serviceConfig = {
102 User = "zipline";
103 Group = "zipline";
104 DynamicUser = true;
105 StateDirectory = "zipline";
106 EnvironmentFile = cfg.environmentFiles;
107 ExecStart = lib.getExe cfg.package;
108
109 # Hardening
110 AmbientCapabilities = "";
111 CapabilityBoundingSet = [ "" ];
112 DevicePolicy = "closed";
113 LockPersonality = true;
114 NoNewPrivileges = true;
115 PrivateDevices = true;
116 PrivateTmp = true;
117 PrivateUsers = true;
118 ProcSubset = "pid";
119 ProtectClock = true;
120 ProtectControlGroups = true;
121 ProtectHome = true;
122 ProtectHostname = true;
123 ProtectKernelLogs = true;
124 ProtectKernelModules = true;
125 ProtectKernelTunables = true;
126 ProtectProc = "invisible";
127 ProtectSystem = "strict";
128 RemoveIPC = true;
129 RestrictAddressFamilies = [ "AF_INET AF_INET6 AF_UNIX AF_NETLINK" ];
130 RestrictNamespaces = true;
131 RestrictRealtime = true;
132 RestrictSUIDSGID = true;
133 SystemCallArchitectures = "native";
134 SystemCallFilter = [
135 "@system-service"
136 "~@privileged"
137 "~@resources"
138 ];
139 UMask = "0077";
140 };
141 };
142 };
143}