mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-02-18 08:33:41 +00:00
The latest version of miniflux can't connect to the database after a fresh installation. This is due to incorrect ownership of /etc/miniflux/database file which is owned by root (and correctly having the permissions 0600). After changes in bug #1078416, miniflux no longer runs as root user and instead runs as miniflux user. This user can't read the database file. The daemon silently falls back to using built in defaults and fails to connect to PostgreSQL database. This is originally caught by functional tests in FreedomBox's miniflux integration. Links: 1) https://bugs.debian.org/1081562 2) https://salsa.debian.org/go-team/packages/miniflux/-/merge_requests/2 Tests: - Freshly install miniflux with the patch and the daemon is running. Ownership for the file /etc/miniflux/database is as expected. - Install miniflux without the patch. Daemon is not running. Apply patch and restart service. miniflux app is updated. Daemon is running. Ownership for the file /etc/miniflux/database is as expected. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
154 lines
4.8 KiB
Python
154 lines
4.8 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""Configuration helper for Miniflux feed reader."""
|
|
|
|
import json
|
|
import os
|
|
import pathlib
|
|
import shutil
|
|
from typing import Tuple
|
|
from urllib.parse import urlparse
|
|
|
|
import pexpect
|
|
|
|
from plinth import action_utils, db
|
|
from plinth.actions import privileged, secret_str
|
|
from plinth.utils import is_non_empty_file
|
|
|
|
STATIC_SETTINGS = {
|
|
'BASE_URL': 'http://localhost/miniflux/',
|
|
'RUN_MIGRATIONS': 1,
|
|
'PORT': 8788
|
|
}
|
|
|
|
ENV_VARS_FILE = '/etc/miniflux/freedombox.conf'
|
|
DATABASE_FILE = '/etc/miniflux/database'
|
|
DB_BACKUP_FILE = '/var/lib/plinth/backups-data/miniflux-database.sql'
|
|
|
|
|
|
def _dict_to_env_file(dictionary: dict[str, str]) -> str:
|
|
"""Write a dictionary into a systemd environment file format."""
|
|
return "\n".join((f"{k}={v}" for k, v in dictionary.items()))
|
|
|
|
|
|
def _env_file_to_dict(env_vars: str) -> dict[str, str]:
|
|
"""Return systemd environtment variables as a dictionary."""
|
|
return {
|
|
line.split('=')[0]: line.split('=')[1].strip()
|
|
for line in env_vars.splitlines()
|
|
if line.strip() and not line.strip().startswith('#')
|
|
}
|
|
|
|
|
|
@privileged
|
|
def pre_setup():
|
|
"""Perform pre-install actions for Miniflux."""
|
|
vars_file = pathlib.Path(ENV_VARS_FILE)
|
|
vars_file.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
existing_settings = {}
|
|
if is_non_empty_file(ENV_VARS_FILE):
|
|
# Any comments in the file will be dropped.
|
|
existing_settings = _env_file_to_dict(vars_file.read_text())
|
|
|
|
new_settings = existing_settings | STATIC_SETTINGS
|
|
vars_file.write_text(_dict_to_env_file(new_settings))
|
|
|
|
|
|
@privileged
|
|
def setup(old_version: int):
|
|
"""Perform post-install actions for Miniflux."""
|
|
# Fix incorrect permissions on database file in version 2.2.0-2. See
|
|
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1081562 . Can be
|
|
# removed after the fix for the bug reaches Trixie/testing.
|
|
shutil.chown(DATABASE_FILE, user='miniflux', group='root')
|
|
if not old_version or action_utils.service_is_enabled('miniflux'):
|
|
# If the service was tried too many times already, it won't
|
|
# successfully restart.
|
|
action_utils.service_reset_failed('miniflux')
|
|
action_utils.service_restart('miniflux')
|
|
|
|
|
|
def _run_miniflux_interactively(command: str, username: str,
|
|
password: str) -> Tuple[str, dict]:
|
|
"""Fill interactive terminal prompt for username and password."""
|
|
args = ['-c', '/etc/miniflux/miniflux.conf', command]
|
|
os.environ['LOG_FORMAT'] = 'json'
|
|
child = pexpect.spawn('miniflux', args, env=os.environ)
|
|
|
|
# The CLI is in English only.
|
|
child.expect('Enter Username: ')
|
|
child.sendline(username)
|
|
|
|
child.expect('Enter Password: ')
|
|
child.sendline(password)
|
|
|
|
child.expect(pexpect.EOF)
|
|
raw_message = child.before.decode()
|
|
try:
|
|
json_message = json.loads(raw_message)
|
|
except (KeyError, json.JSONDecodeError):
|
|
json_message = {}
|
|
|
|
child.close()
|
|
if child.exitstatus or child.signalstatus:
|
|
message = json_message.get('msg') if json_message else raw_message
|
|
raise Exception(message)
|
|
|
|
return raw_message, json_message
|
|
|
|
|
|
@privileged
|
|
def create_admin_user(username: str, password: secret_str):
|
|
"""Create a new admin user for Miniflux CLI.
|
|
|
|
Raise exception if a user with the name already exists or otherwise fails.
|
|
"""
|
|
_, json_message = _run_miniflux_interactively('--create-admin', username,
|
|
password)
|
|
# user_id is allocated only when a new user is created successfully.
|
|
if json_message and not json_message.get('user_id'):
|
|
raise Exception(json_message.get('msg'))
|
|
|
|
|
|
@privileged
|
|
def reset_user_password(username: str, password: secret_str):
|
|
"""Reset a user password using Miniflux CLI.
|
|
|
|
Raise exception if the user does not exist or otherwise fails.
|
|
"""
|
|
_run_miniflux_interactively('--reset-password', username, password)
|
|
|
|
|
|
@privileged
|
|
def uninstall():
|
|
"""Ensure that the database is removed."""
|
|
action_utils.debconf_set_selections(
|
|
['miniflux miniflux/purge boolean true'])
|
|
|
|
|
|
def _get_database_config():
|
|
"""Retrieve database credentials."""
|
|
db_connection_string = pathlib.Path(DATABASE_FILE).read_text().strip()
|
|
parsed_url = urlparse(db_connection_string)
|
|
return {
|
|
'user': parsed_url.username,
|
|
'password': parsed_url.password,
|
|
'database': parsed_url.path.lstrip('/'),
|
|
'host': parsed_url.hostname,
|
|
}
|
|
|
|
|
|
@privileged
|
|
def dump_database():
|
|
"""Dump database to file."""
|
|
config = _get_database_config()
|
|
db.postgres_dump_database(DB_BACKUP_FILE, config['database'],
|
|
config['user'])
|
|
|
|
|
|
@privileged
|
|
def restore_database():
|
|
"""Restore database from file."""
|
|
config = _get_database_config()
|
|
db.postgres_restore_database(DB_BACKUP_FILE, config['database'])
|