Merge pull request #653 from YunoHost/decouple-regenconf-from-services

[enh] Decouple the regen-conf mechanism from services
This commit is contained in:
Alexandre Aubin 2019-04-18 17:07:50 +02:00 committed by GitHub
commit d36c09120f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 690 additions and 522 deletions

View file

@ -1623,6 +1623,32 @@ tools:
full: --force full: --force
action: store_true action: store_true
### tools_regen_conf()
regen-conf:
action_help: Regenerate the configuration file(s)
api: PUT /tools/regenconf
arguments:
names:
help: Categories to regenerate configuration of (all by default)
nargs: "*"
metavar: NAME
-d:
full: --with-diff
help: Show differences in case of configuration changes
action: store_true
-f:
full: --force
help: Override all manual modifications in configuration files
action: store_true
-n:
full: --dry-run
help: Show what would have been regenerated
action: store_true
-p:
full: --list-pending
help: List pending configuration files and exit
action: store_true
subcategories: subcategories:
migrations: migrations:

View file

@ -20,8 +20,6 @@ mysql:
glances: {} glances: {}
ssh: ssh:
log: /var/log/auth.log log: /var/log/auth.log
ssl:
status: null
metronome: metronome:
log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err] log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err]
slapd: slapd:
@ -34,10 +32,9 @@ yunohost-firewall:
need_lock: true need_lock: true
nslcd: nslcd:
log: /var/log/syslog log: /var/log/syslog
nsswitch: nsswitch: null
status: null ssl: null
yunohost: yunohost: null
status: null
bind9: null bind9: null
tahoe-lafs: null tahoe-lafs: null
memcached: null memcached: null

2
debian/postinst vendored
View file

@ -12,7 +12,7 @@ do_configure() {
bash /usr/share/yunohost/hooks/conf_regen/15-nginx init bash /usr/share/yunohost/hooks/conf_regen/15-nginx init
else else
echo "Regenerating configuration, this might take a while..." echo "Regenerating configuration, this might take a while..."
yunohost service regen-conf --output-as none yunohost tools regen-conf --output-as none
echo "Launching migrations.." echo "Launching migrations.."
yunohost tools migrations migrate --auto yunohost tools migrations migrate --auto

View file

@ -262,7 +262,7 @@
"log_selfsigned_cert_install": "Install self signed certificate on '{}' domain", "log_selfsigned_cert_install": "Install self signed certificate on '{}' domain",
"log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate", "log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate",
"log_service_enable": "Enable '{}' service", "log_service_enable": "Enable '{}' service",
"log_service_regen_conf": "Regenerate system configurations '{}'", "log_regen_conf": "Regenerate system configurations '{}'",
"log_user_create": "Add '{}' user", "log_user_create": "Add '{}' user",
"log_user_delete": "Delete '{}' user", "log_user_delete": "Delete '{}' user",
"log_user_update": "Update information of '{}' user", "log_user_update": "Update information of '{}' user",
@ -299,6 +299,7 @@
"migration_description_0006_sync_admin_and_root_passwords": "Synchronize admin and root passwords", "migration_description_0006_sync_admin_and_root_passwords": "Synchronize admin and root passwords",
"migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Let the SSH configuration be managed by YunoHost (step 1, automatic)", "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Let the SSH configuration be managed by YunoHost (step 1, automatic)",
"migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)",
"migration_description_0009_decouple_regenconf_from_services": "Decouple the regen-conf mechanism from services",
"migration_0003_backward_impossible": "The stretch migration cannot be reverted.", "migration_0003_backward_impossible": "The stretch migration cannot be reverted.",
"migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.",
"migration_0003_patching_sources_list": "Patching the sources.lists…", "migration_0003_patching_sources_list": "Patching the sources.lists…",
@ -324,6 +325,7 @@
"migration_0008_dsa": " - the DSA key will be disabled. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server;", "migration_0008_dsa": " - the DSA key will be disabled. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server;",
"migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.",
"migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.",
"migration_0009_not_needed": "This migration already happened somehow ? Skipping.",
"migrations_backward": "Migrating backward.", "migrations_backward": "Migrating backward.",
"migrations_bad_value_for_target": "Invalid number for target argument, available migrations numbers are 0 or {}", "migrations_bad_value_for_target": "Invalid number for target argument, available migrations numbers are 0 or {}",
"migrations_cant_reach_migration_file": "Can't access migrations files at path %s", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s",
@ -391,6 +393,21 @@
"port_available": "Port {port:d} is available", "port_available": "Port {port:d} is available",
"port_unavailable": "Port {port:d} is not available", "port_unavailable": "Port {port:d} is not available",
"recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create' or the admin interface.", "recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create' or the admin interface.",
"regenconf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'",
"regenconf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'",
"regenconf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by regen-conf (category {category}) but has been kept back.",
"regenconf_file_manually_modified": "The configuration file '{conf}' has been manually modified and will not be updated",
"regenconf_file_manually_removed": "The configuration file '{conf}' has been manually removed and will not be created",
"regenconf_file_remove_failed": "Unable to remove the configuration file '{conf}'",
"regenconf_file_removed": "The configuration file '{conf}' has been removed",
"regenconf_file_updated": "The configuration file '{conf}' has been updated",
"regenconf_now_managed_by_yunohost": "The configuration file '{conf}' is now managed by YunoHost (category {category}).",
"regenconf_up_to_date": "The configuration is already up-to-date for category '{category}'",
"regenconf_updated": "The configuration has been updated for category '{category}'",
"regenconf_would_be_updated": "The configuration would have been updated for category '{category}'",
"regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…",
"regenconf_failed": "Unable to regenerate the configuration for category(s): {categories}",
"regenconf_pending_applying": "Applying pending configuration for category '{category}'…",
"restore_action_required": "You must specify something to restore", "restore_action_required": "You must specify something to restore",
"restore_already_installed_app": "An app is already installed with the id '{app:s}'", "restore_already_installed_app": "An app is already installed with the id '{app:s}'",
"restore_app_failed": "Unable to restore the app '{app:s}'", "restore_app_failed": "Unable to restore the app '{app:s}'",
@ -419,18 +436,6 @@
"service_already_started": "Service '{service:s}' has already been started", "service_already_started": "Service '{service:s}' has already been started",
"service_already_stopped": "Service '{service:s}' has already been stopped", "service_already_stopped": "Service '{service:s}' has already been stopped",
"service_cmd_exec_failed": "Unable to execute command '{command:s}'", "service_cmd_exec_failed": "Unable to execute command '{command:s}'",
"service_conf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'",
"service_conf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'",
"service_conf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by service {service} but has been kept back.",
"service_conf_file_manually_modified": "The configuration file '{conf}' has been manually modified and will not be updated",
"service_conf_file_manually_removed": "The configuration file '{conf}' has been manually removed and will not be created",
"service_conf_file_remove_failed": "Unable to remove the configuration file '{conf}'",
"service_conf_file_removed": "The configuration file '{conf}' has been removed",
"service_conf_file_updated": "The configuration file '{conf}' has been updated",
"service_conf_now_managed_by_yunohost": "The configuration file '{conf}' is now managed by YunoHost.",
"service_conf_up_to_date": "The configuration is already up-to-date for service '{service}'",
"service_conf_updated": "The configuration has been updated for service '{service}'",
"service_conf_would_be_updated": "The configuration would have been updated for service '{service}'",
"service_description_avahi-daemon": "allows to reach your server using yunohost.local on your local network", "service_description_avahi-daemon": "allows to reach your server using yunohost.local on your local network",
"service_description_dnsmasq": "handles domain name resolution (DNS)", "service_description_dnsmasq": "handles domain name resolution (DNS)",
"service_description_dovecot": "allows e-mail client to access/fetch email (via IMAP and POP3)", "service_description_dovecot": "allows e-mail client to access/fetch email (via IMAP and POP3)",
@ -454,9 +459,7 @@
"service_enable_failed": "Unable to enable service '{service:s}'\n\nRecent service logs:{logs:s}", "service_enable_failed": "Unable to enable service '{service:s}'\n\nRecent service logs:{logs:s}",
"service_enabled": "The service '{service:s}' has been enabled", "service_enabled": "The service '{service:s}' has been enabled",
"service_no_log": "No log to display for service '{service:s}'", "service_no_log": "No log to display for service '{service:s}'",
"service_regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for service '{service}'…", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is deprecated! Please use 'yunohost tools regen-conf' instead.",
"service_regenconf_failed": "Unable to regenerate the configuration for service(s): {services}",
"service_regenconf_pending_applying": "Applying pending configuration for service '{service}'…",
"service_remove_failed": "Unable to remove service '{service:s}'", "service_remove_failed": "Unable to remove service '{service:s}'",
"service_removed": "The service '{service:s}' has been removed", "service_removed": "The service '{service:s}' has been removed",
"service_reload_failed": "Unable to reload service '{service:s}'\n\nRecent service logs:{logs:s}", "service_reload_failed": "Unable to reload service '{service:s}'\n\nRecent service logs:{logs:s}",

View file

@ -50,7 +50,7 @@ from yunohost.hook import (
) )
from yunohost.monitor import binary_to_human from yunohost.monitor import binary_to_human
from yunohost.tools import tools_postinstall from yunohost.tools import tools_postinstall
from yunohost.service import service_regen_conf from yunohost.regenconf import regen_conf
from yunohost.log import OperationLogger from yunohost.log import OperationLogger
from functools import reduce from functools import reduce
@ -1212,7 +1212,7 @@ class RestoreManager():
else: else:
operation_logger.success() operation_logger.success()
service_regen_conf() regen_conf()
def _restore_apps(self): def _restore_apps(self):
"""Restore all apps targeted""" """Restore all apps targeted"""

