]> git.immae.eu Git - perso/Immae/Config/Nix.git/blame - flakes/mastodon/flake.nix
Bump backports
[perso/Immae/Config/Nix.git] / flakes / mastodon / flake.nix
CommitLineData
bc0f9fcf
IB
1{
2 description = "Your self-hosted, globally interconnected microblogging community";
3 inputs.myuids = {
4 url = "https://git.immae.eu/perso/Immae/Config/Nix.git";
5 type = "git";
6 dir = "flakes/myuids";
7 };
8 inputs.flake-utils.url = "github:numtide/flake-utils";
9 inputs.nixpkgs = {
10 url = "github:NixOS/nixpkgs/840c782d507d60aaa49aa9e3f6d0b0e780912742";
11 flake = false;
12 };
13 inputs.mastodon = {
14 url = "github:tootsuite/mastodon/v2.9.4";
15 flake = false;
16 };
17
18 outputs = { self, myuids, nixpkgs, mastodon, flake-utils }: flake-utils.lib.eachSystem ["x86_64-linux"] (system:
19 let
20 pkgs = import nixpkgs { inherit system; overlays = []; };
21 version = (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.mastodon.original.ref;
22 inherit (pkgs) callPackage;
23 in rec {
24 packages.mastodon = callPackage ./. { src = mastodon // { inherit version; }; };
25 defaultPackage = packages.mastodon;
26 legacyPackages.mastodon = packages.mastodon;
27 checks = {
28 build = defaultPackage;
29 };
30 }
31 ) // rec {
32 overlays = {
33 mastodon = final: prev: {
34 mastodon = self.defaultPackage."${final.system}";
35 };
36 };
37 overlay = overlays.mastodon;
38 nixosModule = { lib, pkgs, config, ... }:
39 let
40 name = "mastodon";
41 cfg = config.immaeServices.mastodon;
42 in
43 {
44 options.immaeServices.mastodon = {
45 enable = lib.mkEnableOption "Enable Mastodon’s service";
46 user = lib.mkOption {
47 type = lib.types.str;
48 default = name;
49 description = "User account under which Mastodon runs";
50 };
51 group = lib.mkOption {
52 type = lib.types.str;
53 default = name;
54 description = "Group under which Mastodon runs";
55 };
56 dataDir = lib.mkOption {
57 type = lib.types.path;
58 default = "/var/lib/${name}";
59 description = ''
60 The directory where Mastodon stores its data.
61 '';
62 };
63 socketsPrefix = lib.mkOption {
64 type = lib.types.str;
65 default = "live";
66 description = ''
67 The prefix to use for Mastodon sockets.
68 '';
69 };
70 socketsDir = lib.mkOption {
71 type = lib.types.path;
72 default = "/run/${name}";
73 description = ''
74 The directory where Mastodon puts runtime files and sockets.
75 '';
76 };
77 configFile = lib.mkOption {
78 type = lib.types.path;
79 description = ''
80 The configuration file path for Mastodon.
81 '';
82 };
83 package = lib.mkOption {
84 type = lib.types.package;
85 default = pkgs.mastodon;
86 description = ''
87 Mastodon package to use.
88 '';
89 };
90 # Output variables
91 workdir = lib.mkOption {
92 type = lib.types.package;
93 default = cfg.package.override { varDir = cfg.dataDir; };
94 description = ''
95 Adjusted mastodon package with overriden varDir
96 '';
97 readOnly = true;
98 };
99 systemdStateDirectory = lib.mkOption {
100 type = lib.types.str;
101 # Use ReadWritePaths= instead if varDir is outside of /var/lib
102 default = assert lib.strings.hasPrefix "/var/lib/" cfg.dataDir;
103 lib.strings.removePrefix "/var/lib/" cfg.dataDir;
104 description = ''
105 Adjusted Mastodon data directory for systemd
106 '';
107 readOnly = true;
108 };
109 systemdRuntimeDirectory = lib.mkOption {
110 type = lib.types.str;
111 # Use ReadWritePaths= instead if socketsDir is outside of /run
112 default = assert lib.strings.hasPrefix "/run/" cfg.socketsDir;
113 lib.strings.removePrefix "/run/" cfg.socketsDir;
114 description = ''
115 Adjusted Mastodon sockets directory for systemd
116 '';
117 readOnly = true;
118 };
119 sockets = lib.mkOption {
120 type = lib.types.attrsOf lib.types.path;
121 default = {
122 node = "${cfg.socketsDir}/${cfg.socketsPrefix}_node.sock";
123 rails = "${cfg.socketsDir}/${cfg.socketsPrefix}_puma.sock";
124 };
125 readOnly = true;
126 description = ''
127 Mastodon sockets
128 '';
129 };
130 };
131
132 config = lib.mkIf cfg.enable {
133 nixpkgs.overlays = [ self.overlay ];
134 users.users = lib.optionalAttrs (cfg.user == name) {
135 "${name}" = {
136 uid = myuids.lib.uids.mastodon;
137 group = cfg.group;
138 description = "Mastodon user";
139 home = cfg.dataDir;
140 useDefaultShell = true;
141 };
142 };
143 users.groups = lib.optionalAttrs (cfg.group == name) {
144 "${name}" = {
145 gid = myuids.lib.gids.mastodon;
146 };
147 };
148
149 systemd.slices.mastodon = {
150 description = "Mastodon slice";
151 };
152
153 systemd.services.mastodon-streaming = {
154 description = "Mastodon Streaming";
155 wantedBy = [ "multi-user.target" ];
156 after = [ "network.target" "mastodon-web.service" ];
157
158 environment.NODE_ENV = "production";
159 environment.SOCKET = cfg.sockets.node;
160
161 path = [ cfg.workdir.nodejs pkgs.bashInteractive ];
162
163 script = ''
164 exec npm run start
165 '';
166
167 postStart = ''
168 while [ ! -S $SOCKET ]; do
169 sleep 0.5
170 done
171 chmod a+w $SOCKET
172 '';
173
174 postStop = ''
175 rm $SOCKET
176 '';
177
178 serviceConfig = {
179 Slice = "mastodon.slice";
180 User = cfg.user;
181 EnvironmentFile = cfg.configFile;
182 PrivateTmp = true;
183 Restart = "always";
184 TimeoutSec = 15;
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-web = {
196 description = "Mastodon Web app";
197 wantedBy = [ "multi-user.target" ];
198 after = [ "network.target" ];
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 pkgs.imagemagick ];
206
207 preStart = ''
208 install -m 0755 -d ${cfg.dataDir}/tmp/cache
209 ./bin/bundle exec rails db:migrate
210 '';
211
212 script = ''
213 exec ./bin/bundle exec puma -C config/puma.rb
214 '';
215
216 postStart = ''
217 exec ./bin/tootctl cache clear
218 '';
219 serviceConfig = {
220 Slice = "mastodon.slice";
221 User = cfg.user;
222 EnvironmentFile = cfg.configFile;
223 PrivateTmp = true;
224 Restart = "always";
225 TimeoutSec = 60;
226 Type = "simple";
227 WorkingDirectory = cfg.workdir;
228 StateDirectory = cfg.systemdStateDirectory;
229 RuntimeDirectory = cfg.systemdRuntimeDirectory;
230 RuntimeDirectoryPreserve = "yes";
231 };
232
233 unitConfig.RequiresMountsFor = cfg.dataDir;
234 };
235
236 # To be run manually because computationnally heavy
237 systemd.services.mastodon-cleanup-manual = {
238 description = "Cleanup mastodon";
239
240 environment.RAILS_ENV = "production";
241 environment.BUNDLE_PATH = "${cfg.workdir.gems}/${cfg.workdir.gems.ruby.gemPath}";
242 environment.BUNDLE_GEMFILE = "${cfg.workdir.gems.confFiles}/Gemfile";
243 environment.SOCKET = cfg.sockets.rails;
244
245 path = [ cfg.workdir.gems cfg.workdir.gems.ruby pkgs.file ];
246
247 script = ''
248 exec ./bin/tootctl statuses remove --days 365
249 '';
250
251 serviceConfig = {
252 User = cfg.user;
253 EnvironmentFile = cfg.configFile;
254 PrivateTmp = true;
255 Type = "oneshot";
256 WorkingDirectory = cfg.workdir;
257 StateDirectory = cfg.systemdStateDirectory;
258 RuntimeDirectory = cfg.systemdRuntimeDirectory;
259 RuntimeDirectoryPreserve = "yes";
260 };
261
262 unitConfig.RequiresMountsFor = cfg.dataDir;
263 };
264
265 systemd.services.mastodon-cleanup = {
266 description = "Cleanup mastodon";
267 startAt = "daily";
268 restartIfChanged = false;
269
270 environment.RAILS_ENV = "production";
271 environment.BUNDLE_PATH = "${cfg.workdir.gems}/${cfg.workdir.gems.ruby.gemPath}";
272 environment.BUNDLE_GEMFILE = "${cfg.workdir.gems.confFiles}/Gemfile";
273 environment.SOCKET = cfg.sockets.rails;
274
275 path = [ cfg.workdir.gems cfg.workdir.gems.ruby pkgs.file ];
276
277 script = ''
278 exec ./bin/tootctl media remove --days 30
279 '';
280
281 serviceConfig = {
282 User = cfg.user;
283 EnvironmentFile = cfg.configFile;
284 PrivateTmp = true;
285 Type = "oneshot";
286 WorkingDirectory = cfg.workdir;
287 StateDirectory = cfg.systemdStateDirectory;
288 RuntimeDirectory = cfg.systemdRuntimeDirectory;
289 RuntimeDirectoryPreserve = "yes";
290 };
291
292 unitConfig.RequiresMountsFor = cfg.dataDir;
293 };
294
295 systemd.services.mastodon-sidekiq = {
296 description = "Mastodon Sidekiq";
297 wantedBy = [ "multi-user.target" ];
298 after = [ "network.target" "mastodon-web.service" ];
299
300 environment.RAILS_ENV="production";
301 environment.BUNDLE_PATH = "${cfg.workdir.gems}/${cfg.workdir.gems.ruby.gemPath}";
302 environment.BUNDLE_GEMFILE = "${cfg.workdir.gems.confFiles}/Gemfile";
303 environment.DB_POOL="5";
304
305 path = [ cfg.workdir.gems cfg.workdir.gems.ruby pkgs.imagemagick pkgs.ffmpeg pkgs.file ];
306
307 script = ''
308 exec ./bin/bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push
309 '';
310
311 serviceConfig = {
312 Slice = "mastodon.slice";
313 User = cfg.user;
314 EnvironmentFile = cfg.configFile;
315 PrivateTmp = true;
316 Restart = "always";
317 TimeoutSec = 15;
318 Type = "simple";
319 WorkingDirectory = cfg.workdir;
320 StateDirectory = cfg.systemdStateDirectory;
321 RuntimeDirectory = cfg.systemdRuntimeDirectory;
322 RuntimeDirectoryPreserve = "yes";
323 };
324
325 unitConfig.RequiresMountsFor = cfg.dataDir;
326 };
327
328 };
329 };
330 };
331}
332
333