Kieran's opinionated (and probably slightly dumb) nix config
1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.atelier.services.emojibot;
9in
10{
11 options.atelier.services.emojibot = {
12 enable = lib.mkEnableOption "Emojibot Slack emoji management service";
13
14 domain = lib.mkOption {
15 type = lib.types.str;
16 description = "Domain to serve emojibot on";
17 };
18
19 port = lib.mkOption {
20 type = lib.types.port;
21 default = 3002;
22 description = "Port to run emojibot on";
23 };
24
25 dataDir = lib.mkOption {
26 type = lib.types.path;
27 default = "/var/lib/emojibot";
28 description = "Directory to store emojibot data";
29 };
30
31 secretsFile = lib.mkOption {
32 type = lib.types.path;
33 description = ''
34 Path to secrets file containing:
35 - SLACK_SIGNING_SECRET
36 - SLACK_BOT_TOKEN
37 - SLACK_APP_TOKEN
38 - SLACK_BOT_USER_TOKEN (get from browser, see emojibot README)
39 - SLACK_COOKIE (get from browser, see emojibot README)
40 - SLACK_WORKSPACE (e.g. "myworkspace" for myworkspace.slack.com)
41 - SLACK_CHANNEL (channel ID where emojis are posted)
42 - ADMINS (comma-separated list of slack user IDs)
43 '';
44 };
45
46 repository = lib.mkOption {
47 type = lib.types.str;
48 default = "https://github.com/taciturnaxolotl/emojibot.git";
49 description = "Git repository URL (optional, for auto-deployment)";
50 };
51
52 autoUpdate = lib.mkEnableOption "Automatically git pull on service restart";
53 };
54
55 config = lib.mkIf cfg.enable {
56 users.groups.services = { };
57
58 users.users.emojibot = {
59 isSystemUser = true;
60 group = "emojibot";
61 extraGroups = [ "services" ];
62 home = cfg.dataDir;
63 createHome = true;
64 shell = pkgs.bash;
65 };
66
67 users.groups.emojibot = { };
68
69 security.sudo.extraRules = [
70 {
71 users = [ "emojibot" ];
72 commands = [
73 {
74 command = "/run/current-system/sw/bin/systemctl restart emojibot.service";
75 options = [ "NOPASSWD" ];
76 }
77 ];
78 }
79 ];
80
81 systemd.services.emojibot = {
82 description = "Emojibot Slack emoji management service";
83 wantedBy = [ "multi-user.target" ];
84 after = [ "network.target" ];
85 path = [ pkgs.git ];
86
87 preStart = ''
88 if [ ! -d ${cfg.dataDir}/app/.git ]; then
89 ${pkgs.git}/bin/git clone ${cfg.repository} ${cfg.dataDir}/app
90 fi
91
92 cd ${cfg.dataDir}/app
93 '' + lib.optionalString cfg.autoUpdate ''
94 ${pkgs.git}/bin/git pull
95 '' + ''
96
97 if [ ! -f src/index.ts ]; then
98 echo "No code found at ${cfg.dataDir}/app/src/index.ts"
99 exit 1
100 fi
101
102 echo "Installing dependencies..."
103 ${pkgs.unstable.bun}/bin/bun install
104 '';
105
106 serviceConfig = {
107 Type = "simple";
108 User = "emojibot";
109 Group = "emojibot";
110 EnvironmentFile = cfg.secretsFile;
111 Environment = [
112 "NODE_ENV=production"
113 "PORT=${toString cfg.port}"
114 ];
115 ExecStart = "${pkgs.bash}/bin/bash -c 'cd ${cfg.dataDir}/app && ${pkgs.unstable.bun}/bin/bun run src/index.ts'";
116 Restart = "always";
117 RestartSec = "10s";
118 };
119
120 serviceConfig.ExecStartPre = [
121 "+${pkgs.writeShellScript "emojibot-setup" ''
122 mkdir -p ${cfg.dataDir}/app
123 chown -R emojibot:services ${cfg.dataDir}
124 chmod -R g+rwX ${cfg.dataDir}
125 ''}"
126 ];
127 };
128
129 services.caddy.virtualHosts.${cfg.domain} = {
130 extraConfig = ''
131 tls {
132 dns cloudflare {env.CLOUDFLARE_API_TOKEN}
133 }
134
135 reverse_proxy localhost:${toString cfg.port}
136 '';
137 };
138 };
139}