]> git.immae.eu Git - perso/Immae/Config/Nix/NUR.git/blob - modules/webapps/mastodon.nix
Initial commit published for NUR
[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.string;
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) (lib.singleton {
100 inherit name;
101 inherit uid;
102 group = cfg.group;
103 description = "Mastodon user";
104 home = cfg.dataDir;
105 useDefaultShell = true;
106 });
107 users.groups = lib.optionalAttrs (cfg.group == name) (lib.singleton {
108 inherit name;
109 inherit gid;
110 });
111
112 systemd.services.mastodon-streaming = {
113 description = "Mastodon Streaming";
114 wantedBy = [ "multi-user.target" ];
115 after = [ "network.target" "mastodon-web.service" ];
116
117 environment.NODE_ENV = "production";
118 environment.SOCKET = cfg.sockets.node;
119
120 path = [ pkgs.nodejs pkgs.bashInteractive ];
121
122 script = ''
123 exec npm run start
124 '';
125
126 postStart = ''
127 while [ ! -S $SOCKET ]; do
128 sleep 0.5
129 done
130 chmod a+w $SOCKET
131 '';
132
133 postStop = ''
134 rm $SOCKET
135 '';
136
137 serviceConfig = {
138 User = cfg.user;
139 EnvironmentFile = cfg.configFile;
140 PrivateTmp = true;
141 Restart = "always";
142 TimeoutSec = 15;
143 Type = "simple";
144 WorkingDirectory = cfg.workdir;
145 StateDirectory = cfg.systemdStateDirectory;
146 RuntimeDirectory = cfg.systemdRuntimeDirectory;
147 RuntimeDirectoryPreserve = "yes";
148 };
149
150 unitConfig.RequiresMountsFor = cfg.dataDir;
151 };
152
153 systemd.services.mastodon-web = {
154 description = "Mastodon Web app";
155 wantedBy = [ "multi-user.target" ];
156 after = [ "network.target" ];
157
158 environment.RAILS_ENV = "production";
159 environment.BUNDLE_PATH = "${cfg.workdir.gems}/${cfg.workdir.gems.ruby.gemPath}";
160 environment.BUNDLE_GEMFILE = "${cfg.workdir.gems.confFiles}/Gemfile";
161 environment.SOCKET = cfg.sockets.rails;
162
163 path = [ cfg.workdir.gems cfg.workdir.gems.ruby pkgs.file ];
164
165 preStart = ''
166 install -m 0755 -d ${cfg.dataDir}/tmp/cache
167 ./bin/bundle exec rails db:migrate
168 '';
169
170 script = ''
171 exec ./bin/bundle exec puma -C config/puma.rb
172 '';
173
174 serviceConfig = {
175 User = cfg.user;
176 EnvironmentFile = cfg.configFile;
177 PrivateTmp = true;
178 Restart = "always";
179 TimeoutSec = 60;
180 Type = "simple";
181 WorkingDirectory = cfg.workdir;
182 StateDirectory = cfg.systemdStateDirectory;
183 RuntimeDirectory = cfg.systemdRuntimeDirectory;
184 RuntimeDirectoryPreserve = "yes";
185 };
186
187 unitConfig.RequiresMountsFor = cfg.dataDir;
188 };
189
190 systemd.services.mastodon-sidekiq = {
191 description = "Mastodon Sidekiq";
192 wantedBy = [ "multi-user.target" ];
193 after = [ "network.target" "mastodon-web.service" ];
194
195 environment.RAILS_ENV="production";
196 environment.BUNDLE_PATH = "${cfg.workdir.gems}/${cfg.workdir.gems.ruby.gemPath}";
197 environment.BUNDLE_GEMFILE = "${cfg.workdir.gems.confFiles}/Gemfile";
198 environment.DB_POOL="5";
199
200 path = [ cfg.workdir.gems cfg.workdir.gems.ruby pkgs.imagemagick pkgs.ffmpeg pkgs.file ];
201
202 script = ''
203 exec ./bin/bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push
204 '';
205
206 serviceConfig = {
207 User = cfg.user;
208 EnvironmentFile = cfg.configFile;
209 PrivateTmp = true;
210 Restart = "always";
211 TimeoutSec = 15;
212 Type = "simple";
213 WorkingDirectory = cfg.workdir;
214 StateDirectory = cfg.systemdStateDirectory;
215 RuntimeDirectory = cfg.systemdRuntimeDirectory;
216 RuntimeDirectoryPreserve = "yes";
217 };
218
219 unitConfig.RequiresMountsFor = cfg.dataDir;
220 };
221
222 };
223 }