1
{ nossa, ... }:
2
{
3
config,
4
lib,
5
pkgs,
6
...
7
}:
8
let
9
cfg = config.services.nossa;
10
inherit (lib)
11
escapeShellArg
12
mkIf
13
mkEnableOption
14
mkOption
15
optional
16
optionalAttrs
17
optionalString
18
types
19
;
20
in
21
{
22
options.services.nossa = {
23
enable = mkEnableOption "Enable the nossa service";
24
package = mkOption {
25
type = types.package;
26
default = nossa;
27
description = "Package to use for nossa.";
28
};
29
gitPackage = mkOption {
30
type = types.package;
31
default = pkgs.git;
32
description = "Package to use for remote git operations.";
33
};
34
user = mkOption {
35
type = types.str;
36
description = "User account to run nossa as (defaults to 'nossa', which I'll create).";
37
default = "nossa";
38
};
39
group = mkOption {
40
type = types.str;
41
description = "Group user to run nossa as (defaults to 'nossa', which I'll create).";
42
default = "nossa";
43
};
44
port = mkOption {
45
type = types.int;
46
description = "Port to listen on.";
47
example = 3000;
48
};
49
metricsPort = mkOption {
50
type = types.int;
51
description = "Port to expose Prometheus metrics on.";
52
example = 3001;
53
};
54
host = mkOption {
55
type = types.str;
56
description = "Value for PHX_HOST.";
57
example = "example.com";
58
};
59
database = mkOption {
60
type = types.nullOr (
61
types.submodule {
62
options = {
63
database = mkOption {
64
type = types.str;
65
description = "Database name";
66
example = "nossa_prod";
67
};
68
hostname = mkOption {
69
type = types.str;
70
description = "Hostname of database server";
71
example = "localhost";
72
};
73
username = mkOption {
74
type = types.nullOr types.str;
75
description = "Username on database server";
76
example = "nossa_prod";
77
};
78
password = mkOption {
79
type = types.nullOr types.str;
80
description = "Password to connect to database server";
81
example = "pass1234";
82
};
83
socketDir = mkOption {
84
type = types.nullOr types.str;
85
description = "Directory containing Unix socket to connect to, if local";
86
example = "/run/postgresql";
87
};
88
};
89
}
90
);
91
description = "Database specification, or null (default) to set one up locally automatically.";
92
default = null;
93
};
94
erlangCookieFile = mkOption {
95
type = types.path;
96
description = "Path to the file containing the Erlang node cookie.";
97
};
98
secretKeyBaseFile = mkOption {
99
type = types.path;
100
description = "Path to the file containing the secret key base.";
101
};
102
contextLinkUrl = mkOption {
103
type = types.nullOr types.str;
104
default = null;
105
description = ''
106
URL behind link displayed at top-right of interface.
107
'';
108
example = "https://en.wiktionary.org";
109
};
110
contextLinkTitle = mkOption {
111
type = types.nullOr types.str;
112
default = null;
113
description = ''
114
Link text for top-right interface link.
115
'';
116
example = "Wiktionary";
117
};
118
registrationEnabled = mkOption {
119
type = types.bool;
120
description = "Enable registration on the instance.";
121
default = false;
122
};
123
maxBodyLength = mkOption {
124
type = types.int;
125
description = "Maximum upload size limit in bytes.";
126
default = 100000000;
127
};
128
};
129
130
config = mkIf (cfg.enable) (
131
let
132
environment =
133
{
134
MIX_ENV = "prod";
135
PHX_HOST = cfg.host;
136
PORT = builtins.toString cfg.port;
137
TELEMETRY_PORT = builtins.toString cfg.metricsPort;
138
NOSSA_MAX_BODY_LENGTH = builtins.toString cfg.maxBodyLength;
139
NOSSA_GIT_HOOKS = "${cfg.package}/priv/hooks";
140
NOSSA_GIT_HTTP_BACKEND = "${cfg.gitPackage}/bin/git-http-backend";
141
}
142
// optionalAttrs (cfg.contextLinkUrl != null && cfg.contextLinkTitle != null) {
143
CONTEXT_LINK_URL = cfg.contextLinkUrl;
144
CONTEXT_LINK_TITLE = cfg.contextLinkTitle;
145
}
146
// optionalAttrs (cfg.registrationEnabled) {
147
NOSSA_REGISTRATION_ENABLED = "true";
148
};
149
150
envVarScript = ''
151
export NOSSA_GIT_ROOT="$STATE_DIRECTORY/git"
152
export RELEASE_COOKIE="$(head -n1 ${escapeShellArg cfg.erlangCookieFile})"
153
export SECRET_KEY_BASE="$(head -n1 ${escapeShellArg cfg.secretKeyBaseFile})"
154
${
155
if (cfg.database == null) then
156
''
157
export DATABASE_PRIMARY_DATABASE="nossa"
158
export DATABASE_PRIMARY_HOSTNAME="localhost"
159
export DATABASE_PRIMARY_SOCKET_DIR="/run/postgresql"
160
''
161
else
162
''
163
export DATABASE_PRIMARY_DATABASE=${escapeShellArg cfg.database.database}
164
export DATABASE_PRIMARY_HOSTNAME=${escapeShellArg cfg.database.hostname}
165
${optionalString (
166
cfg.database.username != null
167
) "export DATABASE_PRIMARY_USERNAME=${escapeShellArg cfg.database.username}"}
168
${optionalString (
169
cfg.database.password != null
170
) "export DATABASE_PRIMARY_PASSWORD=${escapeShellArg cfg.database.password}"}
171
${optionalString (
172
cfg.database.socketDir != null
173
) "export DATABASE_PRIMARY_SOCKET_DIR=${escapeShellArg cfg.database.socketDir}"}
174
''
175
}
176
'';
177
in
178
{
179
users.groups.nossa = mkIf (cfg.group == "nossa") { };
180
users.users.nossa = mkIf (cfg.user == "nossa") {
181
description = "nossa user";
182
group = cfg.group;
183
isSystemUser = true;
184
};
185
186
services.postgresql = mkIf (cfg.database == null) {
187
enable = true;
188
189
ensureUsers = [
190
{
191
name = "nossa";
192
ensureDBOwnership = true;
193
}
194
];
195
ensureDatabases = [ "nossa" ];
196
};
197
198
systemd.services.nossa-migrations = {
199
description = "nossa-migrations";
200
after = [ "networking.target" ] ++ optional (cfg.database == null) "postgresql.service";
201
requires = optional (cfg.database == null) "postgresql.service";
202
203
script = ''
204
${envVarScript}
205
${cfg.package}/bin/nossa-migrate
206
'';
207
208
serviceConfig = {
209
Type = "oneshot";
210
User = cfg.user;
211
ProtectSystem = "strict";
212
PrivateTmp = true;
213
UMask = "0007";
214
RemainAfterExit = "true";
215
};
216
217
inherit environment;
218
};
219
220
systemd.services.nossa = {
221
description = "nossa";
222
wantedBy = [ "multi-user.target" ];
223
after = [ "nossa-migrations.service" ];
224
requires = [
225
"postgresql.service"
226
"nossa-migrations.service"
227
];
228
229
script = ''
230
${envVarScript}
231
${cfg.package}/bin/nossa-server
232
'';
233
234
serviceConfig = {
235
User = cfg.user;
236
ProtectSystem = "strict";
237
PrivateTmp = true;
238
UMask = "0007";
239
Restart = "on-failure";
240
RestartSec = "10s";
241
StateDirectory = "nossa";
242
StateDirectoryMode = "0750";
243
};
244
245
inherit environment;
246
};
247
}
248
);
249
}
250