View file

@ -43,7 +43,8 @@ from yunohost.utils.network import get_public_ip
from moulinette import m18n from moulinette import m18n
from yunohost.app import app_ssowatconf from yunohost.app import app_ssowatconf
from yunohost.service import _run_service_command, service_regen_conf from yunohost.service import _run_service_command
from yunohost.regenconf import regen_conf
from yunohost.log import OperationLogger from yunohost.log import OperationLogger
logger = getActionLogger('yunohost.certmanager') logger = getActionLogger('yunohost.certmanager')
@ -806,7 +807,7 @@ def _enable_certificate(domain, new_cert_folder):
if os.path.isfile('/etc/yunohost/installed'): if os.path.isfile('/etc/yunohost/installed'):
# regen nginx conf to be sure it integrates OCSP Stapling # regen nginx conf to be sure it integrates OCSP Stapling
# (We don't do this yet if postinstall is not finished yet) # (We don't do this yet if postinstall is not finished yet)
service_regen_conf(names=['nginx']) regen_conf(names=['nginx'])
_run_service_command("reload", "nginx") _run_service_command("reload", "nginx")
@ -924,7 +925,7 @@ def _regen_dnsmasq_if_needed():
break break
if do_regen: if do_regen:
service_regen_conf(["dnsmasq"]) regen_conf(["dnsmasq"])
def _name_self_CA(): def _name_self_CA():

View file

@ -10,9 +10,9 @@ from moulinette.utils.filesystem import read_file
from yunohost.tools import Migration from yunohost.tools import Migration
from yunohost.app import unstable_apps from yunohost.app import unstable_apps
from yunohost.service import (_run_service_command, from yunohost.service import _run_service_command
manually_modified_files, from yunohost.regenconf import (manually_modified_files,
manually_modified_files_compared_to_debian_default) manually_modified_files_compared_to_debian_default)
from yunohost.utils.filesystem import free_space_in_directory from yunohost.utils.filesystem import free_space_in_directory
from yunohost.utils.packages import get_installed_version from yunohost.utils.packages import get_installed_version
from yunohost.utils.network import get_network_interfaces from yunohost.utils.network import get_network_interfaces

View file

@ -3,15 +3,12 @@ import re
from shutil import copyfile from shutil import copyfile
from moulinette import m18n
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import mkdir, rm from moulinette.utils.filesystem import mkdir, rm
from yunohost.tools import Migration from yunohost.tools import Migration
from yunohost.service import service_regen_conf, \ from yunohost.service import _run_service_command
_get_conf_hashes, \ from yunohost.regenconf import regen_conf
_calculate_hash, \
_run_service_command
from yunohost.settings import settings_set from yunohost.settings import settings_set
from yunohost.utils.error import YunohostError from yunohost.utils.error import YunohostError
@ -60,7 +57,7 @@ class MyMigration(Migration):
if os.path.exists('/etc/yunohost/from_script'): if os.path.exists('/etc/yunohost/from_script'):
rm('/etc/yunohost/from_script') rm('/etc/yunohost/from_script')
copyfile(SSHD_CONF, '/etc/ssh/sshd_config.bkp') copyfile(SSHD_CONF, '/etc/ssh/sshd_config.bkp')
service_regen_conf(names=['ssh'], force=True) regen_conf(names=['ssh'], force=True)
copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF) copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF)
# Restart ssh and backward if it fail # Restart ssh and backward if it fail

