mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-03-11 09:04:54 +00:00
- Since we are going to be an OpenID Provider, we need to fix the URLs that
other apps will be configured with for authentication. So change now from
/plinth to /freedombox. If done later, it will be harder since all the
configuration files for all dependent apps will need to be updated.
Tests:
- App availability checking works. Request goes to /freedombox URL
- Favicon is served properly and through /favicon.ico URL
- Redirection happens from / to /freedombox directly
- UI is available on /freedombox and on /plinth
- Manual page show /freedombox as the URL in two places
- Static files are successfully served from /freedombox URLs. URLs inside page
start with /freedombox
- backup, bepasty, calibre, config, dynamicdns, ejabberd, featherwiki, gitweb,
ikiwiki, kiwix, miniflux, names, openvpn, shadowsocks, shadowsocksserver,
sharing, shapshot, tiddlywiki, users, wireguard, jsxc, matrixsynapse, first
wizard, storage, samba, tags functional tests work. Backup/restore test for
matrixsynapse fails due to an unrelated bug (server not restarted after
restore).
- Setting the home page works:
- Having /plinth in the home page configuration works. Shows selection
correctly.
- Setting to app works. Shows selection correctly.
- Setting to user home page (sets /freedombox). Shows selection correctly.
- Setting to apache default works. Shows selection correctly.
- Changing back to FreedomBox service works. Shows selection correctly.
- Unit tests work
- Configuration page shows /freedombox in description but not /plinth
- Diagnostics show /freedombox in tests
- Roundcube URL link in email app has /freedombox
- email loads the page /.well-known/autoconfig/mail/config-v1.1.xml correctly
- email app shows /freedombox/apps/roundcube for /roundcube if roundcube is not
installed.
- networks: router configuration page shows URL starting with /freedombox.
- snapshot: Shows URL starting with /freedombox on the app page
- js licenses page uses /freedombox prefix for JSXC.
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
188 lines
6.4 KiB
Python
188 lines
6.4 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""
|
|
Setup CherryPy web server.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import pathlib
|
|
import sys
|
|
import warnings
|
|
from typing import ClassVar
|
|
|
|
import cherrypy
|
|
|
|
from . import app as app_module
|
|
from . import cfg, log, web_framework
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# When an app installs a python module as a dependency and imports it. CherryPy
|
|
# will start monitoring it for changes. When the app is uninstalled, the module
|
|
# is removed from the system leading to change detected by CherryPy. The entire
|
|
# service is then restarted if it is in development mode. This could cause a
|
|
# temporary failure in requests served leading to failures in functional tests.
|
|
# Workaround this by preventing auto-reloading for all but FreedomBox's python
|
|
# modules.
|
|
AUTORELOAD_REGEX = r'^plinth'
|
|
|
|
_CUSTOM_STATIC_URL = '/custom/static'
|
|
|
|
_USER_CSS_PATH = 'css/user.css'
|
|
|
|
|
|
def get_custom_static_url():
|
|
"""Return the URL path fragment for custom static URL."""
|
|
return f'{cfg.server_dir}{_CUSTOM_STATIC_URL}'
|
|
|
|
|
|
def get_user_css():
|
|
"""Return the URL path fragement for user CSS if it exists else None."""
|
|
user_css_path = pathlib.Path(cfg.custom_static_dir) / _USER_CSS_PATH
|
|
if not user_css_path.exists():
|
|
return None
|
|
|
|
return get_custom_static_url() + '/' + _USER_CSS_PATH
|
|
|
|
|
|
def _mount_static_directory(static_dir, static_url):
|
|
config = {
|
|
'/': {
|
|
'tools.staticdir.root': static_dir,
|
|
'tools.staticdir.on': True,
|
|
'tools.staticdir.dir': '.'
|
|
}
|
|
}
|
|
app = cherrypy.tree.mount(None, static_url, config)
|
|
log.setup_cherrypy_static_directory(app)
|
|
|
|
|
|
def init():
|
|
"""Setup CherryPy server"""
|
|
logger.info('Setting up CherryPy server')
|
|
|
|
# Configure default server
|
|
cherrypy.config.update({
|
|
'server.max_request_body_size': 0,
|
|
'server.socket_host': cfg.host,
|
|
'server.socket_port': cfg.port,
|
|
'server.thread_pool': 10,
|
|
# Avoid stating files once per second in production
|
|
'engine.autoreload.on': cfg.develop,
|
|
'engine.autoreload.match': AUTORELOAD_REGEX,
|
|
})
|
|
|
|
def _logging_middleware(application):
|
|
"""A WSGI middleware to log messages before executing them."""
|
|
|
|
def _wrapper(environ, start_response):
|
|
"""Log request, then hand control to original app."""
|
|
logger.info("%s %s", environ['REQUEST_METHOD'],
|
|
environ['PATH_INFO'])
|
|
return application(environ, start_response)
|
|
|
|
return _wrapper
|
|
|
|
application = web_framework.get_wsgi_application()
|
|
cherrypy.tree.graft(_logging_middleware(application), cfg.server_dir)
|
|
|
|
static_dir = os.path.join(cfg.file_root, 'static')
|
|
_mount_static_directory(static_dir, web_framework.get_static_url())
|
|
|
|
custom_static_dir = cfg.custom_static_dir
|
|
custom_static_url = get_custom_static_url()
|
|
if os.path.exists(custom_static_dir):
|
|
_mount_static_directory(custom_static_dir, custom_static_url)
|
|
else:
|
|
logger.debug(
|
|
'Not serving custom static directory %s on %s, '
|
|
'directory does not exist', custom_static_dir, custom_static_url)
|
|
|
|
_mount_static_directory('/usr/share/javascript', '/javascript')
|
|
|
|
for app in app_module.App.list():
|
|
module = sys.modules[app.__module__]
|
|
module_path = os.path.dirname(module.__file__)
|
|
static_dir = os.path.join(module_path, 'static')
|
|
if not os.path.isdir(static_dir):
|
|
continue
|
|
|
|
urlprefix = "%s%s" % (web_framework.get_static_url(), app.app_id)
|
|
_mount_static_directory(static_dir, urlprefix)
|
|
|
|
for component in StaticFiles.list():
|
|
component.mount()
|
|
|
|
cherrypy.engine.signal_handler.subscribe()
|
|
|
|
|
|
def run(on_web_server_stop):
|
|
"""Start the web server and block it until exit."""
|
|
with warnings.catch_warnings():
|
|
# Suppress warning that some of the static directories don't exist.
|
|
# Since there is no way to add/remove those tree mounts at will, we
|
|
# need to add them all before hand even if they don't exist now. If the
|
|
# directories becomes available later, CherryPy serves them just fine.
|
|
warnings.filterwarnings(
|
|
'ignore', '(.|\n)*is not an existing filesystem path(.|\n)*',
|
|
UserWarning)
|
|
cherrypy.engine.start()
|
|
|
|
cherrypy.engine.subscribe('stop', on_web_server_stop)
|
|
|
|
|
|
def block():
|
|
"""Block the calling thread until web server exits."""
|
|
cherrypy.engine.block()
|
|
|
|
|
|
class StaticFiles(app_module.FollowerComponent):
|
|
"""Component to serve static files shipped with an app.
|
|
|
|
Any files in <app>/static directory will be automatically served on
|
|
/static/<app>/ directory by FreedomBox. This allows each app to ship custom
|
|
static files that are served by the web server.
|
|
|
|
However, in some rare circumstances, a system folder will need to be served
|
|
on a path for the app to work. This component allows declaring such
|
|
directories and the web paths they should be served on.
|
|
|
|
"""
|
|
|
|
_all_instances: ClassVar[dict[str, 'StaticFiles']] = {}
|
|
|
|
def __init__(self, component_id, directory_map=None):
|
|
"""Initialize the component.
|
|
|
|
component_id should be a unique ID across all components of an app and
|
|
across all components.
|
|
|
|
directory_map should be a dictionary with keys to be web paths and
|
|
values to be absolute path of the directory on disk to serve. The
|
|
static files from the directory are served over the given web path. The
|
|
web path will be prepended with the FreedomBox's configured base web
|
|
path. For example, {'/foo': '/usr/share/foo'} means that
|
|
'/usr/share/foo/bar.png' will be served over '/freedombox/foo/bar.png'
|
|
if FreedomBox is configured to be served on '/freedombox'.
|
|
|
|
"""
|
|
super().__init__(component_id)
|
|
self.directory_map = directory_map
|
|
self._all_instances[component_id] = self
|
|
|
|
@classmethod
|
|
def list(cls):
|
|
"""Return a list of all instances."""
|
|
return cls._all_instances.values()
|
|
|
|
def mount(self):
|
|
"""Perform configuration of the web server to handle static files.
|
|
|
|
Called by web server abstraction layer after web server has been setup.
|
|
|
|
"""
|
|
if self.directory_map:
|
|
for web_path, file_path in self.directory_map.items():
|
|
web_path = '%s%s' % (cfg.server_dir, web_path)
|
|
_mount_static_directory(file_path, web_path)
|