# SPDX-License-Identifier: AGPL-3.0-or-later """Configuration helper for Zoph server.""" import configparser import os import re import subprocess from typing import Optional from plinth import action_utils from plinth.actions import privileged APACHE_CONF = '/etc/apache2/conf-available/zoph.conf' DB_BACKUP_FILE = '/var/lib/plinth/backups-data/zoph-database.sql' @privileged def pre_install(): """Preseed debconf values before packages are installed.""" action_utils.debconf_set_selections([ 'zoph zoph/dbconfig-install boolean true', 'zoph zoph/mysql/admin-user string root' ]) @privileged def get_configuration() -> dict[str, str]: """Return the current configuration.""" configuration = {} process = subprocess.run(['zoph', '--dump-config'], stdout=subprocess.PIPE, check=True) for line in process.stdout.decode().splitlines(): name, value = line.partition(':')[::2] configuration[name.strip()] = value[1:] return configuration def _zoph_configure(key, value): """Set a configure value in Zoph.""" subprocess.run(['zoph', '--config', key, value], check=True) @privileged def setup(): """Setup Zoph configuration.""" _zoph_configure('import.enable', 'true') _zoph_configure('import.upload', 'true') _zoph_configure('import.rotate', 'true') _zoph_configure('path.unzip', 'unzip') _zoph_configure('path.untar', 'tar xvf') _zoph_configure('path.ungz', 'gunzip') # Maps using OpenStreetMap is enabled by default. _zoph_configure('maps.provider', 'osm') def _get_db_name(): """Return the name of the database configured by dbconfig.""" config = configparser.ConfigParser() with open('/etc/zoph.ini', 'r', encoding='utf-8') as file_handle: config.read_file(file_handle) return config['zoph']['db_name'].strip('"') @privileged def set_configuration(enable_osm: Optional[bool] = None, admin_user: Optional[str] = None): """Setup Zoph Apache configuration.""" _zoph_configure('interface.user.remote', 'true') # Note that using OpenSteetmap as a mapping provider is a very nice # feature, but some people may regard its use as a privacy issue if enable_osm is not None: value = 'osm' if enable_osm else '' _zoph_configure('maps.provider', value) if admin_user: # Edit the database to rename the admin user to FreedomBox admin user. if not re.match(r'^[\w.@][\w.@-]+\Z', admin_user, flags=re.ASCII): # Check to avoid SQL injection raise ValueError('Invalid username') query = f"UPDATE zoph_users SET user_name='{admin_user}' \ WHERE user_name='admin';" subprocess.run(['mysql', _get_db_name()], input=query.encode(), check=True) @privileged def is_configured() -> bool: """Return whether zoph app is configured.""" process = subprocess.run(['zoph', '--get-config', 'interface.user.remote'], stdout=subprocess.PIPE, check=True) return process.stdout.decode().strip() == 'true' @privileged def dump_database(): """Dump database to file.""" db_name = _get_db_name() os.makedirs(os.path.dirname(DB_BACKUP_FILE), exist_ok=True) with open(DB_BACKUP_FILE, 'w', encoding='utf-8') as db_backup_file: subprocess.run(['mysqldump', db_name], stdout=db_backup_file, check=True) @privileged def restore_database(): """Restore database from file.""" db_name = _get_db_name() subprocess.run(['mysqladmin', '--force', 'drop', db_name], check=False) subprocess.run(['mysqladmin', 'create', db_name], check=True) with open(DB_BACKUP_FILE, 'r', encoding='utf-8') as db_restore_file: subprocess.run(['mysql', db_name], stdin=db_restore_file, check=True)