View file

@ -6,9 +6,8 @@ from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import chown from moulinette.utils.filesystem import chown
from yunohost.tools import Migration from yunohost.tools import Migration
from yunohost.service import service_regen_conf, \ from yunohost.regenconf import _get_conf_hashes, _calculate_hash
_get_conf_hashes, \ from yunohost.regenconf import regen_conf
_calculate_hash
from yunohost.settings import settings_set, settings_get from yunohost.settings import settings_set, settings_get
from yunohost.utils.error import YunohostError from yunohost.utils.error import YunohostError
from yunohost.backup import ARCHIVES_PATH from yunohost.backup import ARCHIVES_PATH
@ -36,7 +35,7 @@ class MyMigration(Migration):
def migrate(self): def migrate(self):
settings_set("service.ssh.allow_deprecated_dsa_hostkey", False) settings_set("service.ssh.allow_deprecated_dsa_hostkey", False)
service_regen_conf(names=['ssh'], force=True) regen_conf(names=['ssh'], force=True)
# Update local archives folder permissions, so that # Update local archives folder permissions, so that
# admin can scp archives out of the server # admin can scp archives out of the server

View file

@ -0,0 +1,42 @@
import os
from moulinette import m18n
from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_file
from yunohost.service import _get_services, _save_services
from yunohost.regenconf import _update_conf_hashes, REGEN_CONF_FILE
from yunohost.tools import Migration
logger = getActionLogger('yunohost.migration')
class MyMigration(Migration):
"""
Decouple the regen conf mechanism from the concept of services
"""
def migrate(self):
if "conffiles" not in read_file("/etc/yunohost/services.yml") \
or os.path.exists(REGEN_CONF_FILE):
logger.warning(m18n.n("migration_0009_not_needed"))
return
# For all services
services = _get_services()
for service, infos in services.items():
# If there are some conffiles (file hashes)
if "conffiles" in infos.keys():
# Save them using the new regen conf thingy
_update_conf_hashes(service, infos["conffiles"])
# And delete the old conffile key from the service infos
del services[service]["conffiles"]
# (Actually save the modification of services)
_save_services(services)
def backward(self):
pass

View file

@ -34,7 +34,7 @@ from moulinette.utils.log import getActionLogger
import yunohost.certificate import yunohost.certificate
from yunohost.service import service_regen_conf from yunohost.regenconf import regen_conf
from yunohost.utils.network import get_public_ip from yunohost.utils.network import get_public_ip
from yunohost.log import is_unit_operation from yunohost.log import is_unit_operation
from yunohost.hook import hook_callback from yunohost.hook import hook_callback
@ -112,7 +112,7 @@ def domain_add(operation_logger, auth, domain, dyndns=False):
# Don't regen these conf if we're still in postinstall # Don't regen these conf if we're still in postinstall
if os.path.exists('/etc/yunohost/installed'): if os.path.exists('/etc/yunohost/installed'):
service_regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix', 'rspamd']) regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix', 'rspamd'])
app_ssowatconf(auth) app_ssowatconf(auth)
except Exception: except Exception:
@ -165,7 +165,7 @@ def domain_remove(operation_logger, auth, domain, force=False):
else: else:
raise YunohostError('domain_deletion_failed') raise YunohostError('domain_deletion_failed')
service_regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix']) regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix'])
app_ssowatconf(auth) app_ssowatconf(auth)
hook_callback('post_domain_remove', args=[domain]) hook_callback('post_domain_remove', args=[domain])

555
src/yunohost/regenconf.py Normal file
View file

