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 example = "/var/lib/INGInious/tasks";
117 description = ''
118 Path to the tasks folder.
119 Defaults to the provided test tasks folder (readonly).
120 '';
121 };
122
123 useLTI = mkOption {
124 type = types.bool;
125 default = false;
126 description = ''Whether to start the LTI frontend in place of the webapp.'';
127 };
128
129 superadmins = mkOption {
130 type = types.uniq (types.listOf types.str);
131 default = [ "admin" ];
132 example = [ "john" "pepe" "emilia" ];
133 description = ''List of user logins allowed to administrate the whole server.'';
134 };
135
136 containers = mkOption {
137 type = types.attrsOf types.str;
138 default = {
139 default = "ingi/inginious-c-default";
140 };
141 example = {
142 default = "ingi/inginious-c-default";
143 sekexe = "ingi/inginious-c-sekexe";
144 java = "ingi/inginious-c-java";
145 oz = "ingi/inginious-c-oz";
146 pythia1compat = "ingi/inginious-c-pythia1compat";
147 };
148 description = ''
149 An attrset describing the required containers
150 These containers will be available in INGInious using their short name (key)
151 and will be automatically downloaded before INGInious starts.
152 '';
153 };
154
155 hostPattern = mkOption {
156 type = types.str;
157 default = "^inginious.";
158 example = "^inginious.mydomain.xyz$";
159 description = ''
160 The domain that serves INGInious.
161 INGInious uses absolute paths which makes it difficult to relocate in its own subdir.
162 The default configuration will serve INGInious when the server is accessed with a hostname starting with "inginious.".
163 If left blank, INGInious will take the precedence over all the other lighttpd sites, which is probably not what you want.
164 '';
165 };
166
167 backendType = mkOption {
168 type = types.enum [ "local" "remote_manual" ]; # TODO: support backend "remote"
169 default = "local";
170 description = ''
171 Select how INGINious accesses to grading containers.
172 The default "local" option ensures that Docker is started and provisioned.
173 Fore more information, see http://inginious.readthedocs.io/en/latest/install_doc/config_reference.html
174 Not all backends are supported. Use services.inginious.configFile for full flexibility.
175 '';
176 };
177
178 remoteAgents = mkOption {
179 type = types.listOf (types.attrsOf types.str);
180 default = [];
181 example = [ { host = "192.0.2.25"; port = "1345"; } ];
182 description = ''A list of remote agents, used only when services.inginious.backendType is "remote_manual".'';
183 };
184 };
185
186 config = mkIf cfg.enable (
187 mkMerge [
188 # For a local install, we need docker.
189 (mkIf (cfg.backendType == "local") {
190 virtualisation.docker = {
191 enable = true;
192 # We need docker to listen on port 2375.
193 listenOptions = ["127.0.0.1:2375" "/var/run/docker.sock"];
194 storageDriver = mkDefault "overlay";
195 };
196
197 users.extraUsers."lighttpd".extraGroups = [ "docker" ];
198
199 # Ensure that docker has pulled the required images.
200 systemd.services.inginious-prefetch = {
201 script = let
202 images = lib.unique (
203 [ "centos" "ingi/inginious-agent" ]
204 ++ lib.mapAttrsToList (_: image: image) cfg.containers
205 );
206 in lib.concatMapStrings (image: ''
207 ${pkgs.docker}/bin/docker pull ${image}
208 '') images;
209
210 serviceConfig.Type = "oneshot";
211 wants = [ "docker.service" ];
212 after = [ "docker.service" ];
213 wantedBy = [ "lighttpd.service" ];
214 before = [ "lighttpd.service" ];
215 };
216 })
217
218 # Common
219 {
220 services.lighttpd.inginious.tasksDirectory = mkDefault "${inginious}/lib/python2.7/site-packages/inginious/tasks";
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}