# Copyright 2026 jCloud # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from dataclasses import dataclass from .exceptions import Fail import os import jcloud_config_parser import ipaddress import pathlib import logging import argparse from typing import Optional __all__ = [ 'load_config', 'process_host_and_port', 'GiteaConfig', 'process_gitea_config', ] def load_config(config_directory: pathlib.Path) -> tuple[ jcloud_config_parser.ini.INIConfiguration, jcloud_config_parser.ini.INIConfiguration ]: ''' Loads the configuration and the default configuration. :param config_directory: The configuration directory. :type config_directory: pathlib.Path :return: The configuration. :rtype: tuple[jcloud_config_parser.ini.INIConfiguration, jcloud_config_parser.ini.INIConfiguration] ''' # load default configuration with open(os.path.dirname(__file__) + '/../../../default_configuration.conf') as f: default_configuration = jcloud_config_parser.ini.INIConfiguration.from_string(f.read()) f.close() # load configuration configuration = jcloud_config_parser.ini.INIConfiguration.from_string( (config_directory / 'server.conf').read_text(), default=default_configuration, ignore_errors=True ) return configuration, default_configuration def _validate_host(host: str) -> bool: ''' Checks whether the address is a valid address (``ip:port``), e. g. ``0.0.0.0:3000``. :param addr: The address :type addr: str :return: ``True`` if the address is valid, otherwise ``False``. :rtype: bool ''' try: ipaddress.ip_address(host) return True except Exception: return False def process_host_and_port( configuration: jcloud_config_parser.ini.INIConfiguration, default_configuration: jcloud_config_parser.ini.INIConfiguration, args: argparse.Namespace, logger: logging.Logger ) -> tuple[str, int]: ''' Processes the host and port in the configuration. Uses default values and the configuration data passed via command-line arguments as a fallback. :param configuration: The configuration. :type configuration: jcloud_config_parser.ini.INIConfiguration :param default_configuration: The default configuration. :type default_configuration: jcloud_config_parser.ini.INIConfiguration :param args: The arguments. :type args: argparse.Namespace :return: The host and the port :rtype: tuple[str, int] ''' # host host = args.host or configuration.server.host if not _validate_host(host): logger.error(f'Error in configuration: [server]host: \'{host}\' is not a valid host, using default value.') host = default_configuration.server.host # port port = args.port or configuration.server.port if not port.isdigit(): logger.error(f'Error in configuration: [server]port: \'{port}\' is not an integer, using default value.') port = default_configuration.server.port else: if not (0 <= int(port) <= 65535): logger.error(f'Error in configuration: [server]port: {port} is not between 0 and 65535, using default value.') port = default_configuration.server.port return host, int(port) @dataclass class GiteaConfig: enabled: bool webhook_secret_file_path: Optional[pathlib.Path] def _is_readable_file(path: pathlib.Path) -> bool: ''' Returns whether the file is readable and exists. :param path: The file path. :type path: pathlib.Path :return: Whether the file is readable and exists. :rtype: bool ''' if not path.exists(): return False try: with open(str(path), 'rb'): pass except (OSError, PermissionError): return False return True def process_gitea_config( configuration: jcloud_config_parser.ini.INIConfiguration, logger: logging.Logger ) -> GiteaConfig: ''' Processes the Gitea configuration. :param configuration: The configuration. :type configuration: jcloud_config_parser.ini.INIConfiguration :param logger: The logger. :type logger: logging.Logger :return: The Gitea configuration. :rtype: GiteaConfig ''' if configuration['gitea'].enabled not in ('true', 'yes', 't', 'y'): return GiteaConfig(False, None) secret_file_path = configuration['gitea'].webhook_secret_file if not secret_file_path: # disable secret secret_file_path = None else: secret_file_path = pathlib.Path(secret_file_path) if not _is_readable_file(secret_file_path): logger.critical(f'{secret_file_path}: Cannot read Gitea webhook secret file') raise Fail(f'{secret_file_path}: Cannot read Gitea webhook secret file', exit_code = 2) return GiteaConfig(True, secret_file_path)