diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 5d1b757bd..ea4a1f577 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -467,6 +467,17 @@ domain: help: Target domain extra: pattern: *pattern_domain + + ### domain_push_config() + push-config: + action_help: Push DNS records to registrar + api: GET /domains//push + arguments: + domain: + help: Domain name to add + extra: + pattern: *pattern_domain + ### domain_maindomain() main-domain: @@ -549,26 +560,40 @@ domain: path: help: The path to check (e.g. /coffee) - ### domain_setting() - setting: - action_help: Set or get a domain setting value - api: GET /domains//settings - arguments: - domain: - help: Domain name - extra: - pattern: *pattern_domain - key: - help: Key to get/set - -v: - full: --value - help: Value to set - -d: - full: --delete - help: Delete the key - action: store_true - subcategories: + + config: + subcategory_help: Domain settings + actions: + + ### domain_config_get() + get: + action_help: Display a domain configuration + api: GET /domains//config + arguments: + domain: + help: Domain name + key: + help: A question or form key + nargs: '?' + + ### domain_config_set() + set: + action_help: Apply a new configuration + api: PUT /domains//config + arguments: + app: + help: Domain name + key: + help: The question or form key + nargs: '?' + -v: + full: --value + help: new value + -a: + full: --args + help: Serialized arguments for new configuration (i.e. "mail_in=0&mail_out=0") + registrar: subcategory_help: Manage domains registrars actions: diff --git a/data/other/config_domain.toml b/data/other/config_domain.toml new file mode 100644 index 000000000..4821aa53b --- /dev/null +++ b/data/other/config_domain.toml @@ -0,0 +1,38 @@ +version = "1.0" +i18n = "domain_config" + +[feature] + [feature.mail] + [feature.mail.mail_out] + type = "boolean" + default = true + + [feature.mail.mail_in] + type = "boolean" + default = true + + [feature.mail.backup_mx] + type = "tags" + pattern.regexp = "^([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$" + pattern.error = "pattern_error" + default = [] + + [feature.xmpp] + [feature.mail.xmpp] + type = "boolean" + default = false + +[dns] + [dns.registrar] + [dns.registrar.unsupported] + ask = "DNS zone of this domain can't be auto-configured, you should do it manually." + type = "alert" + style = "info" + helpLink.href = "https://yunohost.org/dns_config" + helpLink.text = "How to configure manually my DNS zone" + + [dns.advanced] + [dns.advanced.ttl] + type = "number" + min = 0 + default = 3600 diff --git a/data/other/registrar_list.toml b/data/other/registrar_list.toml new file mode 100644 index 000000000..33d115075 --- /dev/null +++ b/data/other/registrar_list.toml @@ -0,0 +1,636 @@ +[aliyun] + [aliyun.auth_key_id] + type = "string" + redact = True + + [aliyun.auth_secret] + type = "password" + +[aurora] + [aurora.auth_api_key] + type = "string" + redact = True + + [aurora.auth_secret_key] + type = "password" + +[azure] + [azure.auth_client_id] + type = "string" + redact = True + + [azure.auth_client_secret] + type = "password" + + [azure.auth_tenant_id] + type = "string" + redact = True + + [azure.auth_subscription_id] + type = "string" + redact = True + + [azure.resource_group] + type = "string" + redact = True + +[cloudflare] + [cloudflare.auth_username] + type = "string" + redact = True + + [cloudflare.auth_token] + type = "string" + redact = True + + [cloudflare.zone_id] + type = "string" + redact = True + +[cloudns] + [cloudns.auth_id] + type = "string" + redact = True + + [cloudns.auth_subid] + type = "string" + redact = True + + [cloudns.auth_subuser] + type = "string" + redact = True + + [cloudns.auth_password] + type = "password" + + [cloudns.weight] + type = "number" + + [cloudns.port] + type = "number" +[cloudxns] + [cloudxns.auth_username] + type = "string" + redact = True + + [cloudxns.auth_token] + type = "string" + redact = True + +[conoha] + [conoha.auth_region] + type = "string" + redact = True + + [conoha.auth_token] + type = "string" + redact = True + + [conoha.auth_username] + type = "string" + redact = True + + [conoha.auth_password] + type = "password" + + [conoha.auth_tenant_id] + type = "string" + redact = True + +[constellix] + [constellix.auth_username] + type = "string" + redact = True + + [constellix.auth_token] + type = "string" + redact = True + +[digitalocean] + [digitalocean.auth_token] + type = "string" + redact = True + +[dinahosting] + [dinahosting.auth_username] + type = "string" + redact = True + + [dinahosting.auth_password] + type = "password" + +[directadmin] + [directadmin.auth_password] + type = "password" + + [directadmin.auth_username] + type = "string" + redact = True + + [directadmin.endpoint] + type = "string" + redact = True + +[dnsimple] + [dnsimple.auth_token] + type = "string" + redact = True + + [dnsimple.auth_username] + type = "string" + redact = True + + [dnsimple.auth_password] + type = "password" + + [dnsimple.auth_2fa] + type = "string" + redact = True + +[dnsmadeeasy] + [dnsmadeeasy.auth_username] + type = "string" + redact = True + + [dnsmadeeasy.auth_token] + type = "string" + redact = True + +[dnspark] + [dnspark.auth_username] + type = "string" + redact = True + + [dnspark.auth_token] + type = "string" + redact = True + +[dnspod] + [dnspod.auth_username] + type = "string" + redact = True + + [dnspod.auth_token] + type = "string" + redact = True + +[dreamhost] + [dreamhost.auth_token] + type = "string" + redact = True + +[dynu] + [dynu.auth_token] + type = "string" + redact = True + +[easydns] + [easydns.auth_username] + type = "string" + redact = True + + [easydns.auth_token] + type = "string" + redact = True + +[easyname] + [easyname.auth_username] + type = "string" + redact = True + + [easyname.auth_password] + type = "password" + +[euserv] + [euserv.auth_username] + type = "string" + redact = True + + [euserv.auth_password] + type = "password" + +[exoscale] + [exoscale.auth_key] + type = "string" + redact = True + + [exoscale.auth_secret] + type = "password" + +[gandi] + [gandi.auth_token] + type = "string" + redact = True + + [gandi.api_protocol] + type = "string" + choices.rpc = "RPC" + choices.rest = "REST" + +[gehirn] + [gehirn.auth_token] + type = "string" + redact = True + + [gehirn.auth_secret] + type = "password" + +[glesys] + [glesys.auth_username] + type = "string" + redact = True + + [glesys.auth_token] + type = "string" + redact = True + +[godaddy] + [godaddy.auth_key] + type = "string" + redact = True + + [godaddy.auth_secret] + type = "password" + +[googleclouddns] + [goggleclouddns.auth_service_account_info] + type = "string" + redact = True + +[gransy] + [gransy.auth_username] + type = "string" + redact = True + + [gransy.auth_password] + type = "password" + +[gratisdns] + [gratisdns.auth_username] + type = "string" + redact = True + + [gratisdns.auth_password] + type = "password" + +[henet] + [henet.auth_username] + type = "string" + redact = True + + [henet.auth_password] + type = "password" + +[hetzner] + [hetzner.auth_token] + type = "string" + redact = True + +[hostingde] + [hostingde.auth_token] + type = "string" + redact = True + +[hover] + [hover.auth_username] + type = "string" + redact = True + + [hover.auth_password] + type = "password" + +[infoblox] + [infoblox.auth_user] + type = "string" + redact = True + + [infoblox.auth_psw] + type = "password" + + [infoblox.ib_view] + type = "string" + redact = True + + [infoblox.ib_host] + type = "string" + redact = True + +[infomaniak] + [infomaniak.auth_token] + type = "string" + redact = True + +[internetbs] + [internetbs.auth_key] + type = "string" + redact = True + + [internetbs.auth_password] + type = "string" + redact = True + +[inwx] + [inwx.auth_username] + type = "string" + redact = True + + [inwx.auth_password] + type = "password" + +[joker] + [joker.auth_token] + type = "string" + redact = True + +[linode] + [linode.auth_token] + type = "string" + redact = True + +[linode4] + [linode4.auth_token] + type = "string" + redact = True + +[localzone] + [localzone.filename] + type = "string" + redact = True + +[luadns] + [luadns.auth_username] + type = "string" + redact = True + + [luadns.auth_token] + type = "string" + redact = True + +[memset] + [memset.auth_token] + type = "string" + redact = True + +[mythicbeasts] + [mythicbeasts.auth_username] + type = "string" + redact = True + + [mythicbeasts.auth_password] + type = "password" + + [mythicbeasts.auth_token] + type = "string" + redact = True + +[namecheap] + [namecheap.auth_token] + type = "string" + redact = True + + [namecheap.auth_username] + type = "string" + redact = True + + [namecheap.auth_client_ip] + type = "string" + redact = True + + [namecheap.auth_sandbox] + type = "string" + redact = True + +[namesilo] + [namesilo.auth_token] + type = "string" + redact = True + +[netcup] + [netcup.auth_customer_id] + type = "string" + redact = True + + [netcup.auth_api_key] + type = "string" + redact = True + + [netcup.auth_api_password] + type = "password" + +[nfsn] + [nfsn.auth_username] + type = "string" + redact = True + + [nfsn.auth_token] + type = "string" + redact = True + +[njalla] + [njalla.auth_token] + type = "string" + redact = True + +[nsone] + [nsone.auth_token] + type = "string" + redact = True + +[onapp] + [onapp.auth_username] + type = "string" + redact = True + + [onapp.auth_token] + type = "string" + redact = True + + [onapp.auth_server] + type = "string" + redact = True + +[online] + [online.auth_token] + type = "string" + redact = True + +[ovh] + [ovh.auth_entrypoint] + type = "string" + redact = True + + [ovh.auth_application_key] + type = "string" + redact = True + + [ovh.auth_application_secret] + type = "password" + + [ovh.auth_consumer_key] + type = "string" + redact = True + +[plesk] + [plesk.auth_username] + type = "string" + redact = True + + [plesk.auth_password] + type = "password" + + [plesk.plesk_server] + type = "string" + redact = True + +[pointhq] + [pointhq.auth_username] + type = "string" + redact = True + + [pointhq.auth_token] + type = "string" + redact = True + +[powerdns] + [powerdns.auth_token] + type = "string" + redact = True + + [powerdns.pdns_server] + type = "string" + redact = True + + [powerdns.pdns_server_id] + type = "string" + redact = True + + [powerdns.pdns_disable_notify] + type = "boolean" + +[rackspace] + [rackspace.auth_account] + type = "string" + redact = True + + [rackspace.auth_username] + type = "string" + redact = True + + [rackspace.auth_api_key] + type = "string" + redact = True + + [rackspace.auth_token] + type = "string" + redact = True + + [rackspace.sleep_time] + type = "string" + redact = True + +[rage4] + [rage4.auth_username] + type = "string" + redact = True + + [rage4.auth_token] + type = "string" + redact = True + +[rcodezero] + [rcodezero.auth_token] + type = "string" + redact = True + +[route53] + [route53.auth_access_key] + type = "string" + redact = True + + [route53.auth_access_secret] + type = "password" + + [route53.private_zone] + type = "string" + redact = True + + [route53.auth_username] + type = "string" + redact = True + + [route53.auth_token] + type = "string" + redact = True + +[safedns] + [safedns.auth_token] + type = "string" + redact = True + +[sakuracloud] + [sakuracloud.auth_token] + type = "string" + redact = True + + [sakuracloud.auth_secret] + type = "password" + +[softlayer] + [softlayer.auth_username] + type = "string" + redact = True + + [softlayer.auth_api_key] + type = "string" + redact = True + +[transip] + [transip.auth_username] + type = "string" + redact = True + + [transip.auth_api_key] + type = "string" + redact = True + +[ultradns] + [ultradns.auth_token] + type = "string" + redact = True + + [ultradns.auth_username] + type = "string" + redact = True + + [ultradns.auth_password] + type = "password" + +[vultr] + [vultr.auth_token] + type = "string" + redact = True + +[yandex] + [yandex.auth_token] + type = "string" + redact = True + +[zeit] + [zeit.auth_token] + type = "string" + redact = True + +[zilore] + [zilore.auth_key] + type = "string" + redact = True + +[zonomi] + [zonomy.auth_token] + type = "string" + redact = True + + [zonomy.auth_entrypoint] + type = "string" + redact = True + diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index d05e31f17..59ad68979 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -387,115 +387,58 @@ def _get_maindomain(): return maindomain -def _default_domain_settings(domain): - from yunohost.utils.dns import get_dns_zone_from_domain - return { - "xmpp": domain == domain_list()["main"], - "mail_in": True, - "mail_out": True, - "dns_zone": get_dns_zone_from_domain(domain), - "ttl": 3600, - } - - def _get_domain_settings(domain): """ Retrieve entries in /etc/yunohost/domains/[domain].yml And set default values if needed """ - _assert_domain_exists(domain) - - # Retrieve entries in the YAML - filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" - on_disk_settings = {} - if os.path.exists(filepath) and os.path.isfile(filepath): - on_disk_settings = read_yaml(filepath) or {} - - # Inject defaults if needed (using the magic .update() ;)) - settings = _default_domain_settings(domain) - settings.update(on_disk_settings) - return settings + config = DomainConfigPanel(domain) + return config.get(mode='export') -def domain_setting(domain, key, value=None, delete=False): +def domain_config_get(domain, key='', mode='classic'): """ - Set or get an app setting value - - Keyword argument: - domain -- Domain Name - key -- Key to get/set - value -- Value to set - delete -- Delete the key - + Display a domain configuration """ - domain_settings = _get_domain_settings(domain) + config = DomainConfigPanel(domain) + return config.get(key, mode) - # GET - if value is None and not delete: - if key not in domain_settings: - raise YunohostValidationError("domain_property_unknown", property=key) - - return domain_settings[key] - - # DELETE - if delete: - if key in domain_settings: - del domain_settings[key] - _set_domain_settings(domain, domain_settings) - - # SET - else: - # FIXME : in the future, implement proper setting types (+ defaults), - # maybe inspired from the global settings - - if key in ["mail_in", "mail_out", "xmpp"]: - _is_boolean, value = is_boolean(value) - if not _is_boolean: - raise YunohostValidationError( - "global_settings_bad_type_for_setting", - setting=key, - received_type="not boolean", - expected_type="boolean", - ) - - if "ttl" == key: - try: - value = int(value) - except ValueError: - # TODO add locales - raise YunohostValidationError("invalid_number", value_type=type(value)) - - if value < 0: - raise YunohostValidationError("pattern_positive_number", value_type=type(value)) - - # Set new value - domain_settings[key] = value - # Save settings - _set_domain_settings(domain, domain_settings) - - -def _set_domain_settings(domain, domain_settings): +@is_unit_operation() +def domain_config_set(operation_logger, app, key=None, value=None, args=None, args_file=None): """ - Set settings of a domain - - Keyword arguments: - domain -- The domain name - settings -- Dict with domain settings - + Apply a new domain configuration """ - _assert_domain_exists(domain) + config = DomainConfigPanel(domain) + return config.set(key, value, args, args_file) - defaults = _default_domain_settings(domain) - diff_with_defaults = {k: v for k, v in domain_settings.items() if defaults.get(k) != v} +class DomainConfigPanel(ConfigPanel): + def __init__(domain): + _assert_domain_exist(domain) + self.domain = domain + super().__init( + config_path=DOMAIN_CONFIG_PATH.format(domain=domain), + save_path=DOMAIN_SETTINGS_PATH.format(domain=domain) + ) - # First create the DOMAIN_SETTINGS_DIR if it doesn't exist - if not os.path.exists(DOMAIN_SETTINGS_DIR): - mkdir(DOMAIN_SETTINGS_DIR, mode=0o700) - # Save the settings to the .yaml file - filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml" - write_to_yaml(filepath, diff_with_defaults) + def _get_toml(self): + from yunohost.utils.dns import get_dns_zone_from_domain + toml = super()._get_toml() + self.dns_zone = get_dns_zone_from_domain(self.domain) + + try: + registrar = _relevant_provider_for_domain(self.dns_zone) + except ValueError: + return toml + + registrar_list = read_toml("/usr/share/yunohost/other/registrar_list.toml") + toml['dns']['registrar'] = registrar_list[registrar] + return toml + + def _load_current_values(): + # TODO add mechanism to share some settings with other domains on the same zone + super()._load_current_values() # # diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 34883dcf7..b3ef34c17 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -46,6 +46,7 @@ logger = getActionLogger("yunohost.config") CONFIG_PANEL_VERSION_SUPPORTED = 1.0 class ConfigPanel: + save_mode = "diff" def __init__(self, config_path, save_path=None): self.config_path = config_path @@ -56,6 +57,7 @@ class ConfigPanel: def get(self, key='', mode='classic'): self.filter_key = key or '' + self.mode = mode # Read config panel toml self._get_config_panel() @@ -273,13 +275,39 @@ class ConfigPanel: )) self.new_values = {key: str(value[0]) for key, value in self.new_values.items() if not value[0] is None} + def _get_default_values(self): + return { key: option['default'] + for _, _, option in self._iterate() if 'default' in option } + + def _load_current_values(self): + """ + Retrieve entries in YAML file + And set default values if needed + """ + + # Retrieve entries in the YAML + on_disk_settings = {} + if os.path.exists(self.save_path) and os.path.isfile(self.save_path): + on_disk_settings = read_yaml(self.save_path) or {} + + # Inject defaults if needed (using the magic .update() ;)) + self.values = self._get_default_values(self) + self.values.update(on_disk_settings) + def _apply(self): logger.info("Running config script...") dir_path = os.path.dirname(os.path.realpath(self.save_path)) if not os.path.exists(dir_path): mkdir(dir_path, mode=0o700) + + if self.save_mode == 'diff': + defaults = self._get_default_values() + values_to_save = {k: v for k, v in values.items() if defaults.get(k) != v} + else: + values_to_save = {**self.values, **self.new_values} + # Save the settings to the .yaml file - write_to_yaml(self.save_path, self.new_values) + write_to_yaml(self.save_path, values_to_save) def _reload_services(self):