]> git.immae.eu Git - perso/Immae/Config/Nix/NUR.git/blob - modules/webapps/mastodon.nix
Use fetchgit rather than builtins
[perso/Immae/Config/Nix/NUR.git] / modules / webapps / mastodon.nix
1 { lib, pkgs, config, ... }:
2 let
3 name = "mastodon";
4 cfg = config.services.mastodon;
5
6 uid = config.ids.uids.mastodon;
7 gid = config.ids.gids.mastodon;
8 in
9 {
10 options.services.mastodon = {
11 enable = lib.mkEnableOption "Enable Mastodon’s service";
12 user = lib.mkOption {
13 type = lib.types.str;
14 default = name;
15 description = "User account under which Mastodon runs";
16 };
17 group = lib.mkOption {
18 type = lib.types.str;
19 default = name;
20 description = "Group under which Mastodon runs";
21 };
22 dataDir = lib.mkOption {
23 type = lib.types.path;
24 default = "/var/lib/${name}";
25 description = ''
26 The directory where Mastodon stores its data.
27 '';
28 };
29 socketsPrefix = lib.mkOption {
30 type = lib.types.str;
31 default = "live";
32 description = ''
33 The prefix to use for Mastodon sockets.
34 '';
35 };
36 socketsDir = lib.mkOption {
37 type = lib.types.path;
38 default = "/run/${name}";
39 description = ''
40 The directory where Mastodon puts runtime files and sockets.
41 '';
42 };
43 configFile = lib.mkOption {
44 type = lib.types.path;
45 description = ''
46 The configuration file path for Mastodon.
47 '';
48 };
49 package = lib.mkOption {
50 type = lib.types.package;
51 default = pkgs.webapps.mastodon;
52 description = ''
53 Mastodon package to use.
54 '';
55 };
56 # Output variables
57 workdir = lib.mkOption {
58 type = lib.types.package;
59 default = cfg.package.override { varDir = cfg.dataDir; };
60 description = ''
61 Adjusted mastodon package with overriden varDir
62 '';
63 readOnly = true;
64 };
65 systemdStateDirectory = lib.mkOption {
66 type = lib.types.str;
67 # Use ReadWritePaths= instead if varDir is outside of /var/lib
68 default = assert lib.strings.hasPrefix "/var/lib/" cfg.dataDir;
69 lib.strings.removePrefix "/var/lib/" cfg.dataDir;
70 description = ''
71 Adjusted Mastodon data directory for systemd
72 '';
73 readOnly = true;
74 };
75 systemdRuntimeDirectory = lib.mkOption {
76 type = lib.types.str;
77 # Use ReadWritePaths= instead if socketsDir is outside of /run
78 default = assert lib.strings.hasPrefix "/run/" cfg.socketsDir;
79 lib.strings.removePrefix "/run/" cfg.socketsDir;
80 description = ''
81 Adjusted Mastodon sockets directory for systemd
82 '';
83 readOnly = true;
84 };
85 sockets = lib.mkOption {
86 type = lib.types.attrsOf lib.types.path;
87 default = {
88 node = "${cfg.socketsDir}/${cfg.socketsPrefix}_node.sock";
89 rails = "${cfg.socketsDir}/${cfg.socketsPrefix}_puma.sock";
90 };
91 readOnly = true;
92 description = ''
93 Mastodon sockets
94 '';
95 };
96 };
97
98 config = lib.mkIf cfg.enable {
99 users.users = lib.optionalAttrs (cfg.user == name) {
100 "${name}" = {
101 inherit uid;
102 group = cfg.group;
103 description = "Mastodon user";
104 home = cfg.dataDir;
105 useDefaultShell = true;
106 };
107 };
108 users.groups = lib.optionalAttrs (cfg.group == name) {
109 "${name}" = {
110 inherit gid;
111 };
112 };
113
114 systemd.services.mastodon-streaming = {
115 description = "Mastodon Streaming";
116 wantedBy = [ "multi-user.target" ];
117 after = [ "network.target" "mastodon-web.service" ];
118
119 environment.NODE_ENV = "production";
120 environment.SOCKET = cfg.sockets.node;
121
122 path = [ pkgs.nodejs pkgs.bashInteractive ];
123
124 script = ''
125 exec npm run start
126 '';
127
128 postStart = ''
129 while [ ! -S $SOCKET ]; do
130 sleep 0.5
131 done
132 chmod a+w $SOCKET
133 '';
134
135 postStop = ''
136 rm $SOCKET
137 '';
138
139 serviceConfig = {
140 User = cfg.user;
141 EnvironmentFile = cfg.configFile;
142 PrivateTmp = true;
143 Restart = "always";
144 TimeoutSec = 15;
145 Type = "simple";
146 WorkingDirectory = cfg.workdir;
147 StateDirectory = cfg.systemdStateDirectory;
148 RuntimeDirectory = cfg.systemdRuntimeDirectory;
149 RuntimeDirectoryPreserve = "yes";
150 };
151
152 unitConfig.RequiresMountsFor = cfg.dataDir;
153 };
154
155 systemd.services.mastodon-web = {
156 description = "Mastodon Web app";
157 wantedBy = [ "multi-user.target" ];
158 after = [ "network.target" ];
159
160 environment.RAILS_ENV = "production";
161 environment.BUNDLE_PATH = "${cfg.workdir.gems}/${cfg.workdir.gems.ruby.gemPath}";
162 environment.BUNDLE_GEMFILE = "${cfg.workdir.gems.confFiles}/Gemfile";
163 environment.SOCKET = cfg.sockets.rails;
164
165 path = [ cfg.workdir.gems cfg.workdir.gems.ruby pkgs.file ];
166
167 preStart = ''
168 install -m 0755 -d ${cfg.dataDir}/tmp/cache
169 ./bin/bundle exec rails db:migrate
170 '';
171
172 script = ''
173 exec ./bin/bundle exec puma -C config/puma.rb
174 '';
175
176 postStart = ''
177 exec ./bin/tootctl cache clear
178 '';
179 serviceConfig = {
180 User = cfg.user;
181 EnvironmentFile = cfg.configFile;
182 PrivateTmp = true;
183 Restart = "always";
184 TimeoutSec = 60;
185 Type = "simple";
186 WorkingDirectory = cfg.workdir;
187 StateDirectory = cfg.systemdStateDirectory;
188 RuntimeDirectory = cfg.systemdRuntimeDirectory;
189 RuntimeDirectoryPreserve = "yes";
190 };
191
192 unitConfig.RequiresMountsFor = cfg.dataDir;
193 };
194
195 systemd.services.mastodon-cleanup = {
196 description = "Cleanup mastodon";
197 startAt = "daily";
198 restartIfChanged = false;
199
200 environment.RAILS_ENV = "production";
201 environment.BUNDLE_PATH = "${cfg.workdir.gems}/${cfg.workdir.gems.ruby.gemPath}";
202 environment.BUNDLE_GEMFILE = "${cfg.workdir.gems.confFiles}/Gemfile";
203 environment.SOCKET = cfg.sockets.rails;
204
205 path = [ cfg.workdir.gems cfg.workdir.gems.ruby pkgs.file ];
206
207 script = ''
208 exec ./bin/tootctl media remove --days 30
209 '';
210
211 serviceConfig = {
212 User = cfg.user;
213 EnvironmentFile = cfg.configFile;
214 PrivateTmp = true;
215 Type = "oneshot";
216 WorkingDirectory = cfg.workdir;
217 StateDirectory = cfg.systemdStateDirectory;
218 RuntimeDirectory = cfg.systemdRuntimeDirectory;
219 RuntimeDirectoryPreserve = "yes";
220 };
221
222 unitConfig.RequiresMountsFor = cfg.dataDir;
223 };
224
225 systemd.services.mastodon-sidekiq = {
226 description = "Mastodon Sidekiq";
227 wantedBy = [ "multi-user.target" ];
228 after = [ "network.target" "mastodon-web.service" ];
229
230 environment.RAILS_ENV="production";
231 environment.BUNDLE_PATH = "${cfg.workdir.gems}/${cfg.workdir.gems.ruby.gemPath}";
232 environment.BUNDLE_GEMFILE = "${cfg.workdir.gems.confFiles}/Gemfile";
233 environment.DB_POOL="5";
234
235 path = [ cfg.workdir.gems cfg.workdir.gems.ruby pkgs.imagemagick pkgs.ffmpeg pkgs.file ];
236
237 script = ''
238 exec ./bin/bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push
239 '';
240
241 serviceConfig = {
242 User = cfg.user;
243 EnvironmentFile = cfg.configFile;
244 PrivateTmp = true;
245 Restart = "always";
246 TimeoutSec = 15;
247 Type = "simple";
248 WorkingDirectory = cfg.workdir;
249 StateDirectory = cfg.systemdStateDirectory;
250 RuntimeDirectory = cfg.systemdRuntimeDirectory;
251 RuntimeDirectoryPreserve = "yes";
252 };
253
254 unitConfig.RequiresMountsFor = cfg.dataDir;
255 };
256
257 };
258 }