1{ config, lib, pkgs, ... }:
2with lib;
3
4let
5 cfg = config.services.lighttpd.inginious;
6 inginious = pkgs.inginious;
7 execName = "inginious-${if cfg.useLTI then "lti" else "webapp"}";
8
9 inginiousConfigFile = if cfg.configFile != null then cfg.configFile else pkgs.writeText "inginious.yaml" ''
10 # Backend; can be:
11 # - "local" (run containers on the same machine)
12 # - "remote" (connect to distant docker daemon and auto start agents) (choose this if you use boot2docker)
13 # - "remote_manual" (connect to distant and manually installed agents)
14 backend: "${cfg.backendType}"
15
16 ## TODO (maybe): Add an option for the "remote" backend in this NixOS module.
17 # List of remote docker daemon to which the backend will try
18 # to connect (backend: remote only)
19 #docker_daemons:
20 # - # Host of the docker daemon *from the webapp*
21 # remote_host: "some.remote.server"
22 # # Port of the distant docker daemon *from the webapp*
23 # remote_docker_port: "2375"
24 # # A mandatory port used by the backend and the agent that will be automatically started.
25 # # Needs to be available on the remote host, and to be open in the firewall.
26 # remote_agent_port: "63456"
27 # # Does the remote docker requires tls? Defaults to false.
28 # # Parameter can be set to true or path to the certificates
29 # #use_tls: false
30 # # Link to the docker daemon *from the host that runs the docker daemon*. Defaults to:
31 # #local_location: "unix:///var/run/docker.sock"
32 # # Path to the cgroups "mount" *from the host that runs the docker daemon*. Defaults to:
33 # #cgroups_location: "/sys/fs/cgroup"
34 # # Name that will be used to reference the agent
35 # #"agent_name": "inginious-agent"
36
37 # List of remote agents to which the backend will try
38 # to connect (backend: remote_manual only)
39 # Example:
40 #agents:
41 # - host: "192.168.59.103"
42 # port: 5001
43 agents:
44 ${lib.concatMapStrings (agent:
45 " - host: \"${agent.host}\"\n" +
46 " port: ${agent.port}\n"
47 ) cfg.remoteAgents}
48
49 # Location of the task directory
50 tasks_directory: "${cfg.tasksDirectory}"
51
52 # Super admins: list of user names that can do everything in the backend
53 superadmins:
54 ${lib.concatMapStrings (x: " - \"${x}\"\n") cfg.superadmins}
55
56 # Aliases for containers
57 # Only containers listed here can be used by tasks
58 containers:
59 ${lib.concatStrings (lib.mapAttrsToList (name: fullname:
60 " ${name}: \"${fullname}\"\n"
61 ) cfg.containers)}
62
63 # Use single minified javascript file (production) or multiple files (dev) ?
64 use_minified_js: true
65
66 ## TODO (maybe): Add NixOS options for these parameters.
67
68 # MongoDB options
69 #mongo_opt:
70 # host: localhost
71 # database: INGInious
72
73 # Disable INGInious?
74 #maintenance: false
75
76 #smtp:
77 # sendername: 'INGInious <no-reply@inginious.org>'
78 # host: 'smtp.gmail.com'
79 # port: 587
80 # username: 'configme@gmail.com'
81 # password: 'secret'
82 # starttls: True
83
84 ## NixOS extra config
85
86 ${cfg.extraConfig}
87 '';
88in
89{
90 options.services.lighttpd.inginious = {
91 enable = mkEnableOption "INGInious, an automated code testing and grading system.";
92
93 configFile = mkOption {
94 type = types.nullOr types.path;
95 default = null;
96 example = literalExample ''pkgs.writeText "configuration.yaml" "# custom config options ...";'';
97 description = ''The path to an INGInious configuration file.'';
98 };
99
100 extraConfig = mkOption {
101 type = types.lines;
102 default = "";
103 example = ''
104 # Load the dummy auth plugin.
105 plugins:
106 - plugin_module: inginious.frontend.webapp.plugins.auth.demo_auth
107 users:
108 # register the user "test" with the password "someverycomplexpassword"
109 test: someverycomplexpassword
110 '';
111 description = ''Extra option in YaML format, to be appended to the config file.'';
112 };
113
114 tasksDirectory = mkOption {
115 type = types.path;
116 default = "${inginious}/lib/python2.7/site-packages/inginious/tasks";
117 example = "/var/lib/INGInious/tasks";
118 description = ''
119 Path to the tasks folder.
120 Defaults to the provided test tasks folder (readonly).
121 '';
122 };
123
124 useLTI = mkOption {
125 type = types.bool;
126 default = false;
127 description = ''Whether to start the LTI frontend in place of the webapp.'';
128 };
129
130 superadmins = mkOption {
131 type = types.uniq (types.listOf types.str);
132 default = [ "admin" ];
133 example = [ "john" "pepe" "emilia" ];
134 description = ''List of user logins allowed to administrate the whole server.'';
135 };
136
137 containers = mkOption {
138 type = types.attrsOf types.str;
139 default = {
140 default = "ingi/inginious-c-default";
141 };
142 example = {
143 default = "ingi/inginious-c-default";
144 sekexe = "ingi/inginious-c-sekexe";
145 java = "ingi/inginious-c-java";
146 oz = "ingi/inginious-c-oz";
147 pythia1compat = "ingi/inginious-c-pythia1compat";
148 };
149 description = ''
150 An attrset describing the required containers
151 These containers will be available in INGInious using their short name (key)
152 and will be automatically downloaded before INGInious starts.
153 '';
154 };
155
156 hostPattern = mkOption {
157 type = types.str;
158 default = "^inginious.";
159 example = "^inginious.mydomain.xyz$";
160 description = ''
161 The domain that serves INGInious.
162 INGInious uses absolute paths which makes it difficult to relocate in its own subdir.
163 The default configuration will serve INGInious when the server is accessed with a hostname starting with "inginious.".
164 If left blank, INGInious will take the precedence over all the other lighttpd sites, which is probably not what you want.
165 '';
166 };
167
168 backendType = mkOption {
169 type = types.enum [ "local" "remote_manual" ]; # TODO: support backend "remote"
170 default = "local";
171 description = ''
172 Select how INGINious accesses to grading containers.
173 The default "local" option ensures that Docker is started and provisioned.
174 Fore more information, see http://inginious.readthedocs.io/en/latest/install_doc/config_reference.html
175 Not all backends are supported. Use services.inginious.configFile for full flexibility.
176 '';
177 };
178
179 remoteAgents = mkOption {
180 type = types.listOf (types.attrsOf types.str);
181 default = [];
182 example = [ { host = "192.0.2.25"; port = "1345"; } ];
183 description = ''A list of remote agents, used only when services.inginious.backendType is "remote_manual".'';
184 };
185 };
186
187 config = mkIf cfg.enable (
188 mkMerge [
189 # For a local install, we need docker.
190 (mkIf (cfg.backendType == "local") {
191 virtualisation.docker = {
192 enable = true;
193 # We need docker to listen on port 2375.
194 listenOptions = ["127.0.0.1:2375" "/var/run/docker.sock"];
195 storageDriver = mkDefault "overlay";
196 };
197
198 users.extraUsers."lighttpd".extraGroups = [ "docker" ];
199
200 # Ensure that docker has pulled the required images.
201 systemd.services.inginious-prefetch = {
202 script = let
203 images = lib.unique (
204 [ "centos" "ingi/inginious-agent" ]
205 ++ lib.mapAttrsToList (_: image: image) cfg.containers
206 );
207 in lib.concatMapStrings (image: ''
208 ${pkgs.docker}/bin/docker pull ${image}
209 '') images;
210
211 serviceConfig.Type = "oneshot";
212 wants = [ "docker.service" ];
213 after = [ "docker.service" ];
214 wantedBy = [ "lighttpd.service" ];
215 before = [ "lighttpd.service" ];
216 };
217 })
218
219 # Common
220 {
221 # To access inginous tools (like inginious-test-task)
222 environment.systemPackages = [ inginious ];
223
224 services.mongodb.enable = true;
225
226 services.lighttpd.enable = true;
227 services.lighttpd.enableModules = [ "mod_access" "mod_alias" "mod_fastcgi" "mod_redirect" "mod_rewrite" ];
228 services.lighttpd.extraConfig = ''
229 $HTTP["host"] =~ "${cfg.hostPattern}" {
230 fastcgi.server = ( "/${execName}" =>
231 ((
232 "socket" => "/run/lighttpd/inginious-fastcgi.socket",
233 "bin-path" => "${inginious}/bin/${execName} --config=${inginiousConfigFile}",
234 "max-procs" => 1,
235 "bin-environment" => ( "REAL_SCRIPT_NAME" => "" ),
236 "check-local" => "disable"
237 ))
238 )
239 url.rewrite-once = (
240 "^/.well-known/.*" => "$0",
241 "^/static/.*" => "$0",
242 "^/.*$" => "/${execName}$0",
243 "^/favicon.ico$" => "/static/common/favicon.ico",
244 )
245 alias.url += (
246 "/static/webapp/" => "${inginious}/lib/python2.7/site-packages/inginious/frontend/webapp/static/",
247 "/static/common/" => "${inginious}/lib/python2.7/site-packages/inginious/frontend/common/static/"
248 )
249 }
250 '';
251
252 systemd.services.lighttpd.preStart = ''
253 mkdir -p /run/lighttpd
254 chown lighttpd.lighttpd /run/lighttpd
255 '';
256
257 systemd.services.lighttpd.wants = [ "mongodb.service" "docker.service" ];
258 systemd.services.lighttpd.after = [ "mongodb.service" "docker.service" ];
259 }
260 ]);
261}