Skip to content

Commit

Permalink
Make the webdav a standalone app (#361)
Browse files Browse the repository at this point in the history
* Make the webdav a standalone app

* Update docs for standalone webdav
  • Loading branch information
anthonygego authored and GuillaumeDerval committed Oct 8, 2018
1 parent 4b47930 commit ab62a1a
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 81 deletions.
1 change: 1 addition & 0 deletions doc/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Frontend commands

commands_doc/inginious-install
commands_doc/inginious-webapp
commands_doc/inginious-webdav

Backend commands
----------------
Expand Down
2 changes: 1 addition & 1 deletion doc/commands_doc/inginious-webapp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ inginious-webapp
================

Start the Web App Frontend. This command can run a standalone web server (see ``--host`` and ``--port`` options),
but also as a FastCGI backend.
but also as a FastCGI or WSGI backend.

.. program:: inginious-webapp

Expand Down
32 changes: 32 additions & 0 deletions doc/commands_doc/inginious-webdav.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.. _inginious-webapp:

inginious-webdav
================

Start the Web App Frontend. This command can run a standalone web server (see ``--host`` and ``--port`` options),
but also as a FastCGI or WSGI backend.

.. program:: inginious-webdav

::

inginious-webdav [-h] [--config CONFIG] [--host HOST] [--port PORT]

.. option:: --config

Specify the configuration file to use. By default, it is configuration.yaml or configuration.json, depending on which is found first.
This can also be specified via the ``INGINIOUS_WEBAPP_CONFIG`` environment variable.

.. option:: --host HOST

Specify the host to which to bind to. By default, it is localhost.
This can also be specified via the ``INGINIOUS_WEBDAV_HOST`` environment variable.

.. option:: --port PORT

Specify the port to which to bind to. By default, it is 8080.
This can also be specified via the ``INGINIOUS_WEBDAV_PORT`` environment variable.

.. option:: -h, --help

Display the help message.
5 changes: 5 additions & 0 deletions doc/install_doc/config_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ The different entries are :
If set, it allows to use in-browser task debug via ssh. (See :ref:`_webterm_setup` for
more information)

``webdav_host``
Link to the INGInious webdav app with the following syntax: ``http[s]://host:port``.
If set, a new page displays a WebDAV URL and login/password for administrators to access
the course filesystem.

.. _configuration.example.yaml: https://github.com/UCL-INGI/INGInious/blob/master/configuration.example.yaml
.. _docker-py API: https://github.com/docker/docker-py/blob/master/docs/api.md#client-api

Expand Down
143 changes: 104 additions & 39 deletions doc/install_doc/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,29 @@ To run INInious with a remote backend (and agents), do as follows:

backend: tcp://backend-host:2000
#. Run the frontend using :ref:`inginious-webapp`.
::

inginious-webapp --config /path/to/configuration.yaml

.. _webdav_setup:

WebDAV setup
------------

An optional WebDAV server can be used with INGInious to allow course administrators to access
their course filesystem. This is an additional app that needs to be launched on another port or hostname.
Run the WebDAV server using :ref:`inginious-webdav`.
::

inginious-webdav --config /path/to/configuration.yaml --port 8000

In your configuration file (see :ref:`ConfigReference`), set ``webdav_host`` to:
::

<protocol>://<hostname>:<port>

where ``protocol`` is either ``http`` or ``https``, ``hostname`` and ``port`` the hostname and port
where the WebDAV app is running.

.. _webterm_setup:

Expand Down Expand Up @@ -211,6 +234,9 @@ note that INGInious-xterm must be launched using SSL if the frontend is launched
Webserver configuration
-----------------------

The following guides suggest to run the INGInious webapp on http port and WebDAV on port 8080 on the same host.
You are free to adapt thm to your use case (for instance, adding SSL support or using two hostnames).

.. _lighttpd:

.. WARNING::
Expand Down Expand Up @@ -260,37 +286,59 @@ Once this is done, we can configure lighttpd. First, the file ``/etc/lighttpd/mo
include "conf.d/compress.conf"
include "conf.d/fastcgi.conf"

You can then replace the content of fastcgi.conf with:
You can then add virtual host entries in a ``/etc/lighttpd/vhosts.d/inginious.conf`` file and apply the following rules:
::

server.modules += ( "mod_fastcgi" )
server.modules += ( "mod_rewrite" )

alias.url = (
"/static/" => "/usr/lib/python3.5/site-packages/inginious/frontend/static/"
)

fastcgi.server = ( "/inginious-webapp" =>
(( "socket" => "/tmp/fastcgi.socket",
"bin-path" => "/usr/bin/inginious-webapp",
"max-procs" => 1,
"bin-environment" => (
"INGINIOUS_WEBAPP_HOST" => "0.0.0.0",
"INGINIOUS_WEBAPP_PORT" => "80",
"INGINIOUS_WEBAPP_CONFIG" => "/var/www/INGInious/configuration.yaml",
"REAL_SCRIPT_NAME" => ""
),
"check-local" => "disable"
))
)

url.rewrite-once = (
"^/favicon.ico$" => "/static/icons/favicon.ico",
"^/static/(.*)$" => "/static/$1",
"^/(.*)$" => "/inginious-webapp/$1"
)

The ``INGINIOUS_WEBAPP`` prefixed environment variables are used to replace the default command line parameters.
$SERVER["socket"] == ":80" {
alias.url = (
"/static/" => "/usr/lib/python3.5/site-packages/inginious/frontend/static/"
)

fastcgi.server = ( "/inginious-webapp" =>
(( "socket" => "/tmp/fastcgi.socket",
"bin-path" => "/usr/bin/inginious-webapp",
"max-procs" => 1,
"bin-environment" => (
"INGINIOUS_WEBAPP_HOST" => "0.0.0.0",
"INGINIOUS_WEBAPP_PORT" => "80",
"INGINIOUS_WEBAPP_CONFIG" => "/var/www/INGInious/configuration.yaml",
"REAL_SCRIPT_NAME" => ""
),
"check-local" => "disable"
))
)

url.rewrite-once = (
"^/favicon.ico$" => "/static/icons/favicon.ico",
"^/static/(.*)$" => "/static/$1",
"^/(.*)$" => "/inginious-webapp/$1"
)
}

$SERVER["socket"] == ":8080" {
fastcgi.server = ( "/inginious-webdav" =>
(( "socket" => "/tmp/fastcgi.socket",
"bin-path" => "/usr/bin/inginious-webdav",
"max-procs" => 1,
"bin-environment" => (
"INGINIOUS_WEBDAV_HOST" => "0.0.0.0",
"INGINIOUS_WEBDAV_PORT" => "8080",
"INGINIOUS_WEBAPP_CONFIG" => "/var/www/INGInious/configuration.yaml",
"REAL_SCRIPT_NAME" => ""
),
"check-local" => "disable"
))
)

url.rewrite-once = (
"^/(.*)$" => "/inginious-webdav/$1"
)
}

The ``INGINIOUS_WEBAPP`` and ``INGINIOUS_WEBDAV`` prefixed environment variables are used to replace the default command line parameters.
See :ref:`inginious-webapp` for more details.

The ``REAL_SCRIPT_NAME`` environment variable must be specified under lighttpd if you plan to access the application
Expand Down Expand Up @@ -348,24 +396,41 @@ Set the environment variables used by the INGInious CLI scripts in the Apache se
Please note that the service environment file ``/etc/sysconfig/httpd`` may differ from your distribution and wether it
uses *systemd* or *init*.

You can then modify your ``/etc/httpd/conf/httpd.conf`` file to apply the following rules:
You can then add virtual host entries in a ``/etc/httpd/vhosts.d/inginious.conf`` file and apply the following rules:
::

LoadModule wsgi_module /usr/lib64/python3.5/site-packages/mod_wsgi/server/mod_wsgi-py35.cpython-35m-x86_64-linux-gnu.so
<VirtualHost *:80>
ServerName my_inginious_domain
LoadModule wsgi_module /usr/lib64/python3.5/site-packages/mod_wsgi/server/mod_wsgi-py35.cpython-35m-x86_64-linux-gnu.so

WSGIScriptAlias / "/usr/bin/inginious-webapp"
WSGIScriptReloading On
WSGIScriptAlias / "/usr/bin/inginious-webapp"
WSGIScriptReloading On

Alias /static /usr/lib/python3.5/site-packages/inginious/frontend/static
Alias /static /usr/lib/python3.5/site-packages/inginious/frontend/static

<Directory "/usr/bin">
<Files "inginious-webapp">
Require all granted
</Files>
</Directory>
<Directory "/usr/bin">
<Files "inginious-webapp">
Require all granted
</Files>
</Directory>

<DirectoryMatch "/usr/lib/python3.5/site-packages/inginious/frontend/static">
Require all granted
</DirectoryMatch>
<DirectoryMatch "/usr/lib/python3.5/site-packages/inginious/frontend/static">
Require all granted
</DirectoryMatch>
</VirtualHost>

<VirtualHost *:8080>
ServerName my_inginious_domain
LoadModule wsgi_module /usr/lib64/python3.5/site-packages/mod_wsgi/server/mod_wsgi-py35.cpython-35m-x86_64-linux-gnu.so

WSGIScriptAlias / "/usr/bin/inginious-webdav"
WSGIScriptReloading On

<Directory "/usr/bin">
<Files "inginious-webdav">
Require all granted
</Files>
</Directory>
</VirtualHost>

Please note that the compiled *wsgi* module path may differ according to the exact Python version you are running.
77 changes: 77 additions & 0 deletions inginious-webdav
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
# more information about the licensing of this file.

import os
import sys
import argparse
import logging
import web

from inginious.common.log import init_logging, CustomLogMiddleware
from inginious.common.base import load_json_or_yaml
import inginious.frontend.webdav

# Apache wsgi module only set env vars when a request is done
os.environ['INGINIOUS_WEBAPP_CONFIG'] = os.environ.get("INGINIOUS_WEBAPP_CONFIG", "")
os.environ['INGINIOUS_WEBDAV_HOST'] = os.environ.get("INGINIOUS_WEBDAV_HOST", "localhost")
os.environ['INGINIOUS_WEBDAV_PORT'] = os.environ.get("INGINIOUS_WEBDAV_PORT", "8080")

# If INGInious files are not installed in Python path
sys.path.append(os.path.dirname(__file__))

if __name__ == "__main__":
# Parse the paramaters from command line arguments
parser = argparse.ArgumentParser()
parser.add_argument("--config",
help="Path to configuration file. By default: configuration.yaml or configuration.json", default=os.environ["INGINIOUS_WEBAPP_CONFIG"])
parser.add_argument("--host", help="Host to bind to. Default is localhost.", default=os.environ["INGINIOUS_WEBDAV_HOST"])
parser.add_argument("--port", help="Port to listen to. Default is 8080.", type=int, default=os.environ["INGINIOUS_WEBDAV_PORT"])
args = parser.parse_args()

host = args.host
port = args.port
configfile = args.config
else:
# Parse the parameters from environment variables
host = os.environ["INGINIOUS_WEBDAV_HOST"]
port = os.environ["INGINIOUS_WEBDAV_PORT"]
configfile = os.environ["INGINIOUS_WEBAPP_CONFIG"]

if not configfile:
if os.path.isfile("./configuration.yaml"):
configfile = "./configuration.yaml"
elif os.path.isfile("./configuration.json"):
configfile = "./configuration.json"
else:
raise Exception("No configuration file found")

# Load configuration and application (!!! For mod_wsgi, application identifier must be present)
config = load_json_or_yaml(configfile)
application = inginious.frontend.webdav.get_app(config)

# Init logging
init_logging(config.get('log_level', 'INFO'))
logging.getLogger("inginious.webdav").info("http://%s:%d/" % (host, int(port)))

if 'SERVER_SOFTWARE' in os.environ: # cgi
os.environ['FCGI_FORCE_CGI'] = 'Y'

if 'PHP_FCGI_CHILDREN' in os.environ or 'SERVER_SOFTWARE' in os.environ: # lighttpd fastcgi
import flup.server.fcgi as flups
flups.WSGIServer(application, multiplexed=True, bindAddress=None, debug=False).run()

# Launch the integrated websever if app launched in cmd line
if __name__ == "__main__":
# Add static redirection and request log
application = CustomLogMiddleware(application, logging.getLogger("inginious.webdav.requests"))

# Launch the app
server = web.httpserver.WSGIServer((host, int(port)), application)
try:
server.start()
except KeyboardInterrupt:
server.stop()

14 changes: 2 additions & 12 deletions inginious/frontend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,16 +240,6 @@ def get_app(config):
if config.get('log_level', 'INFO') == 'DEBUG':
appli.internalerror = debugerror

# Init webdav if possible (for now, only available with LocalFSProvider)
if isinstance(fs_provider, LocalFSProvider):
from inginious.frontend.webdav import WebDavProxy, init_webdav
webdav_available = True
webdav = init_webdav(user_manager, course_factory, task_factory)
appli_wsgi = lambda: WebDavProxy(appli.wsgifunc(), webdav)
else:
webdav_available = False
appli_wsgi = lambda: appli.wsgifunc()

# Insert the needed singletons into the application, to allow pages to call them
appli.plugin_manager = plugin_manager
appli.course_factory = course_factory
Expand All @@ -269,7 +259,7 @@ def get_app(config):
appli.available_languages = available_languages
appli.welcome_page = config.get("welcome_page", None)
appli.static_directory = config.get("static_directory", "./static")
appli.webdav_available = webdav_available
appli.webdav_host = config.get("webdav_host", None)

# Init the mapping of the app
appli.init_mapping(urls)
Expand All @@ -280,4 +270,4 @@ def get_app(config):
# Start the inginious.backend
client.start()

return appli_wsgi(), lambda: _close_app(appli, mongo_client, client)
return appli.wsgifunc(), lambda: _close_app(appli, mongo_client, client)
2 changes: 1 addition & 1 deletion inginious/frontend/pages/course_admin/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ def get_menu(course, current, renderer, plugin_manager, user_manager):
("download", "<i class='fa fa-download fa-fw'></i>&nbsp; " + _("Download submissions"))]

