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 extraOptions = "-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock";
195 storageDriver = mkDefault "overlay";
196 socketActivation = false;
197 };
198
199 users.extraUsers."lighttpd".extraGroups = [ "docker" ];
200
201 # Ensure that docker has pulled the required images.
202 systemd.services.inginious-prefetch = {
203 script = let
204 images = lib.unique (
205 [ "centos" "ingi/inginious-agent" ]
206 ++ lib.mapAttrsToList (_: image: image) cfg.containers
207 );
208 in lib.concatMapStrings (image: ''
209 ${pkgs.docker}/bin/docker pull ${image}
210 '') images;
211
212 serviceConfig.Type = "oneshot";
213 wants = [ "docker.service" ];
214 after = [ "docker.service" ];
215 wantedBy = [ "lighttpd.service" ];
216 before = [ "lighttpd.service" ];
217 };
218 })
219
220 # Common
221 {
222 # To access inginous tools (like inginious-test-task)
223 environment.systemPackages = [ inginious ];
224
225 services.mongodb.enable = true;
226
227 services.lighttpd.enable = true;
228 services.lighttpd.enableModules = [ "mod_access" "mod_alias" "mod_fastcgi" "mod_redirect" "mod_rewrite" ];
229 services.lighttpd.extraConfig = ''
230 $HTTP["host"] =~ "${cfg.hostPattern}" {
231 fastcgi.server = ( "/${execName}" =>
232 ((
233 "socket" => "/run/lighttpd/inginious-fastcgi.socket",
234 "bin-path" => "${inginious}/bin/${execName} --config=${inginiousConfigFile}",
235 "max-procs" => 1,
236 "bin-environment" => ( "REAL_SCRIPT_NAME" => "" ),
237 "check-local" => "disable"
238 ))
239 )
240 url.rewrite-once = (
241 "^/.well-known/.*" => "$0",
242 "^/static/.*" => "$0",
243 "^/.*$" => "/${execName}$0",
244 "^/favicon.ico$" => "/static/common/favicon.ico",
245 )
246 alias.url += (
247 "/static/webapp/" => "${inginious}/lib/python2.7/site-packages/inginious/frontend/webapp/static/",
248 "/static/common/" => "${inginious}/lib/python2.7/site-packages/inginious/frontend/common/static/"
249 )
250 }
251 '';
252
253 systemd.services.lighttpd.preStart = ''
254 mkdir -p /run/lighttpd
255 chown lighttpd.lighttpd /run/lighttpd
256 '';
257
258 systemd.services.lighttpd.wants = [ "mongodb.service" "docker.service" ];
259 systemd.services.lighttpd.after = [ "mongodb.service" "docker.service" ];
260 }
261 ]);
262}