diff --git a/README.rst b/README.rst index a79a1fd88b41c2b9ca433f16bc6f44a64fd0c8e4..6e4141741c09c2032e8052557b38f28668aaeccc 100644 --- a/README.rst +++ b/README.rst @@ -43,4 +43,6 @@ All while heeding the event’s design. ``docs/index.rst`` (which includes this file) as well from content that should only appear here. -See ``docs/installation.rst`` for installation instructions. +See ``docs/installation.rst`` for installation instructions. To build the +documentation as HTML locally, run ``pdm run docs`` and open +``docs/_build/index.html``. diff --git a/docs/conf.py b/docs/conf.py index 49bf80711cffb97814a0754ae20ae9bf9402818a..6e37e540da6fe15af1d1710f4215d48fc89e53aa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ release = version # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = [] +extensions = ["sphinx.ext.intersphinx"] templates_path = ["_templates"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] @@ -35,4 +35,10 @@ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "alabaster" -html_static_path = ["_static"] + +intersphinx_mapping = dict( + flask=("https://flask.palletsprojects.com/en/3.0.x/", None), + flask_multipass=("https://flask-multipass.readthedocs.io/en/latest/", None), + flask_sqlalchemy=("https://flask-sqlalchemy.palletsprojects.com/en/3.1.x/", None), + gunicorn=("https://docs.gunicorn.org/en/stable/", None), +) diff --git a/docs/development.rst b/docs/development.rst index 4c1fa63138632342f3ae7c82ca6148480241a83c..ada9c3590d2c3edef9b70d378c336d06b994b060 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -16,7 +16,7 @@ server: .. code:: shell-session - % npm install # may update package-lock.json + % npm install # may update package-lock.json, or instead: % npm clean-install # uses package-lock.json exactly % npm run start diff --git a/docs/installation.rst b/docs/installation.rst index ee5adc48230d192b69479c0246a00acc7a54ce71..c1fdd55efade73716e49fd388dad35931b872bd2 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1,39 +1,224 @@ Installation ============ -It is recommended to use our pre-built `Python package`_ (wheel) or `container -image`_. Building the package yourself is left as an exercise to the reader -(hint: it is a standard Python package, you need Node.js, pass -``--config-setting without-npm`` to ``pyproject-build`` if you do not want it to -build the frontend for you, but then you need to run ``npm run build`` -beforehand). - -The container image ships with all supported features. For the Python package, -some features require optional dependencies: ``auth-ldap``, ``auth-saml``, +To install this software, you can use a container-based setup, or run it +directly in a Python environment. As there are many ways (and opinions!) how to +configure a web server and deploy a Python WSGI application, only some details +are outlined here and the reader is expected to be familiar with at least web +server administration. + +See also the :external+flask:std:doc:`Flask documentation on production +deployment <deploying/index>`. + +Container setup (Podman, Kubernetes, …) +--------------------------------------- + +Pull the `container image`_ (or build it yourself using the provided +``Containerfile``): + +.. code:: shell-session + + % podman pull registry.git.fsmpi.rwth-aachen.de/schilder/schilder2000:{{TAG}} + +Config and data is expected in ``/usr/local/var/schilder2000-instance/``. For +starters, you can use the ``examples/`` directory in the `source repository`_, +which is also included in the container image. To create a volume based on it: + +.. code:: shell-session + + % [[ $(id -u) -eq 0 ]] || podman unshare + # pushd $(podman image mount registry.git.fsmpi.rwth-aachen.de/schilder/schilder2000:{{TAG}}) + # cd usr/local/var/schilder2000-instance + # tar cf - . | podman volume import schilder2000-instance + # popd + # podman image unmount registry.git.fsmpi.rwth-aachen.de/schilder/schilder2000:{{TAG}} + +You can also use a volume just for ``/usr/local/var/schilder2000-instance/data`` +and mount ``/usr/local/var/schilder2000-instance/config/config.py`` separately. + +Continue with :ref:`configuration`. + +Without container +----------------- + +You can use our pre-built `Python package`_ (wheel) or build it yourself. + +Dependencies +~~~~~~~~~~~~ + +You need Python 3.9 or later and the dependencies specified in +``pyproject.toml``. It is suggested to use a virtualenv, as distribution +packages are often missing or outdated. + +Some optional dependencies need additional native libraries, namely +MySQL/MariaDB Connector/C for MySQL/MariaDB support (both should work with +either database), and OpenLDAP and Cyrus SASL for LDAP authentication. If +binary wheels for those are not available, you need the development versions of +those as well as C compiler and Python development infrastructure. +Additionally, Git is required to get the sources of some dependencies. + +.. code:: shell-session + + # apt-get install python3-dev pkg-config gcc libmariadb-dev libldap-dev libsasl2-dev git + # dnf install python3-devel gcc 'pkgconfig(libmariadb)' 'pkgconfig(ldap)' git-core + # apk add -t .schilder2000 python3 mariadb-connector-c libldap # Runtime + # apk add -t .schilder2000-build build-base git mariadb-connector-c-dev \ + openldap-dev python3-dev # Build only, can be removed later + +To use OS packages as much as possible (note that the versions your distribution +provides may be too outdated and you may need to install the development +dependencies from above anyway): + +.. code:: shell-session + + # apt-get install python3 weasyprint python3-jinja2 python3-flask \ + python3-asgiref python3-flask-sqlalchemy python3-flaskext.wtf alembic \ + python3-qrcode python3-ldap python3-authlib python3-psycopg \ + python3-mysqldb git + # dnf install python3 \ + python3dist\({weasyprint,jinja2,flask\\[async\\],flask-sqlalchemy,flask-wtf,alembic,qrcode}\) \ + python3dist\({python-ldap,python3-saml,authlib,psycopg,mysqlclient}\) + # apk add -t .schilder2000 python3 weasyprint py3-jinja2 py3-flask \ + py3-asgiref py3-flask-sqlalchemy py3-flask-wtf py3-alembic py3-ldap \ + py3-python3-saml py3-authlib py3-psycopg py3-mysqlclient git + +If you are not using the pre-built `Python package`_ (wheel), you also need +Node.js and npm to build the frontend: + +.. code:: shell-session + + # apt-get install npm + # dnf install nodejs-npm + # apk add npm + +You will also likely want a WSGI server. If in doubt, choose Gunicorn_: + +.. code:: shell-session + + # apt-get install gunicorn + # dnf install python3-gunicorn + # apk add py3-gunicorn + + +Pre-built package +~~~~~~~~~~~~~~~~~ + +In your chosen deployment location (e. g., virtualenv), install the package with +your desired optional extras. These are ``auth-ldap``, ``auth-saml``, ``auth-oauth``, ``all-auth``, ``db-postgres``, ``db-mysql``, ``all-db``, -``all``. For example, to install the package with support for SAML login and -Postgres database: +``all``. For example, to install with support for SAML login and Postgres +database: + +.. code:: shell-session + + % pip install "schilder2000[auth-saml,db-postgres]" \ + --index-url https://git.fsmpi.rwth-aachen.de/api/v4/projects/305/packages/pypi/simple + +Continue with :ref:`configuration`. + +Building from source +~~~~~~~~~~~~~~~~~~~~ + +This package follows `PEP 517`_ conventions. You can use `build`_ to generate +SDist and wheel packages, or run ``pip install .`` in your local source tree, … +if you are at this point, you probably know your choices anyway. + +The default build process will automatically invoke Node.js/npm to build the +frontend. To disable this behaviour, pass ``without-npm`` as build config +setting. This requires the files to already exist. For example: .. code:: shell-session - % pip install "schilder2000[auth-saml,db-postgres]" --index-url https://git.fsmpi.rwth-aachen.de/api/v4/projects/305/packages/pypi/simple + % npm run build + % python -m build --config-setting without-npm + +.. _configuration: + +Configuration +------------- Configuration and runtime data is stored in the instance directory. For -container installs, mount a volume to ``/usr/local/var/schilder2000-instance/``. +container installs, this is ``/usr/local/var/schilder2000-instance/``. For package installs, this is ``{{ python prefix }}/var/schilder2000-instance``; if in doubt, try to run ``flask -A schilder2000``, the error should tell you -where it expects the instance directory. Example config and data is located in -the ``examples`` directory. The templates there get their footer text and logo -from the application config and should also be useful as an example to write -your own templates. +where it expects the instance directory. -The main application config is located in ``config/config.py``. Customise it as -needed, the example config should provide enough comments. +Example config and data is located in the ``examples`` directory. The templates +there get their footer text and logo from the application config and should also +be useful as an example to write your own templates. -An example config for Gunicorn_ is provided in ``gunicorn.conf.py``. If you -want to use another WSGI server, configure it to use -``schilder2000:create_app()`` as application object. Note that this is a -factory function that returns the application callable, you have to call it! +The main application config is located in ``config/config.py``. Available +options: + +.. py:data:: SQLALCHEMY_DATABASE_URI + :type: str + + **Required**. Database connection URI. See + :external:py:data:`Flask-SQLAlchemy documentation + <flask_sqlalchemy.config.SQLALCHEMY_DATABASE_URI>` for details and + additional options. Note that the ``db-postgres`` optional dependency + install the ``psycopg`` driver (i. e., version 3), not ``psycopg2``. + +.. py:data:: SECRET_KEY + :type: str | bytes + + **Required**. Secret key for signing cookies and other security related + needs. See :external+flask:py:data:`Flask documentation <SECRET_KEY>` + for details. + +.. py:data:: SCHILD_FOOTER + :type: str + + Footer text used by the templates shipped in ``examples/``. + +.. py:data:: SCHILD_LOGO + :type: str + + Logo used by the templates shipped in ``examples/``. Expects a file + relative to ``{{ instance path }}/data/static``. + +.. py:data:: TEMPLATES_AUTO_RELOAD + :type: bool + + Reload templates when they are changed. See + :external+flask:py:data:`Flask documentation <TEMPLATES_AUTO_RELOAD>` + for details. + +.. py:data:: PRINTERS + :type: dict[str, str] + + **Required**. Available printers. Maps display names to IPP(S) URLs. + +.. py:data:: REQUIRE_LOGIN + :type: bool + + **Required**. Whether authentication is required to access the service. + If enabled, requires additional configuration for + :external+flask_multipass:std:doc:`index`. + +.. py:data:: MULTIPASS_AUTH_PROVIDERS + :type: dict[str, dict] + +.. py:data:: MULTIPASS_IDENTITY_PROVIDERS + :type: dict[str, dict] + +.. py:data:: MULTIPASS_PROVIDER_MAP + :type: dict[str, str] + + See :external+flask_multipass:std:doc:`Flask-Multipass documentation + <index>` for details. + +.. py:data:: MULTIPASS_IDENTITY_INFO_KEYS + :type: list + + Required by Flask-Multipass, but can be empty, as identity information + is not used. + +See also :external+flask:std:doc:`Flask documentation <config>` for additional +options and information. + +Database migration +~~~~~~~~~~~~~~~~~~ Unless you use SQLite, create the database in your database server. In all cases, run the migrations: @@ -42,10 +227,28 @@ cases, run the migrations: % flask -A schilder2000 alembic upgrade head -If you want to use your webserver to directly serve static files, route -``/static`` to ``{{ python packages directory }}/schilder2000/static`` and -``/instance/static`` to ``{{ instance path }}/data/static``. +WSGI and webserver setup +~~~~~~~~~~~~~~~~~~~~~~~~ + +An example config for Gunicorn_ is provided in ``gunicorn.conf.py``. This +will listen on ``[::]:8080`` (port 8080, all interfaces), and write the access +log to stdout. + +See :external+flask:std:doc:`Flask documentation on Gunicorn +<deploying/gunicorn>` and :external+gunicorn:std:doc:`Gunicorn documentation +<index>` for further information. + +If you want to use another WSGI server, configure it to use +``schilder2000:create_app()`` as application object. Note that this is a +factory function that returns the application callable, you have to call it! + +To use your webserver to directly serve static files, route ``/static`` to ``{{ +python packages directory }}/schilder2000/static`` and ``/instance/static`` to +``{{ instance path }}/data/static``. .. _`Python package`: https://git.fsmpi.rwth-aachen.de/schilder/schilder2000/-/packages .. _`container image`: https://git.fsmpi.rwth-aachen.de/schilder/schilder2000/container_registry/33 .. _Gunicorn: https://gunicorn.org/ +.. _`source repository`: https://git.fsmpi.rwth-aachen.de/schilder/schilder2000 +.. _`PEP 517`: https://peps.python.org/pep-0517/ +.. _`build`: https://github.com/pypa/build diff --git a/examples/config/config.py b/examples/config/config.py index a6b2cc58edaeaa5e778bcbbb63ba7e1bb4666666..3e025edbb6ddfe048673accf212fe0243330675f 100644 --- a/examples/config/config.py +++ b/examples/config/config.py @@ -12,7 +12,7 @@ SQLALCHEMY_DATABASE_URI = "postgresql+psycopg:///schilder2000" # To generate a secret key: # % python -c 'import secrets; print(secrets.token_hex())' -SECRET_KEY = "abc123" # Replace me! +#SECRET_KEY = ... # Replace me! TEMPLATES_AUTO_RELOAD = True SCHILD_FOOTER = "Organization Without a Cool Acronym – O.W.C.A." diff --git a/pyproject.toml b/pyproject.toml index 227f2389774a65f0ddac037098e121bafbdbde64..9c32596421581f62377d8052e89099601b1ddde3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,7 @@ docs = [ [tool.pdm.scripts] serve = "flask -A schilder2000 run --debug" migrate = "flask -A schilder2000 alembic upgrade head" +docs = "sphinx-build docs docs/_build" [tool.pdm.build] includes = [