[wip] Config Panel for domain

This commit is contained in:
ljf 2021-09-03 17:07:34 +02:00
parent 0da2f137ca
commit 0874e9a646
5 changed files with 783 additions and 113 deletions

View file

@ -468,6 +468,17 @@ domain:
extra: extra:
pattern: *pattern_domain pattern: *pattern_domain
### domain_push_config()
push-config:
action_help: Push DNS records to registrar
api: GET /domains/<domain>/push
arguments:
domain:
help: Domain name to add
extra:
pattern: *pattern_domain
### domain_maindomain() ### domain_maindomain()
main-domain: main-domain:
action_help: Check the current main domain, or change it action_help: Check the current main domain, or change it
@ -549,26 +560,40 @@ domain:
path: path:
help: The path to check (e.g. /coffee) help: The path to check (e.g. /coffee)
### domain_setting()
setting:
action_help: Set or get a domain setting value
api: GET /domains/<domain>/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: subcategories:
config:
subcategory_help: Domain settings
actions:
### domain_config_get()
get:
action_help: Display a domain configuration
api: GET /domains/<domain>/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/<domain>/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: registrar:
subcategory_help: Manage domains registrars subcategory_help: Manage domains registrars
actions: actions:

View file

@ -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

View file

@ -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

View file

@ -387,115 +387,58 @@ def _get_maindomain():
return 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): def _get_domain_settings(domain):
""" """
Retrieve entries in /etc/yunohost/domains/[domain].yml Retrieve entries in /etc/yunohost/domains/[domain].yml
And set default values if needed And set default values if needed
""" """
_assert_domain_exists(domain) config = DomainConfigPanel(domain)
return config.get(mode='export')
# 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
def domain_setting(domain, key, value=None, delete=False): def domain_config_get(domain, key='', mode='classic'):
""" """
Set or get an app setting value Display a domain configuration
Keyword argument:
domain -- Domain Name
key -- Key to get/set
value -- Value to set
delete -- Delete the key
""" """
domain_settings = _get_domain_settings(domain) config = DomainConfigPanel(domain)
return config.get(key, mode)
# GET @is_unit_operation()
if value is None and not delete: def domain_config_set(operation_logger, app, key=None, value=None, args=None, args_file=None):
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):
""" """
Set settings of a domain Apply a new domain configuration
Keyword arguments:
domain -- The domain name
settings -- Dict with domain settings
""" """
_assert_domain_exists(domain) config = DomainConfigPanel(domain)
return config.set(key, value, args, args_file)
defaults = _default_domain_settings(domain) class DomainConfigPanel(ConfigPanel):
diff_with_defaults = {k: v for k, v in domain_settings.items() if defaults.get(k) != v} 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 def _get_toml(self):
if not os.path.exists(DOMAIN_SETTINGS_DIR): from yunohost.utils.dns import get_dns_zone_from_domain
mkdir(DOMAIN_SETTINGS_DIR, mode=0o700) toml = super()._get_toml()
# Save the settings to the .yaml file self.dns_zone = get_dns_zone_from_domain(self.domain)
filepath = f"{DOMAIN_SETTINGS_DIR}/{domain}.yml"
write_to_yaml(filepath, diff_with_defaults) 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()
# #
# #

View file

@ -46,6 +46,7 @@ logger = getActionLogger("yunohost.config")
CONFIG_PANEL_VERSION_SUPPORTED = 1.0 CONFIG_PANEL_VERSION_SUPPORTED = 1.0
class ConfigPanel: class ConfigPanel:
save_mode = "diff"
def __init__(self, config_path, save_path=None): def __init__(self, config_path, save_path=None):
self.config_path = config_path self.config_path = config_path
@ -56,6 +57,7 @@ class ConfigPanel:
def get(self, key='', mode='classic'): def get(self, key='', mode='classic'):
self.filter_key = key or '' self.filter_key = key or ''
self.mode = mode
# Read config panel toml # Read config panel toml
self._get_config_panel() 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} 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): def _apply(self):
logger.info("Running config script...") logger.info("Running config script...")
dir_path = os.path.dirname(os.path.realpath(self.save_path)) dir_path = os.path.dirname(os.path.realpath(self.save_path))
if not os.path.exists(dir_path): if not os.path.exists(dir_path):
mkdir(dir_path, mode=0o700) 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 # 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): def _reload_services(self):