Sunil Mohan Adapa f9c624562a
wordpress: Fix minor issue in restoring database
- Overlooked in !2454.

Tests:

- Take a backup of wordpress app and disable it. Restore from the backup
succeeds after that.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
2024-03-11 15:24:21 -07:00

201 lines
6.3 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""Configuration helper for WordPress."""
import os
import pathlib
import random
import shutil
import string
import subprocess
import augeas
from plinth import action_utils
from plinth.actions import privileged
_public_access_file = pathlib.Path('/etc/wordpress/is_public')
_config_file_path = pathlib.Path('/etc/wordpress/config-default.php')
_static_config_file_path = pathlib.Path('/etc/wordpress/freedombox-static.php')
_db_file_path = pathlib.Path('/etc/wordpress/database.php')
_db_backup_file = pathlib.Path(
'/var/lib/plinth/backups-data/wordpress-database.sql')
DB_HOST = 'localhost'
DB_NAME = 'wordpress_fbx'
DB_USER = 'wordpress_fbx'
@privileged
def setup():
"""Create initial configuration and database for WordPress."""
if _db_file_path.exists() or _config_file_path.exists():
if _config_file_path.exists():
_upgrade_config_file()
return
db_password = _generate_secret_key(16)
_create_config_file(DB_HOST, DB_NAME, DB_USER, db_password)
with action_utils.service_ensure_running('mysql'):
_create_database(DB_NAME)
_set_privileges(DB_HOST, DB_NAME, DB_USER, db_password)
def _create_config_file(db_host, db_name, db_user, db_password):
"""Create a PHP configuration file included by WordPress."""
secret_keys = [_generate_secret_key() for _ in range(8)]
config_contents = f'''<?php
# Created by FreedomBox
include_once('{_static_config_file_path}');
include_once('{_db_file_path}');
define('DB_NAME', $dbname);
define('DB_USER', $dbuser);
define('DB_PASSWORD', $dbpass);
define('DB_HOST', $dbserver);
define('AUTH_KEY', '{secret_keys[0]}');
define('SECURE_AUTH_KEY', '{secret_keys[1]}');
define('LOGGED_IN_KEY', '{secret_keys[2]}');
define('NONCE_KEY', '{secret_keys[3]}');
define('AUTH_SALT', '{secret_keys[4]}');
define('SECURE_AUTH_SALT', '{secret_keys[5]}');
define('LOGGED_IN_SALT', '{secret_keys[6]}');
define('NONCE_SALT', '{secret_keys[7]}');
define('WP_CONTENT_DIR', '/var/lib/wordpress/wp-content');
define('DISABLE_WP_CRON', true);
'''
_config_file_path.write_text(config_contents, encoding='utf-8')
db_contents = f'''<?php
# Created by FreedomBox
$dbuser='{db_user}';
$dbpass='{db_password}';
$dbname='{db_name}';
$dbserver='{db_host}';
'''
old_umask = os.umask(0o037)
try:
_db_file_path.write_text(db_contents, encoding='utf-8')
finally:
os.umask(old_umask)
shutil.chown(_db_file_path, group='www-data')
def _create_database(db_name):
"""Create an empty MySQL database for WordPress."""
# Wordpress' install.php creates the tables.
# SQL injection is avoided due to known input.
query = f'''CREATE DATABASE {db_name};'''
subprocess.run(['mysql', '--user', 'root'], input=query.encode(),
check=True)
def _set_privileges(db_host, db_name, db_user, db_password):
"""Create user, set password and provide permissions on the database."""
# SQL injection is avoided due to known input.
query = f'''GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,ALTER
ON {db_name}.*
TO {db_user}@{db_host}
IDENTIFIED BY '{db_password}';
FLUSH PRIVILEGES;
'''
subprocess.run(['mysql', '--user', 'root'], input=query.encode(),
check=True)
def _generate_secret_key(length=64, chars=None):
"""Generate a new random secret key for use with WordPress."""
chars = chars or (string.ascii_letters + string.digits +
'!@#$%^&*()-_ []{}<>~`+=,.:/?|')
rand = random.SystemRandom()
return ''.join(rand.choice(chars) for _ in range(length))
def _upgrade_config_file():
"""Upgrade existing config file to add changes."""
include_line = f"include_once('{_static_config_file_path}');"
lines = _config_file_path.read_text(encoding='utf-8').splitlines()
if include_line not in lines:
lines.insert(2, include_line) # Insert on 3rd line
_config_file_path.write_text('\n'.join(lines), encoding='utf-8')
@privileged
def set_public(enable: bool):
"""Allow/disallow public access."""
if enable:
_public_access_file.touch()
else:
_public_access_file.unlink(missing_ok=True)
action_utils.service_reload('apache2')
def is_public() -> bool:
"""Return whether public access is enabled."""
return _public_access_file.exists()
@privileged
def dump_database():
"""Dump database to file."""
_db_backup_file.parent.mkdir(parents=True, exist_ok=True)
with action_utils.service_ensure_running('mysql'):
with _db_backup_file.open('w', encoding='utf-8') as file_handle:
subprocess.run([
'mysqldump', '--add-drop-database', '--add-drop-table',
'--add-drop-trigger', '--user', 'root', '--databases', DB_NAME
], stdout=file_handle, check=True)
@privileged
def restore_database():
"""Restore database from file."""
with action_utils.service_ensure_running('mysql'):
with _db_backup_file.open('r', encoding='utf-8') as file_handle:
subprocess.run(['mysql', '--user', 'root'], stdin=file_handle,
check=True)
_set_privileges(DB_HOST, DB_NAME, DB_USER, _read_db_password())
def _read_db_password():
"""Return the password stored in the DB configuration file."""
aug = _load_augeas()
return aug.get('./$dbpass').strip('\'"')
def _load_augeas():
"""Initialize augeas."""
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
augeas.Augeas.NO_MODL_AUTOLOAD)
aug.transform('Phpvars', str(_db_file_path))
aug.set('/augeas/context', '/files' + str(_db_file_path))
aug.load()
return aug
@privileged
def uninstall():
"""Remove config files and drop database."""
_drop_database(DB_HOST, DB_NAME, DB_USER)
for file_ in [_public_access_file, _config_file_path, _db_file_path]:
file_.unlink(missing_ok=True)
def _drop_database(db_host, db_name, db_user):
"""Drop the mysql database that was created during install."""
with action_utils.service_ensure_running('mysql'):
query = f"DROP DATABASE {db_name};"
subprocess.run(['mysql', '--user', 'root'], input=query.encode(),
check=False)
query = f"DROP USER IF EXISTS {db_user}@{db_host};"
subprocess.run(['mysql', '--user', 'root'], input=query.encode(),
check=False)