This repository has no description
3.9 kB
123 lines
1{
2 config,
3 lib,
4 pkgs,
5 self,
6 ...
7}:
8let
9 domain = "when.lassul.us";
10 port = 3002;
11 timeful = self.packages.${pkgs.system}.timeful;
12in
13{
14 # timeful (formerly schej): crab.fit/when2meet-style availability polls.
15 # Creating a poll and painting availability work without an account;
16 # signing in (GitHub OAuth, see pkgs/timeful/self-host.patch) keeps a
17 # dashboard of your polls.
18 services.mongodb = {
19 enable = true;
20 # prebuilt binary; the default `mongodb` builds from source for hours
21 package = pkgs.mongodb-ce;
22 };
23
24 lass.backup.state.timeful.preBackup = ''
25 ${pkgs.mongodb-tools}/bin/mongodump --uri=mongodb://127.0.0.1:27017 \
26 --db=schej-it --gzip --archive=/var/backup/timeful.archive.gz
27 '';
28
29 clan.core.vars.generators.timeful = {
30 files.env = { };
31 runtimeInputs = [ pkgs.openssl ];
32 # ENCRYPTION_KEY is fed raw to aes.NewCipher, so it must be exactly
33 # 16/24/32 bytes (upstream's `openssl rand -base64 32` hint is 44 bytes).
34 script = ''
35 {
36 echo "SESSION_SECRET=$(openssl rand -hex 32)"
37 echo "ENCRYPTION_KEY=$(openssl rand -hex 16)"
38 } > $out/env
39 '';
40 };
41
42 # GitHub OAuth app (https://github.com/settings/developers), callback URL
43 # https://when.lassul.us/api/auth/github/callback
44 clan.core.vars.generators.timeful-github = {
45 files.env = { };
46 prompts.client_id = {
47 description = "GitHub OAuth app client ID for ${domain}";
48 persist = true;
49 };
50 prompts.client_secret = {
51 description = "GitHub OAuth app client secret for ${domain}";
52 type = "hidden";
53 persist = true;
54 };
55 script = ''
56 printf 'GITHUB_CLIENT_ID=%s\nGITHUB_CLIENT_SECRET=%s\n' \
57 "$(cat "$prompts"/client_id)" "$(cat "$prompts"/client_secret)" > "$out/env"
58 '';
59 };
60
61 systemd.services.timeful = {
62 description = "timeful availability polls";
63 wantedBy = [ "multi-user.target" ];
64 requires = [ "mongodb.service" ];
65 after = [
66 "network.target"
67 "mongodb.service"
68 ];
69 environment = {
70 LISTEN_ADDR = "127.0.0.1:${toString port}";
71 MONGODB_URI = "mongodb://127.0.0.1:27017";
72 CORS_ORIGINS = "https://${domain}";
73 # no listmonk mailing list; skip its HTTP calls on sign-up
74 LISTMONK_ENABLED = "false";
75 };
76 serviceConfig = {
77 ExecStart = "${lib.getExe timeful} -release";
78 # The server unconditionally appends everything it prints to
79 # ./logs.log, which would grow forever; the journal already has it.
80 ExecStartPre = "${pkgs.coreutils}/bin/ln -sfn /dev/null logs.log";
81 EnvironmentFile = [
82 config.clan.core.vars.generators.timeful.files.env.path
83 config.clan.core.vars.generators.timeful-github.files.env.path
84 ];
85 DynamicUser = true;
86 StateDirectory = "timeful";
87 WorkingDirectory = "/var/lib/timeful";
88 Restart = "on-failure";
89 CapabilityBoundingSet = "";
90 NoNewPrivileges = true;
91 PrivateDevices = true;
92 ProtectHome = true;
93 ProtectKernelTunables = true;
94 ProtectKernelModules = true;
95 ProtectControlGroups = true;
96 RestrictAddressFamilies = [
97 "AF_INET"
98 "AF_INET6"
99 "AF_UNIX"
100 ];
101 RestrictNamespaces = true;
102 LockPersonality = true;
103 SystemCallArchitectures = "native";
104 };
105 };
106
107 services.nginx.virtualHosts.${domain} = {
108 enableACME = true;
109 forceSSL = true;
110 locations."/" = {
111 proxyPass = "http://127.0.0.1:${toString port}";
112 # gin trusts every proxy and takes the leftmost X-Forwarded-For entry
113 # for its per-IP rate limiter, so overwrite instead of appending
114 # (recommendedProxySettings appends a client-controlled value).
115 extraConfig = ''
116 proxy_set_header Host $host;
117 proxy_set_header X-Real-IP $remote_addr;
118 proxy_set_header X-Forwarded-For $remote_addr;
119 proxy_set_header X-Forwarded-Proto $scheme;
120 '';
121 };
122 };
123}