if user_manager.has_admin_rights_on_course(course):
if web.ctx.app_stack[0].webdav_available:
if web.ctx.app_stack[0].webdav_host:
default_entries += [("webdav", "<i class='fa fa-folder-open fa-fw'></i>&nbsp; " + _("WebDAV access"))]
default_entries += [("replay", "<i class='fa fa-refresh fa-fw'></i>&nbsp; " + _("Replay submissions")),
("danger", "<i class='fa fa-bomb fa-fw'></i>&nbsp; " + _("Danger zone"))]
Expand Down
4 changes: 2 additions & 2 deletions inginious/frontend/pages/course_admin/webdav.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ def GET_AUTH(self, courseid): # pylint: disable=arguments-differ

def page(self, course):
""" Get all data and display the page """
if not self.webdav_available:
if not self.webdav_host:
raise web.notfound()

url = web.ctx.home + "/dav/"+course.get_id()
url = self.webdav_host + "/" + course.get_id()
username = self.user_manager.session_username()
apikey = self.user_manager.session_api_key()
return self.template_helper.get_renderer().course_admin.webdav(course, url, username, apikey)
4 changes: 2 additions & 2 deletions inginious/frontend/pages/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ def lti_outcome_manager(self) -> LTIOutcomeManager:
return self.app.lti_outcome_manager

@property
def webdav_available(self) -> bool:
def webdav_host(self) -> str:
""" True if webdav is available """
return self.app.webdav_available
return self.app.webdav_host

@property
def logger(self) -> logging.Logger:
Expand Down
Loading

0 comments on commit ab62a1a

Please sign in to comment.