@ -0,0 +1,555 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2019 YunoHost
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program; if not, see http://www.gnu.org/licenses
"""
import os
import yaml
import json
import subprocess
import shutil
import hashlib
from difflib import unified_diff
from datetime import datetime
from moulinette import m18n
from moulinette.utils import log, filesystem
from moulinette.utils.filesystem import read_file
from yunohost.utils.error import YunohostError
from yunohost.log import is_unit_operation
from yunohost.hook import hook_callback, hook_list
BASE_CONF_PATH = '/home/yunohost.conf'
BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup')
PENDING_CONF_DIR = os.path.join(BASE_CONF_PATH, 'pending')
REGEN_CONF_FILE = '/etc/yunohost/regenconf.yml'
logger = log.getActionLogger('yunohost.regenconf')
# FIXME : those ain't just services anymore ... what are we supposed to do with this ...
# FIXME : check for all reference of 'service' close to operation_logger stuff
@is_unit_operation([('names', 'configuration')])
def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run=False,
list_pending=False):
"""
Regenerate the configuration file(s)
Keyword argument:
names -- Categories to regenerate configuration of
with_diff -- Show differences in case of configuration changes
force -- Override all manual modifications in configuration files
dry_run -- Show what would have been regenerated
list_pending -- List pending configuration files and exit
"""
# Legacy code to automatically run the migration
# This is required because regen_conf is called before the migration call
# in debian's postinst script
if os.path.exists("/etc/yunohost/installed") \
and ("conffiles" in read_file("/etc/yunohost/services.yml") \
or not os.path.exists(REGEN_CONF_FILE)):
from yunohost.tools import _get_migration_by_name
migration = _get_migration_by_name("decouple_regenconf_from_services")
migration.migrate()
result = {}
# Return the list of pending conf
if list_pending:
pending_conf = _get_pending_conf(names)
if not with_diff:
return pending_conf
for category, conf_files in pending_conf.items():
for system_path, pending_path in conf_files.items():
pending_conf[category][system_path] = {
'pending_conf': pending_path,
'diff': _get_files_diff(
system_path, pending_path, True),
}
return pending_conf
if not dry_run:
operation_logger.related_to = [('configuration', x) for x in names]
if not names:
operation_logger.name_parameter_override = 'all'
elif len(names) != 1:
operation_logger.name_parameter_override = str(len(operation_logger.related_to)) + '_categories'
operation_logger.start()
# Clean pending conf directory
if os.path.isdir(PENDING_CONF_DIR):
if not names:
shutil.rmtree(PENDING_CONF_DIR, ignore_errors=True)
else:
for name in names:
shutil.rmtree(os.path.join(PENDING_CONF_DIR, name),
ignore_errors=True)
else:
filesystem.mkdir(PENDING_CONF_DIR, 0o755, True)
# Format common hooks arguments
common_args = [1 if force else 0, 1 if dry_run else 0]
# Execute hooks for pre-regen
pre_args = ['pre', ] + common_args
def _pre_call(name, priority, path, args):
# create the pending conf directory for the category
category_pending_path = os.path.join(PENDING_CONF_DIR, name)
filesystem.mkdir(category_pending_path, 0o755, True, uid='root')
# return the arguments to pass to the script
return pre_args + [category_pending_path, ]
# Don't regen SSH if not specifically specified
if not names:
names = hook_list('conf_regen', list_by='name',
show_info=False)['hooks']
names.remove('ssh')
pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call)
# Keep only the hook names with at least one success
names = [hook for hook, infos in pre_result.items()
if any(result["state"] == "succeed" for result in infos.values())]
# FIXME : what do in case of partial success/failure ...
if not names:
ret_failed = [hook for hook, infos in pre_result.items()
if any(result["state"] == "failed" for result in infos.values())]
raise YunohostError('regenconf_failed',
categories=', '.join(ret_failed))
# Set the processing method
_regen = _process_regen_conf if not dry_run else lambda *a, **k: True
operation_logger.related_to = []
# Iterate over categories and process pending conf
for category, conf_files in _get_pending_conf(names).items():
if not dry_run:
operation_logger.related_to.append(('configuration', category))
logger.debug(m18n.n(
'regenconf_pending_applying' if not dry_run else
'regenconf_dry_pending_applying',
category=category))
conf_hashes = _get_conf_hashes(category)
succeed_regen = {}
failed_regen = {}
for system_path, pending_path in conf_files.items():
logger.debug("processing pending conf '%s' to system conf '%s'",
pending_path, system_path)
conf_status = None
regenerated = False
# Get the diff between files
conf_diff = _get_files_diff(
system_path, pending_path, True) if with_diff else None
# Check if the conf must be removed
to_remove = True if os.path.getsize(pending_path) == 0 else False
# Retrieve and calculate hashes
system_hash = _calculate_hash(system_path)
saved_hash = conf_hashes.get(system_path, None)
new_hash = None if to_remove else _calculate_hash(pending_path)
# -> system conf does not exists
if not system_hash:
if to_remove:
logger.debug("> system conf is already removed")
os.remove(pending_path)
continue
if not saved_hash or force:
if force:
logger.debug("> system conf has been manually removed")
conf_status = 'force-created'
else:
logger.debug("> system conf does not exist yet")
conf_status = 'created'
regenerated = _regen(
system_path, pending_path, save=False)
else:
logger.info(m18n.n(
'regenconf_file_manually_removed',
conf=system_path))
conf_status = 'removed'
# -> system conf is not managed yet
elif not saved_hash:
logger.debug("> system conf is not managed yet")
if system_hash == new_hash:
logger.debug("> no changes to system conf has been made")
conf_status = 'managed'
regenerated = True
elif not to_remove:
# If the conf exist but is not managed yet, and is not to be removed,
# we assume that it is safe to regen it, since the file is backuped
# anyway (by default in _regen), as long as we warn the user
# appropriately.
logger.info(m18n.n('regenconf_now_managed_by_yunohost',
conf=system_path, category=category))
regenerated = _regen(system_path, pending_path)
conf_status = 'new'
elif force:
regenerated = _regen(system_path)
conf_status = 'force-removed'
else:
logger.info(m18n.n('regenconf_file_kept_back',
conf=system_path, category=category))
conf_status = 'unmanaged'
# -> system conf has not been manually modified
elif system_hash == saved_hash:
if to_remove:
regenerated = _regen(system_path)
conf_status = 'removed'
elif system_hash != new_hash:
regenerated = _regen(system_path, pending_path)
conf_status = 'updated'
else:
logger.debug("> system conf is already up-to-date")
os.remove(pending_path)
continue
else:
logger.debug("> system conf has been manually modified")
if system_hash == new_hash:
logger.debug("> new conf is as current system conf")
conf_status = 'managed'
regenerated = True
elif force:
regenerated = _regen(system_path, pending_path)
conf_status = 'force-updated'
else:
logger.warning(m18n.n(
'regenconf_file_manually_modified',
conf=system_path))
conf_status = 'modified'
# Store the result
conf_result = {'status': conf_status}
if conf_diff is not None:
conf_result['diff'] = conf_diff
if regenerated:
succeed_regen[system_path] = conf_result
conf_hashes[system_path] = new_hash
if os.path.isfile(pending_path):
os.remove(pending_path)
else:
failed_regen[system_path] = conf_result
# Check for category conf changes
if not succeed_regen and not failed_regen:
logger.debug(m18n.n('regenconf_up_to_date', category=category))
continue
elif not failed_regen:
logger.success(m18n.n(
'regenconf_updated' if not dry_run else
'regenconf_would_be_updated',
category=category))
if succeed_regen and not dry_run:
_update_conf_hashes(category, conf_hashes)
# Append the category results
result[category] = {
'applied': succeed_regen,
'pending': failed_regen
}
# Return in case of dry run
if dry_run:
return result
# Execute hooks for post-regen
post_args = ['post', ] + common_args
def _pre_call(name, priority, path, args):
# append coma-separated applied changes for the category
if name in result and result[name]['applied']:
regen_conf_files = ','.join(result[name]['applied'].keys())
else:
regen_conf_files = ''
return post_args + [regen_conf_files, ]
hook_callback('conf_regen', names, pre_callback=_pre_call)
operation_logger.success()
return result
def _get_regenconf_infos():
"""
Get a dict of regen conf informations
"""
try:
with open(REGEN_CONF_FILE, 'r') as f:
return yaml.load(f)
except:
return {}
def _save_regenconf_infos(infos):
"""
Save the regen conf informations
Keyword argument:
categories -- A dict containing the regenconf infos
"""
try:
with open(REGEN_CONF_FILE, 'w') as f:
yaml.safe_dump(infos, f, default_flow_style=False)
except Exception as e:
logger.warning('Error while saving regenconf infos, exception: %s', e, exc_info=1)
raise
def _get_files_diff(orig_file, new_file, as_string=False, skip_header=True):
"""Compare two files and return the differences
Read and compare two files. The differences are returned either as a delta
in unified diff format or a formatted string if as_string is True. The
header can also be removed if skip_header is True.
"""
if os.path.exists(orig_file):
with open(orig_file, 'r') as orig_file:
orig_file = orig_file.readlines()
else:
orig_file = []
if os.path.exists(new_file):
with open(new_file, 'r') as new_file:
new_file = new_file.readlines()
else:
new_file = []
# Compare files and format output
diff = unified_diff(orig_file, new_file)
if skip_header:
try:
next(diff)
next(diff)
except:
pass
if as_string:
return ''.join(diff).rstrip()
return diff
def _calculate_hash(path):
"""Calculate the MD5 hash of a file"""
if not os.path.exists(path):
return None
hasher = hashlib.md5()
try:
with open(path, 'rb') as f:
hasher.update(f.read())
return hasher.hexdigest()
except IOError as e:
logger.warning("Error while calculating file '%s' hash: %s", path, e, exc_info=1)
return None
def _get_pending_conf(categories=[]):
"""Get pending configuration for categories
Iterate over the pending configuration directory for given categories - or
all if empty - and look for files inside. Each file is considered as a
pending configuration file and therefore must be in the same directory
tree than the system file that it replaces.
The result is returned as a dict of categories with pending configuration as
key and a dict of `system_conf_path` => `pending_conf_path` as value.
"""
result = {}
if not os.path.isdir(PENDING_CONF_DIR):
return result
if not categories:
categories = os.listdir(PENDING_CONF_DIR)
for name in categories:
category_pending_path = os.path.join(PENDING_CONF_DIR, name)
if not os.path.isdir(category_pending_path):
continue
path_index = len(category_pending_path)
category_conf = {}
for root, dirs, files in os.walk(category_pending_path):
for filename in files:
pending_path = os.path.join(root, filename)
category_conf[pending_path[path_index:]] = pending_path
if category_conf:
result[name] = category_conf
else:
# remove empty directory
shutil.rmtree(category_pending_path, ignore_errors=True)
return result
def _get_conf_hashes(category):
"""Get the registered conf hashes for a category"""
categories = _get_regenconf_infos()
if category not in categories:
logger.debug("category %s is not in categories.yml yet.", category)
return {}
elif categories[category] is None or 'conffiles' not in categories[category]:
logger.debug("No configuration files for category %s.", category)
return {}
else:
return categories[category]['conffiles']
def _update_conf_hashes(category, hashes):
"""Update the registered conf hashes for a category"""
logger.debug("updating conf hashes for '%s' with: %s",
category, hashes)
categories = _get_regenconf_infos()
category_conf = categories.get(category, {})
# Handle the case where categories[category] is set to null in the yaml
if category_conf is None:
category_conf = {}
category_conf['conffiles'] = hashes
categories[category] = category_conf
_save_regenconf_infos(categories)
def _process_regen_conf(system_conf, new_conf=None, save=True):
"""Regenerate a given system configuration file
Replace a given system configuration file by a new one or delete it if
new_conf is None. A backup of the file - keeping its directory tree - will
be done in the backup conf directory before any operation if save is True.
"""
if save:
backup_path = os.path.join(BACKUP_CONF_DIR, '{0}-{1}'.format(
system_conf.lstrip('/'), datetime.utcnow().strftime("%Y%m%d.%H%M%S")))
backup_dir = os.path.dirname(backup_path)
if not os.path.isdir(backup_dir):
filesystem.mkdir(backup_dir, 0o755, True)
shutil.copy2(system_conf, backup_path)
logger.debug(m18n.n('regenconf_file_backed_up',
conf=system_conf, backup=backup_path))
try:
if not new_conf:
os.remove(system_conf)
logger.debug(m18n.n('regenconf_file_removed',
conf=system_conf))
else:
system_dir = os.path.dirname(system_conf)
if not os.path.isdir(system_dir):
filesystem.mkdir(system_dir, 0o755, True)
shutil.copyfile(new_conf, system_conf)
logger.debug(m18n.n('regenconf_file_updated',
conf=system_conf))
except Exception as e:
logger.warning("Exception while trying to regenerate conf '%s': %s", system_conf, e, exc_info=1)
if not new_conf and os.path.exists(system_conf):
logger.warning(m18n.n('regenconf_file_remove_failed',
conf=system_conf),
exc_info=1)
return False
elif new_conf:
try:
# From documentation:
# Raise an exception if an os.stat() call on either pathname fails.
# (os.stats returns a series of information from a file like type, size...)
copy_succeed = os.path.samefile(system_conf, new_conf)
except:
copy_succeed = False
finally:
if not copy_succeed:
logger.warning(m18n.n('regenconf_file_copy_failed',
conf=system_conf, new=new_conf),
exc_info=1)
return False
return True
def manually_modified_files():
# We do this to have --quiet, i.e. don't throw a whole bunch of logs
# just to fetch this...
# Might be able to optimize this by looking at what the regen conf does
# and only do the part that checks file hashes...
cmd = "yunohost tools regen-conf --dry-run --output-as json --quiet"
j = json.loads(subprocess.check_output(cmd.split()))
# j is something like :
# {"postfix": {"applied": {}, "pending": {"/etc/postfix/main.cf": {"status": "modified"}}}
output = []
for app, actions in j.items():
for action, files in actions.items():
for filename, infos in files.items():
if infos["status"] == "modified":
output.append(filename)
return output
def manually_modified_files_compared_to_debian_default():
# from https://serverfault.com/a/90401
r = subprocess.check_output("dpkg-query -W -f='${Conffiles}\n' '*' \
| awk 'OFS=\" \"{print $2,$1}' \
| md5sum -c 2>/dev/null \
| awk -F': ' '$2 !~ /OK/{print $1}'", shell=True)
return r.strip().split("\n")

View file

@ -26,13 +26,9 @@
import os import os
import time import time
import yaml import yaml
import json
import subprocess import subprocess
import shutil
import hashlib
from glob import glob from glob import glob
from difflib import unified_diff
from datetime import datetime from datetime import datetime
from moulinette import m18n from moulinette import m18n
@ -40,11 +36,7 @@ from yunohost.utils.error import YunohostError
from moulinette.utils import log, filesystem from moulinette.utils import log, filesystem
from yunohost.log import is_unit_operation from yunohost.log import is_unit_operation
from yunohost.hook import hook_callback, hook_list
BASE_CONF_PATH = '/home/yunohost.conf'
BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup')
PENDING_CONF_DIR = os.path.join(BASE_CONF_PATH, 'pending')
MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock"
logger = log.getActionLogger('yunohost.service') logger = log.getActionLogger('yunohost.service')
@ -424,253 +416,25 @@ def service_log(name, number=50):
return result return result
@is_unit_operation([('names', 'service')]) def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False,
def service_regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run=False,
list_pending=False): list_pending=False):
"""
Regenerate the configuration file(s) for a service
Keyword argument: services = _get_services()
names -- Services name to regenerate configuration of
with_diff -- Show differences in case of configuration changes
force -- Override all manual modifications in configuration files
dry_run -- Show what would have been regenerated
list_pending -- List pending configuration files and exit
""" if isinstance(names, str):
result = {} names = [names]
# Return the list of pending conf for name in names:
if list_pending: if name not in services.keys():
pending_conf = _get_pending_conf(names) raise YunohostError('service_unknown', service=name)
if not with_diff: if names is []:
return pending_conf names = services.keys()
for service, conf_files in pending_conf.items(): logger.warning(m18n.n("service_regen_conf_is_deprecated"))
for system_path, pending_path in conf_files.items():
pending_conf[service][system_path] = { from yunohost.regenconf import regen_conf
'pending_conf': pending_path, return regen_conf(names, with_diff, force, dry_run, list_pending)
'diff': _get_files_diff(
system_path, pending_path, True),
}
return pending_conf
if not dry_run:
operation_logger.related_to = [('service', x) for x in names]
if not names:
operation_logger.name_parameter_override = 'all'
elif len(names) != 1:
operation_logger.name_parameter_override = str(len(operation_logger.related_to)) + '_services'
operation_logger.start()
# Clean pending conf directory
if os.path.isdir(PENDING_CONF_DIR):
if not names:
shutil.rmtree(PENDING_CONF_DIR, ignore_errors=True)
else:
for name in names:
shutil.rmtree(os.path.join(PENDING_CONF_DIR, name),
ignore_errors=True)
else:
filesystem.mkdir(PENDING_CONF_DIR, 0o755, True)
# Format common hooks arguments
common_args = [1 if force else 0, 1 if dry_run else 0]
# Execute hooks for pre-regen
pre_args = ['pre', ] + common_args
def _pre_call(name, priority, path, args):
# create the pending conf directory for the service
service_pending_path = os.path.join(PENDING_CONF_DIR, name)
filesystem.mkdir(service_pending_path, 0o755, True, uid='root')
# return the arguments to pass to the script
return pre_args + [service_pending_path, ]
# Don't regen SSH if not specifically specified
if not names:
names = hook_list('conf_regen', list_by='name',
show_info=False)['hooks']
names.remove('ssh')
pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call)
# Keep only the hook names with at least one success
names = [hook for hook, infos in pre_result.items()
if any(result["state"] == "succeed" for result in infos.values())]
# FIXME : what do in case of partial success/failure ...
if not names:
ret_failed = [hook for hook, infos in pre_result.items()
if any(result["state"] == "failed" for result in infos.values())]
raise YunohostError('service_regenconf_failed',
services=', '.join(ret_failed))
# Set the processing method
_regen = _process_regen_conf if not dry_run else lambda *a, **k: True
operation_logger.related_to = []
# Iterate over services and process pending conf
for service, conf_files in _get_pending_conf(names).items():
if not dry_run:
operation_logger.related_to.append(('service', service))
logger.debug(m18n.n(
'service_regenconf_pending_applying' if not dry_run else
'service_regenconf_dry_pending_applying',
service=service))
conf_hashes = _get_conf_hashes(service)
succeed_regen = {}
failed_regen = {}
for system_path, pending_path in conf_files.items():
logger.debug("processing pending conf '%s' to system conf '%s'",
pending_path, system_path)
conf_status = None
regenerated = False
# Get the diff between files
conf_diff = _get_files_diff(
system_path, pending_path, True) if with_diff else None
# Check if the conf must be removed
to_remove = True if os.path.getsize(pending_path) == 0 else False
# Retrieve and calculate hashes
system_hash = _calculate_hash(system_path)
saved_hash = conf_hashes.get(system_path, None)
new_hash = None if to_remove else _calculate_hash(pending_path)
# -> system conf does not exists
if not system_hash:
if to_remove:
logger.debug("> system conf is already removed")
os.remove(pending_path)
continue
if not saved_hash or force:
if force:
logger.debug("> system conf has been manually removed")
conf_status = 'force-created'
else:
logger.debug("> system conf does not exist yet")
conf_status = 'created'
regenerated = _regen(
system_path, pending_path, save=False)
else:
logger.info(m18n.n(
'service_conf_file_manually_removed',
conf=system_path))
conf_status = 'removed'
# -> system conf is not managed yet
elif not saved_hash:
logger.debug("> system conf is not managed yet")
if system_hash == new_hash:
logger.debug("> no changes to system conf has been made")
conf_status = 'managed'
regenerated = True
elif not to_remove:
# If the conf exist but is not managed yet, and is not to be removed,
# we assume that it is safe to regen it, since the file is backuped
# anyway (by default in _regen), as long as we warn the user
# appropriately.
logger.info(m18n.n('service_conf_now_managed_by_yunohost',
conf=system_path))
regenerated = _regen(system_path, pending_path)
conf_status = 'new'
elif force:
regenerated = _regen(system_path)
conf_status = 'force-removed'
else:
logger.info(m18n.n('service_conf_file_kept_back',
conf=system_path, service=service))
conf_status = 'unmanaged'
# -> system conf has not been manually modified
elif system_hash == saved_hash:
if to_remove:
regenerated = _regen(system_path)
conf_status = 'removed'
elif system_hash != new_hash:
regenerated = _regen(system_path, pending_path)
conf_status = 'updated'
else:
logger.debug("> system conf is already up-to-date")
os.remove(pending_path)
continue
else:
logger.debug("> system conf has been manually modified")
if system_hash == new_hash:
logger.debug("> new conf is as current system conf")
conf_status = 'managed'
regenerated = True
elif force:
regenerated = _regen(system_path, pending_path)
conf_status = 'force-updated'
else:
logger.warning(m18n.n(
'service_conf_file_manually_modified',
conf=system_path))
conf_status = 'modified'
# Store the result
conf_result = {'status': conf_status}
if conf_diff is not None:
conf_result['diff'] = conf_diff
if regenerated:
succeed_regen[system_path] = conf_result
conf_hashes[system_path] = new_hash
if os.path.isfile(pending_path):
os.remove(pending_path)
else:
failed_regen[system_path] = conf_result
# Check for service conf changes
if not succeed_regen and not failed_regen:
logger.debug(m18n.n('service_conf_up_to_date', service=service))
continue
elif not failed_regen:
logger.success(m18n.n(
'service_conf_updated' if not dry_run else
'service_conf_would_be_updated',
service=service))
if succeed_regen and not dry_run:
_update_conf_hashes(service, conf_hashes)
# Append the service results
result[service] = {
'applied': succeed_regen,
'pending': failed_regen
}
# Return in case of dry run
if dry_run:
return result
# Execute hooks for post-regen
post_args = ['post', ] + common_args
def _pre_call(name, priority, path, args):
# append coma-separated applied changes for the service
if name in result and result[name]['applied']:
regen_conf_files = ','.join(result[name]['applied'].keys())
else:
regen_conf_files = ''
return post_args + [regen_conf_files, ]
hook_callback('conf_regen', names, pre_callback=_pre_call)
operation_logger.success()
return result
def _run_service_command(action, service): def _run_service_command(action, service):
@ -870,231 +634,9 @@ def _find_previous_log_file(file):
return None return None
def _get_files_diff(orig_file, new_file, as_string=False, skip_header=True):
"""Compare two files and return the differences
Read and compare two files. The differences are returned either as a delta
in unified diff format or a formatted string if as_string is True. The
header can also be removed if skip_header is True.
"""
if os.path.exists(orig_file):
with open(orig_file, 'r') as orig_file:
orig_file = orig_file.readlines()
else:
orig_file = []
if os.path.exists(new_file):
with open(new_file, 'r') as new_file:
new_file = new_file.readlines()
else:
new_file = []
# Compare files and format output
diff = unified_diff(orig_file, new_file)
if skip_header:
try:
next(diff)
next(diff)
except:
pass
if as_string:
return ''.join(diff).rstrip()
return diff
def _calculate_hash(path):
"""Calculate the MD5 hash of a file"""
if not os.path.exists(path):
return None
hasher = hashlib.md5()
try:
with open(path, 'rb') as f:
hasher.update(f.read())
return hasher.hexdigest()
except IOError as e:
logger.warning("Error while calculating file '%s' hash: %s", path, e, exc_info=1)
return None
def _get_pending_conf(services=[]):
"""Get pending configuration for service(s)
Iterate over the pending configuration directory for given service(s) - or
all if empty - and look for files inside. Each file is considered as a
pending configuration file and therefore must be in the same directory
tree than the system file that it replaces.
The result is returned as a dict of services with pending configuration as
key and a dict of `system_conf_path` => `pending_conf_path` as value.
"""
result = {}
if not os.path.isdir(PENDING_CONF_DIR):
return result
if not services:
services = os.listdir(PENDING_CONF_DIR)
for name in services:
service_pending_path = os.path.join(PENDING_CONF_DIR, name)
if not os.path.isdir(service_pending_path):
continue
path_index = len(service_pending_path)
service_conf = {}
for root, dirs, files in os.walk(service_pending_path):
for filename in files:
pending_path = os.path.join(root, filename)
service_conf[pending_path[path_index:]] = pending_path
if service_conf:
result[name] = service_conf
else:
# remove empty directory
shutil.rmtree(service_pending_path, ignore_errors=True)
return result
def _get_conf_hashes(service):
"""Get the registered conf hashes for a service"""
services = _get_services()
if service not in services:
logger.debug("Service %s is not in services.yml yet.", service)
return {}
elif services[service] is None or 'conffiles' not in services[service]:
logger.debug("No configuration files for service %s.", service)
return {}
else:
return services[service]['conffiles']
def _update_conf_hashes(service, hashes):
"""Update the registered conf hashes for a service"""
logger.debug("updating conf hashes for '%s' with: %s",
service, hashes)
services = _get_services()
service_conf = services.get(service, {})
# Handle the case where services[service] is set to null in the yaml
if service_conf is None:
service_conf = {}
service_conf['conffiles'] = hashes
services[service] = service_conf
_save_services(services)
def _process_regen_conf(system_conf, new_conf=None, save=True):
"""Regenerate a given system configuration file
Replace a given system configuration file by a new one or delete it if
new_conf is None. A backup of the file - keeping its directory tree - will
be done in the backup conf directory before any operation if save is True.
"""
if save:
backup_path = os.path.join(BACKUP_CONF_DIR, '{0}-{1}'.format(
system_conf.lstrip('/'), datetime.utcnow().strftime("%Y%m%d.%H%M%S")))
backup_dir = os.path.dirname(backup_path)
if not os.path.isdir(backup_dir):
filesystem.mkdir(backup_dir, 0o755, True)
shutil.copy2(system_conf, backup_path)
logger.debug(m18n.n('service_conf_file_backed_up',
conf=system_conf, backup=backup_path))
try:
if not new_conf:
os.remove(system_conf)
logger.debug(m18n.n('service_conf_file_removed',
conf=system_conf))
else:
system_dir = os.path.dirname(system_conf)
if not os.path.isdir(system_dir):
filesystem.mkdir(system_dir, 0o755, True)
shutil.copyfile(new_conf, system_conf)
logger.debug(m18n.n('service_conf_file_updated',
conf=system_conf))
except Exception as e:
logger.warning("Exception while trying to regenerate conf '%s': %s", system_conf, e, exc_info=1)
if not new_conf and os.path.exists(system_conf):
logger.warning(m18n.n('service_conf_file_remove_failed',
conf=system_conf),
exc_info=1)
return False
elif new_conf:
try:
# From documentation:
# Raise an exception if an os.stat() call on either pathname fails.
# (os.stats returns a series of information from a file like type, size...)
copy_succeed = os.path.samefile(system_conf, new_conf)
except:
copy_succeed = False
finally:
if not copy_succeed:
logger.warning(m18n.n('service_conf_file_copy_failed',
conf=system_conf, new=new_conf),
exc_info=1)
return False
return True
def manually_modified_files():
# We do this to have --quiet, i.e. don't throw a whole bunch of logs
# just to fetch this...
# Might be able to optimize this by looking at what service_regenconf does
# and only do the part that checks file hashes...
cmd = "yunohost service regen-conf --dry-run --output-as json --quiet"
j = json.loads(subprocess.check_output(cmd.split()))
# j is something like :
# {"postfix": {"applied": {}, "pending": {"/etc/postfix/main.cf": {"status": "modified"}}}
output = []
for app, actions in j.items():
for action, files in actions.items():
for filename, infos in files.items():
if infos["status"] == "modified":
output.append(filename)
return output
def _get_journalctl_logs(service, number="all"): def _get_journalctl_logs(service, number="all"):
try: try:
return subprocess.check_output("journalctl -xn -u {0} -n{1}".format(service, number), shell=True) return subprocess.check_output("journalctl -xn -u {0} -n{1}".format(service, number), shell=True)
except: except:
import traceback import traceback
return "error while get services logs from journalctl:\n%s" % traceback.format_exc() return "error while get services logs from journalctl:\n%s" % traceback.format_exc()
def manually_modified_files_compared_to_debian_default():
# from https://serverfault.com/a/90401
r = subprocess.check_output("dpkg-query -W -f='${Conffiles}\n' '*' \
| awk 'OFS=\" \"{print $2,$1}' \
| md5sum -c 2>/dev/null \
| awk -F': ' '$2 !~ /OK/{print $1}'", shell=True)
return r.strip().split("\n")

View file

@ -48,7 +48,8 @@ from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, a
from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain
from yunohost.dyndns import _dyndns_available, _dyndns_provides from yunohost.dyndns import _dyndns_available, _dyndns_provides
from yunohost.firewall import firewall_upnp from yunohost.firewall import firewall_upnp
from yunohost.service import service_status, service_regen_conf, service_log, service_start, service_enable from yunohost.service import service_status, service_log, service_start, service_enable
from yunohost.regenconf import regen_conf
from yunohost.monitor import monitor_disk, monitor_system from yunohost.monitor import monitor_disk, monitor_system
from yunohost.utils.packages import ynh_packages_version from yunohost.utils.packages import ynh_packages_version
from yunohost.utils.network import get_public_ip from yunohost.utils.network import get_public_ip
@ -213,7 +214,7 @@ def tools_maindomain(operation_logger, auth, new_domain=None):
# Regen configurations # Regen configurations
try: try:
with open('/etc/yunohost/installed', 'r'): with open('/etc/yunohost/installed', 'r'):
service_regen_conf() regen_conf()
except IOError: except IOError:
pass pass
@ -331,7 +332,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
operation_logger.start() operation_logger.start()
logger.info(m18n.n('yunohost_installing')) logger.info(m18n.n('yunohost_installing'))
service_regen_conf(['nslcd', 'nsswitch'], force=True) regen_conf(['nslcd', 'nsswitch'], force=True)
# Initialize LDAP for YunoHost # Initialize LDAP for YunoHost
# TODO: Improve this part by integrate ldapinit into conf_regen hook # TODO: Improve this part by integrate ldapinit into conf_regen hook
@ -382,7 +383,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
os.system('chmod 644 /etc/ssowat/conf.json.persistent') os.system('chmod 644 /etc/ssowat/conf.json.persistent')
# Create SSL CA # Create SSL CA
service_regen_conf(['ssl'], force=True) regen_conf(['ssl'], force=True)
ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA'
# (Update the serial so that it's specific to this very instance) # (Update the serial so that it's specific to this very instance)
os.system("openssl rand -hex 19 > %s/serial" % ssl_dir) os.system("openssl rand -hex 19 > %s/serial" % ssl_dir)
@ -411,7 +412,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
logger.success(m18n.n('yunohost_ca_creation_success')) logger.success(m18n.n('yunohost_ca_creation_success'))
# New domain config # New domain config
service_regen_conf(['nsswitch'], force=True) regen_conf(['nsswitch'], force=True)
domain_add(auth, domain, dyndns) domain_add(auth, domain, dyndns)
tools_maindomain(auth, domain) tools_maindomain(auth, domain)
@ -439,7 +440,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
service_enable("yunohost-firewall") service_enable("yunohost-firewall")
service_start("yunohost-firewall") service_start("yunohost-firewall")
service_regen_conf(force=True) regen_conf(force=True)
# Restore original ssh conf, as chosen by the # Restore original ssh conf, as chosen by the
# admin during the initial install # admin during the initial install
@ -456,13 +457,18 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
else: else:
# We need to explicitly ask the regen conf to regen ssh # We need to explicitly ask the regen conf to regen ssh
# (by default, i.e. first argument = None, it won't because it's too touchy) # (by default, i.e. first argument = None, it won't because it's too touchy)
service_regen_conf(names=["ssh"], force=True) regen_conf(names=["ssh"], force=True)
logger.success(m18n.n('yunohost_configured')) logger.success(m18n.n('yunohost_configured'))
logger.warning(m18n.n('recommend_to_add_first_user')) logger.warning(m18n.n('recommend_to_add_first_user'))
def tools_regen_conf(names=[], with_diff=False, force=False, dry_run=False,
list_pending=False):
return regen_conf(names, with_diff, force, dry_run, list_pending)
def tools_update(ignore_apps=False, ignore_packages=False): def tools_update(ignore_apps=False, ignore_packages=False):
""" """
Update apps & package cache, then display changelog Update apps & package cache, then display changelog
@ -758,7 +764,7 @@ def tools_diagnosis(auth, private=False):
# Domains # Domains
diagnosis['private']['domains'] = domain_list(auth)['domains'] diagnosis['private']['domains'] = domain_list(auth)['domains']
diagnosis['private']['regen_conf'] = service_regen_conf(with_diff=True, dry_run=True) diagnosis['private']['regen_conf'] = regen_conf(with_diff=True, dry_run=True)
try: try:
diagnosis['security'] = { diagnosis['security'] = {