mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-05-04 20:23:49 +00:00
189 lines
6.6 KiB
Python
189 lines
6.6 KiB
Python
#
|
|
# Copyright (c) 2021-2024 ahriman team.
|
|
#
|
|
# This file is part of ahriman
|
|
# (see https://github.com/arcan1s/ahriman).
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
import aiohttp_jinja2
|
|
import jinja2
|
|
import logging
|
|
import socket
|
|
|
|
from aiohttp.web import Application, normalize_path_middleware, run_app
|
|
|
|
from ahriman.core.auth import Auth
|
|
from ahriman.core.configuration import Configuration
|
|
from ahriman.core.database import SQLite
|
|
from ahriman.core.distributed import WorkersCache
|
|
from ahriman.core.exceptions import InitializeError
|
|
from ahriman.core.spawn import Spawn
|
|
from ahriman.core.status.client import Client
|
|
from ahriman.core.status.watcher import Watcher
|
|
from ahriman.models.repository_id import RepositoryId
|
|
from ahriman.web.apispec import setup_apispec
|
|
from ahriman.web.cors import setup_cors
|
|
from ahriman.web.keys import AuthKey, ConfigurationKey, SpawnKey, WatcherKey, WorkersKey
|
|
from ahriman.web.middlewares.exception_handler import exception_handler
|
|
from ahriman.web.routes import setup_routes
|
|
|
|
|
|
__all__ = ["run_server", "setup_server"]
|
|
|
|
|
|
def _create_socket(configuration: Configuration, application: Application) -> socket.socket | None:
|
|
"""
|
|
create unix socket based on configuration option
|
|
|
|
Args:
|
|
configuration(Configuration): configuration instance
|
|
application(Application): web application instance
|
|
|
|
Returns:
|
|
socket.socket | None: unix socket object if set by option
|
|
"""
|
|
unix_socket = configuration.getpath("web", "unix_socket", fallback=None)
|
|
if unix_socket is None:
|
|
return None # no option set
|
|
# create unix socket and bind it
|
|
unix_socket.unlink(missing_ok=True) # remove socket file if it wasn't removed somehow before
|
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
sock.bind(str(unix_socket))
|
|
# allow everyone to write to the socket, otherwise API methods are not allowed by non-ahriman user
|
|
# by default sockets are created with same rights as directory is, but it seems that x bit is not really required
|
|
# see also https://github.com/aio-libs/aiohttp/issues/4155
|
|
if configuration.getboolean("web", "unix_socket_unsafe", fallback=True):
|
|
unix_socket.chmod(0o666) # for the glory of satan of course
|
|
|
|
# register socket removal
|
|
async def remove_socket(_: Application) -> None:
|
|
unix_socket.unlink(missing_ok=True)
|
|
|
|
application.on_shutdown.append(remove_socket)
|
|
|
|
return sock
|
|
|
|
|
|
async def _on_shutdown(application: Application) -> None:
|
|
"""
|
|
web application shutdown handler
|
|
|
|
Args:
|
|
application(Application): web application instance
|
|
"""
|
|
application.logger.warning("server terminated")
|
|
|
|
|
|
async def _on_startup(application: Application) -> None:
|
|
"""
|
|
web application start handler
|
|
|
|
Args:
|
|
application(Application): web application instance
|
|
|
|
Raises:
|
|
InitializeError: in case if matched could not be loaded
|
|
"""
|
|
application.logger.info("server started")
|
|
|
|
try:
|
|
for watcher in application[WatcherKey].values():
|
|
watcher.load()
|
|
except Exception:
|
|
message = "could not load packages"
|
|
application.logger.exception(message)
|
|
raise InitializeError(message)
|
|
|
|
|
|
def run_server(application: Application) -> None:
|
|
"""
|
|
run web application
|
|
|
|
Args:
|
|
application(Application): web application instance
|
|
"""
|
|
application.logger.info("start server")
|
|
|
|
configuration = application[ConfigurationKey]
|
|
host = configuration.get("web", "host")
|
|
port = configuration.getint("web", "port")
|
|
unix_socket = _create_socket(configuration, application)
|
|
|
|
run_app(application, host=host, port=port, sock=unix_socket, handle_signals=True,
|
|
access_log=logging.getLogger("http"))
|
|
|
|
|
|
def setup_server(configuration: Configuration, spawner: Spawn, repositories: list[RepositoryId]) -> Application:
|
|
"""
|
|
create web application
|
|
|
|
Args:
|
|
configuration(Configuration): configuration instance
|
|
spawner(Spawn): spawner thread
|
|
repositories(list[RepositoryId]): list of known repositories
|
|
|
|
Returns:
|
|
Application: web application instance
|
|
|
|
Raises:
|
|
InitializeError: if no repositories set
|
|
"""
|
|
application = Application(logger=logging.getLogger(__name__))
|
|
application.on_shutdown.append(_on_shutdown)
|
|
application.on_startup.append(_on_startup)
|
|
|
|
application.middlewares.append(normalize_path_middleware(append_slash=False, remove_slash=True))
|
|
application.middlewares.append(exception_handler(application.logger))
|
|
|
|
application.logger.info("setup routes")
|
|
setup_routes(application, configuration)
|
|
|
|
application.logger.info("setup CORS")
|
|
setup_cors(application)
|
|
|
|
application.logger.info("setup templates")
|
|
loader = jinja2.FileSystemLoader(searchpath=configuration.getpathlist("web", "templates"))
|
|
aiohttp_jinja2.setup(application, trim_blocks=True, lstrip_blocks=True, autoescape=True, loader=loader)
|
|
|
|
application.logger.info("setup configuration")
|
|
application[ConfigurationKey] = configuration
|
|
|
|
application.logger.info("setup services")
|
|
# package cache
|
|
if not repositories:
|
|
raise InitializeError("No repositories configured, exiting")
|
|
database = SQLite.load(configuration)
|
|
watchers: dict[RepositoryId, Watcher] = {}
|
|
for repository_id in repositories:
|
|
application.logger.info("load repository %s", repository_id)
|
|
client = Client.load(repository_id, configuration, database, report=False) # explicitly load local client
|
|
watchers[repository_id] = Watcher(client)
|
|
application[WatcherKey] = watchers
|
|
# workers cache
|
|
application[WorkersKey] = WorkersCache(configuration)
|
|
# process spawner
|
|
application[SpawnKey] = spawner
|
|
|
|
application.logger.info("setup authorization")
|
|
validator = application[AuthKey] = Auth.load(configuration, database)
|
|
if validator.enabled:
|
|
from ahriman.web.middlewares.auth_handler import setup_auth
|
|
setup_auth(application, configuration, validator)
|
|
|
|
application.logger.info("setup api docs")
|
|
setup_apispec(application)
|
|
|
|
return application
|