Merge pull request #16086 from layus/inginious

INGInious: provide a NixOS module

authored by Frederik Rietdijk and committed by GitHub 9e2866d5 bc6b9351

+343 -68
+1
nixos/modules/module-list.nix
··· 462 ./services/web-servers/lighttpd/cgit.nix 463 ./services/web-servers/lighttpd/default.nix 464 ./services/web-servers/lighttpd/gitweb.nix 465 ./services/web-servers/nginx/default.nix 466 ./services/web-servers/phpfpm.nix 467 ./services/web-servers/shellinabox.nix
··· 462 ./services/web-servers/lighttpd/cgit.nix 463 ./services/web-servers/lighttpd/default.nix 464 ./services/web-servers/lighttpd/gitweb.nix 465 + ./services/web-servers/lighttpd/inginious.nix 466 ./services/web-servers/nginx/default.nix 467 ./services/web-servers/phpfpm.nix 468 ./services/web-servers/shellinabox.nix
+262
nixos/modules/services/web-servers/lighttpd/inginious.nix
···
··· 1 + { config, lib, pkgs, ... }: 2 + with lib; 3 + 4 + let 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 + ''; 88 + in 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 + }
+71
pkgs/servers/inginious/default.nix
···
··· 1 + { pkgs, lib, pythonPackages }: 2 + with lib; 3 + 4 + let 5 + docker_1_7_2 = pythonPackages.docker.override rec { 6 + name = "docker-py-1.7.2"; 7 + 8 + src = pkgs.fetchurl { 9 + url = "mirror://pypi/d/docker-py/${name}.tar.gz"; 10 + sha256 = "0k6hm3vmqh1d3wr9rryyif5n4rzvcffdlb1k4jvzp7g4996d3ccm"; 11 + }; 12 + }; 13 + 14 + webpy-custom = pythonPackages.web.override { 15 + name = "web.py-INGI"; 16 + src = pkgs.fetchFromGitHub { 17 + owner = "UCL-INGI"; 18 + repo = "webpy-INGI"; 19 + # tip of branch "ingi" 20 + rev = "f487e78d65d6569eb70003e588d5c6ace54c384f"; 21 + sha256 = "159vwmb8554xk98rw380p3ah170r6gm861r1nqf2l452vvdfxscd"; 22 + }; 23 + }; 24 + 25 + in pythonPackages.buildPythonApplication rec { 26 + version = "0.3a2.dev0"; 27 + name = "inginious-${version}"; 28 + 29 + disabled = pythonPackages.isPy3k; 30 + 31 + patchPhase = '' 32 + # transient failures 33 + substituteInPlace inginious/backend/tests/TestRemoteAgent.py \ 34 + --replace "test_update_task_directory" "noop" 35 + ''; 36 + 37 + propagatedBuildInputs = with pythonPackages; [ 38 + requests2 39 + cgroup-utils docker_1_7_2 docutils lti mock pygments 40 + pymongo pyyaml rpyc sh simpleldap sphinx_rtd_theme tidylib 41 + websocket_client watchdog webpy-custom flup 42 + ]; 43 + 44 + buildInputs = with pythonPackages; [ nose selenium virtual-display ]; 45 + 46 + /* Hydra fix exists only on github for now. 47 + src = pkgs.fetchurl { 48 + url = "mirror://pypi/I/INGInious/INGInious-${version}.tar.gz"; 49 + }; 50 + */ 51 + src = pkgs.fetchFromGitHub { 52 + owner = "UCL-INGI"; 53 + repo = "INGInious"; 54 + rev = "07d111c0a3045c7cc4e464d4adb8aa28b75a6948"; 55 + sha256 = "0kldbkc9yw1mgg5w5q5v8k2hz089c5c4rvxb5xhbagkzgm2gn230"; 56 + }; 57 + 58 + # Only patch shebangs in /bin, other scripts are run within docker 59 + # containers and will fail if patched. 60 + dontPatchShebangs = true; 61 + preFixup = '' 62 + patchShebangs $prefix/bin 63 + ''; 64 + 65 + meta = { 66 + description = "An intelligent grader that allows secured and automated testing of code made by students"; 67 + homepage = "https://github.com/UCL-INGI/INGInious"; 68 + license = licenses.agpl3; 69 + maintainers = with maintainers; [ layus ]; 70 + }; 71 + }
+2
pkgs/top-level/all-packages.nix
··· 13140 13141 inferno = callPackage_i686 ../applications/inferno { }; 13142 13143 inkscape = callPackage ../applications/graphics/inkscape { 13144 inherit (pythonPackages) python pyxml lxml numpy; 13145 lcms = lcms2;
··· 13140 13141 inferno = callPackage_i686 ../applications/inferno { }; 13142 13143 + inginious = callPackage ../servers/inginious {}; 13144 + 13145 inkscape = callPackage ../applications/graphics/inkscape { 13146 inherit (pythonPackages) python pyxml lxml numpy; 13147 lcms = lcms2;
+7 -68
pkgs/top-level/python-packages.nix
··· 6965 }; 6966 }; 6967 6968 - lti = buildPythonPackage rec { 6969 - version = "0.4.0"; 6970 name = "PyLTI-${version}"; 6971 6972 propagatedBuildInputs = with self; [ httplib2 oauth oauth2 semantic-version ]; 6973 - buildInputs = with self; [ 6974 flask httpretty oauthlib pyflakes pytest pytestcache pytestcov covCore 6975 pytestflakes pytestpep8 sphinx mock 6976 ]; 6977 6978 src = pkgs.fetchurl { 6979 url = "mirror://pypi/P/PyLTI/${name}.tar.gz"; 6980 - sha256 = "1lkk6qx8yfx1h0rhi4abnd44x0wakggi6zs0nvi572lajf6ydmdh"; 6981 }; 6982 6983 meta = { ··· 11172 }; 11173 }; 11174 11175 - inginious = let 11176 - # patched version of docker bindings. 11177 - docker-custom = self.docker.override { 11178 - name = "docker-1.3.0-dirty"; 11179 - src = pkgs.fetchFromGitHub { 11180 - owner = "GuillaumeDerval"; 11181 - repo = "docker-py"; 11182 - # tip of branch "master" 11183 - rev = "966becd0af514e67de5afbf885257a5005e49626"; 11184 - sha256 = "09k41dh86cbb7z4b8926fi5b2qq670mm6agl5py3giacakrap66c"; 11185 - }; 11186 - }; 11187 - 11188 - webpy-custom = self.web.override { 11189 - name = "web.py-INGI"; 11190 - src = pkgs.fetchFromGitHub { 11191 - owner = "UCL-INGI"; 11192 - repo = "webpy-INGI"; 11193 - # tip of branch "ingi" 11194 - rev = "f487e78d65d6569eb70003e588d5c6ace54c384f"; 11195 - sha256 = "159vwmb8554xk98rw380p3ah170r6gm861r1nqf2l452vvdfxscd"; 11196 - }; 11197 - }; 11198 - in buildPythonPackage rec { 11199 - version = "0.3a2.dev0"; 11200 - name = "inginious-${version}"; 11201 - 11202 - disabled = isPy3k; 11203 - 11204 - patchPhase = '' 11205 - # transient failures 11206 - substituteInPlace inginious/backend/tests/TestRemoteAgent.py \ 11207 - --replace "test_update_task_directory" "noop" 11208 - ''; 11209 - 11210 - propagatedBuildInputs = with self; [ 11211 - requests2 11212 - cgroup-utils docker-custom docutils lti mock pygments 11213 - pymongo pyyaml rpyc sh simpleldap sphinx_rtd_theme tidylib 11214 - websocket_client watchdog webpy-custom 11215 - ]; 11216 - 11217 - buildInputs = with self; [ nose selenium virtual-display ]; 11218 - 11219 - /* Hydra fix exists only on github for now. 11220 - src = pkgs.fetchurl { 11221 - url = "mirror://pypi/I/INGInious/INGInious-${version}.tar.gz"; 11222 - md5 = "40474dd6b6d4fc26e47a1d9c77bcf943"; 11223 - }; 11224 - */ 11225 - src = pkgs.fetchFromGitHub { 11226 - owner = "UCL-INGI"; 11227 - repo = "INGInious"; 11228 - rev = "e019a0e28c442b4201ec4a0be2a816c4ab639683"; 11229 - sha256 = "1pwbm7f7xn50rxzwrqpji58n2ami5r3lgbdpb61q0w3dwkxvvvfk"; 11230 - }; 11231 - 11232 - meta = { 11233 - description = "An intelligent grader that allows secured and automated testing of code made by students"; 11234 - homepage = "https://github.com/UCL-INGI/INGInious"; 11235 - license = licenses.agpl3; 11236 - maintainers = with maintainers; [ layus ]; 11237 - }; 11238 - }; 11239 11240 interruptingcow = buildPythonPackage rec { 11241 name = "interruptingcow-${version}";
··· 6965 }; 6966 }; 6967 6968 + lti = let self' = (self.override {self = self';}) // {pytest = self.pytest_27;}; 6969 + in buildPythonPackage rec { 6970 + version = "0.4.1"; 6971 name = "PyLTI-${version}"; 6972 6973 + disabled = !isPy27; 6974 + 6975 propagatedBuildInputs = with self; [ httplib2 oauth oauth2 semantic-version ]; 6976 + buildInputs = with self'; [ 6977 flask httpretty oauthlib pyflakes pytest pytestcache pytestcov covCore 6978 pytestflakes pytestpep8 sphinx mock 6979 ]; 6980 6981 src = pkgs.fetchurl { 6982 url = "mirror://pypi/P/PyLTI/${name}.tar.gz"; 6983 + sha256 = "076llj10j85zw3zq2gygx2pcfqi9rgcld5m4vq1iai1fk15x60fz"; 6984 }; 6985 6986 meta = { ··· 11175 }; 11176 }; 11177 11178 11179 interruptingcow = buildPythonPackage rec { 11180 name = "interruptingcow-${version}";