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