mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge remote-tracking branch 'origin/enh-global-settings-configpanel' into ip_pool
This commit is contained in:
commit
6f282b2bcf
23 changed files with 591 additions and 507 deletions
|
@ -81,7 +81,7 @@ alias_maps = hash:/etc/aliases
|
||||||
alias_database = hash:/etc/aliases
|
alias_database = hash:/etc/aliases
|
||||||
mydomain = {{ main_domain }}
|
mydomain = {{ main_domain }}
|
||||||
mydestination = localhost
|
mydestination = localhost
|
||||||
{% if relay_host == "" %}
|
{% if relay_enabled != "True" %}
|
||||||
relayhost =
|
relayhost =
|
||||||
{% else %}
|
{% else %}
|
||||||
relayhost = [{{ relay_host }}]:{{ relay_port }}
|
relayhost = [{{ relay_host }}]:{{ relay_port }}
|
||||||
|
@ -198,7 +198,7 @@ smtpd_client_recipient_rate_limit=150
|
||||||
# and after to send spam
|
# and after to send spam
|
||||||
disable_vrfy_command = yes
|
disable_vrfy_command = yes
|
||||||
|
|
||||||
{% if relay_user != "" %}
|
{% if relay_enabled == "True" %}
|
||||||
# Relay email through an other smtp account
|
# Relay email through an other smtp account
|
||||||
# enable SASL authentication
|
# enable SASL authentication
|
||||||
smtp_sasl_auth_enable = yes
|
smtp_sasl_auth_enable = yes
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
Protocol 2
|
Protocol 2
|
||||||
# PLEASE: if you wish to change the ssh port properly in YunoHost, use this command:
|
# PLEASE: if you wish to change the ssh port properly in YunoHost, use this command:
|
||||||
# yunohost settings set security.ssh.port -v <port>
|
# yunohost settings set security.ssh.ssh_port -v <port>
|
||||||
Port {{ port }}
|
Port {{ port }}
|
||||||
|
|
||||||
{% if ipv6_enabled == "true" %}ListenAddress ::{% endif %}
|
{% if ipv6_enabled == "true" %}ListenAddress ::{% endif %}
|
||||||
|
@ -56,7 +56,7 @@ ChallengeResponseAuthentication no
|
||||||
UsePAM yes
|
UsePAM yes
|
||||||
|
|
||||||
# PLEASE: if you wish to force everybody to authenticate using ssh keys, run this command:
|
# PLEASE: if you wish to force everybody to authenticate using ssh keys, run this command:
|
||||||
# yunohost settings set security.ssh.password_authentication -v no
|
# yunohost settings set security.ssh.ssh_password_authentication -v no
|
||||||
{% if password_authentication == "False" %}
|
{% if password_authentication == "False" %}
|
||||||
PasswordAuthentication no
|
PasswordAuthentication no
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
|
@ -13,6 +13,6 @@ backup_dir="${1}/conf/ynh"
|
||||||
ynh_backup "/etc/yunohost/firewall.yml" "${backup_dir}/firewall.yml"
|
ynh_backup "/etc/yunohost/firewall.yml" "${backup_dir}/firewall.yml"
|
||||||
ynh_backup "/etc/yunohost/current_host" "${backup_dir}/current_host"
|
ynh_backup "/etc/yunohost/current_host" "${backup_dir}/current_host"
|
||||||
[ ! -d "/etc/yunohost/domains" ] || ynh_backup "/etc/yunohost/domains" "${backup_dir}/domains"
|
[ ! -d "/etc/yunohost/domains" ] || ynh_backup "/etc/yunohost/domains" "${backup_dir}/domains"
|
||||||
[ ! -e "/etc/yunohost/settings.json" ] || ynh_backup "/etc/yunohost/settings.json" "${backup_dir}/settings.json"
|
[ ! -e "/etc/yunohost/settings.yml" ] || ynh_backup "/etc/yunohost/settings.yml" "${backup_dir}/settings.yml"
|
||||||
[ ! -d "/etc/yunohost/dyndns" ] || ynh_backup "/etc/yunohost/dyndns" "${backup_dir}/dyndns"
|
[ ! -d "/etc/yunohost/dyndns" ] || ynh_backup "/etc/yunohost/dyndns" "${backup_dir}/dyndns"
|
||||||
[ ! -d "/etc/dkim" ] || ynh_backup "/etc/dkim" "${backup_dir}/dkim"
|
[ ! -d "/etc/dkim" ] || ynh_backup "/etc/dkim" "${backup_dir}/dkim"
|
||||||
|
|
|
@ -15,14 +15,14 @@ do_pre_regen() {
|
||||||
ssh_keys=$(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key 2>/dev/null || true)
|
ssh_keys=$(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key 2>/dev/null || true)
|
||||||
|
|
||||||
# Support legacy setting (this setting might be disabled by a user during a migration)
|
# Support legacy setting (this setting might be disabled by a user during a migration)
|
||||||
if [[ "$(yunohost settings get 'service.ssh.allow_deprecated_dsa_hostkey')" == "True" ]]; then
|
if [[ "$(yunohost settings get 'security.ssh.ssh_allow_deprecated_dsa_hostkey')" == "True" ]]; then
|
||||||
ssh_keys="$ssh_keys $(ls /etc/ssh/ssh_host_dsa_key 2>/dev/null || true)"
|
ssh_keys="$ssh_keys $(ls /etc/ssh/ssh_host_dsa_key 2>/dev/null || true)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Support different strategy for security configurations
|
# Support different strategy for security configurations
|
||||||
export compatibility="$(yunohost settings get 'security.ssh.compatibility')"
|
export compatibility="$(yunohost settings get 'security.ssh.ssh_compatibility')"
|
||||||
export port="$(yunohost settings get 'security.ssh.port')"
|
export port="$(yunohost settings get 'security.ssh.ssh_port')"
|
||||||
export password_authentication="$(yunohost settings get 'security.ssh.password_authentication')"
|
export password_authentication="$(yunohost settings get 'security.ssh.ssh_password_authentication')"
|
||||||
export ssh_keys
|
export ssh_keys
|
||||||
export ipv6_enabled
|
export ipv6_enabled
|
||||||
ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config"
|
ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config"
|
||||||
|
|
|
@ -56,7 +56,7 @@ do_pre_regen() {
|
||||||
# install / update plain conf files
|
# install / update plain conf files
|
||||||
cp plain/* "$nginx_conf_dir"
|
cp plain/* "$nginx_conf_dir"
|
||||||
# remove the panel overlay if this is specified in settings
|
# remove the panel overlay if this is specified in settings
|
||||||
panel_overlay=$(yunohost settings get 'ssowat.panel_overlay.enabled')
|
panel_overlay=$(yunohost settings get 'misc.ssowat.ssowat_panel_overlay_enabled')
|
||||||
if [ "$panel_overlay" == "false" ] || [ "$panel_overlay" == "False" ]; then
|
if [ "$panel_overlay" == "false" ] || [ "$panel_overlay" == "False" ]; then
|
||||||
echo "#" >"${nginx_conf_dir}/yunohost_panel.conf.inc"
|
echo "#" >"${nginx_conf_dir}/yunohost_panel.conf.inc"
|
||||||
fi
|
fi
|
||||||
|
@ -65,9 +65,9 @@ do_pre_regen() {
|
||||||
main_domain=$(cat /etc/yunohost/current_host)
|
main_domain=$(cat /etc/yunohost/current_host)
|
||||||
|
|
||||||
# Support different strategy for security configurations
|
# Support different strategy for security configurations
|
||||||
export redirect_to_https="$(yunohost settings get 'security.nginx.redirect_to_https')"
|
export redirect_to_https="$(yunohost settings get 'security.nginx.nginx_redirect_to_https')"
|
||||||
export compatibility="$(yunohost settings get 'security.nginx.compatibility')"
|
export compatibility="$(yunohost settings get 'security.nginx.nginx_compatibility')"
|
||||||
export experimental="$(yunohost settings get 'security.experimental.enabled')"
|
export experimental="$(yunohost settings get 'security.experimental.security_experimental_enabled')"
|
||||||
ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc"
|
ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc"
|
||||||
|
|
||||||
cert_status=$(yunohost domain cert status --json)
|
cert_status=$(yunohost domain cert status --json)
|
||||||
|
@ -92,9 +92,9 @@ do_pre_regen() {
|
||||||
|
|
||||||
done
|
done
|
||||||
|
|
||||||
export webadmin_allowlist_enabled=$(yunohost settings get security.webadmin.allowlist.enabled)
|
export webadmin_allowlist_enabled=$(yunohost settings get security.webadmin.webadmin_allowlist_enabled)
|
||||||
if [ "$webadmin_allowlist_enabled" == "True" ]; then
|
if [ "$webadmin_allowlist_enabled" == "True" ]; then
|
||||||
export webadmin_allowlist=$(yunohost settings get security.webadmin.allowlist)
|
export webadmin_allowlist=$(yunohost settings get security.webadmin.webadmin_allowlist)
|
||||||
fi
|
fi
|
||||||
ynh_render_template "yunohost_admin.conf.inc" "${nginx_conf_dir}/yunohost_admin.conf.inc"
|
ynh_render_template "yunohost_admin.conf.inc" "${nginx_conf_dir}/yunohost_admin.conf.inc"
|
||||||
ynh_render_template "yunohost_api.conf.inc" "${nginx_conf_dir}/yunohost_api.conf.inc"
|
ynh_render_template "yunohost_api.conf.inc" "${nginx_conf_dir}/yunohost_api.conf.inc"
|
||||||
|
|
|
@ -22,17 +22,19 @@ do_pre_regen() {
|
||||||
main_domain=$(cat /etc/yunohost/current_host)
|
main_domain=$(cat /etc/yunohost/current_host)
|
||||||
|
|
||||||
# Support different strategy for security configurations
|
# Support different strategy for security configurations
|
||||||
export compatibility="$(yunohost settings get 'security.postfix.compatibility')"
|
export compatibility="$(yunohost settings get 'security.postfix.postfix_compatibility')"
|
||||||
|
|
||||||
# Add possibility to specify a relay
|
# Add possibility to specify a relay
|
||||||
# Could be useful with some isp with no 25 port open or more complex setup
|
# Could be useful with some isp with no 25 port open or more complex setup
|
||||||
export relay_port=""
|
export relay_port=""
|
||||||
export relay_user=""
|
export relay_user=""
|
||||||
export relay_host="$(yunohost settings get 'smtp.relay.host')"
|
export relay_host=""
|
||||||
if [ -n "${relay_host}" ]; then
|
export relay_enabled="$(yunohost settings get 'email.smtp.smtp_relay_enabled')"
|
||||||
relay_port="$(yunohost settings get 'smtp.relay.port')"
|
if [ "${relay_enabled}" == "True" ]; then
|
||||||
relay_user="$(yunohost settings get 'smtp.relay.user')"
|
relay_host="$(yunohost settings get 'email.smtp.smtp_relay_host')"
|
||||||
relay_password="$(yunohost settings get 'smtp.relay.password')"
|
relay_port="$(yunohost settings get 'email.smtp.smtp_relay_port')"
|
||||||
|
relay_user="$(yunohost settings get 'email.smtp.smtp_relay_user')"
|
||||||
|
relay_password="$(yunohost settings get 'email.smtp.smtp_relay_password')"
|
||||||
|
|
||||||
# Avoid to display "Relay account paswword" to other users
|
# Avoid to display "Relay account paswword" to other users
|
||||||
touch ${postfix_dir}/sasl_passwd
|
touch ${postfix_dir}/sasl_passwd
|
||||||
|
@ -54,7 +56,7 @@ do_pre_regen() {
|
||||||
>"${default_dir}/postsrsd"
|
>"${default_dir}/postsrsd"
|
||||||
|
|
||||||
# adapt it for IPv4-only hosts
|
# adapt it for IPv4-only hosts
|
||||||
ipv6="$(yunohost settings get 'smtp.allow_ipv6')"
|
ipv6="$(yunohost settings get 'email.smtp.smtp_allow_ipv6')"
|
||||||
if [ "$ipv6" == "False" ] || [ ! -f /proc/net/if_inet6 ]; then
|
if [ "$ipv6" == "False" ] || [ ! -f /proc/net/if_inet6 ]; then
|
||||||
sed -i \
|
sed -i \
|
||||||
's/ \[::ffff:127.0.0.0\]\/104 \[::1\]\/128//g' \
|
's/ \[::ffff:127.0.0.0\]\/104 \[::1\]\/128//g' \
|
||||||
|
|
|
@ -16,7 +16,7 @@ do_pre_regen() {
|
||||||
cp dovecot-ldap.conf "${dovecot_dir}/dovecot-ldap.conf"
|
cp dovecot-ldap.conf "${dovecot_dir}/dovecot-ldap.conf"
|
||||||
cp dovecot.sieve "${dovecot_dir}/global_script/dovecot.sieve"
|
cp dovecot.sieve "${dovecot_dir}/global_script/dovecot.sieve"
|
||||||
|
|
||||||
export pop3_enabled="$(yunohost settings get 'pop3.enabled')"
|
export pop3_enabled="$(yunohost settings get 'email.pop3.pop3_enabled')"
|
||||||
export main_domain=$(cat /etc/yunohost/current_host)
|
export main_domain=$(cat /etc/yunohost/current_host)
|
||||||
export domain_list="$YNH_DOMAINS"
|
export domain_list="$YNH_DOMAINS"
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ do_pre_regen() {
|
||||||
cp yunohost.conf "${fail2ban_dir}/filter.d/yunohost.conf"
|
cp yunohost.conf "${fail2ban_dir}/filter.d/yunohost.conf"
|
||||||
cp jail.conf "${fail2ban_dir}/jail.conf"
|
cp jail.conf "${fail2ban_dir}/jail.conf"
|
||||||
|
|
||||||
export ssh_port="$(yunohost settings get 'security.ssh.port')"
|
export ssh_port="$(yunohost settings get 'security.ssh.ssh_port')"
|
||||||
ynh_render_template "yunohost-jails.conf" "${fail2ban_dir}/jail.d/yunohost-jails.conf"
|
ynh_render_template "yunohost-jails.conf" "${fail2ban_dir}/jail.d/yunohost-jails.conf"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,6 @@ backup_dir="$1/conf/ynh"
|
||||||
cp -a "${backup_dir}/current_host" /etc/yunohost/current_host
|
cp -a "${backup_dir}/current_host" /etc/yunohost/current_host
|
||||||
cp -a "${backup_dir}/firewall.yml" /etc/yunohost/firewall.yml
|
cp -a "${backup_dir}/firewall.yml" /etc/yunohost/firewall.yml
|
||||||
[ ! -d "${backup_dir}/domains" ] || cp -a "${backup_dir}/domains" /etc/yunohost/domains
|
[ ! -d "${backup_dir}/domains" ] || cp -a "${backup_dir}/domains" /etc/yunohost/domains
|
||||||
[ ! -e "${backup_dir}/settings.json" ] || cp -a "${backup_dir}/settings.json" "/etc/yunohost/settings.json"
|
[ ! -e "${backup_dir}/settings.yml" ] || cp -a "${backup_dir}/settings.yml" "/etc/yunohost/settings.yml"
|
||||||
[ ! -d "${backup_dir}/dyndns" ] || cp -raT "${backup_dir}/dyndns" "/etc/yunohost/dyndns"
|
[ ! -d "${backup_dir}/dyndns" ] || cp -raT "${backup_dir}/dyndns" "/etc/yunohost/dyndns"
|
||||||
[ ! -d "${backup_dir}/dkim" ] || cp -raT "${backup_dir}/dkim" "/etc/dkim"
|
[ ! -d "${backup_dir}/dkim" ] || cp -raT "${backup_dir}/dkim" "/etc/dkim"
|
||||||
|
|
|
@ -251,7 +251,7 @@
|
||||||
"diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Current reverse DNS: <code>{rdns_domain}</code><br>Expected value: <code>{ehlo_domain}</code>",
|
"diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Current reverse DNS: <code>{rdns_domain}</code><br>Expected value: <code>{ehlo_domain}</code>",
|
||||||
"diagnosis_mail_fcrdns_dns_missing": "No reverse DNS is defined in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.",
|
"diagnosis_mail_fcrdns_dns_missing": "No reverse DNS is defined in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.",
|
||||||
"diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If you are experiencing issues because of this, consider the following solutions:<br> - Some ISP provide the alternative of <a href='https://yunohost.org/#/email_configure_relay'>using a mail server relay</a> though it implies that the relay will be able to spy on your email traffic.<br>- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See <a href='https://yunohost.org/#/vpn_advantage'>https://yunohost.org/#/vpn_advantage</a><br>- Or it's possible to <a href='https://yunohost.org/#/isp'>switch to a different provider</a>",
|
"diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If you are experiencing issues because of this, consider the following solutions:<br> - Some ISP provide the alternative of <a href='https://yunohost.org/#/email_configure_relay'>using a mail server relay</a> though it implies that the relay will be able to spy on your email traffic.<br>- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See <a href='https://yunohost.org/#/vpn_advantage'>https://yunohost.org/#/vpn_advantage</a><br>- Or it's possible to <a href='https://yunohost.org/#/isp'>switch to a different provider</a>",
|
||||||
"diagnosis_mail_fcrdns_nok_alternatives_6": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If your reverse DNS is correctly configured for IPv4, you can try disabling the use of IPv6 when sending emails by running <cmd>yunohost settings set smtp.allow_ipv6 -v off</cmd>. Note: this last solution means that you won't be able to send or receive emails from the few IPv6-only servers out there.",
|
"diagnosis_mail_fcrdns_nok_alternatives_6": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If your reverse DNS is correctly configured for IPv4, you can try disabling the use of IPv6 when sending emails by running <cmd>yunohost settings set email.smtp.smtp_allow_ipv6 -v off</cmd>. Note: this last solution means that you won't be able to send or receive emails from the few IPv6-only servers out there.",
|
||||||
"diagnosis_mail_fcrdns_nok_details": "You should first try to configure the reverse DNS with <code>{ehlo_domain}</code> in your internet router interface or your hosting provider interface. (Some hosting provider may require you to send them a support ticket for this).",
|
"diagnosis_mail_fcrdns_nok_details": "You should first try to configure the reverse DNS with <code>{ehlo_domain}</code> in your internet router interface or your hosting provider interface. (Some hosting provider may require you to send them a support ticket for this).",
|
||||||
"diagnosis_mail_fcrdns_ok": "Your reverse DNS is correctly configured!",
|
"diagnosis_mail_fcrdns_ok": "Your reverse DNS is correctly configured!",
|
||||||
"diagnosis_mail_outgoing_port_25_blocked": "The SMTP mail server cannot send emails to other servers because outgoing port 25 is blocked in IPv{ipversion}.",
|
"diagnosis_mail_outgoing_port_25_blocked": "The SMTP mail server cannot send emails to other servers because outgoing port 25 is blocked in IPv{ipversion}.",
|
||||||
|
@ -288,8 +288,8 @@
|
||||||
"diagnosis_services_bad_status_tip": "You can try to <a href='#/services/{service}'>restart the service</a>, and if it doesn't work, have a look at <a href='#/services/{service}'>the service logs in the webadmin</a> (from the command line, you can do this with <cmd>yunohost service restart {service}</cmd> and <cmd>yunohost service log {service}</cmd>).",
|
"diagnosis_services_bad_status_tip": "You can try to <a href='#/services/{service}'>restart the service</a>, and if it doesn't work, have a look at <a href='#/services/{service}'>the service logs in the webadmin</a> (from the command line, you can do this with <cmd>yunohost service restart {service}</cmd> and <cmd>yunohost service log {service}</cmd>).",
|
||||||
"diagnosis_services_conf_broken": "Configuration is broken for service {service}!",
|
"diagnosis_services_conf_broken": "Configuration is broken for service {service}!",
|
||||||
"diagnosis_services_running": "Service {service} is running!",
|
"diagnosis_services_running": "Service {service} is running!",
|
||||||
"diagnosis_sshd_config_inconsistent": "It looks like the SSH port was manually modified in /etc/ssh/sshd_config. Since YunoHost 4.2, a new global setting 'security.ssh.port' is available to avoid manually editing the configuration.",
|
"diagnosis_sshd_config_inconsistent": "It looks like the SSH port was manually modified in /etc/ssh/sshd_config. Since YunoHost 4.2, a new global setting 'security.ssh.ssh_port' is available to avoid manually editing the configuration.",
|
||||||
"diagnosis_sshd_config_inconsistent_details": "Please run <cmd>yunohost settings set security.ssh.port -v YOUR_SSH_PORT</cmd> to define the SSH port, and check <cmd>yunohost tools regen-conf ssh --dry-run --with-diff</cmd> and <cmd>yunohost tools regen-conf ssh --force</cmd> to reset your conf to the YunoHost recommendation.",
|
"diagnosis_sshd_config_inconsistent_details": "Please run <cmd>yunohost settings set security.ssh.ssh_port -v YOUR_SSH_PORT</cmd> to define the SSH port, and check <cmd>yunohost tools regen-conf ssh --dry-run --with-diff</cmd> and <cmd>yunohost tools regen-conf ssh --force</cmd> to reset your conf to the YunoHost recommendation.",
|
||||||
"diagnosis_sshd_config_insecure": "The SSH configuration appears to have been manually modified, and is insecure because it contains no 'AllowGroups' or 'AllowUsers' directive to limit access to authorized users.",
|
"diagnosis_sshd_config_insecure": "The SSH configuration appears to have been manually modified, and is insecure because it contains no 'AllowGroups' or 'AllowUsers' directive to limit access to authorized users.",
|
||||||
"diagnosis_swap_none": "The system has no swap at all. You should consider adding at least {recommended} of swap to avoid situations where the system runs out of memory.",
|
"diagnosis_swap_none": "The system has no swap at all. You should consider adding at least {recommended} of swap to avoid situations where the system runs out of memory.",
|
||||||
"diagnosis_swap_notsomuch": "The system has only {total} swap. You should consider having at least {recommended} to avoid situations where the system runs out of memory.",
|
"diagnosis_swap_notsomuch": "The system has only {total} swap. You should consider having at least {recommended} to avoid situations where the system runs out of memory.",
|
||||||
|
@ -367,33 +367,41 @@
|
||||||
"firewall_reload_failed": "Could not reload the firewall",
|
"firewall_reload_failed": "Could not reload the firewall",
|
||||||
"firewall_reloaded": "Firewall reloaded",
|
"firewall_reloaded": "Firewall reloaded",
|
||||||
"firewall_rules_cmd_failed": "Some firewall rule commands have failed. More info in log.",
|
"firewall_rules_cmd_failed": "Some firewall rule commands have failed. More info in log.",
|
||||||
"global_settings_bad_choice_for_enum": "Bad choice for setting {setting}, received '{choice}', but available choices are: {available_choices}",
|
"global_settings_reset_success": "Reset global settings",
|
||||||
"global_settings_bad_type_for_setting": "Bad type for setting {setting}, received {received_type}, expected {expected_type}",
|
"global_settings_setting_admin_strength": "Admin password strength",
|
||||||
"global_settings_cant_open_settings": "Could not open settings file, reason: {reason}",
|
"global_settings_setting_backup_compress_tar_archives": "Compress backups",
|
||||||
"global_settings_cant_serialize_settings": "Could not serialize settings data, reason: {reason}",
|
"global_settings_setting_backup_compress_tar_archives_help": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.",
|
||||||
"global_settings_cant_write_settings": "Could not save settings file, reason: {reason}",
|
"global_settings_setting_nginx_compatibility": "Compatibility",
|
||||||
"global_settings_key_doesnt_exists": "The key '{settings_key}' does not exist in the global settings, you can see all the available keys by running 'yunohost settings list'",
|
"global_settings_setting_nginx_compatibility_help": "Compatibility vs. security tradeoff for the web server NGINX. Affects the ciphers (and other security-related aspects)",
|
||||||
"global_settings_reset_success": "Previous settings now backed up to {path}",
|
"global_settings_setting_nginx_redirect_to_https": "Force HTTPS",
|
||||||
"global_settings_setting_backup_compress_tar_archives": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.",
|
"global_settings_setting_nginx_redirect_to_https_help": "Redirect HTTP requests to HTTPs by default (DO NOT TURN OFF unless you really know what you're doing!)",
|
||||||
"global_settings_setting_pop3_enabled": "Enable the POP3 protocol for the mail server",
|
"global_settings_setting_pop3_enabled": "Enable POP3",
|
||||||
"global_settings_setting_security_experimental_enabled": "Enable experimental security features (don't enable this if you don't know what you're doing!)",
|
"global_settings_setting_pop3_enabled_help": "Enable the POP3 protocol for the mail server",
|
||||||
"global_settings_setting_security_nginx_compatibility": "Compatibility vs. security tradeoff for the web server NGINX. Affects the ciphers (and other security-related aspects)",
|
"global_settings_setting_postfix_compatibility": "Compatibility",
|
||||||
"global_settings_setting_security_nginx_redirect_to_https": "Redirect HTTP requests to HTTPs by default (DO NOT TURN OFF unless you really know what you're doing!)",
|
"global_settings_setting_postfix_compatibility_help": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)",
|
||||||
"global_settings_setting_security_password_admin_strength": "Admin password strength",
|
"global_settings_setting_security_experimental_enabled": "Experimental security features",
|
||||||
"global_settings_setting_security_password_user_strength": "User password strength",
|
"global_settings_setting_security_experimental_enabled_help": "Enable experimental security features (don't enable this if you don't know what you're doing!)",
|
||||||
"global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)",
|
"global_settings_setting_smtp_allow_ipv6": "Allow IPv6",
|
||||||
"global_settings_setting_security_ssh_compatibility": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)",
|
"global_settings_setting_smtp_allow_ipv6_help": "Allow the use of IPv6 to receive and send mail",
|
||||||
"global_settings_setting_security_ssh_password_authentication": "Allow password authentication for SSH",
|
"global_settings_setting_smtp_relay_enabled": "Enable SMTP relay",
|
||||||
"global_settings_setting_security_ssh_port": "SSH port",
|
"global_settings_setting_smtp_relay_enabled_help": "Enable the SMTP relay to use in order to send mail instead of this yunohost instance. Useful if you are in one of this situation: your 25 port is blocked by your ISP or VPS provider, you have a residential IP listed on DUHL, you are not able to configure reverse DNS or this server is not directly exposed on the internet and you want use an other one to send mails.",
|
||||||
"global_settings_setting_security_webadmin_allowlist": "IP adresses allowed to access the webadmin. Comma-separated.",
|
"global_settings_setting_smtp_relay_host": "Relay host",
|
||||||
"global_settings_setting_security_webadmin_allowlist_enabled": "Allow only some IPs to access the webadmin.",
|
"global_settings_setting_smtp_relay_password": "Relay password",
|
||||||
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration",
|
"global_settings_setting_smtp_relay_port": "Relay port",
|
||||||
"global_settings_setting_smtp_allow_ipv6": "Allow the use of IPv6 to receive and send mail",
|
"global_settings_setting_smtp_relay_user": "Relay user",
|
||||||
"global_settings_setting_smtp_relay_host": "SMTP relay host to use in order to send mail instead of this yunohost instance. Useful if you are in one of this situation: your 25 port is blocked by your ISP or VPS provider, you have a residential IP listed on DUHL, you are not able to configure reverse DNS or this server is not directly exposed on the internet and you want use an other one to send mails.",
|
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey": "Allow DSA hostkey",
|
||||||
"global_settings_setting_smtp_relay_password": "SMTP relay host password",
|
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration",
|
||||||
"global_settings_setting_smtp_relay_port": "SMTP relay port",
|
"global_settings_setting_ssh_compatibility": "Compatibility",
|
||||||
"global_settings_setting_smtp_relay_user": "SMTP relay user account",
|
"global_settings_setting_ssh_compatibility_help": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)",
|
||||||
"global_settings_setting_ssowat_panel_overlay_enabled": "Enable SSOwat panel overlay",
|
"global_settings_setting_ssh_password_authentication": "Password authentication",
|
||||||
|
"global_settings_setting_ssh_password_authentication_help": "Allow password authentication for SSH",
|
||||||
|
"global_settings_setting_ssh_port": "SSH port",
|
||||||
|
"global_settings_setting_ssowat_panel_overlay_enabled": "SSOwat panel overlay",
|
||||||
|
"global_settings_setting_user_strength": "User password strength",
|
||||||
|
"global_settings_setting_webadmin_allowlist": "Webadmin IP allowlist",
|
||||||
|
"global_settings_setting_webadmin_allowlist_help": "IP adresses allowed to access the webadmin.",
|
||||||
|
"global_settings_setting_webadmin_allowlist_enabled": "Enable Webadmin IP allowlist",
|
||||||
|
"global_settings_setting_webadmin_allowlist_enabled_help": "Allow only some IPs to access the webadmin.",
|
||||||
"global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key}', discard it and save it in /etc/yunohost/settings-unknown.json",
|
"global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key}', discard it and save it in /etc/yunohost/settings-unknown.json",
|
||||||
"global_settings_unknown_type": "Unexpected situation, the setting {setting} appears to have the type {unknown_type} but it is not a type supported by the system.",
|
"global_settings_unknown_type": "Unexpected situation, the setting {setting} appears to have the type {unknown_type} but it is not a type supported by the system.",
|
||||||
"good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).",
|
"good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).",
|
||||||
|
@ -478,6 +486,9 @@
|
||||||
"log_user_permission_reset": "Reset permission '{}'",
|
"log_user_permission_reset": "Reset permission '{}'",
|
||||||
"log_user_permission_update": "Update accesses for permission '{}'",
|
"log_user_permission_update": "Update accesses for permission '{}'",
|
||||||
"log_user_update": "Update info for user '{}'",
|
"log_user_update": "Update info for user '{}'",
|
||||||
|
"log_settings_set": "Apply settings",
|
||||||
|
"log_settings_reset": "Reset setting",
|
||||||
|
"log_settings_reset_all": "Reset all setting",
|
||||||
"mail_alias_remove_failed": "Could not remove e-mail alias '{mail}'",
|
"mail_alias_remove_failed": "Could not remove e-mail alias '{mail}'",
|
||||||
"mail_domain_unknown": "Invalid e-mail address for domain '{domain}'. Please, use a domain administrated by this server.",
|
"mail_domain_unknown": "Invalid e-mail address for domain '{domain}'. Please, use a domain administrated by this server.",
|
||||||
"mail_forward_remove_failed": "Could not remove e-mail forwarding '{mail}'",
|
"mail_forward_remove_failed": "Could not remove e-mail forwarding '{mail}'",
|
||||||
|
|
|
@ -100,7 +100,7 @@ def find_expected_string_keys():
|
||||||
yield m
|
yield m
|
||||||
|
|
||||||
# Global settings descriptions
|
# Global settings descriptions
|
||||||
# Will be on a line like : ("service.ssh.allow_deprecated_dsa_hostkey", {"type": "bool", ...
|
# Will be on a line like : ("security.ssh.ssh_allow_deprecated_dsa_hostkey", {"type": "bool", ...
|
||||||
p5 = re.compile(r" \(\n*\s*[\"\'](\w[\w\.]+)[\"\'],")
|
p5 = re.compile(r" \(\n*\s*[\"\'](\w[\w\.]+)[\"\'],")
|
||||||
content = open(ROOT + "src/settings.py").read()
|
content = open(ROOT + "src/settings.py").read()
|
||||||
for m in (
|
for m in (
|
||||||
|
|
|
@ -1093,6 +1093,11 @@ settings:
|
||||||
list:
|
list:
|
||||||
action_help: list all entries of the settings
|
action_help: list all entries of the settings
|
||||||
api: GET /settings
|
api: GET /settings
|
||||||
|
arguments:
|
||||||
|
-f:
|
||||||
|
full: --full
|
||||||
|
help: Display all details (meant to be used by the API)
|
||||||
|
action: store_true
|
||||||
|
|
||||||
### settings_get()
|
### settings_get()
|
||||||
get:
|
get:
|
||||||
|
@ -1101,22 +1106,29 @@ settings:
|
||||||
arguments:
|
arguments:
|
||||||
key:
|
key:
|
||||||
help: Settings key
|
help: Settings key
|
||||||
--full:
|
-f:
|
||||||
help: Show more details
|
full: --full
|
||||||
|
help: Display all details (meant to be used by the API)
|
||||||
|
action: store_true
|
||||||
|
-e:
|
||||||
|
full: --export
|
||||||
|
help: Only export key/values, meant to be reimported using "config set --args-file"
|
||||||
action: store_true
|
action: store_true
|
||||||
|
|
||||||
### settings_set()
|
### settings_set()
|
||||||
set:
|
set:
|
||||||
action_help: set an entry value in the settings
|
action_help: set an entry value in the settings
|
||||||
api: POST /settings/<key>
|
api: PUT /settings
|
||||||
arguments:
|
arguments:
|
||||||
key:
|
key:
|
||||||
help: Settings key
|
help: The question or form key
|
||||||
|
nargs: '?'
|
||||||
-v:
|
-v:
|
||||||
full: --value
|
full: --value
|
||||||
help: new value
|
help: new value
|
||||||
extra:
|
-a:
|
||||||
required: True
|
full: --args
|
||||||
|
help: Serialized arguments for new configuration (i.e. "mail_in=0&mail_out=0")
|
||||||
|
|
||||||
### settings_reset_all()
|
### settings_reset_all()
|
||||||
reset-all:
|
reset-all:
|
||||||
|
|
145
share/config_settings.toml
Normal file
145
share/config_settings.toml
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
version = "1.0"
|
||||||
|
i18n = "global_settings_setting"
|
||||||
|
|
||||||
|
[security]
|
||||||
|
name = "Security"
|
||||||
|
[security.password]
|
||||||
|
name = "Passwords"
|
||||||
|
[security.password.admin_strength]
|
||||||
|
type = "number"
|
||||||
|
default = 1
|
||||||
|
|
||||||
|
[security.password.user_strength]
|
||||||
|
type = "number"
|
||||||
|
default = 1
|
||||||
|
|
||||||
|
[security.ssh]
|
||||||
|
name = "SSH"
|
||||||
|
[security.ssh.ssh_compatibility]
|
||||||
|
type = "select"
|
||||||
|
default = "modern"
|
||||||
|
choices = ["intermediate", "modern"]
|
||||||
|
|
||||||
|
[security.ssh.ssh_port]
|
||||||
|
type = "number"
|
||||||
|
default = 22
|
||||||
|
|
||||||
|
[security.ssh.ssh_password_authentication]
|
||||||
|
type = "boolean"
|
||||||
|
yes = "True"
|
||||||
|
no = "False"
|
||||||
|
default = "False"
|
||||||
|
|
||||||
|
[security.ssh.ssh_allow_deprecated_dsa_hostkey]
|
||||||
|
type = "boolean"
|
||||||
|
yes = "True"
|
||||||
|
no = "False"
|
||||||
|
default = "False"
|
||||||
|
|
||||||
|
[security.nginx]
|
||||||
|
name = "NGINX"
|
||||||
|
[security.nginx.nginx_redirect_to_https]
|
||||||
|
type = "boolean"
|
||||||
|
yes = "True"
|
||||||
|
no = "False"
|
||||||
|
default = "True"
|
||||||
|
|
||||||
|
[security.nginx.nginx_compatibility]
|
||||||
|
type = "select"
|
||||||
|
default = "intermediate"
|
||||||
|
choices = ["intermediate", "modern"]
|
||||||
|
|
||||||
|
[security.postfix]
|
||||||
|
name = "Postfix"
|
||||||
|
[security.postfix.postfix_compatibility]
|
||||||
|
type = "select"
|
||||||
|
default = "intermediate"
|
||||||
|
choices = ["intermediate", "modern"]
|
||||||
|
|
||||||
|
[security.webadmin]
|
||||||
|
name = "Webadmin"
|
||||||
|
[security.webadmin.webadmin_allowlist_enabled]
|
||||||
|
type = "boolean"
|
||||||
|
yes = "True"
|
||||||
|
no = "False"
|
||||||
|
default = "False"
|
||||||
|
|
||||||
|
[security.webadmin.webadmin_allowlist]
|
||||||
|
type = "tags"
|
||||||
|
visible = "webadmin_allowlist_enabled"
|
||||||
|
optional = true
|
||||||
|
default = ""
|
||||||
|
|
||||||
|
[security.experimental]
|
||||||
|
name = "Experimental"
|
||||||
|
[security.experimental.security_experimental_enabled]
|
||||||
|
type = "boolean"
|
||||||
|
yes = "True"
|
||||||
|
no = "False"
|
||||||
|
default = "False"
|
||||||
|
|
||||||
|
|
||||||
|
[email]
|
||||||
|
name = "Email"
|
||||||
|
[email.pop3]
|
||||||
|
name = "POP3"
|
||||||
|
[email.pop3.pop3_enabled]
|
||||||
|
type = "boolean"
|
||||||
|
yes = "True"
|
||||||
|
no = "False"
|
||||||
|
default = "False"
|
||||||
|
|
||||||
|
[email.smtp]
|
||||||
|
name = "SMTP"
|
||||||
|
[email.smtp.smtp_allow_ipv6]
|
||||||
|
type = "boolean"
|
||||||
|
yes = "True"
|
||||||
|
no = "False"
|
||||||
|
default = "True"
|
||||||
|
|
||||||
|
[email.smtp.smtp_relay_enabled]
|
||||||
|
type = "boolean"
|
||||||
|
yes = "True"
|
||||||
|
no = "False"
|
||||||
|
default = "False"
|
||||||
|
|
||||||
|
[email.smtp.smtp_relay_host]
|
||||||
|
type = "string"
|
||||||
|
default = ""
|
||||||
|
optional = true
|
||||||
|
visible="smtp_relay_enabled"
|
||||||
|
|
||||||
|
[email.smtp.smtp_relay_port]
|
||||||
|
type = "number"
|
||||||
|
default = 587
|
||||||
|
visible="smtp_relay_enabled"
|
||||||
|
|
||||||
|
[email.smtp.smtp_relay_user]
|
||||||
|
type = "string"
|
||||||
|
default = ""
|
||||||
|
optional = true
|
||||||
|
visible="smtp_relay_enabled"
|
||||||
|
|
||||||
|
[email.smtp.smtp_relay_password]
|
||||||
|
type = "password"
|
||||||
|
default = ""
|
||||||
|
optional = true
|
||||||
|
visible="smtp_relay_enabled"
|
||||||
|
|
||||||
|
[misc]
|
||||||
|
name = "Other"
|
||||||
|
[misc.ssowat]
|
||||||
|
name = "SSOwat"
|
||||||
|
[misc.ssowat.ssowat_panel_overlay_enabled]
|
||||||
|
type = "boolean"
|
||||||
|
yes = "True"
|
||||||
|
no = "False"
|
||||||
|
default = "True"
|
||||||
|
|
||||||
|
[misc.backup]
|
||||||
|
name = "Backup"
|
||||||
|
[misc.backup.backup_compress_tar_archives]
|
||||||
|
type = "boolean"
|
||||||
|
yes = "True"
|
||||||
|
no = "False"
|
||||||
|
default = "False"
|
|
@ -1928,7 +1928,7 @@ class TarBackupMethod(BackupMethod):
|
||||||
def _archive_file(self):
|
def _archive_file(self):
|
||||||
|
|
||||||
if isinstance(self.manager, BackupManager) and settings_get(
|
if isinstance(self.manager, BackupManager) and settings_get(
|
||||||
"backup.compress_tar_archives"
|
"misc.backup.backup_compress_tar_archives"
|
||||||
):
|
):
|
||||||
return os.path.join(self.repo, self.name + ".tar.gz")
|
return os.path.join(self.repo, self.name + ".tar.gz")
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ from datetime import datetime
|
||||||
|
|
||||||
from moulinette import m18n
|
from moulinette import m18n
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
from moulinette.utils.filesystem import read_file
|
from moulinette.utils.filesystem import read_file, chown, chmod
|
||||||
|
|
||||||
from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate
|
from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate
|
||||||
from yunohost.utils.error import YunohostError, YunohostValidationError
|
from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||||
|
@ -719,11 +719,8 @@ def _generate_key(destination_path):
|
||||||
|
|
||||||
|
|
||||||
def _set_permissions(path, user, group, permissions):
|
def _set_permissions(path, user, group, permissions):
|
||||||
uid = pwd.getpwnam(user).pw_uid
|
chown(path, user, group)
|
||||||
gid = grp.getgrnam(group).gr_gid
|
chmod(path, permissions)
|
||||||
|
|
||||||
os.chown(path, uid, gid)
|
|
||||||
os.chmod(path, permissions)
|
|
||||||
|
|
||||||
|
|
||||||
def _enable_certificate(domain, new_cert_folder):
|
def _enable_certificate(domain, new_cert_folder):
|
||||||
|
|
|
@ -5,7 +5,7 @@ import random
|
||||||
import requests
|
import requests
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from moulinette.utils.filesystem import read_file
|
from moulinette.utils.filesystem import read_file, mkdir, rm
|
||||||
|
|
||||||
from yunohost.diagnosis import Diagnoser
|
from yunohost.diagnosis import Diagnoser
|
||||||
from yunohost.domain import domain_list
|
from yunohost.domain import domain_list
|
||||||
|
@ -46,8 +46,8 @@ class MyDiagnoser(Diagnoser):
|
||||||
domains_to_check.append(domain)
|
domains_to_check.append(domain)
|
||||||
|
|
||||||
self.nonce = "".join(random.choice("0123456789abcedf") for i in range(16))
|
self.nonce = "".join(random.choice("0123456789abcedf") for i in range(16))
|
||||||
os.system("rm -rf /tmp/.well-known/ynh-diagnosis/")
|
rm("/tmp/.well-known/ynh-diagnosis/", recursive=True, force=True)
|
||||||
os.system("mkdir -p /tmp/.well-known/ynh-diagnosis/")
|
mkdir("/tmp/.well-known/ynh-diagnosis/", parents=True)
|
||||||
os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % self.nonce)
|
os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % self.nonce)
|
||||||
|
|
||||||
if not domains_to_check:
|
if not domains_to_check:
|
||||||
|
|
|
@ -291,7 +291,7 @@ class MyDiagnoser(Diagnoser):
|
||||||
if global_ipv4:
|
if global_ipv4:
|
||||||
outgoing_ips.append(global_ipv4)
|
outgoing_ips.append(global_ipv4)
|
||||||
|
|
||||||
if settings_get("smtp.allow_ipv6"):
|
if settings_get("email.smtp.smtp_allow_ipv6"):
|
||||||
ipv6 = Diagnoser.get_cached_report("ip", {"test": "ipv6"}) or {}
|
ipv6 = Diagnoser.get_cached_report("ip", {"test": "ipv6"}) or {}
|
||||||
if ipv6.get("status") == "SUCCESS":
|
if ipv6.get("status") == "SUCCESS":
|
||||||
outgoing_ipversions.append(6)
|
outgoing_ipversions.append(6)
|
||||||
|
|
|
@ -53,7 +53,7 @@ class MyDiagnoser(Diagnoser):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check consistency between actual ssh port in sshd_config vs. setting
|
# Check consistency between actual ssh port in sshd_config vs. setting
|
||||||
ssh_port_setting = settings_get("security.ssh.port")
|
ssh_port_setting = settings_get("security.ssh.ssh_port")
|
||||||
ssh_port_line = re.findall(
|
ssh_port_line = re.findall(
|
||||||
r"\bPort *([0-9]{2,5})\b", read_file("/etc/ssh/sshd_config")
|
r"\bPort *([0-9]{2,5})\b", read_file("/etc/ssh/sshd_config")
|
||||||
)
|
)
|
||||||
|
|
44
src/migrations/0024_global_settings_to_configpanel.py
Normal file
44
src/migrations/0024_global_settings_to_configpanel.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import urllib
|
||||||
|
import os
|
||||||
|
|
||||||
|
from yunohost.utils.error import YunohostError
|
||||||
|
from moulinette.utils.log import getActionLogger
|
||||||
|
from moulinette.utils.filesystem import (
|
||||||
|
read_json,
|
||||||
|
write_to_yaml
|
||||||
|
)
|
||||||
|
|
||||||
|
from yunohost.tools import Migration
|
||||||
|
from yunohost.settings import settings_set
|
||||||
|
from yunohost.utils.legacy import translate_legacy_settings_to_configpanel_settings
|
||||||
|
|
||||||
|
logger = getActionLogger("yunohost.migration")
|
||||||
|
|
||||||
|
SETTINGS_PATH = "/etc/yunohost/settings.yml"
|
||||||
|
OLD_SETTINGS_PATH = "/etc/yunohost/settings.json"
|
||||||
|
|
||||||
|
class MyMigration(Migration):
|
||||||
|
|
||||||
|
"Migrate old global settings to the new ConfigPanel global settings"
|
||||||
|
|
||||||
|
dependencies = ["migrate_to_bullseye"]
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if not os.path.exists(OLD_SETTINGS_PATH):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
old_settings = read_json(OLD_SETTINGS_PATH)
|
||||||
|
except Exception as e:
|
||||||
|
raise YunohostError("global_settings_cant_open_settings", reason=e)
|
||||||
|
|
||||||
|
settings = { translate_legacy_settings_to_configpanel_settings(k): v['value'] for k,v in old_settings.items() }
|
||||||
|
|
||||||
|
if settings.get('email.smtp.smtp_relay_host') != "":
|
||||||
|
settings['email.smtp.smtp_relay_enabled'] = "True"
|
||||||
|
|
||||||
|
# Here we don't use settings_set() from settings.py to prevent
|
||||||
|
# Questions to be asked when one run the migration from CLI.
|
||||||
|
write_to_yaml(SETTINGS_PATH, settings)
|
438
src/settings.py
438
src/settings.py
|
@ -2,128 +2,26 @@ import os
|
||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
from moulinette import m18n
|
from moulinette import m18n
|
||||||
from yunohost.utils.error import YunohostError, YunohostValidationError
|
from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||||
|
from yunohost.utils.config import ConfigPanel, Question
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
from yunohost.regenconf import regen_conf
|
from yunohost.regenconf import regen_conf
|
||||||
from yunohost.firewall import firewall_reload
|
from yunohost.firewall import firewall_reload
|
||||||
|
from yunohost.log import is_unit_operation
|
||||||
|
from yunohost.utils.legacy import translate_legacy_settings_to_configpanel_settings
|
||||||
|
|
||||||
logger = getActionLogger("yunohost.settings")
|
logger = getActionLogger("yunohost.settings")
|
||||||
|
|
||||||
SETTINGS_PATH = "/etc/yunohost/settings.json"
|
SETTINGS_PATH = "/etc/yunohost/settings.yml"
|
||||||
SETTINGS_PATH_OTHER_LOCATION = "/etc/yunohost/settings-%s.json"
|
|
||||||
|
BOOLEANS = {
|
||||||
|
"True": True,
|
||||||
|
"False": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def is_boolean(value):
|
def settings_get(key="", full=False, export=False):
|
||||||
TRUE = ["true", "on", "yes", "y", "1"]
|
|
||||||
FALSE = ["false", "off", "no", "n", "0"]
|
|
||||||
|
|
||||||
"""
|
|
||||||
Ensure a string value is intended as a boolean
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
arg -- The string to check
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(is_boolean, boolean_value)
|
|
||||||
|
|
||||||
"""
|
|
||||||
if isinstance(value, bool):
|
|
||||||
return True, value
|
|
||||||
if value in [0, 1]:
|
|
||||||
return True, bool(value)
|
|
||||||
elif isinstance(value, str):
|
|
||||||
if str(value).lower() in TRUE + FALSE:
|
|
||||||
return True, str(value).lower() in TRUE
|
|
||||||
else:
|
|
||||||
return False, None
|
|
||||||
else:
|
|
||||||
return False, None
|
|
||||||
|
|
||||||
|
|
||||||
# a settings entry is in the form of:
|
|
||||||
# namespace.subnamespace.name: {type, value, default, description, [choices]}
|
|
||||||
# choices is only for enum
|
|
||||||
# the keyname can have as many subnamespace as needed but should have at least
|
|
||||||
# one level of namespace
|
|
||||||
|
|
||||||
# description is implied from the translated strings
|
|
||||||
# the key is "global_settings_setting_%s" % key.replace(".", "_")
|
|
||||||
|
|
||||||
# type can be:
|
|
||||||
# * bool
|
|
||||||
# * int
|
|
||||||
# * string
|
|
||||||
# * enum (in the form of a python list)
|
|
||||||
|
|
||||||
DEFAULTS = OrderedDict(
|
|
||||||
[
|
|
||||||
# Password Validation
|
|
||||||
# -1 disabled, 0 alert if listed, 1 8-letter, 2 normal, 3 strong, 4 strongest
|
|
||||||
("security.password.admin.strength", {"type": "int", "default": 1}),
|
|
||||||
("security.password.user.strength", {"type": "int", "default": 1}),
|
|
||||||
(
|
|
||||||
"service.ssh.allow_deprecated_dsa_hostkey",
|
|
||||||
{"type": "bool", "default": False},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"security.ssh.compatibility",
|
|
||||||
{
|
|
||||||
"type": "enum",
|
|
||||||
"default": "modern",
|
|
||||||
"choices": ["intermediate", "modern"],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"security.ssh.port",
|
|
||||||
{"type": "int", "default": 22},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"security.ssh.password_authentication",
|
|
||||||
{"type": "bool", "default": True},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"security.nginx.redirect_to_https",
|
|
||||||
{
|
|
||||||
"type": "bool",
|
|
||||||
"default": True,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"security.nginx.compatibility",
|
|
||||||
{
|
|
||||||
"type": "enum",
|
|
||||||
"default": "intermediate",
|
|
||||||
"choices": ["intermediate", "modern"],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"security.postfix.compatibility",
|
|
||||||
{
|
|
||||||
"type": "enum",
|
|
||||||
"default": "intermediate",
|
|
||||||
"choices": ["intermediate", "modern"],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
("pop3.enabled", {"type": "bool", "default": False}),
|
|
||||||
("smtp.allow_ipv6", {"type": "bool", "default": True}),
|
|
||||||
("smtp.relay.host", {"type": "string", "default": ""}),
|
|
||||||
("smtp.relay.port", {"type": "int", "default": 587}),
|
|
||||||
("smtp.relay.user", {"type": "string", "default": ""}),
|
|
||||||
("smtp.relay.password", {"type": "string", "default": ""}),
|
|
||||||
("backup.compress_tar_archives", {"type": "bool", "default": False}),
|
|
||||||
("ssowat.panel_overlay.enabled", {"type": "bool", "default": True}),
|
|
||||||
("security.webadmin.allowlist.enabled", {"type": "bool", "default": False}),
|
|
||||||
("security.webadmin.allowlist", {"type": "string", "default": ""}),
|
|
||||||
("security.experimental.enabled", {"type": "bool", "default": False}),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def settings_get(key, full=False):
|
|
||||||
"""
|
"""
|
||||||
Get an entry value in the settings
|
Get an entry value in the settings
|
||||||
|
|
||||||
|
@ -131,28 +29,40 @@ def settings_get(key, full=False):
|
||||||
key -- Settings key
|
key -- Settings key
|
||||||
|
|
||||||
"""
|
"""
|
||||||
settings = _get_settings()
|
if full and export:
|
||||||
|
|
||||||
if key not in settings:
|
|
||||||
raise YunohostValidationError(
|
raise YunohostValidationError(
|
||||||
"global_settings_key_doesnt_exists", settings_key=key
|
"You can't use --full and --export together.", raw_msg=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if full:
|
if full:
|
||||||
return settings[key]
|
mode = "full"
|
||||||
|
elif export:
|
||||||
|
mode = "export"
|
||||||
|
else:
|
||||||
|
mode = "classic"
|
||||||
|
|
||||||
return settings[key]["value"]
|
if mode == "classic" and key == "":
|
||||||
|
raise YunohostValidationError("Missing key", raw_msg=True)
|
||||||
|
|
||||||
|
settings = SettingsConfigPanel()
|
||||||
|
key = translate_legacy_settings_to_configpanel_settings(key)
|
||||||
|
return settings.get(key, mode)
|
||||||
|
|
||||||
|
|
||||||
def settings_list():
|
def settings_list(full=False, export=True):
|
||||||
"""
|
"""
|
||||||
List all entries of the settings
|
List all entries of the settings
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return _get_settings()
|
|
||||||
|
if full:
|
||||||
|
export = False
|
||||||
|
|
||||||
|
return settings_get(full=full, export=export)
|
||||||
|
|
||||||
|
|
||||||
def settings_set(key, value):
|
@is_unit_operation()
|
||||||
|
def settings_set(operation_logger, key=None, value=None, args=None, args_file=None):
|
||||||
"""
|
"""
|
||||||
Set an entry value in the settings
|
Set an entry value in the settings
|
||||||
|
|
||||||
|
@ -161,78 +71,14 @@ def settings_set(key, value):
|
||||||
value -- New value
|
value -- New value
|
||||||
|
|
||||||
"""
|
"""
|
||||||
settings = _get_settings()
|
Question.operation_logger = operation_logger
|
||||||
|
settings = SettingsConfigPanel()
|
||||||
if key not in settings:
|
key = translate_legacy_settings_to_configpanel_settings(key)
|
||||||
raise YunohostValidationError(
|
return settings.set(key, value, args, args_file, operation_logger=operation_logger)
|
||||||
"global_settings_key_doesnt_exists", settings_key=key
|
|
||||||
)
|
|
||||||
|
|
||||||
key_type = settings[key]["type"]
|
|
||||||
|
|
||||||
if key_type == "bool":
|
|
||||||
boolean_value = is_boolean(value)
|
|
||||||
if boolean_value[0]:
|
|
||||||
value = boolean_value[1]
|
|
||||||
else:
|
|
||||||
raise YunohostValidationError(
|
|
||||||
"global_settings_bad_type_for_setting",
|
|
||||||
setting=key,
|
|
||||||
received_type=type(value).__name__,
|
|
||||||
expected_type=key_type,
|
|
||||||
)
|
|
||||||
elif key_type == "int":
|
|
||||||
if not isinstance(value, int) or isinstance(value, bool):
|
|
||||||
if isinstance(value, str):
|
|
||||||
try:
|
|
||||||
value = int(value)
|
|
||||||
except Exception:
|
|
||||||
raise YunohostValidationError(
|
|
||||||
"global_settings_bad_type_for_setting",
|
|
||||||
setting=key,
|
|
||||||
received_type=type(value).__name__,
|
|
||||||
expected_type=key_type,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise YunohostValidationError(
|
|
||||||
"global_settings_bad_type_for_setting",
|
|
||||||
setting=key,
|
|
||||||
received_type=type(value).__name__,
|
|
||||||
expected_type=key_type,
|
|
||||||
)
|
|
||||||
elif key_type == "string":
|
|
||||||
if not isinstance(value, str):
|
|
||||||
raise YunohostValidationError(
|
|
||||||
"global_settings_bad_type_for_setting",
|
|
||||||
setting=key,
|
|
||||||
received_type=type(value).__name__,
|
|
||||||
expected_type=key_type,
|
|
||||||
)
|
|
||||||
elif key_type == "enum":
|
|
||||||
if value not in settings[key]["choices"]:
|
|
||||||
raise YunohostValidationError(
|
|
||||||
"global_settings_bad_choice_for_enum",
|
|
||||||
setting=key,
|
|
||||||
choice=str(value),
|
|
||||||
available_choices=", ".join(settings[key]["choices"]),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise YunohostValidationError(
|
|
||||||
"global_settings_unknown_type", setting=key, unknown_type=key_type
|
|
||||||
)
|
|
||||||
|
|
||||||
old_value = settings[key].get("value")
|
|
||||||
settings[key]["value"] = value
|
|
||||||
_save_settings(settings)
|
|
||||||
|
|
||||||
try:
|
|
||||||
trigger_post_change_hook(key, old_value, value)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Post-change hook for setting {key} failed : {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def settings_reset(key):
|
@is_unit_operation()
|
||||||
|
def settings_reset(operation_logger, key):
|
||||||
"""
|
"""
|
||||||
Set an entry value to its default one
|
Set an entry value to its default one
|
||||||
|
|
||||||
|
@ -240,18 +86,14 @@ def settings_reset(key):
|
||||||
key -- Settings key
|
key -- Settings key
|
||||||
|
|
||||||
"""
|
"""
|
||||||
settings = _get_settings()
|
|
||||||
|
|
||||||
if key not in settings:
|
settings = SettingsConfigPanel()
|
||||||
raise YunohostValidationError(
|
key = translate_legacy_settings_to_configpanel_settings(key)
|
||||||
"global_settings_key_doesnt_exists", settings_key=key
|
return settings.reset(key, operation_logger=operation_logger)
|
||||||
)
|
|
||||||
|
|
||||||
settings[key]["value"] = settings[key]["default"]
|
|
||||||
_save_settings(settings)
|
|
||||||
|
|
||||||
|
|
||||||
def settings_reset_all():
|
@is_unit_operation()
|
||||||
|
def settings_reset_all(operation_logger):
|
||||||
"""
|
"""
|
||||||
Reset all settings to their default value
|
Reset all settings to their default value
|
||||||
|
|
||||||
|
@ -259,110 +101,83 @@ def settings_reset_all():
|
||||||
yes -- Yes I'm sure I want to do that
|
yes -- Yes I'm sure I want to do that
|
||||||
|
|
||||||
"""
|
"""
|
||||||
settings = _get_settings()
|
settings = SettingsConfigPanel()
|
||||||
|
return settings.reset(operation_logger=operation_logger)
|
||||||
# For now on, we backup the previous settings in case of but we don't have
|
|
||||||
# any mecanism to take advantage of those backups. It could be a nice
|
|
||||||
# addition but we'll see if this is a common need.
|
|
||||||
# Another solution would be to use etckeeper and integrate those
|
|
||||||
# modification inside of it and take advantage of its git history
|
|
||||||
old_settings_backup_path = (
|
|
||||||
SETTINGS_PATH_OTHER_LOCATION % datetime.utcnow().strftime("%F_%X")
|
|
||||||
)
|
|
||||||
_save_settings(settings, location=old_settings_backup_path)
|
|
||||||
|
|
||||||
for value in settings.values():
|
|
||||||
value["value"] = value["default"]
|
|
||||||
|
|
||||||
_save_settings(settings)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"old_settings_backup_path": old_settings_backup_path,
|
|
||||||
"message": m18n.n(
|
|
||||||
"global_settings_reset_success", path=old_settings_backup_path
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _get_setting_description(key):
|
class SettingsConfigPanel(ConfigPanel):
|
||||||
return m18n.n(f"global_settings_setting_{key}".replace(".", "_"))
|
entity_type = "settings"
|
||||||
|
save_path_tpl = SETTINGS_PATH
|
||||||
|
save_mode = "diff"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, config_path=None, save_path=None, creation=False
|
||||||
|
):
|
||||||
|
super().__init__("settings")
|
||||||
|
|
||||||
def _get_settings():
|
def _apply(self):
|
||||||
|
super()._apply()
|
||||||
|
|
||||||
settings = {}
|
settings = { k: v for k, v in self.future_values.items() if self.values.get(k) != v }
|
||||||
|
for setting_name, value in settings.items():
|
||||||
|
try:
|
||||||
|
trigger_post_change_hook(setting_name, self.values.get(setting_name), value)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Post-change hook for setting failed : {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
for key, value in DEFAULTS.copy().items():
|
def get(self, key="", mode="classic"):
|
||||||
settings[key] = value
|
result = super().get(key=key, mode=mode)
|
||||||
settings[key]["value"] = value["default"]
|
|
||||||
settings[key]["description"] = _get_setting_description(key)
|
|
||||||
|
|
||||||
if not os.path.exists(SETTINGS_PATH):
|
if mode == "full":
|
||||||
return settings
|
for panel, section, option in self._iterate():
|
||||||
|
if m18n.key_exists(self.config["i18n"] + "_" + option["id"] + "_help"):
|
||||||
|
option["help"] = m18n.n(self.config["i18n"] + "_" + option["id"] + "_help")
|
||||||
|
return self.config
|
||||||
|
|
||||||
# we have a very strict policy on only allowing settings that we know in
|
# Dirty hack to let settings_get() to work from a python script
|
||||||
# the OrderedDict DEFAULTS
|
if isinstance(result, str) and result in BOOLEANS:
|
||||||
# For various reason, while reading the local settings we might encounter
|
result = BOOLEANS[result]
|
||||||
# settings that aren't in DEFAULTS, those can come from settings key that
|
|
||||||
# we have removed, errors or the user trying to modify
|
return result
|
||||||
# /etc/yunohost/settings.json
|
|
||||||
# To avoid to simply overwrite them, we store them in
|
def reset(self, key = "", operation_logger=None):
|
||||||
# /etc/yunohost/settings-unknown.json in case of
|
self.filter_key = key
|
||||||
unknown_settings = {}
|
|
||||||
unknown_settings_path = SETTINGS_PATH_OTHER_LOCATION % "unknown"
|
# Read config panel toml
|
||||||
|
self._get_config_panel()
|
||||||
|
|
||||||
|
if not self.config:
|
||||||
|
raise YunohostValidationError("config_no_panel")
|
||||||
|
|
||||||
|
# Replace all values with default values
|
||||||
|
self.values = self._get_default_values()
|
||||||
|
|
||||||
|
Question.operation_logger = operation_logger
|
||||||
|
|
||||||
|
if operation_logger:
|
||||||
|
operation_logger.start()
|
||||||
|
|
||||||
if os.path.exists(unknown_settings_path):
|
|
||||||
try:
|
try:
|
||||||
unknown_settings = json.load(open(unknown_settings_path, "r"))
|
self._apply()
|
||||||
except Exception as e:
|
except YunohostError:
|
||||||
logger.warning(f"Error while loading unknown settings {e}")
|
raise
|
||||||
|
# Script got manually interrupted ...
|
||||||
|
# N.B. : KeyboardInterrupt does not inherit from Exception
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
error = m18n.n("operation_interrupted")
|
||||||
|
logger.error(m18n.n("config_apply_failed", error=error))
|
||||||
|
raise
|
||||||
|
# Something wrong happened in Yunohost's code (most probably hook_exec)
|
||||||
|
except Exception:
|
||||||
|
import traceback
|
||||||
|
|
||||||
try:
|
error = m18n.n("unexpected_error", error="\n" + traceback.format_exc())
|
||||||
with open(SETTINGS_PATH) as settings_fd:
|
logger.error(m18n.n("config_apply_failed", error=error))
|
||||||
local_settings = json.load(settings_fd)
|
raise
|
||||||
|
|
||||||
for key, value in local_settings.items():
|
logger.success(m18n.n("global_settings_reset_success"))
|
||||||
if key in settings:
|
operation_logger.success()
|
||||||
settings[key] = value
|
|
||||||
settings[key]["description"] = _get_setting_description(key)
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
m18n.n(
|
|
||||||
"global_settings_unknown_setting_from_settings_file",
|
|
||||||
setting_key=key,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
unknown_settings[key] = value
|
|
||||||
except Exception as e:
|
|
||||||
raise YunohostValidationError("global_settings_cant_open_settings", reason=e)
|
|
||||||
|
|
||||||
if unknown_settings:
|
|
||||||
try:
|
|
||||||
_save_settings(unknown_settings, location=unknown_settings_path)
|
|
||||||
_save_settings(settings)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Failed to save unknown settings (because {e}), aborting.")
|
|
||||||
|
|
||||||
return settings
|
|
||||||
|
|
||||||
|
|
||||||
def _save_settings(settings, location=SETTINGS_PATH):
|
|
||||||
settings_without_description = {}
|
|
||||||
for key, value in settings.items():
|
|
||||||
settings_without_description[key] = value
|
|
||||||
if "description" in value:
|
|
||||||
del settings_without_description[key]["description"]
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = json.dumps(settings_without_description, indent=4)
|
|
||||||
except Exception as e:
|
|
||||||
raise YunohostError("global_settings_cant_serialize_settings", reason=e)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(location, "w") as settings_fd:
|
|
||||||
settings_fd.write(result)
|
|
||||||
except Exception as e:
|
|
||||||
raise YunohostError("global_settings_cant_write_settings", reason=e)
|
|
||||||
|
|
||||||
|
|
||||||
# Meant to be a dict of setting_name -> function to call
|
# Meant to be a dict of setting_name -> function to call
|
||||||
|
@ -370,13 +185,8 @@ post_change_hooks = {}
|
||||||
|
|
||||||
|
|
||||||
def post_change_hook(setting_name):
|
def post_change_hook(setting_name):
|
||||||
|
# TODO: Check that setting_name exists
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
assert (
|
|
||||||
setting_name in DEFAULTS.keys()
|
|
||||||
), f"The setting {setting_name} does not exists"
|
|
||||||
assert (
|
|
||||||
setting_name not in post_change_hooks
|
|
||||||
), f"You can only register one post change hook per setting (in particular for {setting_name})"
|
|
||||||
post_change_hooks[setting_name] = func
|
post_change_hooks[setting_name] = func
|
||||||
return func
|
return func
|
||||||
|
|
||||||
|
@ -404,48 +214,48 @@ def trigger_post_change_hook(setting_name, old_value, new_value):
|
||||||
# ===========================================
|
# ===========================================
|
||||||
|
|
||||||
|
|
||||||
@post_change_hook("ssowat.panel_overlay.enabled")
|
@post_change_hook("ssowat_panel_overlay_enabled")
|
||||||
@post_change_hook("security.nginx.redirect_to_https")
|
@post_change_hook("nginx_redirect_to_https")
|
||||||
@post_change_hook("security.nginx.compatibility")
|
@post_change_hook("nginx_compatibility")
|
||||||
@post_change_hook("security.webadmin.allowlist.enabled")
|
@post_change_hook("webadmin_allowlist_enabled")
|
||||||
@post_change_hook("security.webadmin.allowlist")
|
@post_change_hook("webadmin_allowlist")
|
||||||
def reconfigure_nginx(setting_name, old_value, new_value):
|
def reconfigure_nginx(setting_name, old_value, new_value):
|
||||||
if old_value != new_value:
|
if old_value != new_value:
|
||||||
regen_conf(names=["nginx"])
|
regen_conf(names=["nginx"])
|
||||||
|
|
||||||
|
|
||||||
@post_change_hook("security.experimental.enabled")
|
@post_change_hook("security_experimental_enabled")
|
||||||
def reconfigure_nginx_and_yunohost(setting_name, old_value, new_value):
|
def reconfigure_nginx_and_yunohost(setting_name, old_value, new_value):
|
||||||
if old_value != new_value:
|
if old_value != new_value:
|
||||||
regen_conf(names=["nginx", "yunohost"])
|
regen_conf(names=["nginx", "yunohost"])
|
||||||
|
|
||||||
|
|
||||||
@post_change_hook("security.ssh.compatibility")
|
@post_change_hook("ssh_compatibility")
|
||||||
@post_change_hook("security.ssh.password_authentication")
|
@post_change_hook("ssh_password_authentication")
|
||||||
def reconfigure_ssh(setting_name, old_value, new_value):
|
def reconfigure_ssh(setting_name, old_value, new_value):
|
||||||
if old_value != new_value:
|
if old_value != new_value:
|
||||||
regen_conf(names=["ssh"])
|
regen_conf(names=["ssh"])
|
||||||
|
|
||||||
|
|
||||||
@post_change_hook("security.ssh.port")
|
@post_change_hook("ssh_port")
|
||||||
def reconfigure_ssh_and_fail2ban(setting_name, old_value, new_value):
|
def reconfigure_ssh_and_fail2ban(setting_name, old_value, new_value):
|
||||||
if old_value != new_value:
|
if old_value != new_value:
|
||||||
regen_conf(names=["ssh", "fail2ban"])
|
regen_conf(names=["ssh", "fail2ban"])
|
||||||
firewall_reload()
|
firewall_reload()
|
||||||
|
|
||||||
|
|
||||||
@post_change_hook("smtp.allow_ipv6")
|
@post_change_hook("smtp_allow_ipv6")
|
||||||
@post_change_hook("smtp.relay.host")
|
@post_change_hook("smtp_relay_host")
|
||||||
@post_change_hook("smtp.relay.port")
|
@post_change_hook("smtp_relay_port")
|
||||||
@post_change_hook("smtp.relay.user")
|
@post_change_hook("smtp_relay_user")
|
||||||
@post_change_hook("smtp.relay.password")
|
@post_change_hook("smtp_relay_password")
|
||||||
@post_change_hook("security.postfix.compatibility")
|
@post_change_hook("postfix_compatibility")
|
||||||
def reconfigure_postfix(setting_name, old_value, new_value):
|
def reconfigure_postfix(setting_name, old_value, new_value):
|
||||||
if old_value != new_value:
|
if old_value != new_value:
|
||||||
regen_conf(names=["postfix"])
|
regen_conf(names=["postfix"])
|
||||||
|
|
||||||
|
|
||||||
@post_change_hook("pop3.enabled")
|
@post_change_hook("pop3_enabled")
|
||||||
def reconfigure_dovecot(setting_name, old_value, new_value):
|
def reconfigure_dovecot(setting_name, old_value, new_value):
|
||||||
dovecot_package = "dovecot-pop3d"
|
dovecot_package = "dovecot-pop3d"
|
||||||
|
|
||||||
|
|
|
@ -3,177 +3,215 @@ import json
|
||||||
import glob
|
import glob
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from yunohost.utils.error import YunohostError
|
import moulinette
|
||||||
|
from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||||
|
|
||||||
import yunohost.settings as settings
|
import yunohost.settings as settings
|
||||||
|
|
||||||
from yunohost.settings import (
|
from yunohost.settings import (
|
||||||
settings_get,
|
settings_get,
|
||||||
settings_list,
|
settings_list,
|
||||||
_get_settings,
|
|
||||||
settings_set,
|
settings_set,
|
||||||
settings_reset,
|
settings_reset,
|
||||||
settings_reset_all,
|
settings_reset_all,
|
||||||
SETTINGS_PATH_OTHER_LOCATION,
|
SETTINGS_PATH
|
||||||
SETTINGS_PATH,
|
|
||||||
DEFAULTS,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
DEFAULTS["example.bool"] = {"type": "bool", "default": True}
|
EXAMPLE_SETTINGS = """
|
||||||
DEFAULTS["example.int"] = {"type": "int", "default": 42}
|
[example]
|
||||||
DEFAULTS["example.string"] = {"type": "string", "default": "yolo swag"}
|
[example.example]
|
||||||
DEFAULTS["example.enum"] = {"type": "enum", "default": "a", "choices": ["a", "b", "c"]}
|
[example.example.boolean]
|
||||||
|
type = "boolean"
|
||||||
|
yes = "True"
|
||||||
|
no = "False"
|
||||||
|
default = "True"
|
||||||
|
|
||||||
|
[example.example.number]
|
||||||
|
type = "number"
|
||||||
|
default = 42
|
||||||
|
|
||||||
|
[example.example.string]
|
||||||
|
type = "string"
|
||||||
|
default = "yolo swag"
|
||||||
|
|
||||||
|
[example.example.select]
|
||||||
|
type = "select"
|
||||||
|
choices = ["a", "b", "c"]
|
||||||
|
default = "a"
|
||||||
|
"""
|
||||||
|
|
||||||
def setup_function(function):
|
def setup_function(function):
|
||||||
os.system("mv /etc/yunohost/settings.json /etc/yunohost/settings.json.saved")
|
# Backup settings
|
||||||
|
if os.path.exists(SETTINGS_PATH):
|
||||||
|
os.system(f"mv {SETTINGS_PATH} {SETTINGS_PATH}.saved")
|
||||||
|
# Add example settings to config panel
|
||||||
|
os.system("cp /usr/share/yunohost/config_settings.toml /usr/share/yunohost/config_settings.toml.saved")
|
||||||
|
with open("/usr/share/yunohost/config_settings.toml", "a") as file:
|
||||||
|
file.write(EXAMPLE_SETTINGS)
|
||||||
|
|
||||||
|
|
||||||
def teardown_function(function):
|
def teardown_function(function):
|
||||||
os.system("mv /etc/yunohost/settings.json.saved /etc/yunohost/settings.json")
|
if os.path.exists("/etc/yunohost/settings.yml.saved"):
|
||||||
for filename in glob.glob("/etc/yunohost/settings-*.json"):
|
os.system(f"mv {SETTINGS_PATH}.saved {SETTINGS_PATH}")
|
||||||
os.remove(filename)
|
elif os.path.exists(SETTINGS_PATH):
|
||||||
|
os.remove(SETTINGS_PATH)
|
||||||
|
os.system("mv /usr/share/yunohost/config_settings.toml.saved /usr/share/yunohost/config_settings.toml")
|
||||||
|
|
||||||
|
|
||||||
def monkey_get_setting_description(key):
|
old_translate = moulinette.core.Translator.translate
|
||||||
return "Dummy %s setting" % key.split(".")[-1]
|
|
||||||
|
def _monkeypatch_translator(self, key, *args, **kwargs):
|
||||||
|
|
||||||
|
if key.startswith("global_settings_setting_"):
|
||||||
|
return f"Dummy translation for {key}"
|
||||||
|
|
||||||
|
return old_translate(self, key, *args, **kwargs)
|
||||||
|
|
||||||
|
moulinette.core.Translator.translate = _monkeypatch_translator
|
||||||
|
|
||||||
|
|
||||||
settings._get_setting_description = monkey_get_setting_description
|
def _get_settings():
|
||||||
|
return yaml.load(open(SETTINGS_PATH, "r"))
|
||||||
|
|
||||||
|
|
||||||
def test_settings_get_bool():
|
def test_settings_get_bool():
|
||||||
assert settings_get("example.bool")
|
assert settings_get("example.example.boolean")
|
||||||
|
|
||||||
|
|
||||||
def test_settings_get_full_bool():
|
# FIXME : Testing this doesn't make sense ? This should be tested in test_config.py ?
|
||||||
assert settings_get("example.bool", True) == {
|
#def test_settings_get_full_bool():
|
||||||
"type": "bool",
|
# assert settings_get("example.example.boolean", True) == {'version': '1.0',
|
||||||
"value": True,
|
# 'i18n': 'global_settings_setting',
|
||||||
"default": True,
|
# 'panels': [{'services': [],
|
||||||
"description": "Dummy bool setting",
|
# 'actions': {'apply': {'en': 'Apply'}},
|
||||||
}
|
# 'sections': [{'name': '',
|
||||||
|
# 'services': [],
|
||||||
|
# 'optional': True,
|
||||||
|
# 'options': [{'type': 'boolean',
|
||||||
|
# 'yes': 'True',
|
||||||
|
# 'no': 'False',
|
||||||
|
# 'default': 'True',
|
||||||
|
# 'id': 'boolean',
|
||||||
|
# 'name': 'boolean',
|
||||||
|
# 'optional': True,
|
||||||
|
# 'current_value': 'True',
|
||||||
|
# 'ask': 'global_settings_setting_boolean',
|
||||||
|
# 'choices': []}],
|
||||||
|
# 'id': 'example'}],
|
||||||
|
# 'id': 'example',
|
||||||
|
# 'name': {'en': 'Example'}}]}
|
||||||
|
|
||||||
|
|
||||||
def test_settings_get_int():
|
def test_settings_get_int():
|
||||||
assert settings_get("example.int") == 42
|
assert settings_get("example.example.number") == 42
|
||||||
|
|
||||||
|
|
||||||
def test_settings_get_full_int():
|
#def test_settings_get_full_int():
|
||||||
assert settings_get("example.int", True) == {
|
# assert settings_get("example.int", True) == {
|
||||||
"type": "int",
|
# "type": "int",
|
||||||
"value": 42,
|
# "value": 42,
|
||||||
"default": 42,
|
# "default": 42,
|
||||||
"description": "Dummy int setting",
|
# "description": "Dummy int setting",
|
||||||
}
|
# }
|
||||||
|
|
||||||
|
|
||||||
def test_settings_get_string():
|
def test_settings_get_string():
|
||||||
assert settings_get("example.string") == "yolo swag"
|
assert settings_get("example.example.string") == "yolo swag"
|
||||||
|
|
||||||
|
|
||||||
def test_settings_get_full_string():
|
#def test_settings_get_full_string():
|
||||||
assert settings_get("example.string", True) == {
|
# assert settings_get("example.example.string", True) == {
|
||||||
"type": "string",
|
# "type": "string",
|
||||||
"value": "yolo swag",
|
# "value": "yolo swag",
|
||||||
"default": "yolo swag",
|
# "default": "yolo swag",
|
||||||
"description": "Dummy string setting",
|
# "description": "Dummy string setting",
|
||||||
}
|
# }
|
||||||
|
|
||||||
|
|
||||||
def test_settings_get_enum():
|
def test_settings_get_select():
|
||||||
assert settings_get("example.enum") == "a"
|
assert settings_get("example.example.select") == "a"
|
||||||
|
|
||||||
|
|
||||||
def test_settings_get_full_enum():
|
#def test_settings_get_full_select():
|
||||||
assert settings_get("example.enum", True) == {
|
# option = settings_get("example.example.select", full=True).get('panels')[0].get('sections')[0].get('options')[0]
|
||||||
"type": "enum",
|
# assert option.get('choices') == ["a", "b", "c"]
|
||||||
"value": "a",
|
|
||||||
"default": "a",
|
|
||||||
"description": "Dummy enum setting",
|
|
||||||
"choices": ["a", "b", "c"],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_settings_get_doesnt_exists():
|
def test_settings_get_doesnt_exists():
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostValidationError):
|
||||||
settings_get("doesnt.exists")
|
settings_get("doesnt.exists")
|
||||||
|
|
||||||
|
|
||||||
def test_settings_list():
|
#def test_settings_list():
|
||||||
assert settings_list() == _get_settings()
|
# assert settings_list() == _get_settings()
|
||||||
|
|
||||||
|
|
||||||
def test_settings_set():
|
def test_settings_set():
|
||||||
settings_set("example.bool", False)
|
settings_set("example.example.boolean", False)
|
||||||
assert settings_get("example.bool") is False
|
assert settings_get("example.example.boolean") is False
|
||||||
|
|
||||||
settings_set("example.bool", "on")
|
settings_set("example.example.boolean", "on")
|
||||||
assert settings_get("example.bool") is True
|
assert settings_get("example.example.boolean") is True
|
||||||
|
|
||||||
|
|
||||||
def test_settings_set_int():
|
def test_settings_set_int():
|
||||||
settings_set("example.int", 21)
|
settings_set("example.example.number", 21)
|
||||||
assert settings_get("example.int") == 21
|
assert settings_get("example.example.number") == 21
|
||||||
|
|
||||||
|
|
||||||
def test_settings_set_enum():
|
def test_settings_set_select():
|
||||||
settings_set("example.enum", "c")
|
settings_set("example.example.select", "c")
|
||||||
assert settings_get("example.enum") == "c"
|
assert settings_get("example.example.select") == "c"
|
||||||
|
|
||||||
|
|
||||||
def test_settings_set_doesexit():
|
def test_settings_set_doesexit():
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostValidationError):
|
||||||
settings_set("doesnt.exist", True)
|
settings_set("doesnt.exist", True)
|
||||||
|
|
||||||
|
|
||||||
def test_settings_set_bad_type_bool():
|
def test_settings_set_bad_type_bool():
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
settings_set("example.bool", 42)
|
settings_set("example.example.boolean", 42)
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
settings_set("example.bool", "pouet")
|
settings_set("example.example.boolean", "pouet")
|
||||||
|
|
||||||
|
|
||||||
def test_settings_set_bad_type_int():
|
def test_settings_set_bad_type_int():
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
settings_set("example.int", True)
|
settings_set("example.example.number", True)
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
settings_set("example.int", "pouet")
|
settings_set("example.example.number", "pouet")
|
||||||
|
|
||||||
|
|
||||||
def test_settings_set_bad_type_string():
|
def test_settings_set_bad_type_string():
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
settings_set("example.string", True)
|
settings_set("example.example.string", True)
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
settings_set("example.string", 42)
|
settings_set("example.example.string", 42)
|
||||||
|
|
||||||
|
|
||||||
def test_settings_set_bad_value_enum():
|
def test_settings_set_bad_value_select():
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
settings_set("example.enum", True)
|
settings_set("example.example.select", True)
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
settings_set("example.enum", "e")
|
settings_set("example.example.select", "e")
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
settings_set("example.enum", 42)
|
settings_set("example.example.select", 42)
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
settings_set("example.enum", "pouet")
|
settings_set("example.example.select", "pouet")
|
||||||
|
|
||||||
|
|
||||||
def test_settings_list_modified():
|
def test_settings_list_modified():
|
||||||
settings_set("example.int", 21)
|
settings_set("example.example.number", 21)
|
||||||
assert settings_list()["example.int"] == {
|
assert settings_list()["number"] == 21
|
||||||
"default": 42,
|
|
||||||
"description": "Dummy int setting",
|
|
||||||
"type": "int",
|
|
||||||
"value": 21,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_reset():
|
def test_reset():
|
||||||
settings_set("example.int", 21)
|
option = settings_get("example.example.number", full=True).get('panels')[0].get('sections')[0].get('options')[0]
|
||||||
assert settings_get("example.int") == 21
|
settings_set("example.example.number", 21)
|
||||||
settings_reset("example.int")
|
assert settings_get("number") == 21
|
||||||
assert settings_get("example.int") == settings_get("example.int", True)["default"]
|
settings_reset("example.example.number")
|
||||||
|
assert settings_get("example.example.number") == option["default"]
|
||||||
|
|
||||||
|
|
||||||
def test_settings_reset_doesexit():
|
def test_settings_reset_doesexit():
|
||||||
|
@ -183,10 +221,10 @@ def test_settings_reset_doesexit():
|
||||||
|
|
||||||
def test_reset_all():
|
def test_reset_all():
|
||||||
settings_before = settings_list()
|
settings_before = settings_list()
|
||||||
settings_set("example.bool", False)
|
settings_set("example.example.boolean", False)
|
||||||
settings_set("example.int", 21)
|
settings_set("example.example.number", 21)
|
||||||
settings_set("example.string", "pif paf pouf")
|
settings_set("example.example.string", "pif paf pouf")
|
||||||
settings_set("example.enum", "c")
|
settings_set("example.example.select", "c")
|
||||||
assert settings_before != settings_list()
|
assert settings_before != settings_list()
|
||||||
settings_reset_all()
|
settings_reset_all()
|
||||||
if settings_before != settings_list():
|
if settings_before != settings_list():
|
||||||
|
@ -194,30 +232,30 @@ def test_reset_all():
|
||||||
assert settings_before[i] == settings_list()[i]
|
assert settings_before[i] == settings_list()[i]
|
||||||
|
|
||||||
|
|
||||||
def test_reset_all_backup():
|
#def test_reset_all_backup():
|
||||||
settings_before = settings_list()
|
# settings_before = settings_list()
|
||||||
settings_set("example.bool", False)
|
# settings_set("example.bool", False)
|
||||||
settings_set("example.int", 21)
|
# settings_set("example.int", 21)
|
||||||
settings_set("example.string", "pif paf pouf")
|
# settings_set("example.string", "pif paf pouf")
|
||||||
settings_set("example.enum", "c")
|
# settings_set("example.select", "c")
|
||||||
settings_after_modification = settings_list()
|
# settings_after_modification = settings_list()
|
||||||
assert settings_before != settings_after_modification
|
# assert settings_before != settings_after_modification
|
||||||
old_settings_backup_path = settings_reset_all()["old_settings_backup_path"]
|
# old_settings_backup_path = settings_reset_all()["old_settings_backup_path"]
|
||||||
|
#
|
||||||
for i in settings_after_modification:
|
# for i in settings_after_modification:
|
||||||
del settings_after_modification[i]["description"]
|
# del settings_after_modification[i]["description"]
|
||||||
|
#
|
||||||
assert settings_after_modification == json.load(open(old_settings_backup_path, "r"))
|
# assert settings_after_modification == json.load(open(old_settings_backup_path, "r"))
|
||||||
|
|
||||||
|
|
||||||
def test_unknown_keys():
|
#def test_unknown_keys():
|
||||||
unknown_settings_path = SETTINGS_PATH_OTHER_LOCATION % "unknown"
|
# unknown_settings_path = SETTINGS_PATH_OTHER_LOCATION % "unknown"
|
||||||
unknown_setting = {
|
# unknown_setting = {
|
||||||
"unkown_key": {"value": 42, "default": 31, "type": "int"},
|
# "unkown_key": {"value": 42, "default": 31, "type": "int"},
|
||||||
}
|
# }
|
||||||
open(SETTINGS_PATH, "w").write(json.dumps(unknown_setting))
|
# open(SETTINGS_PATH, "w").write(json.dumps(unknown_setting))
|
||||||
|
#
|
||||||
# stimulate a write
|
# # stimulate a write
|
||||||
settings_reset_all()
|
# settings_reset_all()
|
||||||
|
#
|
||||||
assert unknown_setting == json.load(open(unknown_settings_path, "r"))
|
# assert unknown_setting == json.load(open(unknown_settings_path, "r"))
|
||||||
|
|
|
@ -62,6 +62,31 @@ LEGACY_PERMISSION_LABEL = {
|
||||||
): "api", # $excaped_domain$excaped_path/[%w-.]*/[%w-.]*/git%-receive%-pack,$excaped_domain$excaped_path/[%w-.]*/[%w-.]*/git%-upload%-pack,$excaped_domain$excaped_path/[%w-.]*/[%w-.]*/info/refs
|
): "api", # $excaped_domain$excaped_path/[%w-.]*/[%w-.]*/git%-receive%-pack,$excaped_domain$excaped_path/[%w-.]*/[%w-.]*/git%-upload%-pack,$excaped_domain$excaped_path/[%w-.]*/[%w-.]*/info/refs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LEGACY_SETTINGS = {
|
||||||
|
"security.password.admin.strength": "security.password.admin_strength",
|
||||||
|
"security.password.user.strength": "security.password.user_strength",
|
||||||
|
"security.ssh.compatibility": "security.ssh.ssh_compatibility",
|
||||||
|
"security.ssh.port": "security.ssh.ssh_port",
|
||||||
|
"security.ssh.password_authentication": "security.ssh.ssh_password_authentication",
|
||||||
|
"service.ssh.allow_deprecated_dsa_hostkey": "security.ssh.ssh_allow_deprecated_dsa_hostkey",
|
||||||
|
"security.nginx.redirect_to_https": "security.nginx.nginx_redirect_to_https",
|
||||||
|
"security.nginx.compatibility": "security.nginx.nginx_compatibility",
|
||||||
|
"security.postfix.compatibility": "security.postfix.postfix_compatibility",
|
||||||
|
"pop3.enabled": "email.pop3.pop3_enabled",
|
||||||
|
"smtp.allow_ipv6": "email.smtp.smtp_allow_ipv6",
|
||||||
|
"smtp.relay.host": "email.smtp.smtp_relay_host",
|
||||||
|
"smtp.relay.port": "email.smtp.smtp_relay_port",
|
||||||
|
"smtp.relay.user": "email.smtp.smtp_relay_user",
|
||||||
|
"smtp.relay.password": "email.smtp.smtp_relay_password",
|
||||||
|
"backup.compress_tar_archives": "misc.backup.backup_compress_tar_archives",
|
||||||
|
"ssowat.panel_overlay.enabled": "misc.ssowat.ssowat_panel_overlay_enabled",
|
||||||
|
"security.webadmin.allowlist.enabled": "security.webadmin.webadmin_allowlist_enabled",
|
||||||
|
"security.webadmin.allowlist": "security.webadmin.webadmin_allowlist",
|
||||||
|
"security.experimental.enabled": "security.experimental.security_experimental_enabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
def translate_legacy_settings_to_configpanel_settings(settings):
|
||||||
|
return LEGACY_SETTINGS.get(settings, settings)
|
||||||
|
|
||||||
def legacy_permission_label(app, permission_type):
|
def legacy_permission_label(app, permission_type):
|
||||||
return LEGACY_PERMISSION_LABEL.get(
|
return LEGACY_PERMISSION_LABEL.get(
|
||||||
|
|
|
@ -21,9 +21,9 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import json
|
|
||||||
import string
|
import string
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import yaml
|
||||||
|
|
||||||
SMALL_PWD_LIST = [
|
SMALL_PWD_LIST = [
|
||||||
"yunohost",
|
"yunohost",
|
||||||
|
@ -58,7 +58,7 @@ class PasswordValidator:
|
||||||
|
|
||||||
The profile shall be either "user" or "admin"
|
The profile shall be either "user" or "admin"
|
||||||
and will correspond to a validation strength
|
and will correspond to a validation strength
|
||||||
defined via the setting "security.password.<profile>.strength"
|
defined via the setting "security.password.<profile>_strength"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.profile = profile
|
self.profile = profile
|
||||||
|
@ -67,9 +67,9 @@ class PasswordValidator:
|
||||||
# from settings.py because this file is also meant to be
|
# from settings.py because this file is also meant to be
|
||||||
# use as a script by ssowat.
|
# use as a script by ssowat.
|
||||||
# (or at least that's my understanding -- Alex)
|
# (or at least that's my understanding -- Alex)
|
||||||
settings = json.load(open("/etc/yunohost/settings.json", "r"))
|
settings = yaml.load(open("/etc/yunohost/settings.yml", "r"))
|
||||||
setting_key = "security.password." + profile + ".strength"
|
setting_key = "security.password." + profile + "_strength"
|
||||||
self.validation_strength = int(settings[setting_key]["value"])
|
self.validation_strength = int(settings[setting_key])
|
||||||
except Exception:
|
except Exception:
|
||||||
# Fallback to default value if we can't fetch settings for some reason
|
# Fallback to default value if we can't fetch settings for some reason
|
||||||
self.validation_strength = 1
|
self.validation_strength = 1
|
||||||
|
|
Loading…
Add table
Reference in a new issue