1{
2 lib,
3 config,
4 self,
5 self',
6 pkgs,
7 ...
8}:
9let
10 d = self.lib.data.services.planka;
11
12 commonServiceConfig = {
13 EnvironmentFile = config.age.secrets.planka-env.path;
14 StateDirectory = "planka";
15 WorkingDirectory = "/var/lib/planka";
16 User = "planka";
17 Group = "planka";
18
19 # Hardening
20 LockPersonality = true;
21 NoNewPrivileges = true;
22 PrivateDevices = true;
23 PrivateMounts = true;
24 PrivateTmp = true;
25 PrivateUsers = true;
26 ProtectClock = true;
27 ProtectControlGroups = true;
28 ProtectHome = true;
29 ProtectHostname = true;
30 ProtectKernelLogs = true;
31 ProtectKernelModules = true;
32 ProtectKernelTunables = true;
33 ProtectProc = "invisible";
34 RemoveIPC = true;
35 RestrictRealtime = true;
36 RestrictSUIDSGID = true;
37 UMask = "0660";
38 RestrictAddressFamilies = [
39 "AF_UNIX"
40 "AF_INET"
41 "AF_INET6"
42 ];
43 };
44in
45{
46 systemd = {
47 tmpfiles.settings = {
48 "10-planka"."/var/lib/planka".d = {
49 group = "planka";
50 user = "planka";
51 mode = "0755";
52 };
53 };
54 services = {
55 planka-init-db = {
56 wantedBy = [ "multi-user.target" ];
57 after = [ "postgres.target" ];
58 description = "Planka Kanban Database Init Script";
59 path = [
60 pkgs.nodejs
61 ];
62 script = ''
63 if [ ! -f /var/lib/planka/db-init-ran ]; then
64 node run ${self'.packages.planka}/lib/node_modules/planka/db/init.js && \
65 touch /var/lib/planka/db-init-ran
66 fi
67 '';
68 serviceConfig = commonServiceConfig // {
69 Type = "oneshot";
70 SyslogIdentifier = "planka-init-db";
71 };
72 };
73 planka-server = {
74 after = [ "planka-init-db.service" ];
75 wantedBy = [ "multi-user.target" ];
76 description = "Planka Kanban Server";
77 documentation = [ "https://docs.planka.cloud" ];
78 environment = {
79 DATABASE_URL = "postgresql://%2Frun%2Fpostgresql/planka";
80 DEFAULT_ADMIN_EMAIL = "pyrox@pyrox.dev";
81 DEFAULT_ADMIN_USERNAME = "pyrox";
82 TRUST_PROXY = "true";
83 DEFAULT_LANGUAGE = "en-US";
84 BASE_URL = "https://${d.extUrl}";
85 NODE_ENV = "production";
86 };
87 serviceConfig = commonServiceConfig // {
88 Type = "simple";
89 ExecStart = "${lib.getExe self'.packages.planka} --port ${toString d.port}";
90 SyslogIdentifier = "planka";
91 };
92 };
93 };
94 };
95 users.users.planka = {
96 isSystemUser = true;
97 group = "planka";
98 };
99 users.groups.planka = { };
100 services.postgresql = {
101 ensureUsers = [
102 {
103 name = "planka";
104 ensureDBOwnership = true;
105 ensureClauses.login = true;
106 }
107 ];
108 ensureDatabases = [ "planka" ];
109 };
110 age.secrets.planka-env = {
111 file = ./secrets/planka-env.age;
112 owner = "planka";
113 group = "planka";
114 };
115 services.anubis.instances.planka = {
116 settings = {
117 COOKIE_DOMAIN = ".cs2a.club";
118 BIND = ":${toString d.anubis}";
119 TARGET = "http://localhost:${toString d.port}";
120 };
121 };
122}