mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
commit
9f60913bf8
97 changed files with 364 additions and 590 deletions
|
@ -37,7 +37,7 @@ workflow:
|
||||||
- when: always
|
- when: always
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
YNH_BUILD_DIR: "ynh-build"
|
YNH_BUILD_DIR: "/ynh-build"
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- template: Code-Quality.gitlab-ci.yml
|
- template: Code-Quality.gitlab-ci.yml
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
- DEBIAN_FRONTEND=noninteractive apt update
|
- DEBIAN_FRONTEND=noninteractive apt update
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- $YNH_BUILD_DIR/*.deb
|
- ./*.deb
|
||||||
|
|
||||||
.build_script: &build_script
|
.build_script: &build_script
|
||||||
- DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" install devscripts --no-install-recommends
|
- DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" install devscripts --no-install-recommends
|
||||||
|
@ -17,6 +17,8 @@
|
||||||
- VERSION_NIGHTLY="${VERSION}+$(date +%Y%m%d%H%M)"
|
- VERSION_NIGHTLY="${VERSION}+$(date +%Y%m%d%H%M)"
|
||||||
- dch --package "${PACKAGE}" --force-bad-version -v "${VERSION_NIGHTLY}" -D "unstable" --force-distribution "Daily build."
|
- dch --package "${PACKAGE}" --force-bad-version -v "${VERSION_NIGHTLY}" -D "unstable" --force-distribution "Daily build."
|
||||||
- debuild --no-lintian -us -uc
|
- debuild --no-lintian -us -uc
|
||||||
|
- cp $YNH_BUILD_DIR/*.deb ${CI_PROJECT_DIR}/
|
||||||
|
- cd ${CI_PROJECT_DIR}
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
# BUILD DEB
|
# BUILD DEB
|
||||||
|
@ -31,7 +33,7 @@ build-yunohost:
|
||||||
- mkdir -p $YNH_BUILD_DIR/$PACKAGE
|
- mkdir -p $YNH_BUILD_DIR/$PACKAGE
|
||||||
- cat archive.tar.gz | tar -xz -C $YNH_BUILD_DIR/$PACKAGE
|
- cat archive.tar.gz | tar -xz -C $YNH_BUILD_DIR/$PACKAGE
|
||||||
- rm archive.tar.gz
|
- rm archive.tar.gz
|
||||||
- DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE
|
- DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $YNH_BUILD_DIR/$PACKAGE
|
||||||
- *build_script
|
- *build_script
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,7 +44,7 @@ build-ssowat:
|
||||||
script:
|
script:
|
||||||
- DEBIAN_DEPENDS=$(cat debian/control | tr "," "\n" | grep -Po "ssowat \([>,=,<]+ .*\)" | grep -Po "[0-9\.]+")
|
- DEBIAN_DEPENDS=$(cat debian/control | tr "," "\n" | grep -Po "ssowat \([>,=,<]+ .*\)" | grep -Po "[0-9\.]+")
|
||||||
- git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE -b $DEBIAN_DEPENDS $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE --depth 1
|
- git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE -b $DEBIAN_DEPENDS $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE --depth 1
|
||||||
- DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE
|
- DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $YNH_BUILD_DIR/$PACKAGE
|
||||||
- *build_script
|
- *build_script
|
||||||
|
|
||||||
build-moulinette:
|
build-moulinette:
|
||||||
|
@ -52,5 +54,5 @@ build-moulinette:
|
||||||
script:
|
script:
|
||||||
- DEBIAN_DEPENDS=$(cat debian/control | tr "," "\n" | grep -Po "moulinette \([>,=,<]+ .*\)" | grep -Po "[0-9\.]+")
|
- DEBIAN_DEPENDS=$(cat debian/control | tr "," "\n" | grep -Po "moulinette \([>,=,<]+ .*\)" | grep -Po "[0-9\.]+")
|
||||||
- git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE -b $DEBIAN_DEPENDS $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE --depth 1
|
- git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE -b $DEBIAN_DEPENDS $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE --depth 1
|
||||||
- DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE
|
- DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $YNH_BUILD_DIR/$PACKAGE
|
||||||
- *build_script
|
- *build_script
|
||||||
|
|
|
@ -17,7 +17,7 @@ upgrade:
|
||||||
image: "after-install"
|
image: "after-install"
|
||||||
script:
|
script:
|
||||||
- apt-get update -o Acquire::Retries=3
|
- apt-get update -o Acquire::Retries=3
|
||||||
- DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb
|
- DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ${CI_PROJECT_DIR}/*.deb
|
||||||
|
|
||||||
|
|
||||||
install-postinstall:
|
install-postinstall:
|
||||||
|
@ -25,5 +25,5 @@ install-postinstall:
|
||||||
image: "before-install"
|
image: "before-install"
|
||||||
script:
|
script:
|
||||||
- apt-get update -o Acquire::Retries=3
|
- apt-get update -o Acquire::Retries=3
|
||||||
- DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb
|
- DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ${CI_PROJECT_DIR}/*.deb
|
||||||
- yunohost tools postinstall -d domain.tld -u syssa -F 'Syssa Mine' -p the_password --ignore-dyndns --force-diskspace
|
- yunohost tools postinstall -d domain.tld -u syssa -F 'Syssa Mine' -p the_password --ignore-dyndns --force-diskspace
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.install_debs: &install_debs
|
.install_debs: &install_debs
|
||||||
- apt-get update -o Acquire::Retries=3
|
- apt-get update -o Acquire::Retries=3
|
||||||
- DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb
|
- DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ${CI_PROJECT_DIR}/*.deb
|
||||||
- pip3 install -U mock pip pytest pytest-cov pytest-mock pytest-sugar requests-mock tox ansi2html black jinja2 "packaging<22"
|
- pip3 install -U mock pip pytest pytest-cov pytest-mock pytest-sugar requests-mock tox ansi2html black jinja2 "packaging<22"
|
||||||
|
|
||||||
.test-stage:
|
.test-stage:
|
||||||
|
|
|
@ -6,7 +6,7 @@ map $http_upgrade $connection_upgrade {
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
listen [::]:80;
|
listen [::]:80;
|
||||||
server_name {{ domain }}{% if xmpp_enabled != "True" %} xmpp-upload.{{ domain }} muc.{{ domain }}{% endif %};
|
server_name {{ domain }}{% if xmpp_enabled == "True" %} xmpp-upload.{{ domain }} muc.{{ domain }}{% endif %};
|
||||||
|
|
||||||
access_by_lua_file /usr/share/ssowat/access.lua;
|
access_by_lua_file /usr/share/ssowat/access.lua;
|
||||||
|
|
||||||
|
|
49
debian/changelog
vendored
49
debian/changelog
vendored
|
@ -1,3 +1,52 @@
|
||||||
|
yunohost (11.1.5) stable; urgency=low
|
||||||
|
|
||||||
|
- Release as stable !
|
||||||
|
|
||||||
|
- diagnosis: we can't yield an ERROR if there's no IPv6, otherwise that blocks all subsequent network-related diagnoser because of the dependency system ... (ade92e43)
|
||||||
|
- domains: fix domain_config.toml typos in conditions (480f7a43)
|
||||||
|
- certs: Don't try restarting metronome if no domain configured for it (452ba8bb)
|
||||||
|
|
||||||
|
Thanks to all contributors <3 ! (Axolotle)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 01 Feb 2023 20:21:56 +0100
|
||||||
|
|
||||||
|
yunohost (11.1.4.1) testing; urgency=low
|
||||||
|
|
||||||
|
- debian: don't dump upgradable apps during postinst's catalog update (82d30f02)
|
||||||
|
- ynh_setup_source: Output checksums when source is 'corrupt' ([#1578](https://github.com/yunohost/yunohost/pull/1578))
|
||||||
|
- metronome: Auto-enable/disable metronome if there's no/at least one domain configured for XMPP (c990cee6)
|
||||||
|
|
||||||
|
Thanks to all contributors <3 ! (tituspijean)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 01 Feb 2023 17:10:32 +0100
|
||||||
|
|
||||||
|
yunohost (11.1.4) testing; urgency=low
|
||||||
|
|
||||||
|
- settings: Add DNS exposure setting given the IP version ([#1451](https://github.com/yunohost/yunohost/pull/1451))
|
||||||
|
|
||||||
|
Thanks to all contributors <3 ! (Tagada)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 30 Jan 2023 16:28:56 +0100
|
||||||
|
|
||||||
|
yunohost (11.1.3.1) testing; urgency=low
|
||||||
|
|
||||||
|
- nginx: add xmpp-upload. and muc. server_name only if xmpp_enabled is enabled (c444dee4)
|
||||||
|
- [i18n] Translations updated for Arabic, Basque, French, Galician, Spanish, Turkish
|
||||||
|
|
||||||
|
Thanks to all contributors <3 ! (Alperen İsa Nalbant, ButterflyOfFire, cristian amoyao, Éric Gaspar, José M, Kayou, ppr, quiwy, xabirequejo)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 30 Jan 2023 15:44:30 +0100
|
||||||
|
|
||||||
|
yunohost (11.1.3) testing; urgency=low
|
||||||
|
|
||||||
|
- helpers: Include procedures in MySQL database backup ([#1570](https://github.com/yunohost/yunohost/pull/1570))
|
||||||
|
- users: be able to change the loginShell of a user ([#1538](https://github.com/yunohost/yunohost/pull/1538))
|
||||||
|
- debian: refresh catalog upon package upgrade (be5b1c1b)
|
||||||
|
|
||||||
|
Thanks to all contributors <3 ! (Éric Gaspar, Kay0u, ljf, Metin Bektas)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 19 Jan 2023 23:08:10 +0100
|
||||||
|
|
||||||
yunohost (11.1.2.2) testing; urgency=low
|
yunohost (11.1.2.2) testing; urgency=low
|
||||||
|
|
||||||
- Minor technical fixes (b37d4baf, 68342171)
|
- Minor technical fixes (b37d4baf, 68342171)
|
||||||
|
|
2
debian/control
vendored
2
debian/control
vendored
|
@ -10,7 +10,7 @@ Package: yunohost
|
||||||
Essential: yes
|
Essential: yes
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: ${python3:Depends}, ${misc:Depends}
|
Depends: ${python3:Depends}, ${misc:Depends}
|
||||||
, moulinette (>= 11.0), ssowat (>= 11.0)
|
, moulinette (>= 11.1), ssowat (>= 11.1)
|
||||||
, python3-psutil, python3-requests, python3-dnspython, python3-openssl
|
, python3-psutil, python3-requests, python3-dnspython, python3-openssl
|
||||||
, python3-miniupnpc, python3-dbus, python3-jinja2
|
, python3-miniupnpc, python3-dbus, python3-jinja2
|
||||||
, python3-toml, python3-packaging, python3-publicsuffix2
|
, python3-toml, python3-packaging, python3-publicsuffix2
|
||||||
|
|
3
debian/postinst
vendored
3
debian/postinst
vendored
|
@ -28,6 +28,9 @@ do_configure() {
|
||||||
|
|
||||||
echo "Re-diagnosing server health..."
|
echo "Re-diagnosing server health..."
|
||||||
yunohost diagnosis run --force
|
yunohost diagnosis run --force
|
||||||
|
|
||||||
|
echo "Refreshing app catalog..."
|
||||||
|
yunohost tools update apps --output-as none || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Trick to let yunohost handle the restart of the API,
|
# Trick to let yunohost handle the restart of the API,
|
||||||
|
|
|
@ -22,19 +22,18 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
import json
|
import json
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
""" """
|
|
||||||
with open("../share/actionsmap.yml") as f:
|
with open("../share/actionsmap.yml") as f:
|
||||||
action_map = yaml.safe_load(f)
|
action_map = yaml.safe_load(f)
|
||||||
|
|
||||||
try:
|
# try:
|
||||||
with open("/etc/yunohost/current_host", "r") as f:
|
# with open("/etc/yunohost/current_host", "r") as f:
|
||||||
domain = f.readline().rstrip()
|
# domain = f.readline().rstrip()
|
||||||
except IOError:
|
# except IOError:
|
||||||
domain = requests.get("http://ip.yunohost.org").text
|
# domain = requests.get("http://ip.yunohost.org").text
|
||||||
|
|
||||||
with open("../debian/changelog") as f:
|
with open("../debian/changelog") as f:
|
||||||
top_changelog = f.readline()
|
top_changelog = f.readline()
|
||||||
api_version = top_changelog[top_changelog.find("(") + 1 : top_changelog.find(")")]
|
api_version = top_changelog[top_changelog.find("(") + 1 : top_changelog.find(")")]
|
||||||
|
|
|
@ -31,7 +31,6 @@ def get_dict_actions(OPTION_SUBTREE, category):
|
||||||
|
|
||||||
|
|
||||||
with open(ACTIONSMAP_FILE, "r") as stream:
|
with open(ACTIONSMAP_FILE, "r") as stream:
|
||||||
|
|
||||||
# Getting the dictionary containning what actions are possible per category
|
# Getting the dictionary containning what actions are possible per category
|
||||||
OPTION_TREE = yaml.safe_load(stream)
|
OPTION_TREE = yaml.safe_load(stream)
|
||||||
|
|
||||||
|
@ -65,7 +64,6 @@ with open(ACTIONSMAP_FILE, "r") as stream:
|
||||||
os.makedirs(BASH_COMPLETION_FOLDER, exist_ok=True)
|
os.makedirs(BASH_COMPLETION_FOLDER, exist_ok=True)
|
||||||
|
|
||||||
with open(BASH_COMPLETION_FILE, "w") as generated_file:
|
with open(BASH_COMPLETION_FILE, "w") as generated_file:
|
||||||
|
|
||||||
# header of the file
|
# header of the file
|
||||||
generated_file.write("#\n")
|
generated_file.write("#\n")
|
||||||
generated_file.write("# completion for yunohost\n")
|
generated_file.write("# completion for yunohost\n")
|
||||||
|
|
|
@ -20,7 +20,6 @@ def get_current_commit():
|
||||||
|
|
||||||
|
|
||||||
def render(helpers):
|
def render(helpers):
|
||||||
|
|
||||||
current_commit = get_current_commit()
|
current_commit = get_current_commit()
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
@ -56,20 +55,17 @@ def render(helpers):
|
||||||
|
|
||||||
class Parser:
|
class Parser:
|
||||||
def __init__(self, filename):
|
def __init__(self, filename):
|
||||||
|
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.file = open(filename, "r").readlines()
|
self.file = open(filename, "r").readlines()
|
||||||
self.blocks = None
|
self.blocks = None
|
||||||
|
|
||||||
def parse_blocks(self):
|
def parse_blocks(self):
|
||||||
|
|
||||||
self.blocks = []
|
self.blocks = []
|
||||||
|
|
||||||
current_reading = "void"
|
current_reading = "void"
|
||||||
current_block = {"name": None, "line": -1, "comments": [], "code": []}
|
current_block = {"name": None, "line": -1, "comments": [], "code": []}
|
||||||
|
|
||||||
for i, line in enumerate(self.file):
|
for i, line in enumerate(self.file):
|
||||||
|
|
||||||
if line.startswith("#!/bin/bash"):
|
if line.startswith("#!/bin/bash"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -117,7 +113,6 @@ class Parser:
|
||||||
current_reading = "code"
|
current_reading = "code"
|
||||||
|
|
||||||
elif current_reading == "code":
|
elif current_reading == "code":
|
||||||
|
|
||||||
if line == "}":
|
if line == "}":
|
||||||
# We're getting out of the function
|
# We're getting out of the function
|
||||||
current_reading = "void"
|
current_reading = "void"
|
||||||
|
@ -138,7 +133,6 @@ class Parser:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def parse_block(self, b):
|
def parse_block(self, b):
|
||||||
|
|
||||||
b["brief"] = ""
|
b["brief"] = ""
|
||||||
b["details"] = ""
|
b["details"] = ""
|
||||||
b["usage"] = ""
|
b["usage"] = ""
|
||||||
|
@ -164,7 +158,6 @@ class Parser:
|
||||||
|
|
||||||
elif subblock.startswith("usage"):
|
elif subblock.startswith("usage"):
|
||||||
for line in subblock.split("\n"):
|
for line in subblock.split("\n"):
|
||||||
|
|
||||||
if line.startswith("| arg"):
|
if line.startswith("| arg"):
|
||||||
linesplit = line.split()
|
linesplit = line.split()
|
||||||
argname = linesplit[2]
|
argname = linesplit[2]
|
||||||
|
@ -216,7 +209,6 @@ def malformed_error(line_number):
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
helper_files = sorted(glob.glob("../helpers/*"))
|
helper_files = sorted(glob.glob("../helpers/*"))
|
||||||
helpers = []
|
helpers = []
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,6 @@ def main():
|
||||||
|
|
||||||
# man pages of "yunohost *"
|
# man pages of "yunohost *"
|
||||||
with open(ACTIONSMAP_FILE, "r") as actionsmap:
|
with open(ACTIONSMAP_FILE, "r") as actionsmap:
|
||||||
|
|
||||||
# Getting the dictionary containning what actions are possible per domain
|
# Getting the dictionary containning what actions are possible per domain
|
||||||
actionsmap = ordered_yaml_load(actionsmap)
|
actionsmap = ordered_yaml_load(actionsmap)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ from yunohost.utils.resources import AppResourceClassesByType
|
||||||
resources = sorted(AppResourceClassesByType.values(), key=lambda r: r.priority)
|
resources = sorted(AppResourceClassesByType.values(), key=lambda r: r.priority)
|
||||||
|
|
||||||
for klass in resources:
|
for klass in resources:
|
||||||
|
|
||||||
doc = klass.__doc__.replace("\n ", "\n")
|
doc = klass.__doc__.replace("\n ", "\n")
|
||||||
|
|
||||||
print("")
|
print("")
|
||||||
|
|
|
@ -167,7 +167,7 @@ ynh_setup_source() {
|
||||||
|| ynh_die --message="$out"
|
|| ynh_die --message="$out"
|
||||||
# Check the control sum
|
# Check the control sum
|
||||||
echo "${src_sum} ${src_filename}" | ${src_sumprg} --check --status \
|
echo "${src_sum} ${src_filename}" | ${src_sumprg} --check --status \
|
||||||
|| ynh_die --message="Corrupt source"
|
|| ynh_die --message="Corrupt source for ${src_filename}: Expected ${src_sum} but got $(${src_sumprg} ${src_filename} | cut --delimiter=' ' --fields=1) (size: $(du -hs ${src_filename} | cut --delimiter=' ' --fields=1))."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Keep files to be backup/restored at the end of the helper
|
# Keep files to be backup/restored at the end of the helper
|
||||||
|
|
|
@ -74,8 +74,22 @@ do_post_regen() {
|
||||||
chown -R metronome: /var/lib/metronome/
|
chown -R metronome: /var/lib/metronome/
|
||||||
chown -R metronome: /etc/metronome/conf.d/
|
chown -R metronome: /etc/metronome/conf.d/
|
||||||
|
|
||||||
|
if [[ -z "$(ls /etc/metronome/conf.d/*.cfg.lua 2>/dev/null)" ]]
|
||||||
|
then
|
||||||
|
if systemctl is-enabled metronome &>/dev/null
|
||||||
|
then
|
||||||
|
systemctl disable metronome --now 2>/dev/null
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! systemctl is-enabled metronome &>/dev/null
|
||||||
|
then
|
||||||
|
systemctl enable metronome --now 2>/dev/null
|
||||||
|
sleep 3
|
||||||
|
fi
|
||||||
|
|
||||||
[[ -z "$regen_conf_files" ]] \
|
[[ -z "$regen_conf_files" ]] \
|
||||||
|| systemctl restart metronome
|
|| systemctl restart metronome
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
do_$1_regen ${@:2}
|
do_$1_regen ${@:2}
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
"app_not_installed": "إنّ التطبيق {app} غير مُنصَّب",
|
"app_not_installed": "إنّ التطبيق {app} غير مُنصَّب",
|
||||||
"app_not_properly_removed": "لم يتم حذف تطبيق {app} بشكلٍ جيّد",
|
"app_not_properly_removed": "لم يتم حذف تطبيق {app} بشكلٍ جيّد",
|
||||||
"app_removed": "تمت إزالة تطبيق {app}",
|
"app_removed": "تمت إزالة تطبيق {app}",
|
||||||
"app_requirements_checking": "جار فحص الحزم اللازمة لـ {app}…",
|
"app_requirements_checking": "جار فحص متطلبات تطبيق {app}…",
|
||||||
"app_sources_fetch_failed": "تعذرت عملية جلب مصادر الملفات",
|
"app_sources_fetch_failed": "تعذر جلب ملفات المصدر ، هل عنوان URL صحيح؟",
|
||||||
"app_unknown": "برنامج مجهول",
|
"app_unknown": "برنامج مجهول",
|
||||||
"app_upgrade_app_name": "جارٍ تحديث {app}…",
|
"app_upgrade_app_name": "جارٍ تحديث {app}…",
|
||||||
"app_upgrade_failed": "تعذرت عملية ترقية {app}",
|
"app_upgrade_failed": "تعذرت عملية ترقية {app}",
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
"done": "تم",
|
"done": "تم",
|
||||||
"downloading": "عملية التنزيل جارية …",
|
"downloading": "عملية التنزيل جارية …",
|
||||||
"dyndns_ip_updated": "لقد تم تحديث عنوان الإيبي الخاص بك على نظام أسماء النطاقات الديناميكي",
|
"dyndns_ip_updated": "لقد تم تحديث عنوان الإيبي الخاص بك على نظام أسماء النطاقات الديناميكي",
|
||||||
"dyndns_key_generating": "عملية توليد مفتاح نظام أسماء النطاقات جارية. يمكن للعملية أن تستغرق بعضا من الوقت…",
|
"dyndns_key_generating": "جارٍ إنشاء مفتاح DNS ... قد يستغرق الأمر بعض الوقت.",
|
||||||
"dyndns_key_not_found": "لم يتم العثور على مفتاح DNS الخاص باسم النطاق هذا",
|
"dyndns_key_not_found": "لم يتم العثور على مفتاح DNS الخاص باسم النطاق هذا",
|
||||||
"extracting": "عملية فك الضغط جارية…",
|
"extracting": "عملية فك الضغط جارية…",
|
||||||
"installation_complete": "إكتملت عملية التنصيب",
|
"installation_complete": "إكتملت عملية التنصيب",
|
||||||
|
@ -49,15 +49,15 @@
|
||||||
"pattern_domain": "يتوجب أن يكون إسم نطاق صالح (مثل my-domain.org)",
|
"pattern_domain": "يتوجب أن يكون إسم نطاق صالح (مثل my-domain.org)",
|
||||||
"pattern_email": "يتوجب أن يكون عنوان بريد إلكتروني صالح (مثل someone@domain.org)",
|
"pattern_email": "يتوجب أن يكون عنوان بريد إلكتروني صالح (مثل someone@domain.org)",
|
||||||
"pattern_password": "يتوجب أن تكون مكونة من 3 حروف على الأقل",
|
"pattern_password": "يتوجب أن تكون مكونة من 3 حروف على الأقل",
|
||||||
"restore_extracting": "جارٍ فك الضغط عن الملفات التي نحتاجها من النسخة الاحتياطية…",
|
"restore_extracting": "جارٍ فك الضغط عن الملفات اللازمة من النسخة الاحتياطية…",
|
||||||
"server_shutdown": "سوف ينطفئ الخادوم",
|
"server_shutdown": "سوف ينطفئ الخادوم",
|
||||||
"server_shutdown_confirm": "سوف ينطفئ الخادوم حالا. متأكد ؟ [{answers}]",
|
"server_shutdown_confirm": "سوف ينطفئ الخادوم حالا. متأكد ؟ [{answers}]",
|
||||||
"server_reboot": "سيعاد تشغيل الخادوم",
|
"server_reboot": "سيعاد تشغيل الخادوم",
|
||||||
"server_reboot_confirm": "سيعاد تشغيل الخادوم في الحين. هل أنت متأكد ؟ [{answers}]",
|
"server_reboot_confirm": "سيعاد تشغيل الخادوم في الحين. هل أنت متأكد ؟ [{answers}]",
|
||||||
"service_add_failed": "تعذرت إضافة خدمة '{service}'",
|
"service_add_failed": "تعذرت إضافة خدمة '{service}'",
|
||||||
"service_already_stopped": "إنّ خدمة '{service}' متوقفة مِن قبلُ",
|
"service_already_stopped": "إنّ خدمة '{service}' متوقفة مِن قبلُ",
|
||||||
"service_disabled": "لن يتم إطلاق خدمة '{service}' أثناء بداية تشغيل النظام.",
|
"service_disabled": "لن يتم إطلاق خدمة '{service}' أثناء بداية تشغيل النظام بتاتا.",
|
||||||
"service_enabled": "تم تنشيط خدمة '{service}'",
|
"service_enabled": "سيتم الآن بدء تشغيل الخدمة '{service}' تلقائيًا أثناء تمهيد النظام.",
|
||||||
"service_removed": "تمت إزالة خدمة '{service}'",
|
"service_removed": "تمت إزالة خدمة '{service}'",
|
||||||
"service_started": "تم إطلاق تشغيل خدمة '{service}'",
|
"service_started": "تم إطلاق تشغيل خدمة '{service}'",
|
||||||
"service_stopped": "تمّ إيقاف خدمة '{service}'",
|
"service_stopped": "تمّ إيقاف خدمة '{service}'",
|
||||||
|
@ -71,10 +71,10 @@
|
||||||
"user_deleted": "تم حذف المستخدم",
|
"user_deleted": "تم حذف المستخدم",
|
||||||
"user_deletion_failed": "لا يمكن حذف المستخدم",
|
"user_deletion_failed": "لا يمكن حذف المستخدم",
|
||||||
"user_unknown": "المستخدم {user} مجهول",
|
"user_unknown": "المستخدم {user} مجهول",
|
||||||
"user_update_failed": "لا يمكن تحديث المستخدم",
|
"user_update_failed": "لا يمكن تحديث المستخدم {user}: {error}",
|
||||||
"user_updated": "تم تحديث المستخدم",
|
"user_updated": "تم تحديث معلومات المستخدم",
|
||||||
"yunohost_installing": "عملية تنصيب واي يونوهوست جارية …",
|
"yunohost_installing": "عملية تنصيب واي يونوهوست جارية …",
|
||||||
"yunohost_not_installed": "إنَّ واي يونوهوست ليس مُنَصَّب أو هو مثبت حاليا بشكل خاطئ. قم بتنفيذ الأمر 'yunohost tools postinstall'",
|
"yunohost_not_installed": "إنَّ واي يونوهوست ليس مُنَصَّب بشكل جيد. فضلًا قم بتنفيذ الأمر 'yunohost tools postinstall'",
|
||||||
"migrations_list_conflict_pending_done": "لا يمكنك استخدام --previous و --done معًا على نفس سطر الأوامر.",
|
"migrations_list_conflict_pending_done": "لا يمكنك استخدام --previous و --done معًا على نفس سطر الأوامر.",
|
||||||
"service_description_metronome": "يُدير حسابات الدردشة الفورية XMPP",
|
"service_description_metronome": "يُدير حسابات الدردشة الفورية XMPP",
|
||||||
"service_description_nginx": "يقوم بتوفير النفاذ و السماح بالوصول إلى كافة مواقع الويب المستضافة على خادومك",
|
"service_description_nginx": "يقوم بتوفير النفاذ و السماح بالوصول إلى كافة مواقع الويب المستضافة على خادومك",
|
||||||
|
@ -193,5 +193,21 @@
|
||||||
"diagnosis_ports_ok": "المنفذ {port} مفتوح ومتاح الوصول إليه مِن الخارج.",
|
"diagnosis_ports_ok": "المنفذ {port} مفتوح ومتاح الوصول إليه مِن الخارج.",
|
||||||
"global_settings_setting_smtp_allow_ipv6": "سماح IPv6",
|
"global_settings_setting_smtp_allow_ipv6": "سماح IPv6",
|
||||||
"disk_space_not_sufficient_update": "ليس هناك مساحة كافية لتحديث هذا التطبيق",
|
"disk_space_not_sufficient_update": "ليس هناك مساحة كافية لتحديث هذا التطبيق",
|
||||||
"domain_cert_gen_failed": "لا يمكن إعادة توليد الشهادة"
|
"domain_cert_gen_failed": "لا يمكن إعادة توليد الشهادة",
|
||||||
|
"diagnosis_apps_issue": "تم العثور على مشكلة في تطبيق {app}",
|
||||||
|
"tools_upgrade": "تحديث حُزم النظام",
|
||||||
|
"service_description_yunomdns": "يسمح لك بالوصول إلى خادمك الخاص باستخدام 'yunohost.local' في شبكتك المحلية",
|
||||||
|
"good_practices_about_user_password": "أنت الآن على وشك تحديد كلمة مرور مستخدم جديدة. يجب أن تتكون كلمة المرور من 8 أحرف على الأقل - على الرغم من أنه من الممارسات الجيدة استخدام كلمة مرور أطول (أي عبارة مرور) و / أو مجموعة متنوعة من الأحرف (الأحرف الكبيرة والصغيرة والأرقام والأحرف الخاصة).",
|
||||||
|
"root_password_changed": "تم تغيير كلمة مرور الجذر",
|
||||||
|
"root_password_desynchronized": "تم تغيير كلمة مرور المدير ، لكن لم يتمكن YunoHost من نشرها على كلمة مرور الجذر!",
|
||||||
|
"user_import_bad_line": "سطر غير صحيح {line}: {details}",
|
||||||
|
"user_import_success": "تم استيراد المستخدمين بنجاح",
|
||||||
|
"visitors": "الزوار",
|
||||||
|
"password_too_simple_3": "يجب أن تتكون كلمة المرور من 8 أحرف على الأقل وأن تحتوي على أرقام وأرقام علوية وسفلية وأحرف خاصة",
|
||||||
|
"password_too_simple_4": "يجب أن تتكون كلمة المرور من 12 حرفًا على الأقل وأن تحتوي على أرقام وأرقام علوية وسفلية وأحرف خاصة",
|
||||||
|
"service_unknown": "الخدمة '{service}' غير معروفة",
|
||||||
|
"unbackup_app": "لن يتم حفظ التطبيق '{app}'",
|
||||||
|
"unrestore_app": "لن يتم استعادة التطبيق '{app}'",
|
||||||
|
"yunohost_already_installed": "إنّ YunoHost مُنصّب مِن قَبل",
|
||||||
|
"hook_name_unknown": "إسم الإجراء '{name}' غير معروف"
|
||||||
}
|
}
|
|
@ -565,7 +565,6 @@
|
||||||
"diagnosis_apps_issue": "Ein Problem für die App {app} ist aufgetreten",
|
"diagnosis_apps_issue": "Ein Problem für die App {app} ist aufgetreten",
|
||||||
"config_validate_time": "Sollte eine zulässige Zeit wie HH:MM sein",
|
"config_validate_time": "Sollte eine zulässige Zeit wie HH:MM sein",
|
||||||
"config_validate_url": "Sollte eine zulässige web URL sein",
|
"config_validate_url": "Sollte eine zulässige web URL sein",
|
||||||
"config_version_not_supported": "Konfigurationspanel Versionen '{version}' sind nicht unterstützt.",
|
|
||||||
"diagnosis_apps_allgood": "Alle installierten Apps berücksichtigen die grundlegenden Paketierungspraktiken",
|
"diagnosis_apps_allgood": "Alle installierten Apps berücksichtigen die grundlegenden Paketierungspraktiken",
|
||||||
"diagnosis_apps_broken": "Diese App ist im YunoHost-Applikationskatalog momentan als defekt gekennzeichnet. Es könnte sich dabei um einen vorübergehendes Problem handeln. Während der/die Betreuer:in versucht das Problem zu beheben, ist die Upgrade-Funktion für diese App gesperrt.",
|
"diagnosis_apps_broken": "Diese App ist im YunoHost-Applikationskatalog momentan als defekt gekennzeichnet. Es könnte sich dabei um einen vorübergehendes Problem handeln. Während der/die Betreuer:in versucht das Problem zu beheben, ist die Upgrade-Funktion für diese App gesperrt.",
|
||||||
"diagnosis_apps_not_in_app_catalog": "Diese Applikation steht nicht im Applikationskatalog von YunoHost. Sie sollten in Betracht ziehen, sie zu deinstallieren, weil sie keine Aktualisierungen mehr erhält und die Integrität und die Sicherheit Ihres Systems kompromittieren könnte.",
|
"diagnosis_apps_not_in_app_catalog": "Diese Applikation steht nicht im Applikationskatalog von YunoHost. Sie sollten in Betracht ziehen, sie zu deinstallieren, weil sie keine Aktualisierungen mehr erhält und die Integrität und die Sicherheit Ihres Systems kompromittieren könnte.",
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
"app_full_domain_unavailable": "Sorry, this app must be installed on a domain of its own, but other apps are already installed on the domain '{domain}'. You could use a subdomain dedicated to this app instead.",
|
"app_full_domain_unavailable": "Sorry, this app must be installed on a domain of its own, but other apps are already installed on the domain '{domain}'. You could use a subdomain dedicated to this app instead.",
|
||||||
"app_id_invalid": "Invalid app ID",
|
"app_id_invalid": "Invalid app ID",
|
||||||
"app_install_failed": "Unable to install {app}: {error}",
|
"app_install_failed": "Unable to install {app}: {error}",
|
||||||
"app_resource_failed": "Provisioning, deprovisioning, or updating resources for {app} failed: {error}",
|
|
||||||
"app_install_files_invalid": "These files cannot be installed",
|
"app_install_files_invalid": "These files cannot be installed",
|
||||||
"app_install_script_failed": "An error occurred inside the app installation script",
|
"app_install_script_failed": "An error occurred inside the app installation script",
|
||||||
"app_label_deprecated": "This command is deprecated! Please use the new command 'yunohost user permission update' to manage the app label.",
|
"app_label_deprecated": "This command is deprecated! Please use the new command 'yunohost user permission update' to manage the app label.",
|
||||||
|
@ -50,6 +49,7 @@
|
||||||
"app_remove_after_failed_install": "Removing the app after installation failure...",
|
"app_remove_after_failed_install": "Removing the app after installation failure...",
|
||||||
"app_removed": "{app} uninstalled",
|
"app_removed": "{app} uninstalled",
|
||||||
"app_requirements_checking": "Checking requirements for {app}...",
|
"app_requirements_checking": "Checking requirements for {app}...",
|
||||||
|
"app_resource_failed": "Provisioning, deprovisioning, or updating resources for {app} failed: {error}",
|
||||||
"app_restore_failed": "Could not restore {app}: {error}",
|
"app_restore_failed": "Could not restore {app}: {error}",
|
||||||
"app_restore_script_failed": "An error occured inside the app restore script",
|
"app_restore_script_failed": "An error occured inside the app restore script",
|
||||||
"app_sources_fetch_failed": "Could not fetch source files, is the URL correct?",
|
"app_sources_fetch_failed": "Could not fetch source files, is the URL correct?",
|
||||||
|
@ -245,6 +245,7 @@
|
||||||
"diagnosis_ip_no_ipv4": "The server does not have working IPv4.",
|
"diagnosis_ip_no_ipv4": "The server does not have working IPv4.",
|
||||||
"diagnosis_ip_no_ipv6": "The server does not have working IPv6.",
|
"diagnosis_ip_no_ipv6": "The server does not have working IPv6.",
|
||||||
"diagnosis_ip_no_ipv6_tip": "Having a working IPv6 is not mandatory for your server to work, but it is better for the health of the Internet as a whole. IPv6 should usually be automatically configured by the system or your provider if it's available. Otherwise, you might need to configure a few things manually as explained in the documentation here: <a href='https://yunohost.org/#/ipv6'>https://yunohost.org/#/ipv6</a>. If you cannot enable IPv6 or if it seems too technical for you, you can also safely ignore this warning.",
|
"diagnosis_ip_no_ipv6_tip": "Having a working IPv6 is not mandatory for your server to work, but it is better for the health of the Internet as a whole. IPv6 should usually be automatically configured by the system or your provider if it's available. Otherwise, you might need to configure a few things manually as explained in the documentation here: <a href='https://yunohost.org/#/ipv6'>https://yunohost.org/#/ipv6</a>. If you cannot enable IPv6 or if it seems too technical for you, you can also safely ignore this warning.",
|
||||||
|
"diagnosis_ip_no_ipv6_tip_important": "IPv6 should usually be automatically configured by the system or your provider if it's available. Otherwise, you might need to configure a few things manually as explained in the documentation here: <a href='https://yunohost.org/#/ipv6'>https://yunohost.org/#/ipv6</a>.",
|
||||||
"diagnosis_ip_not_connected_at_all": "The server does not seem to be connected to the Internet at all!?",
|
"diagnosis_ip_not_connected_at_all": "The server does not seem to be connected to the Internet at all!?",
|
||||||
"diagnosis_ip_weird_resolvconf": "DNS resolution seems to be working, but it looks like you're using a custom <code>/etc/resolv.conf</code>.",
|
"diagnosis_ip_weird_resolvconf": "DNS resolution seems to be working, but it looks like you're using a custom <code>/etc/resolv.conf</code>.",
|
||||||
"diagnosis_ip_weird_resolvconf_details": "The file <code>/etc/resolv.conf</code> should be a symlink to <code>/etc/resolvconf/run/resolv.conf</code> itself pointing to <code>127.0.0.1</code> (dnsmasq). If you want to manually configure DNS resolvers, please edit <code>/etc/resolv.dnsmasq.conf</code>.",
|
"diagnosis_ip_weird_resolvconf_details": "The file <code>/etc/resolv.conf</code> should be a symlink to <code>/etc/resolvconf/run/resolv.conf</code> itself pointing to <code>127.0.0.1</code> (dnsmasq). If you want to manually configure DNS resolvers, please edit <code>/etc/resolv.dnsmasq.conf</code>.",
|
||||||
|
@ -399,6 +400,8 @@
|
||||||
"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_reset_success": "Reset global settings",
|
"global_settings_reset_success": "Reset global settings",
|
||||||
|
"global_settings_setting_dns_exposure": "IP versions to consider for DNS configuration and diagnosis",
|
||||||
|
"global_settings_setting_dns_exposure_help": "NB: This only affects the recommended DNS configuration and diagnosis checks. This does not affect system configurations.",
|
||||||
"global_settings_setting_admin_strength": "Admin password strength requirements",
|
"global_settings_setting_admin_strength": "Admin password strength requirements",
|
||||||
"global_settings_setting_admin_strength_help": "These requirements are only enforced when initializing or changing the password",
|
"global_settings_setting_admin_strength_help": "These requirements are only enforced when initializing or changing the password",
|
||||||
"global_settings_setting_backup_compress_tar_archives": "Compress backups",
|
"global_settings_setting_backup_compress_tar_archives": "Compress backups",
|
||||||
|
@ -408,7 +411,6 @@
|
||||||
"global_settings_setting_nginx_redirect_to_https": "Force HTTPS",
|
"global_settings_setting_nginx_redirect_to_https": "Force HTTPS",
|
||||||
"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_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_passwordless_sudo": "Allow admins to use 'sudo' without re-typing their passwords",
|
"global_settings_setting_passwordless_sudo": "Allow admins to use 'sudo' without re-typing their passwords",
|
||||||
"global_settings_setting_passwordless_sudo_help": "FIXME",
|
|
||||||
"global_settings_setting_pop3_enabled": "Enable POP3",
|
"global_settings_setting_pop3_enabled": "Enable POP3",
|
||||||
"global_settings_setting_pop3_enabled_help": "Enable the POP3 protocol for the mail server",
|
"global_settings_setting_pop3_enabled_help": "Enable the POP3 protocol for the mail server",
|
||||||
"global_settings_setting_portal_theme": "Portal theme",
|
"global_settings_setting_portal_theme": "Portal theme",
|
||||||
|
@ -471,6 +473,7 @@
|
||||||
"invalid_number_max": "Must be lesser than {max}",
|
"invalid_number_max": "Must be lesser than {max}",
|
||||||
"invalid_number_min": "Must be greater than {min}",
|
"invalid_number_min": "Must be greater than {min}",
|
||||||
"invalid_regex": "Invalid regex:'{regex}'",
|
"invalid_regex": "Invalid regex:'{regex}'",
|
||||||
|
"invalid_shell": "Invalid shell: {shell}",
|
||||||
"ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it",
|
"ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it",
|
||||||
"iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it",
|
"iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it",
|
||||||
"ldap_attribute_already_exists": "LDAP attribute '{attribute}' already exists with value '{value}'",
|
"ldap_attribute_already_exists": "LDAP attribute '{attribute}' already exists with value '{value}'",
|
||||||
|
|
|
@ -78,8 +78,8 @@
|
||||||
"pattern_backup_archive_name": "Debe ser un nombre de archivo válido con un máximo de 30 caracteres, solo se admiten caracteres alfanuméricos y los caracteres -_. (guiones y punto)",
|
"pattern_backup_archive_name": "Debe ser un nombre de archivo válido con un máximo de 30 caracteres, solo se admiten caracteres alfanuméricos y los caracteres -_. (guiones y punto)",
|
||||||
"pattern_domain": "El nombre de dominio debe ser válido (por ejemplo mi-dominio.org)",
|
"pattern_domain": "El nombre de dominio debe ser válido (por ejemplo mi-dominio.org)",
|
||||||
"pattern_email": "Debe ser una dirección de correo electrónico válida, sin el símbolo '+' (ej. alguien@ejemplo.com)",
|
"pattern_email": "Debe ser una dirección de correo electrónico válida, sin el símbolo '+' (ej. alguien@ejemplo.com)",
|
||||||
"pattern_firstname": "Debe ser un nombre válido",
|
"pattern_firstname": "Debe ser un nombre válido (al menos 3 caracteres)",
|
||||||
"pattern_lastname": "Debe ser un apellido válido",
|
"pattern_lastname": "Debe ser un apellido válido (al menos 3 caracteres)",
|
||||||
"pattern_mailbox_quota": "Debe ser un tamaño con el sufijo «b/k/M/G/T» o «0» para no tener una cuota",
|
"pattern_mailbox_quota": "Debe ser un tamaño con el sufijo «b/k/M/G/T» o «0» para no tener una cuota",
|
||||||
"pattern_password": "Debe contener al menos 3 caracteres",
|
"pattern_password": "Debe contener al menos 3 caracteres",
|
||||||
"pattern_port_or_range": "Debe ser un número de puerto válido (es decir entre 0-65535) o un intervalo de puertos (por ejemplo 100:200)",
|
"pattern_port_or_range": "Debe ser un número de puerto válido (es decir entre 0-65535) o un intervalo de puertos (por ejemplo 100:200)",
|
||||||
|
@ -266,7 +266,7 @@
|
||||||
"migrations_failed_to_load_migration": "No se pudo cargar la migración {id}: {error}",
|
"migrations_failed_to_load_migration": "No se pudo cargar la migración {id}: {error}",
|
||||||
"migrations_dependencies_not_satisfied": "Ejecutar estas migraciones: «{dependencies_id}» antes de migrar {id}.",
|
"migrations_dependencies_not_satisfied": "Ejecutar estas migraciones: «{dependencies_id}» antes de migrar {id}.",
|
||||||
"migrations_already_ran": "Esas migraciones ya se han realizado: {ids}",
|
"migrations_already_ran": "Esas migraciones ya se han realizado: {ids}",
|
||||||
"mail_unavailable": "Esta dirección de correo está reservada y será asignada automáticamente al primer usuario",
|
"mail_unavailable": "Esta dirección de correo electrónico está reservada para el grupo de administradores",
|
||||||
"mailbox_disabled": "Correo desactivado para usuario {user}",
|
"mailbox_disabled": "Correo desactivado para usuario {user}",
|
||||||
"log_tools_reboot": "Reiniciar el servidor",
|
"log_tools_reboot": "Reiniciar el servidor",
|
||||||
"log_tools_shutdown": "Apagar el servidor",
|
"log_tools_shutdown": "Apagar el servidor",
|
||||||
|
@ -316,7 +316,7 @@
|
||||||
"dyndns_could_not_check_available": "No se pudo comprobar si {domain} está disponible en {provider}.",
|
"dyndns_could_not_check_available": "No se pudo comprobar si {domain} está disponible en {provider}.",
|
||||||
"domain_dns_conf_is_just_a_recommendation": "Este comando muestra la configuración *recomendada*. No configura las entradas DNS por ti. Es tu responsabilidad configurar la zona DNS en su registrador según esta recomendación.",
|
"domain_dns_conf_is_just_a_recommendation": "Este comando muestra la configuración *recomendada*. No configura las entradas DNS por ti. Es tu responsabilidad configurar la zona DNS en su registrador según esta recomendación.",
|
||||||
"dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento ,parece que programa está usando el bloqueo de dpkg (el gestor de paquetes del sistema)",
|
"dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento ,parece que programa está usando el bloqueo de dpkg (el gestor de paquetes del sistema)",
|
||||||
"dpkg_is_broken": "No puede hacer esto en este momento porque dpkg/APT (los gestores de paquetes del sistema) parecen estar mal configurados... Puede tratar de solucionar este problema conectando a través de SSH y ejecutando `sudo apt install --fix-broken` y/o `sudo dpkg --configure -a`.",
|
"dpkg_is_broken": "No puede hacer esto en este momento porque dpkg/APT (los gestores de paquetes del sistema) parecen estar mal configurados... Puede tratar de solucionar este problema conectando a través de SSH y ejecutando `sudo apt install --fix-broken` y/o `sudo dpkg --configure -a` y/o `sudo dpkg --audit`.",
|
||||||
"confirm_app_install_thirdparty": "¡PELIGRO! Esta aplicación no forma parte del catálogo de aplicaciones de YunoHost. La instalación de aplicaciones de terceros puede comprometer la integridad y seguridad de tu sistema. Probablemente NO deberías instalarla a menos que sepas lo que estás haciendo. NO se proporcionará NINGÚN SOPORTE si esta aplicación no funciona o rompe su sistema… Si de todos modos quieres correr ese riesgo, escribe '{answers}'",
|
"confirm_app_install_thirdparty": "¡PELIGRO! Esta aplicación no forma parte del catálogo de aplicaciones de YunoHost. La instalación de aplicaciones de terceros puede comprometer la integridad y seguridad de tu sistema. Probablemente NO deberías instalarla a menos que sepas lo que estás haciendo. NO se proporcionará NINGÚN SOPORTE si esta aplicación no funciona o rompe su sistema… Si de todos modos quieres correr ese riesgo, escribe '{answers}'",
|
||||||
"confirm_app_install_danger": "¡PELIGRO! ¡Esta aplicación sigue siendo experimental (si no es expresamente no funcional)! Probablemente NO deberías instalarla a menos que sepas lo que estás haciendo. NO se proporcionará NINGÚN SOPORTE si esta aplicación no funciona o rompe tu sistema… Si de todos modos quieres correr ese riesgo, escribe '{answers}'",
|
"confirm_app_install_danger": "¡PELIGRO! ¡Esta aplicación sigue siendo experimental (si no es expresamente no funcional)! Probablemente NO deberías instalarla a menos que sepas lo que estás haciendo. NO se proporcionará NINGÚN SOPORTE si esta aplicación no funciona o rompe tu sistema… Si de todos modos quieres correr ese riesgo, escribe '{answers}'",
|
||||||
"confirm_app_install_warning": "Aviso: esta aplicación puede funcionar pero no está bien integrada en YunoHost. Algunas herramientas como la autentificación única y respaldo/restauración podrían no estar disponibles. ¿Instalar de todos modos? [{answers}] ",
|
"confirm_app_install_warning": "Aviso: esta aplicación puede funcionar pero no está bien integrada en YunoHost. Algunas herramientas como la autentificación única y respaldo/restauración podrían no estar disponibles. ¿Instalar de todos modos? [{answers}] ",
|
||||||
|
@ -454,7 +454,7 @@
|
||||||
"diagnosis_ports_forwarding_tip": "Para solucionar este incidente, lo más seguro deberías configurar la redirección de los puertos en el router como se especifica en <a href='https://yunohost.org/isp_box_config'>https://yunohost.org/isp_box_config</a>",
|
"diagnosis_ports_forwarding_tip": "Para solucionar este incidente, lo más seguro deberías configurar la redirección de los puertos en el router como se especifica en <a href='https://yunohost.org/isp_box_config'>https://yunohost.org/isp_box_config</a>",
|
||||||
"certmanager_warning_subdomain_dns_record": "El subdominio '{subdomain}' no se resuelve en la misma dirección IP que '{domain}'. Algunas funciones no estarán disponibles hasta que solucione esto y regenere el certificado.",
|
"certmanager_warning_subdomain_dns_record": "El subdominio '{subdomain}' no se resuelve en la misma dirección IP que '{domain}'. Algunas funciones no estarán disponibles hasta que solucione esto y regenere el certificado.",
|
||||||
"domain_cannot_add_xmpp_upload": "No puede agregar dominios que comiencen con 'xmpp-upload'. Este tipo de nombre está reservado para la función de carga XMPP integrada en YunoHost.",
|
"domain_cannot_add_xmpp_upload": "No puede agregar dominios que comiencen con 'xmpp-upload'. Este tipo de nombre está reservado para la función de carga XMPP integrada en YunoHost.",
|
||||||
"yunohost_postinstall_end_tip": "¡La post-instalación completada! Para finalizar su configuración, por favor considere:\n - agregar un primer usuario a través de la sección 'Usuarios' del administrador web (o 'yunohost user create <username>' en la línea de comandos);\n - diagnosticar problemas potenciales a través de la sección 'Diagnóstico' del administrador web (o 'yunohost diagnosis run' en la línea de comandos);\n - leyendo las partes 'Finalizando su configuración' y 'Conociendo YunoHost' en la documentación del administrador: https://yunohost.org/admindoc.",
|
"yunohost_postinstall_end_tip": "¡La post-instalación completada! Para finalizar su configuración, por favor considere:\n - diagnosticar problemas potenciales a través de la sección 'Diagnóstico' del administrador web (o 'yunohost diagnosis run' en la línea de comandos);\n - leyendo las partes 'Finalizando su configuración' y 'Conociendo YunoHost' en la documentación del administrador: https://yunohost.org/admindoc.",
|
||||||
"diagnosis_dns_point_to_doc": "Por favor, consulta la documentación en <a href='https://yunohost.org/dns_config'>https://yunohost.org/dns_config</a> si necesitas ayuda para configurar los registros DNS.",
|
"diagnosis_dns_point_to_doc": "Por favor, consulta la documentación en <a href='https://yunohost.org/dns_config'>https://yunohost.org/dns_config</a> si necesitas ayuda para configurar los registros DNS.",
|
||||||
"diagnosis_ip_global": "IP Global: <code>{global}</code>",
|
"diagnosis_ip_global": "IP Global: <code>{global}</code>",
|
||||||
"diagnosis_mail_outgoing_port_25_ok": "El servidor de email SMTP puede mandar emails (puerto saliente 25 no está bloqueado).",
|
"diagnosis_mail_outgoing_port_25_ok": "El servidor de email SMTP puede mandar emails (puerto saliente 25 no está bloqueado).",
|
||||||
|
@ -552,7 +552,6 @@
|
||||||
"config_validate_email": "Debe ser una dirección de correo correcta",
|
"config_validate_email": "Debe ser una dirección de correo correcta",
|
||||||
"config_validate_time": "Debe ser una hora valida en formato HH:MM",
|
"config_validate_time": "Debe ser una hora valida en formato HH:MM",
|
||||||
"config_validate_url": "Debe ser una URL válida",
|
"config_validate_url": "Debe ser una URL válida",
|
||||||
"config_version_not_supported": "Las versiones del panel de configuración '{version}' no están soportadas.",
|
|
||||||
"domain_remove_confirm_apps_removal": "La supresión de este dominio también eliminará las siguientes aplicaciones:\n{apps}\n\n¿Seguro? [{answers}]",
|
"domain_remove_confirm_apps_removal": "La supresión de este dominio también eliminará las siguientes aplicaciones:\n{apps}\n\n¿Seguro? [{answers}]",
|
||||||
"domain_registrar_is_not_configured": "El registrador aún no ha configurado el dominio {domain}.",
|
"domain_registrar_is_not_configured": "El registrador aún no ha configurado el dominio {domain}.",
|
||||||
"diagnosis_apps_not_in_app_catalog": "Esta aplicación se encuentra ausente o ya no figura en el catálogo de aplicaciones de YunoHost. Deberías considerar desinstalarla ya que no recibirá actualizaciones y podría comprometer la integridad y seguridad de tu sistema.",
|
"diagnosis_apps_not_in_app_catalog": "Esta aplicación se encuentra ausente o ya no figura en el catálogo de aplicaciones de YunoHost. Deberías considerar desinstalarla ya que no recibirá actualizaciones y podría comprometer la integridad y seguridad de tu sistema.",
|
||||||
|
@ -599,7 +598,7 @@
|
||||||
"postinstall_low_rootfsspace": "El sistema de archivos raíz tiene un espacio total inferior a 10 GB, ¡lo cual es bastante preocupante! ¡Es probable que se quede sin espacio en disco muy rápidamente! Se recomienda tener al menos 16 GB para el sistema de archivos raíz. Si desea instalar YunoHost a pesar de esta advertencia, vuelva a ejecutar la instalación posterior con --force-diskspace",
|
"postinstall_low_rootfsspace": "El sistema de archivos raíz tiene un espacio total inferior a 10 GB, ¡lo cual es bastante preocupante! ¡Es probable que se quede sin espacio en disco muy rápidamente! Se recomienda tener al menos 16 GB para el sistema de archivos raíz. Si desea instalar YunoHost a pesar de esta advertencia, vuelva a ejecutar la instalación posterior con --force-diskspace",
|
||||||
"migration_ldap_rollback_success": "Sistema revertido.",
|
"migration_ldap_rollback_success": "Sistema revertido.",
|
||||||
"permission_protected": "Permiso {permission} está protegido. No puede agregar o quitar el grupo de visitantes a/desde este permiso.",
|
"permission_protected": "Permiso {permission} está protegido. No puede agregar o quitar el grupo de visitantes a/desde este permiso.",
|
||||||
"global_settings_setting_ssowat_panel_overlay_enabled": "Habilitar la superposición del panel SSOwat",
|
"global_settings_setting_ssowat_panel_overlay_enabled": "Habilitar el pequeño cuadrado de acceso directo al portal \"YunoHost\" en las aplicaciones",
|
||||||
"migration_0021_start": "Iniciando migración a Bullseye",
|
"migration_0021_start": "Iniciando migración a Bullseye",
|
||||||
"migration_0021_patching_sources_list": "Parcheando los sources.lists...",
|
"migration_0021_patching_sources_list": "Parcheando los sources.lists...",
|
||||||
"migration_0021_main_upgrade": "Iniciando actualización principal...",
|
"migration_0021_main_upgrade": "Iniciando actualización principal...",
|
||||||
|
@ -678,5 +677,76 @@
|
||||||
"config_action_failed": "Error al ejecutar la acción '{action}': {error}",
|
"config_action_failed": "Error al ejecutar la acción '{action}': {error}",
|
||||||
"config_forbidden_readonly_type": "El tipo '{type}' no puede establecerse como solo lectura, utilice otro tipo para representar este valor (arg id relevante: '{id}').",
|
"config_forbidden_readonly_type": "El tipo '{type}' no puede establecerse como solo lectura, utilice otro tipo para representar este valor (arg id relevante: '{id}').",
|
||||||
"diagnosis_using_stable_codename": "<cmd>apt</cmd> (el gestor de paquetes del sistema) está configurado actualmente para instalar paquetes de nombre en clave 'estable', en lugar del nombre en clave de la versión actual de Debian (bullseye).",
|
"diagnosis_using_stable_codename": "<cmd>apt</cmd> (el gestor de paquetes del sistema) está configurado actualmente para instalar paquetes de nombre en clave 'estable', en lugar del nombre en clave de la versión actual de Debian (bullseye).",
|
||||||
"diagnosis_using_stable_codename_details": "Esto suele deberse a una configuración incorrecta de su proveedor de alojamiento. Esto es peligroso, porque tan pronto como la siguiente versión de Debian se convierta en la nueva 'estable', <cmd>apt</cmd> querrá actualizar todos los paquetes del sistema sin pasar por un procedimiento de migración adecuado. Se recomienda arreglar esto editando la fuente de apt para el repositorio base de Debian, y reemplazar la palabra clave <cmd>stable</cmd> por <cmd>bullseye</cmd>. El fichero de configuración correspondiente debería ser <cmd>/etc/apt/sources.list</cmd>, o un fichero en <cmd>/etc/apt/sources.list.d/</cmd>."
|
"diagnosis_using_stable_codename_details": "Esto suele deberse a una configuración incorrecta de su proveedor de alojamiento. Esto es peligroso, porque tan pronto como la siguiente versión de Debian se convierta en la nueva 'estable', <cmd>apt</cmd> querrá actualizar todos los paquetes del sistema sin pasar por un procedimiento de migración adecuado. Se recomienda arreglar esto editando la fuente de apt para el repositorio base de Debian, y reemplazar la palabra clave <cmd>stable</cmd> por <cmd>bullseye</cmd>. El fichero de configuración correspondiente debería ser <cmd>/etc/apt/sources.list</cmd>, o un fichero en <cmd>/etc/apt/sources.list.d/</cmd>.",
|
||||||
|
"domain_config_cert_install": "Instalar el certificado Let's Encrypt",
|
||||||
|
"domain_cannot_add_muc_upload": "No puedes añadir dominios que empiecen por 'muc.'. Este tipo de nombre está reservado para la función de chat XMPP multi-usuarios integrada en YunoHost.",
|
||||||
|
"domain_config_cert_renew_help": "El certificado se renovará automáticamente durante los últimos 15 días de validez. Si lo desea, puede renovarlo manualmente. (No recomendado).",
|
||||||
|
"domain_config_cert_summary_expired": "CRÍTICO: ¡El certificado actual no es válido! ¡HTTPS no funcionará en absoluto!",
|
||||||
|
"domain_config_cert_summary_letsencrypt": "¡Muy bien! Estás utilizando un certificado Let's Encrypt válido.",
|
||||||
|
"global_settings_setting_postfix_compatibility": "Compatibilidad con Postfix",
|
||||||
|
"global_settings_setting_root_password_confirm": "Nueva contraseña de root (confirmar)",
|
||||||
|
"global_settings_setting_webadmin_allowlist_enabled": "Activar la lista de IPs permitidas para Webadmin",
|
||||||
|
"migration_0024_rebuild_python_venv_broken_app": "Omitiendo {app} porque virtualenv no puede ser reconstruido fácilmente para esta app. En su lugar, deberías arreglar la situación forzando la actualización de esta app usando `yunohost app upgrade --force {app}`.",
|
||||||
|
"migration_0024_rebuild_python_venv_in_progress": "Ahora intentando reconstruir el virtualenv de Python para `{app}`",
|
||||||
|
"confirm_app_insufficient_ram": "¡PELIGRO! Esta aplicación requiere {required} de RAM para ser instalada/actualizada, pero solo hay {current} disponible actualmente. Incluso si esta aplicación pudiera ejecutarse, su proceso de instalación/actualización requiere una gran cantidad de RAM, por lo que tu servidor puede congelarse y fallar miserablemente. Si estás dispuesto a asumir ese riesgo de todos modos, escribe '{answers}'",
|
||||||
|
"confirm_notifications_read": "ADVERTENCIA: Deberías revisar las notificaciones de la aplicación antes de continuar, puede haber información importante que debes conocer. [{answers}]",
|
||||||
|
"domain_config_cert_summary_selfsigned": "ADVERTENCIA: El certificado actual es autofirmado. ¡Los navegadores mostrarán una espeluznante advertencia a los nuevos visitantes!.",
|
||||||
|
"global_settings_setting_backup_compress_tar_archives": "Comprimir las copias de seguridad",
|
||||||
|
"global_settings_setting_root_access_explain": "En sistemas Linux, 'root' es el administrador absoluto. En el contexto de YunoHost, el acceso directo 'root' SSH está deshabilitado por defecto - excepto desde la red local del servidor. Los miembros del grupo 'admins' pueden usar el comando sudo para actuar como root desde la linea de comandos. Sin embargo, puede ser útil tener una contraseña de root (robusta) para depurar el sistema si por alguna razón los administradores regulares ya no pueden iniciar sesión.",
|
||||||
|
"migration_0021_not_buster2": "¡La distribución Debian actual no es Buster! Si ya ha ejecutado la migración Buster->Bullseye, entonces este error es sintomático del hecho de que el procedimiento de migración no fue 100% exitoso (de lo contrario YunoHost lo habría marcado como completado). Se recomienda investigar lo sucedido con el equipo de soporte, que necesitará el registro **completo** de la `migración, que se puede encontrar en Herramientas > Registros en el webadmin.",
|
||||||
|
"global_settings_reset_success": "Restablecer la configuración global",
|
||||||
|
"global_settings_setting_nginx_compatibility": "Compatibilidad con NGINX",
|
||||||
|
"global_settings_setting_nginx_redirect_to_https": "Forzar HTTPS",
|
||||||
|
"global_settings_setting_user_strength_help": "Estos requisitos sólo se aplican al inicializar o cambiar la contraseña",
|
||||||
|
"log_resource_snippet": "Aprovisionar/desaprovisionar/actualizar un recurso",
|
||||||
|
"global_settings_setting_pop3_enabled": "Habilitar POP3",
|
||||||
|
"global_settings_setting_smtp_allow_ipv6": "Permitir IPv6",
|
||||||
|
"global_settings_setting_security_experimental_enabled": "Funciones de seguridad experimentales",
|
||||||
|
"migration_0024_rebuild_python_venv_disclaimer_ignored": "Virtualenvs no puede reconstruirse automáticamente para esas aplicaciones. Necesitas forzar una actualización para ellas, lo que puede hacerse desde la línea de comandos con: `yunohost app upgrade --force APP`: {ignored_apps}",
|
||||||
|
"migration_0024_rebuild_python_venv_failed": "Error al reconstruir el virtualenv de Python para {app}. La aplicación puede no funcionar mientras esto no se resuelva. Deberías arreglar la situación forzando la actualización de esta app usando `yunohost app upgrade --force {app}`.",
|
||||||
|
"app_arch_not_supported": "Esta aplicación sólo puede instalarse en arquitecturas {', '.join(required)} pero la arquitectura de su servidor es {current}",
|
||||||
|
"app_resource_failed": "Falló la asignación, desasignación o actualización de recursos para {app}: {error}",
|
||||||
|
"app_not_enough_disk": "Esta aplicación requiere {required} espacio libre.",
|
||||||
|
"app_not_enough_ram": "Esta aplicación requiere {required} de RAM para ser instalada/actualizada, pero solo hay {current} disponible actualmente.",
|
||||||
|
"app_yunohost_version_not_supported": "Esta aplicación requiere YunoHost >= {required} pero la versión actualmente instalada es {current}",
|
||||||
|
"global_settings_setting_ssh_compatibility": "Compatibilidad con SSH",
|
||||||
|
"root_password_changed": "la contraseña de root fue cambiada",
|
||||||
|
"domain_config_acme_eligible_explain": "Este dominio no parece estar preparado para un certificado Let's Encrypt. Compruebe la configuración DNS y la accesibilidad del servidor HTTP. Las secciones \"Registros DNS\" y \"Web\" de <a href='#/diagnosis'>la página de diagnóstico</a> pueden ayudarte a entender qué está mal configurado.",
|
||||||
|
"domain_config_cert_no_checks": "Ignorar las comprobaciones de diagnóstico",
|
||||||
|
"domain_config_cert_renew": "Renovar el certificado Let's Encrypt",
|
||||||
|
"domain_config_cert_summary": "Estado del certificado",
|
||||||
|
"domain_config_cert_summary_abouttoexpire": "El certificado actual está a punto de caducar. Pronto debería renovarse automáticamente.",
|
||||||
|
"domain_config_cert_summary_ok": "Muy bien, ¡el certificado actual tiene buena pinta!",
|
||||||
|
"domain_config_cert_validity": "Validez",
|
||||||
|
"global_settings_setting_admin_strength_help": "Estos requisitos sólo se aplican al inicializar o cambiar la contraseña",
|
||||||
|
"global_settings_setting_pop3_enabled_help": "Habilitar el protocolo POP3 para el servidor de correo",
|
||||||
|
"log_settings_reset_all": "Restablecer todos los ajustes",
|
||||||
|
"log_settings_set": "Aplicar ajustes",
|
||||||
|
"pattern_fullname": "Debe ser un nombre completo válido (al menos 3 caracteres)",
|
||||||
|
"password_confirmation_not_the_same": "La contraseña y su confirmación no coinciden",
|
||||||
|
"password_too_long": "Elija una contraseña de menos de 127 caracteres",
|
||||||
|
"diagnosis_using_yunohost_testing": "<cmd>apt</cmd> (el gestor de paquetes del sistema) está actualmente configurado para instalar cualquier actualización de 'testing' para el núcleo de YunoHost.",
|
||||||
|
"diagnosis_using_yunohost_testing_details": "Esto probablemente esté bien si sabes lo que estás haciendo, ¡pero presta atención a las notas de la versión antes de instalar actualizaciones de YunoHost! Si quieres deshabilitar las actualizaciones de prueba, debes eliminar la palabra clave <cmd>testing</cmd> de <cmd>/etc/apt/sources.list.d/yunohost.list</cmd>.",
|
||||||
|
"global_settings_setting_passwordless_sudo": "Permitir a los administradores utilizar 'sudo' sin tener que volver a escribir sus contraseñas.",
|
||||||
|
"group_update_aliases": "Actualizando alias para el grupo '{group}'",
|
||||||
|
"group_no_change": "Nada que cambiar para el grupo '{group}'",
|
||||||
|
"global_settings_setting_portal_theme": "Tema del portal",
|
||||||
|
"global_settings_setting_portal_theme_help": "Más información sobre la creación de temas de portal personalizados en https://yunohost.org/theming",
|
||||||
|
"invalid_credentials": "Contraseña o nombre de usuario no válidos",
|
||||||
|
"global_settings_setting_root_password": "Nueva contraseña de root",
|
||||||
|
"global_settings_setting_webadmin_allowlist": "Lista de IPs permitidas para Webadmin",
|
||||||
|
"migration_0024_rebuild_python_venv_disclaimer_base": "Tras la actualización a Debian Bullseye, algunas aplicaciones Python necesitan ser parcialmente reconstruidas para ser convertidas a la nueva versión de Python distribuida en Debian (en términos técnicos: lo que se llama el 'virtualenv' necesita ser recreado). Mientras tanto, esas aplicaciones Python pueden no funcionar. YunoHost puede intentar reconstruir el virtualenv para algunas de ellas, como se detalla a continuación. Para otras aplicaciones, o si el intento de reconstrucción falla, necesitarás forzar manualmente una actualización para esas aplicaciones.",
|
||||||
|
"migration_description_0024_rebuild_python_venv": "Reparar la aplicación Python tras la migración a bullseye",
|
||||||
|
"global_settings_setting_smtp_relay_enabled": "Activar el relé SMTP",
|
||||||
|
"domain_config_acme_eligible": "Elegibilidad ACME",
|
||||||
|
"global_settings_setting_ssh_password_authentication": "Autenticación por contraseña",
|
||||||
|
"domain_config_cert_issuer": "Autoridad de certificación",
|
||||||
|
"invalid_shell": "Shell inválido: {shell}",
|
||||||
|
"log_settings_reset": "Restablecer ajuste",
|
||||||
|
"migration_description_0026_new_admins_group": "Migrar al nuevo sistema de 'varios administradores'",
|
||||||
|
"visitors": "Visitantes",
|
||||||
|
"global_settings_setting_smtp_relay_host": "Host de retransmisión SMTP",
|
||||||
|
"migration_0024_rebuild_python_venv_disclaimer_rebuild": "Se intentará reconstruir el virtualenv para las siguientes apps (NB: ¡la operación puede llevar algún tiempo!): {rebuild_apps}",
|
||||||
|
"migration_description_0025_global_settings_to_configpanel": "Migración de la nomenclatura de ajustes globales heredada a la nomenclatura nueva y moderna",
|
||||||
|
"registrar_infos": "Información sobre el registrador"
|
||||||
}
|
}
|
|
@ -49,7 +49,6 @@
|
||||||
"config_validate_email": "Benetazko posta elektronikoa izan behar da",
|
"config_validate_email": "Benetazko posta elektronikoa izan behar da",
|
||||||
"config_validate_time": "OO:MM formatua duen ordu bat izan behar da",
|
"config_validate_time": "OO:MM formatua duen ordu bat izan behar da",
|
||||||
"config_validate_url": "Benetazko URL bat izan behar da",
|
"config_validate_url": "Benetazko URL bat izan behar da",
|
||||||
"config_version_not_supported": "Ezinezkoa da konfigurazio-panelaren '{version}' bertsioa erabiltzea.",
|
|
||||||
"app_restore_script_failed": "Errorea gertatu da aplikazioa lehengoratzeko aginduan",
|
"app_restore_script_failed": "Errorea gertatu da aplikazioa lehengoratzeko aginduan",
|
||||||
"app_upgrade_some_app_failed": "Ezinezkoa izan da aplikazio batzuk eguneratzea",
|
"app_upgrade_some_app_failed": "Ezinezkoa izan da aplikazio batzuk eguneratzea",
|
||||||
"app_install_failed": "Ezinezkoa izan da {app} instalatzea: {error}",
|
"app_install_failed": "Ezinezkoa izan da {app} instalatzea: {error}",
|
||||||
|
@ -736,5 +735,17 @@
|
||||||
"password_too_long": "Aukeratu 127 karaktere baino laburragoa den pasahitz bat",
|
"password_too_long": "Aukeratu 127 karaktere baino laburragoa den pasahitz bat",
|
||||||
"diagnosis_using_stable_codename_details": "Ostatatzaileak zerbait oker ezarri duenean gertatu ohi da hau. Arriskutsua da, Debianen datorren bertsioa 'estable' (egonkorra) bilakatzen denean, <cmd>apt</cmd>-ek sistemaren pakete guztiak bertsio-berritzen saiatuko da, beharrezko migrazio-prozedurarik burutu gabe. Debianen repositorioan apt iturria editatzen konpontzea da gomendioa, <cmd>stable</cmd> gakoa <cmd>bullseye</cmd> gakoarekin ordezkatuz. Ezarpen-fitxategia <cmd>/etc/apt/sources.list</cmd> izan beharko litzateke, edo <cmd>/etc/apt/sources.list.d/</cmd> direktorioko fitxategiren bat.",
|
"diagnosis_using_stable_codename_details": "Ostatatzaileak zerbait oker ezarri duenean gertatu ohi da hau. Arriskutsua da, Debianen datorren bertsioa 'estable' (egonkorra) bilakatzen denean, <cmd>apt</cmd>-ek sistemaren pakete guztiak bertsio-berritzen saiatuko da, beharrezko migrazio-prozedurarik burutu gabe. Debianen repositorioan apt iturria editatzen konpontzea da gomendioa, <cmd>stable</cmd> gakoa <cmd>bullseye</cmd> gakoarekin ordezkatuz. Ezarpen-fitxategia <cmd>/etc/apt/sources.list</cmd> izan beharko litzateke, edo <cmd>/etc/apt/sources.list.d/</cmd> direktorioko fitxategiren bat.",
|
||||||
"group_update_aliases": "'{group}' taldearen aliasak eguneratzen",
|
"group_update_aliases": "'{group}' taldearen aliasak eguneratzen",
|
||||||
"group_no_change": "Ez da ezer aldatu behar '{group}' talderako"
|
"group_no_change": "Ez da ezer aldatu behar '{group}' talderako",
|
||||||
|
"app_not_enough_ram": "Aplikazio honek {required} RAM behar ditu instalatu edo bertsio-berritzeko, baina {current} bakarrik daude erabilgarri une honetan.",
|
||||||
|
"domain_cannot_add_muc_upload": "Ezin duzu 'muc.'-ekin hasten den domeinurik gehitu. Mota honetako izenak YunoHosten integratuta dagoen XMPP taldeko txatek erabil ditzaten gordeta daude.",
|
||||||
|
"confirm_app_insufficient_ram": "KONTUZ! Aplikazio honek {required} RAM behar ditu instalatu edo bertsio-berritzeko baina unean {current} bakarrik daude erabilgarri. Aplikazioa ibiliko balitz ere, instalazioak edo bertsio-berritzeak RAM koporu handia eskatzen du eta zure zerbitzaria izoztu eta huts egin lezake. Hala ere arriskatu nahi baduzu idatzi '{answers}'",
|
||||||
|
"confirm_notifications_read": "ADI: ikuskatu aplikazioaren jakinarazpenak jarraitu baino lehen, baliteke jakin beharreko zerbait esatea. [{answers}]",
|
||||||
|
"app_arch_not_supported": "Aplikazio hau {', '.join(required)} arkitekturan instala daiteke bakarrik, baina zure zerbitzariaren arkitektura {current} da",
|
||||||
|
"app_resource_failed": "Huts egin du {app} aplikaziorako baliabideak",
|
||||||
|
"app_not_enough_disk": "Aplikazio honek {required} espazio libre behar ditu.",
|
||||||
|
"app_yunohost_version_not_supported": "Aplikazio honek YunoHost >= {required} behar du baina unean instalatutako bertsioa {current} da",
|
||||||
|
"global_settings_setting_passwordless_sudo": "Baimendu administrariek 'sudo' erabiltzea pasahitzak berriro idatzi beharrik gabe",
|
||||||
|
"global_settings_setting_portal_theme": "Atariko gaia",
|
||||||
|
"global_settings_setting_portal_theme_help": "Atariko gai propioak sortzeari buruzko informazio gehiago: https://yunohost.org/theming",
|
||||||
|
"invalid_shell": "Shell baliogabea: {shell}"
|
||||||
}
|
}
|
|
@ -590,7 +590,6 @@
|
||||||
"config_validate_email": "Doit être un email valide",
|
"config_validate_email": "Doit être un email valide",
|
||||||
"config_validate_time": "Doit être une heure valide comme HH:MM",
|
"config_validate_time": "Doit être une heure valide comme HH:MM",
|
||||||
"config_validate_url": "Doit être une URL Web valide",
|
"config_validate_url": "Doit être une URL Web valide",
|
||||||
"config_version_not_supported": "Les versions du panneau de configuration '{version}' ne sont pas prises en charge.",
|
|
||||||
"danger": "Danger :",
|
"danger": "Danger :",
|
||||||
"invalid_number_min": "Doit être supérieur à {min}",
|
"invalid_number_min": "Doit être supérieur à {min}",
|
||||||
"invalid_number_max": "Doit être inférieur à {max}",
|
"invalid_number_max": "Doit être inférieur à {max}",
|
||||||
|
@ -628,7 +627,7 @@
|
||||||
"diagnosis_http_special_use_tld": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial tel que .local ou .test et n'est donc pas censé être exposé en dehors du réseau local.",
|
"diagnosis_http_special_use_tld": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial tel que .local ou .test et n'est donc pas censé être exposé en dehors du réseau local.",
|
||||||
"domain_dns_conf_special_use_tld": "Ce domaine est basé sur un domaine de premier niveau (TLD) à usage spécial tel que .local ou .test et ne devrait donc pas avoir d'enregistrements DNS réels.",
|
"domain_dns_conf_special_use_tld": "Ce domaine est basé sur un domaine de premier niveau (TLD) à usage spécial tel que .local ou .test et ne devrait donc pas avoir d'enregistrements DNS réels.",
|
||||||
"other_available_options": "... et {n} autres options disponibles non affichées",
|
"other_available_options": "... et {n} autres options disponibles non affichées",
|
||||||
"domain_config_auth_consumer_key": "La clé utilisateur",
|
"domain_config_auth_consumer_key": "Clé utilisateur",
|
||||||
"domain_unknown": "Domaine '{domain}' inconnu",
|
"domain_unknown": "Domaine '{domain}' inconnu",
|
||||||
"migration_0021_start": "Démarrage de la migration vers Bullseye",
|
"migration_0021_start": "Démarrage de la migration vers Bullseye",
|
||||||
"migration_0021_patching_sources_list": "Mise à jour du fichier sources.lists...",
|
"migration_0021_patching_sources_list": "Mise à jour du fichier sources.lists...",
|
||||||
|
@ -656,7 +655,7 @@
|
||||||
"global_settings_setting_backup_compress_tar_archives_help": "Lors de la création de nouvelles sauvegardes, compresser automatiquement les archives (.tar.gz) au lieu des archives non compressées (.tar). N.B. : activer cette option permet de créer des archives plus légères, mais la procédure de sauvegarde initiale sera significativement plus longues et plus gourmandes en CPU.",
|
"global_settings_setting_backup_compress_tar_archives_help": "Lors de la création de nouvelles sauvegardes, compresser automatiquement les archives (.tar.gz) au lieu des archives non compressées (.tar). N.B. : activer cette option permet de créer des archives plus légères, mais la procédure de sauvegarde initiale sera significativement plus longues et plus gourmandes en CPU.",
|
||||||
"global_settings_setting_security_experimental_enabled": "Fonctionnalités de sécurité expérimentales",
|
"global_settings_setting_security_experimental_enabled": "Fonctionnalités de sécurité expérimentales",
|
||||||
"global_settings_setting_security_experimental_enabled_help": "Activer les fonctionnalités de sécurité expérimentales (ne l'activez pas si vous ne savez pas ce que vous faites !)",
|
"global_settings_setting_security_experimental_enabled_help": "Activer les fonctionnalités de sécurité expérimentales (ne l'activez pas si vous ne savez pas ce que vous faites !)",
|
||||||
"global_settings_setting_nginx_compatibility_help": "Compromis 'compatibilité versus sécurité' pour le serveur web Nginx. Affecte les cryptogrammes utilisés (et d'autres aspects liés à la sécurité)",
|
"global_settings_setting_nginx_compatibility_help": "Compromis 'compatibilité versus sécurité' pour le serveur web NGINX. Affecte les cryptogrammes utilisés (et d'autres aspects liés à la sécurité)",
|
||||||
"global_settings_setting_nginx_redirect_to_https_help": "Rediriger les requêtes HTTP vers HTTPS par défaut (NE PAS DÉSACTIVER à moins de savoir vraiment ce que vous faites !)",
|
"global_settings_setting_nginx_redirect_to_https_help": "Rediriger les requêtes HTTP vers HTTPS par défaut (NE PAS DÉSACTIVER à moins de savoir vraiment ce que vous faites !)",
|
||||||
"global_settings_setting_admin_strength": "Critères pour les mots de passe administrateur",
|
"global_settings_setting_admin_strength": "Critères pour les mots de passe administrateur",
|
||||||
"global_settings_setting_user_strength": "Critères pour les mots de passe utilisateurs",
|
"global_settings_setting_user_strength": "Critères pour les mots de passe utilisateurs",
|
||||||
|
@ -709,7 +708,7 @@
|
||||||
"visitors": "Visiteurs",
|
"visitors": "Visiteurs",
|
||||||
"global_settings_reset_success": "Réinitialisation des paramètres généraux",
|
"global_settings_reset_success": "Réinitialisation des paramètres généraux",
|
||||||
"domain_config_acme_eligible": "Éligibilité au protocole ACME (Automatic Certificate Management Environment, littéralement : environnement de gestion automatique de certificat)",
|
"domain_config_acme_eligible": "Éligibilité au protocole ACME (Automatic Certificate Management Environment, littéralement : environnement de gestion automatique de certificat)",
|
||||||
"domain_config_acme_eligible_explain": "Ce domaine ne semble pas près pour installer un certificat Let's Encrypt. Veuillez vérifier votre configuration DNS mais aussi que votre serveur est bien joignable en HTTP. Les sections 'Enregistrements DNS' et 'Web' de <a href='#/diagnosis'>la page Diagnostic</a> peuvent vous aider à comprendre ce qui est mal configuré.",
|
"domain_config_acme_eligible_explain": "Ce domaine ne semble pas prêt pour installer un certificat Let's Encrypt. Veuillez vérifier votre configuration DNS mais aussi que votre serveur est bien joignable en HTTP. Les sections 'Enregistrements DNS' et 'Web' de <a href='#/diagnosis'>la page Diagnostic</a> peuvent vous aider à comprendre ce qui est mal configuré.",
|
||||||
"domain_config_cert_install": "Installer un certificat Let's Encrypt",
|
"domain_config_cert_install": "Installer un certificat Let's Encrypt",
|
||||||
"domain_config_cert_issuer": "Autorité de certification",
|
"domain_config_cert_issuer": "Autorité de certification",
|
||||||
"domain_config_cert_no_checks": "Ignorer les tests et autres vérifications du diagnostic",
|
"domain_config_cert_no_checks": "Ignorer les tests et autres vérifications du diagnostic",
|
||||||
|
@ -744,9 +743,13 @@
|
||||||
"global_settings_setting_passwordless_sudo": "Permettre aux administrateurs d'utiliser 'sudo' sans retaper leur mot de passe",
|
"global_settings_setting_passwordless_sudo": "Permettre aux administrateurs d'utiliser 'sudo' sans retaper leur mot de passe",
|
||||||
"app_arch_not_supported": "Cette application ne peut être installée que sur les architectures {', '.join(required)}. L'architecture de votre serveur est {current}",
|
"app_arch_not_supported": "Cette application ne peut être installée que sur les architectures {', '.join(required)}. L'architecture de votre serveur est {current}",
|
||||||
"app_resource_failed": "L'allocation automatique des ressources (provisioning), la suppression d'accès à ces ressources (déprovisioning) ou la mise à jour des ressources pour {app} a échoué : {error}",
|
"app_resource_failed": "L'allocation automatique des ressources (provisioning), la suppression d'accès à ces ressources (déprovisioning) ou la mise à jour des ressources pour {app} a échoué : {error}",
|
||||||
"confirm_app_insufficient_ram": "ATTENTION ! Cette application requiert {required} de RAM pour l'installation/mise à niveau mais il n'y a que {current} de disponible actuellement. Même si cette application pouvait fonctionner, son processus d'installation/mise à niveau nécessite une grande quantité de RAM. Votre serveur pourrait donc geler (freezer) et planter lamentablement. Si vous êtes prêt à prendre ce risque, tapez '{answers}'",
|
"confirm_app_insufficient_ram": "ATTENTION ! Cette application requiert {required} de RAM pour l'installation/mise à niveau mais il n'y a que {current} de disponible actuellement. Même si cette application pouvait fonctionner, son processus d'installation/mise à niveau nécessite une grande quantité de RAM. Votre serveur pourrait donc geler et planter lamentablement. Si vous êtes prêt à prendre ce risque, tapez '{answers}'",
|
||||||
"app_not_enough_disk": "Cette application nécessite {required} d'espace libre.",
|
"app_not_enough_disk": "Cette application nécessite {required} d'espace libre.",
|
||||||
"app_not_enough_ram": "Cette application nécessite {required} de mémoire vive (RAM) pour être installée/mise à niveau mais seule {current} de mémoire est disponible actuellement.",
|
"app_not_enough_ram": "Cette application nécessite {required} de mémoire vive (RAM) pour être installée/mise à niveau mais seule {current} de mémoire est disponible actuellement.",
|
||||||
"app_yunohost_version_not_supported": "Cette application nécessite une version de YunoHost >= {required}. La version installée est {current}",
|
"app_yunohost_version_not_supported": "Cette application nécessite une version de YunoHost >= {required}. La version installée est {current}",
|
||||||
"confirm_notifications_read": "AVERTISSEMENT : Vous devriez vérifier les notifications de l'application susmentionnée avant de continuer, il pourrait y avoir des éléments d'information importants à connaître. [{answers}]"
|
"confirm_notifications_read": "AVERTISSEMENT : Vous devriez vérifier les notifications de l'application susmentionnée avant de continuer, il pourrait y avoir des informations importantes à connaître. [{answers}]",
|
||||||
|
"invalid_shell": "Shell invalide : {shell}",
|
||||||
|
"global_settings_setting_dns_exposure": "Suites d'IP à prendre en compte pour la configuration et le diagnostic du DNS",
|
||||||
|
"global_settings_setting_dns_exposure_help": "NB : Ceci n'affecte que la configuration DNS recommandée et les vérifications de diagnostic. Cela n'affecte pas les configurations du système.",
|
||||||
|
"diagnosis_ip_no_ipv6_tip_important": "IPv6 devrait généralement être configuré automatiquement par le système ou par votre fournisseur d'accès à internet (FAI) s'il est disponible. Sinon, vous devrez peut-être configurer quelques éléments manuellement, comme expliqué dans la documentation ici : <a href='https://yunohost.org/#/ipv6'>https://yunohost.org/#/ipv6</a>."
|
||||||
}
|
}
|
|
@ -590,7 +590,6 @@
|
||||||
"log_app_config_set": "Aplicar a configuración á app '{}'",
|
"log_app_config_set": "Aplicar a configuración á app '{}'",
|
||||||
"app_config_unable_to_apply": "Fallou a aplicación dos valores de configuración.",
|
"app_config_unable_to_apply": "Fallou a aplicación dos valores de configuración.",
|
||||||
"config_cant_set_value_on_section": "Non podes establecer un valor único na sección completa de configuración.",
|
"config_cant_set_value_on_section": "Non podes establecer un valor único na sección completa de configuración.",
|
||||||
"config_version_not_supported": "A versión do panel de configuración '{version}' non está soportada.",
|
|
||||||
"invalid_number_max": "Ten que ser menor de {max}",
|
"invalid_number_max": "Ten que ser menor de {max}",
|
||||||
"service_not_reloading_because_conf_broken": "Non se recargou/reiniciou o servizo '{name}' porque a súa configuración está estragada: {errors}",
|
"service_not_reloading_because_conf_broken": "Non se recargou/reiniciou o servizo '{name}' porque a súa configuración está estragada: {errors}",
|
||||||
"diagnosis_http_special_use_tld": "O dominio {domain} baséase nun dominio de alto-nivel (TLD) especial como .local ou .test e por isto non é de agardar que esté exposto fóra da rede local.",
|
"diagnosis_http_special_use_tld": "O dominio {domain} baséase nun dominio de alto-nivel (TLD) especial como .local ou .test e por isto non é de agardar que esté exposto fóra da rede local.",
|
||||||
|
@ -740,5 +739,14 @@
|
||||||
"domain_cannot_add_muc_upload": "Non podes engadir dominios que comecen por 'muc.'. Este tipo de dominio está reservado para as salas de conversa de XMPP integradas en YunoHost.",
|
"domain_cannot_add_muc_upload": "Non podes engadir dominios que comecen por 'muc.'. Este tipo de dominio está reservado para as salas de conversa de XMPP integradas en YunoHost.",
|
||||||
"global_settings_setting_passwordless_sudo": "Permitir a Admins usar 'sudo' sen ter que volver a escribir o contrasinal",
|
"global_settings_setting_passwordless_sudo": "Permitir a Admins usar 'sudo' sen ter que volver a escribir o contrasinal",
|
||||||
"global_settings_setting_portal_theme": "Decorado do Portal",
|
"global_settings_setting_portal_theme": "Decorado do Portal",
|
||||||
"global_settings_setting_portal_theme_help": "Tes máis info acerca da creación de decorados para o portal de acceso en https://yunohost.org/theming"
|
"global_settings_setting_portal_theme_help": "Tes máis info acerca da creación de decorados para o portal de acceso en https://yunohost.org/theming",
|
||||||
|
"app_arch_not_supported": "Esta app só pode ser instalada e arquitecturas {', '.join(required)} pero a arquitectura do teu servidor é {current}",
|
||||||
|
"app_not_enough_disk": "Esta app precisa {required} de espazo libre.",
|
||||||
|
"app_yunohost_version_not_supported": "Esta app require YunoHost >= {required} pero a versión actual instalada é {current}",
|
||||||
|
"confirm_app_insufficient_ram": "PERIGO! Esta app precisa {required} de RAM para instalar/actualizar pero só hai {current} dispoñibles. Incluso se a app funcionase, o seu proceso de instalación/actualización require gran cantidade de RAM e o teu servidor podería colgarse e fallar. Se queres asumir o risco, escribe '{answers}'",
|
||||||
|
"confirm_notifications_read": "AVISO: Deberías comprobar as notificacións da app antes de continuar, poderías ter información importante que revisar. [{answers}]",
|
||||||
|
"app_not_enough_ram": "Esta app require {required} de RAM para instalar/actualizar pero só hai {current} dispoñible.",
|
||||||
|
"global_settings_setting_dns_exposure": "Versións de IP a ter en conta para a configuración DNS e diagnóstico",
|
||||||
|
"global_settings_setting_dns_exposure_help": "Nota: Esto só lle afecta á configuración DNS recomendada e diagnóstico do sistema. Non lle afecta aos axustes do sistema.",
|
||||||
|
"diagnosis_ip_no_ipv6_tip_important": "Se está dispoñible, IPv6 debería estar automáticamente configurado polo sistema ou o teu provedor. Se non, pode que teñas que facer algúns axustes manualmente tal como se explica na documentación: <a href='https://yunohost.org/#/ipv6'>https://yunohost.org/#/ipv6</a>."
|
||||||
}
|
}
|
|
@ -618,7 +618,6 @@
|
||||||
"domain_config_auth_consumer_key": "Chiave consumatore",
|
"domain_config_auth_consumer_key": "Chiave consumatore",
|
||||||
"ldap_attribute_already_exists": "L’attributo LDAP '{attribute}' esiste già con il valore '{value}'",
|
"ldap_attribute_already_exists": "L’attributo LDAP '{attribute}' esiste già con il valore '{value}'",
|
||||||
"config_validate_time": "È necessario inserire un orario valido, come HH:MM",
|
"config_validate_time": "È necessario inserire un orario valido, come HH:MM",
|
||||||
"config_version_not_supported": "Le versioni '{version}' del pannello di configurazione non sono supportate.",
|
|
||||||
"danger": "Attenzione:",
|
"danger": "Attenzione:",
|
||||||
"log_domain_config_set": "Aggiorna la configurazione per il dominio '{}'",
|
"log_domain_config_set": "Aggiorna la configurazione per il dominio '{}'",
|
||||||
"domain_dns_push_managed_in_parent_domain": "La configurazione automatica del DNS è gestita nel dominio genitore {parent_domain}.",
|
"domain_dns_push_managed_in_parent_domain": "La configurazione automatica del DNS è gestita nel dominio genitore {parent_domain}.",
|
||||||
|
|
1
locales/lt.json
Normal file
1
locales/lt.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -204,7 +204,6 @@
|
||||||
"config_cant_set_value_on_section": "Você não pode setar um único valor na seção de configuração inteira.",
|
"config_cant_set_value_on_section": "Você não pode setar um único valor na seção de configuração inteira.",
|
||||||
"config_validate_time": "Deve ser um horário válido como HH:MM",
|
"config_validate_time": "Deve ser um horário válido como HH:MM",
|
||||||
"config_validate_url": "Deve ser uma URL válida",
|
"config_validate_url": "Deve ser uma URL válida",
|
||||||
"config_version_not_supported": "Versões do painel de configuração '{version}' não são suportadas.",
|
|
||||||
"danger": "Perigo:",
|
"danger": "Perigo:",
|
||||||
"diagnosis_basesystem_ynh_inconsistent_versions": "Você está executando versões inconsistentes dos pacotes YunoHost... provavelmente por causa de uma atualização parcial ou que falhou.",
|
"diagnosis_basesystem_ynh_inconsistent_versions": "Você está executando versões inconsistentes dos pacotes YunoHost... provavelmente por causa de uma atualização parcial ou que falhou.",
|
||||||
"diagnosis_description_basesystem": "Sistema base",
|
"diagnosis_description_basesystem": "Sistema base",
|
||||||
|
|
|
@ -106,7 +106,6 @@
|
||||||
"certmanager_domain_dns_ip_differs_from_public_ip": "DNS-записи для домена '{domain}' отличаются от IP этого сервера. Пожалуйста, проверьте категорию 'DNS-записи' (основные) в диагностике для получения дополнительной информации. Если вы недавно изменили свою A-запись, пожалуйста, подождите, пока она распространится (некоторые программы проверки распространения DNS доступны в интернете). (Если вы знаете, что делаете, используйте '--no-checks', чтобы отключить эти проверки.)",
|
"certmanager_domain_dns_ip_differs_from_public_ip": "DNS-записи для домена '{domain}' отличаются от IP этого сервера. Пожалуйста, проверьте категорию 'DNS-записи' (основные) в диагностике для получения дополнительной информации. Если вы недавно изменили свою A-запись, пожалуйста, подождите, пока она распространится (некоторые программы проверки распространения DNS доступны в интернете). (Если вы знаете, что делаете, используйте '--no-checks', чтобы отключить эти проверки.)",
|
||||||
"certmanager_domain_not_diagnosed_yet": "Для домена {domain} еще нет результатов диагностики. Пожалуйста, перезапустите диагностику для категорий 'DNS-записи' и 'Домены', чтобы проверить, готов ли домен к Let's Encrypt. (Или, если вы знаете, что делаете, используйте '--no-checks', чтобы отключить эти проверки.)",
|
"certmanager_domain_not_diagnosed_yet": "Для домена {domain} еще нет результатов диагностики. Пожалуйста, перезапустите диагностику для категорий 'DNS-записи' и 'Домены', чтобы проверить, готов ли домен к Let's Encrypt. (Или, если вы знаете, что делаете, используйте '--no-checks', чтобы отключить эти проверки.)",
|
||||||
"config_validate_url": "Должна быть правильная ссылка",
|
"config_validate_url": "Должна быть правильная ссылка",
|
||||||
"config_version_not_supported": "Версии конфигурационной панели '{version}' не поддерживаются.",
|
|
||||||
"confirm_app_install_danger": "ОПАСНО! Это приложение все еще является экспериментальным (если не сказать, что оно явно не работает)! Вам НЕ следует устанавливать его, если вы НЕ знаете, что делаете. Если это приложение не будет работать или сломает вашу систему, мы НЕ будем оказывать техническую поддержку... Если вы все равно готовы рискнуть, введите '{answers}'",
|
"confirm_app_install_danger": "ОПАСНО! Это приложение все еще является экспериментальным (если не сказать, что оно явно не работает)! Вам НЕ следует устанавливать его, если вы НЕ знаете, что делаете. Если это приложение не будет работать или сломает вашу систему, мы НЕ будем оказывать техническую поддержку... Если вы все равно готовы рискнуть, введите '{answers}'",
|
||||||
"confirm_app_install_thirdparty": "ВАЖНО! Это приложение не входит в каталог приложений YunoHost. Установка сторонних приложений может нарушить целостность и безопасность вашей системы. Вам НЕ следует устанавливать его, если вы НЕ знаете, что делаете. Если это приложение не будет работать или сломает вашу систему, мы НЕ будем оказывать техническую поддержку... Если вы все равно готовы рискнуть, введите '{answers}'",
|
"confirm_app_install_thirdparty": "ВАЖНО! Это приложение не входит в каталог приложений YunoHost. Установка сторонних приложений может нарушить целостность и безопасность вашей системы. Вам НЕ следует устанавливать его, если вы НЕ знаете, что делаете. Если это приложение не будет работать или сломает вашу систему, мы НЕ будем оказывать техническую поддержку... Если вы все равно готовы рискнуть, введите '{answers}'",
|
||||||
"config_apply_failed": "Не удалось применить новую конфигурацию: {error}",
|
"config_apply_failed": "Не удалось применить новую конфигурацию: {error}",
|
||||||
|
|
|
@ -144,7 +144,6 @@
|
||||||
"config_validate_email": "Toto by mal byť platný e-mail",
|
"config_validate_email": "Toto by mal byť platný e-mail",
|
||||||
"config_validate_time": "Toto by mal byť platný čas vo formáte HH:MM",
|
"config_validate_time": "Toto by mal byť platný čas vo formáte HH:MM",
|
||||||
"config_validate_url": "Toto by mala byť platná URL adresa webu",
|
"config_validate_url": "Toto by mala byť platná URL adresa webu",
|
||||||
"config_version_not_supported": "Verzie konfiguračného panela '{version}' nie sú podporované.",
|
|
||||||
"danger": "Nebezpečenstvo:",
|
"danger": "Nebezpečenstvo:",
|
||||||
"confirm_app_install_danger": "NEBEZPEČENSTVO! Táto aplikácia je experimentálna (ak vôbec funguje)! Pravdepodobne by ste ju NEMALI inštalovať, pokiaľ si nie ste istý, čo robíte. NEPOSKYTNEME VÁM ŽIADNU POMOC, ak táto aplikácia nebude fungovať alebo rozbije Váš systém… Ak sa rozhodnete i napriek tomu podstúpiť toto riziko, zadajte '{answers}'",
|
"confirm_app_install_danger": "NEBEZPEČENSTVO! Táto aplikácia je experimentálna (ak vôbec funguje)! Pravdepodobne by ste ju NEMALI inštalovať, pokiaľ si nie ste istý, čo robíte. NEPOSKYTNEME VÁM ŽIADNU POMOC, ak táto aplikácia nebude fungovať alebo rozbije Váš systém… Ak sa rozhodnete i napriek tomu podstúpiť toto riziko, zadajte '{answers}'",
|
||||||
"confirm_app_install_thirdparty": "NEBEZPEČENSTVO! Táto aplikácia nie je súčasťou katalógu aplikácií YunoHost. Inštalovaním aplikácií tretích strán môžete ohroziť integritu a bezpečnosť Vášho systému. Pravdepodobne by ste NEMALI pokračovať v inštalácií, pokiaľ neviete, čo robíte. NEPOSKYTNEME VÁM ŽIADNU POMOC, ak táto aplikácia nebude fungovať alebo rozbije Váš systém… Ak sa rozhodnete i napriek tomu podstúpiť toto riziko, zadajte '{answers}'",
|
"confirm_app_install_thirdparty": "NEBEZPEČENSTVO! Táto aplikácia nie je súčasťou katalógu aplikácií YunoHost. Inštalovaním aplikácií tretích strán môžete ohroziť integritu a bezpečnosť Vášho systému. Pravdepodobne by ste NEMALI pokračovať v inštalácií, pokiaľ neviete, čo robíte. NEPOSKYTNEME VÁM ŽIADNU POMOC, ak táto aplikácia nebude fungovať alebo rozbije Váš systém… Ak sa rozhodnete i napriek tomu podstúpiť toto riziko, zadajte '{answers}'",
|
||||||
|
|
|
@ -5,5 +5,15 @@
|
||||||
"already_up_to_date": "Yapılacak yeni bir şey yok. Her şey zaten güncel.",
|
"already_up_to_date": "Yapılacak yeni bir şey yok. Her şey zaten güncel.",
|
||||||
"app_action_broke_system": "Bu işlem bazı hizmetleri bozmuş olabilir: {services}",
|
"app_action_broke_system": "Bu işlem bazı hizmetleri bozmuş olabilir: {services}",
|
||||||
"good_practices_about_user_password": "Şimdi yeni bir kullanıcı şifresi tanımlamak üzeresiniz. Parola en az 8 karakter uzunluğunda olmalıdır - ancak daha uzun bir parola (yani bir parola) ve/veya çeşitli karakterler (büyük harf, küçük harf, rakamlar ve özel karakterler) daha iyidir.",
|
"good_practices_about_user_password": "Şimdi yeni bir kullanıcı şifresi tanımlamak üzeresiniz. Parola en az 8 karakter uzunluğunda olmalıdır - ancak daha uzun bir parola (yani bir parola) ve/veya çeşitli karakterler (büyük harf, küçük harf, rakamlar ve özel karakterler) daha iyidir.",
|
||||||
"aborting": "İptal ediliyor."
|
"aborting": "İptal ediliyor.",
|
||||||
|
"app_action_failed": "{app} uygulaması için {action} eylemini çalıştırma başarısız",
|
||||||
|
"admins": "Yöneticiler",
|
||||||
|
"all_users": "Tüm YunoHost kullanıcıları",
|
||||||
|
"app_already_up_to_date": "{app} zaten güncel",
|
||||||
|
"app_already_installed": "{app} zaten kurulu",
|
||||||
|
"app_already_installed_cant_change_url": "Bu uygulama zaten kurulu. URL yalnızca bu işlev kullanarak değiştirilemez. Eğer varsa `app changeurl`'i kontrol edin.",
|
||||||
|
"additional_urls_already_added": "Ek URL '{url}' zaten '{permission}' izni için ek URL'ye eklendi",
|
||||||
|
"additional_urls_already_removed": "Ek URL '{url}', '{permission}' izni için ek URL'de zaten kaldırıldı",
|
||||||
|
"app_action_cannot_be_ran_because_required_services_down": "Bu eylemi gerçekleştirmek için şu servisler çalışıyor olmalıdır: {services}. Devam etmek için onları yeniden başlatın (ve muhtemelen neden çalışmadığını araştırın).",
|
||||||
|
"app_arch_not_supported": "Bu uygulama yalnızca {', '.join(required)} işlemci mimarisi üzerine kurulabilir ancak sunucunuzun işlemci mimarisi {current}."
|
||||||
}
|
}
|
|
@ -587,7 +587,6 @@
|
||||||
"config_validate_email": "Е-пошта має бути дійсною",
|
"config_validate_email": "Е-пошта має бути дійсною",
|
||||||
"config_validate_time": "Час має бути дійсним, наприклад ГГ:ХХ",
|
"config_validate_time": "Час має бути дійсним, наприклад ГГ:ХХ",
|
||||||
"config_validate_url": "Вебадреса має бути дійсною",
|
"config_validate_url": "Вебадреса має бути дійсною",
|
||||||
"config_version_not_supported": "Версії конфігураційної панелі '{version}' не підтримуються.",
|
|
||||||
"danger": "Небезпека:",
|
"danger": "Небезпека:",
|
||||||
"invalid_number_min": "Має бути більшим за {min}",
|
"invalid_number_min": "Має бути більшим за {min}",
|
||||||
"invalid_number_max": "Має бути меншим за {max}",
|
"invalid_number_max": "Має бути меншим за {max}",
|
||||||
|
|
|
@ -571,7 +571,6 @@
|
||||||
"config_validate_email": "是有效的电子邮件",
|
"config_validate_email": "是有效的电子邮件",
|
||||||
"config_validate_time": "应该是像 HH:MM 这样的有效时间",
|
"config_validate_time": "应该是像 HH:MM 这样的有效时间",
|
||||||
"config_validate_url": "应该是有效的URL",
|
"config_validate_url": "应该是有效的URL",
|
||||||
"config_version_not_supported": "不支持配置面板版本“{ version }”。",
|
|
||||||
"danger": "警告:",
|
"danger": "警告:",
|
||||||
"diagnosis_apps_allgood": "所有已安装的应用程序都遵守基本的打包原则",
|
"diagnosis_apps_allgood": "所有已安装的应用程序都遵守基本的打包原则",
|
||||||
"diagnosis_apps_deprecated_practices": "此应用程序的安装 版本仍然使用一些超旧的弃用打包原则。推荐您升级它。",
|
"diagnosis_apps_deprecated_practices": "此应用程序的安装 版本仍然使用一些超旧的弃用打包原则。推荐您升级它。",
|
||||||
|
|
|
@ -32,7 +32,6 @@ def autofix_i18n_placeholders():
|
||||||
|
|
||||||
# We iterate over all keys/string in en.json
|
# We iterate over all keys/string in en.json
|
||||||
for key, string in reference.items():
|
for key, string in reference.items():
|
||||||
|
|
||||||
# Ignore check if there's no translation yet for this key
|
# Ignore check if there's no translation yet for this key
|
||||||
if key not in this_locale:
|
if key not in this_locale:
|
||||||
continue
|
continue
|
||||||
|
@ -89,7 +88,6 @@ Please fix it manually !
|
||||||
|
|
||||||
def autofix_orthotypography_and_standardized_words():
|
def autofix_orthotypography_and_standardized_words():
|
||||||
def reformat(lang, transformations):
|
def reformat(lang, transformations):
|
||||||
|
|
||||||
locale = open(f"{LOCALE_FOLDER}{lang}.json").read()
|
locale = open(f"{LOCALE_FOLDER}{lang}.json").read()
|
||||||
for pattern, replace in transformations.items():
|
for pattern, replace in transformations.items():
|
||||||
locale = re.compile(pattern).sub(replace, locale)
|
locale = re.compile(pattern).sub(replace, locale)
|
||||||
|
@ -146,11 +144,9 @@ def autofix_orthotypography_and_standardized_words():
|
||||||
|
|
||||||
|
|
||||||
def remove_stale_translated_strings():
|
def remove_stale_translated_strings():
|
||||||
|
|
||||||
reference = json.loads(open(LOCALE_FOLDER + "en.json").read())
|
reference = json.loads(open(LOCALE_FOLDER + "en.json").read())
|
||||||
|
|
||||||
for locale_file in TRANSLATION_FILES:
|
for locale_file in TRANSLATION_FILES:
|
||||||
|
|
||||||
print(locale_file)
|
print(locale_file)
|
||||||
this_locale = json.loads(
|
this_locale = json.loads(
|
||||||
open(LOCALE_FOLDER + locale_file).read(), object_pairs_hook=OrderedDict
|
open(LOCALE_FOLDER + locale_file).read(), object_pairs_hook=OrderedDict
|
||||||
|
|
|
@ -19,7 +19,6 @@ REFERENCE_FILE = LOCALE_FOLDER + "en.json"
|
||||||
|
|
||||||
|
|
||||||
def find_expected_string_keys():
|
def find_expected_string_keys():
|
||||||
|
|
||||||
# Try to find :
|
# Try to find :
|
||||||
# m18n.n( "foo"
|
# m18n.n( "foo"
|
||||||
# YunohostError("foo"
|
# YunohostError("foo"
|
||||||
|
@ -197,7 +196,6 @@ undefined_keys = sorted(undefined_keys)
|
||||||
|
|
||||||
mode = sys.argv[1].strip("-")
|
mode = sys.argv[1].strip("-")
|
||||||
if mode == "check":
|
if mode == "check":
|
||||||
|
|
||||||
# Unused keys are not too problematic, will be automatically
|
# Unused keys are not too problematic, will be automatically
|
||||||
# removed by the other autoreformat script,
|
# removed by the other autoreformat script,
|
||||||
# but still informative to display them
|
# but still informative to display them
|
||||||
|
|
|
@ -116,6 +116,11 @@ user:
|
||||||
pattern: &pattern_mailbox_quota
|
pattern: &pattern_mailbox_quota
|
||||||
- !!str ^(\d+[bkMGT])|0$
|
- !!str ^(\d+[bkMGT])|0$
|
||||||
- "pattern_mailbox_quota"
|
- "pattern_mailbox_quota"
|
||||||
|
-s:
|
||||||
|
full: --loginShell
|
||||||
|
help: The login shell used
|
||||||
|
default: "/bin/bash"
|
||||||
|
|
||||||
|
|
||||||
### user_delete()
|
### user_delete()
|
||||||
delete:
|
delete:
|
||||||
|
@ -195,6 +200,10 @@ user:
|
||||||
metavar: "{SIZE|0}"
|
metavar: "{SIZE|0}"
|
||||||
extra:
|
extra:
|
||||||
pattern: *pattern_mailbox_quota
|
pattern: *pattern_mailbox_quota
|
||||||
|
-s:
|
||||||
|
full: --loginShell
|
||||||
|
help: The login shell used
|
||||||
|
default: "/bin/bash"
|
||||||
|
|
||||||
### user_info()
|
### user_info()
|
||||||
info:
|
info:
|
||||||
|
|
|
@ -64,24 +64,24 @@ name = "Certificate"
|
||||||
[cert.cert.acme_eligible_explain]
|
[cert.cert.acme_eligible_explain]
|
||||||
type = "alert"
|
type = "alert"
|
||||||
style = "warning"
|
style = "warning"
|
||||||
visible = "acme_eligible == false || acme_elligible == null"
|
visible = "acme_eligible == false || acme_eligible == null"
|
||||||
|
|
||||||
[cert.cert.cert_no_checks]
|
[cert.cert.cert_no_checks]
|
||||||
ask = "Ignore diagnosis checks"
|
ask = "Ignore diagnosis checks"
|
||||||
type = "boolean"
|
type = "boolean"
|
||||||
default = false
|
default = false
|
||||||
visible = "acme_eligible == false || acme_elligible == null"
|
visible = "acme_eligible == false || acme_eligible == null"
|
||||||
|
|
||||||
[cert.cert.cert_install]
|
[cert.cert.cert_install]
|
||||||
type = "button"
|
type = "button"
|
||||||
icon = "star"
|
icon = "star"
|
||||||
style = "success"
|
style = "success"
|
||||||
visible = "issuer != 'letsencrypt'"
|
visible = "cert_issuer != 'letsencrypt'"
|
||||||
enabled = "acme_eligible || cert_no_checks"
|
enabled = "acme_eligible || cert_no_checks"
|
||||||
|
|
||||||
[cert.cert.cert_renew]
|
[cert.cert.cert_renew]
|
||||||
type = "button"
|
type = "button"
|
||||||
icon = "refresh"
|
icon = "refresh"
|
||||||
style = "warning"
|
style = "warning"
|
||||||
visible = "issuer == 'letsencrypt'"
|
visible = "cert_issuer == 'letsencrypt'"
|
||||||
enabled = "acme_eligible || cert_no_checks"
|
enabled = "acme_eligible || cert_no_checks"
|
||||||
|
|
|
@ -160,3 +160,12 @@ name = "Other"
|
||||||
[misc.backup.backup_compress_tar_archives]
|
[misc.backup.backup_compress_tar_archives]
|
||||||
type = "boolean"
|
type = "boolean"
|
||||||
default = false
|
default = false
|
||||||
|
|
||||||
|
[misc.network]
|
||||||
|
name = "Network"
|
||||||
|
[misc.network.dns_exposure]
|
||||||
|
type = "select"
|
||||||
|
choices.both = "Both"
|
||||||
|
choices.ipv4 = "IPv4 Only"
|
||||||
|
choices.ipv6 = "IPv6 Only"
|
||||||
|
default = "both"
|
||||||
|
|
|
@ -32,7 +32,6 @@ def is_installed():
|
||||||
|
|
||||||
|
|
||||||
def cli(debug, quiet, output_as, timeout, args, parser):
|
def cli(debug, quiet, output_as, timeout, args, parser):
|
||||||
|
|
||||||
init_logging(interface="cli", debug=debug, quiet=quiet)
|
init_logging(interface="cli", debug=debug, quiet=quiet)
|
||||||
|
|
||||||
# Check that YunoHost is installed
|
# Check that YunoHost is installed
|
||||||
|
@ -51,7 +50,6 @@ def cli(debug, quiet, output_as, timeout, args, parser):
|
||||||
|
|
||||||
|
|
||||||
def api(debug, host, port):
|
def api(debug, host, port):
|
||||||
|
|
||||||
init_logging(interface="api", debug=debug)
|
init_logging(interface="api", debug=debug)
|
||||||
|
|
||||||
def is_installed_api():
|
def is_installed_api():
|
||||||
|
@ -71,7 +69,6 @@ def api(debug, host, port):
|
||||||
|
|
||||||
|
|
||||||
def check_command_is_valid_before_postinstall(args):
|
def check_command_is_valid_before_postinstall(args):
|
||||||
|
|
||||||
allowed_if_not_postinstalled = [
|
allowed_if_not_postinstalled = [
|
||||||
"tools postinstall",
|
"tools postinstall",
|
||||||
"tools versions",
|
"tools versions",
|
||||||
|
@ -109,7 +106,6 @@ def init_i18n():
|
||||||
|
|
||||||
|
|
||||||
def init_logging(interface="cli", debug=False, quiet=False, logdir="/var/log/yunohost"):
|
def init_logging(interface="cli", debug=False, quiet=False, logdir="/var/log/yunohost"):
|
||||||
|
|
||||||
logfile = os.path.join(logdir, "yunohost-%s.log" % interface)
|
logfile = os.path.join(logdir, "yunohost-%s.log" % interface)
|
||||||
|
|
||||||
if not os.path.isdir(logdir):
|
if not os.path.isdir(logdir):
|
||||||
|
|
36
src/app.py
36
src/app.py
|
@ -238,7 +238,6 @@ def app_info(app, full=False, upgradable=False):
|
||||||
|
|
||||||
|
|
||||||
def _app_upgradable(app_infos):
|
def _app_upgradable(app_infos):
|
||||||
|
|
||||||
# Determine upgradability
|
# Determine upgradability
|
||||||
|
|
||||||
app_in_catalog = app_infos.get("from_catalog")
|
app_in_catalog = app_infos.get("from_catalog")
|
||||||
|
@ -374,7 +373,6 @@ def app_map(app=None, raw=False, user=None):
|
||||||
)
|
)
|
||||||
|
|
||||||
for url in perm_all_urls:
|
for url in perm_all_urls:
|
||||||
|
|
||||||
# Here, we decide to completely ignore regex-type urls ...
|
# Here, we decide to completely ignore regex-type urls ...
|
||||||
# Because :
|
# Because :
|
||||||
# - displaying them in regular "yunohost app map" output creates
|
# - displaying them in regular "yunohost app map" output creates
|
||||||
|
@ -716,7 +714,6 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
|
|
||||||
# If upgrade failed, try to restore the safety backup
|
# If upgrade failed, try to restore the safety backup
|
||||||
if (
|
if (
|
||||||
upgrade_failed
|
upgrade_failed
|
||||||
|
@ -762,7 +759,6 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False
|
||||||
# If upgrade failed or broke the system,
|
# If upgrade failed or broke the system,
|
||||||
# raise an error and interrupt all other pending upgrades
|
# raise an error and interrupt all other pending upgrades
|
||||||
if upgrade_failed or broke_the_system:
|
if upgrade_failed or broke_the_system:
|
||||||
|
|
||||||
# display this if there are remaining apps
|
# display this if there are remaining apps
|
||||||
if apps[number + 1 :]:
|
if apps[number + 1 :]:
|
||||||
not_upgraded_apps = apps[number:]
|
not_upgraded_apps = apps[number:]
|
||||||
|
@ -843,7 +839,6 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False
|
||||||
|
|
||||||
|
|
||||||
def app_manifest(app, with_screenshot=False):
|
def app_manifest(app, with_screenshot=False):
|
||||||
|
|
||||||
manifest, extracted_app_folder = _extract_app(app)
|
manifest, extracted_app_folder = _extract_app(app)
|
||||||
|
|
||||||
raw_questions = manifest.get("install", {}).values()
|
raw_questions = manifest.get("install", {}).values()
|
||||||
|
@ -886,7 +881,6 @@ def app_manifest(app, with_screenshot=False):
|
||||||
|
|
||||||
|
|
||||||
def _confirm_app_install(app, force=False):
|
def _confirm_app_install(app, force=False):
|
||||||
|
|
||||||
# Ignore if there's nothing for confirm (good quality app), if --force is used
|
# Ignore if there's nothing for confirm (good quality app), if --force is used
|
||||||
# or if request on the API (confirm already implemented on the API side)
|
# or if request on the API (confirm already implemented on the API side)
|
||||||
if force or Moulinette.interface.type == "api":
|
if force or Moulinette.interface.type == "api":
|
||||||
|
@ -1036,7 +1030,6 @@ def app_install(
|
||||||
# If packaging_format v2+, save all install questions as settings
|
# If packaging_format v2+, save all install questions as settings
|
||||||
if packaging_format >= 2:
|
if packaging_format >= 2:
|
||||||
for question in questions:
|
for question in questions:
|
||||||
|
|
||||||
# Except user-provider passwords
|
# Except user-provider passwords
|
||||||
if question.type == "password":
|
if question.type == "password":
|
||||||
continue
|
continue
|
||||||
|
@ -1135,7 +1128,6 @@ def app_install(
|
||||||
|
|
||||||
# If the install failed or broke the system, we remove it
|
# If the install failed or broke the system, we remove it
|
||||||
if install_failed or broke_the_system:
|
if install_failed or broke_the_system:
|
||||||
|
|
||||||
# This option is meant for packagers to debug their apps more easily
|
# This option is meant for packagers to debug their apps more easily
|
||||||
if no_remove_on_failure:
|
if no_remove_on_failure:
|
||||||
raise YunohostError(
|
raise YunohostError(
|
||||||
|
@ -1390,7 +1382,6 @@ def app_setting(app, key, value=None, delete=False):
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_legacy_permission_setting:
|
if is_legacy_permission_setting:
|
||||||
|
|
||||||
from yunohost.permission import (
|
from yunohost.permission import (
|
||||||
user_permission_list,
|
user_permission_list,
|
||||||
user_permission_update,
|
user_permission_update,
|
||||||
|
@ -1433,7 +1424,6 @@ def app_setting(app, key, value=None, delete=False):
|
||||||
|
|
||||||
# SET
|
# SET
|
||||||
else:
|
else:
|
||||||
|
|
||||||
urls = value
|
urls = value
|
||||||
# If the request is about the root of the app (/), ( = the vast majority of cases)
|
# If the request is about the root of the app (/), ( = the vast majority of cases)
|
||||||
# we interpret this as a change for the main permission
|
# we interpret this as a change for the main permission
|
||||||
|
@ -1445,7 +1435,6 @@ def app_setting(app, key, value=None, delete=False):
|
||||||
else:
|
else:
|
||||||
user_permission_update(app + ".main", remove="visitors")
|
user_permission_update(app + ".main", remove="visitors")
|
||||||
else:
|
else:
|
||||||
|
|
||||||
urls = urls.split(",")
|
urls = urls.split(",")
|
||||||
if key.endswith("_regex"):
|
if key.endswith("_regex"):
|
||||||
urls = ["re:" + url for url in urls]
|
urls = ["re:" + url for url in urls]
|
||||||
|
@ -1604,7 +1593,6 @@ def app_ssowatconf():
|
||||||
)
|
)
|
||||||
|
|
||||||
for app in _installed_apps():
|
for app in _installed_apps():
|
||||||
|
|
||||||
app_settings = read_yaml(APPS_SETTING_PATH + app + "/settings.yml") or {}
|
app_settings = read_yaml(APPS_SETTING_PATH + app + "/settings.yml") or {}
|
||||||
|
|
||||||
# Redirected
|
# Redirected
|
||||||
|
@ -1630,7 +1618,6 @@ def app_ssowatconf():
|
||||||
|
|
||||||
# New permission system
|
# New permission system
|
||||||
for perm_name, perm_info in all_permissions.items():
|
for perm_name, perm_info in all_permissions.items():
|
||||||
|
|
||||||
uris = (
|
uris = (
|
||||||
[]
|
[]
|
||||||
+ ([perm_info["url"]] if perm_info["url"] else [])
|
+ ([perm_info["url"]] if perm_info["url"] else [])
|
||||||
|
@ -1694,13 +1681,11 @@ def app_change_label(app, new_label):
|
||||||
|
|
||||||
|
|
||||||
def app_action_list(app):
|
def app_action_list(app):
|
||||||
|
|
||||||
return AppConfigPanel(app).list_actions()
|
return AppConfigPanel(app).list_actions()
|
||||||
|
|
||||||
|
|
||||||
@is_unit_operation()
|
@is_unit_operation()
|
||||||
def app_action_run(operation_logger, app, action, args=None, args_file=None):
|
def app_action_run(operation_logger, app, action, args=None, args_file=None):
|
||||||
|
|
||||||
return AppConfigPanel(app).run_action(
|
return AppConfigPanel(app).run_action(
|
||||||
action, args=args, args_file=args_file, operation_logger=operation_logger
|
action, args=args, args_file=args_file, operation_logger=operation_logger
|
||||||
)
|
)
|
||||||
|
@ -2036,12 +2021,10 @@ def _get_manifest_of_app(path):
|
||||||
|
|
||||||
|
|
||||||
def _parse_app_doc_and_notifications(path):
|
def _parse_app_doc_and_notifications(path):
|
||||||
|
|
||||||
doc = {}
|
doc = {}
|
||||||
notification_names = ["PRE_INSTALL", "POST_INSTALL", "PRE_UPGRADE", "POST_UPGRADE"]
|
notification_names = ["PRE_INSTALL", "POST_INSTALL", "PRE_UPGRADE", "POST_UPGRADE"]
|
||||||
|
|
||||||
for filepath in glob.glob(os.path.join(path, "doc") + "/*.md"):
|
for filepath in glob.glob(os.path.join(path, "doc") + "/*.md"):
|
||||||
|
|
||||||
# to be improved : [a-z]{2,3} is a clumsy way of parsing the
|
# to be improved : [a-z]{2,3} is a clumsy way of parsing the
|
||||||
# lang code ... some lang code are more complex that this é_è
|
# lang code ... some lang code are more complex that this é_è
|
||||||
m = re.match("([A-Z]*)(_[a-z]{2,3})?.md", filepath.split("/")[-1])
|
m = re.match("([A-Z]*)(_[a-z]{2,3})?.md", filepath.split("/")[-1])
|
||||||
|
@ -2091,11 +2074,9 @@ def _parse_app_doc_and_notifications(path):
|
||||||
|
|
||||||
|
|
||||||
def _hydrate_app_template(template, data):
|
def _hydrate_app_template(template, data):
|
||||||
|
|
||||||
stuff_to_replace = set(re.findall(r"__[A-Z0-9]+?[A-Z0-9_]*?[A-Z0-9]*?__", template))
|
stuff_to_replace = set(re.findall(r"__[A-Z0-9]+?[A-Z0-9_]*?[A-Z0-9]*?__", template))
|
||||||
|
|
||||||
for stuff in stuff_to_replace:
|
for stuff in stuff_to_replace:
|
||||||
|
|
||||||
varname = stuff.strip("_").lower()
|
varname = stuff.strip("_").lower()
|
||||||
|
|
||||||
if varname in data:
|
if varname in data:
|
||||||
|
@ -2105,7 +2086,6 @@ def _hydrate_app_template(template, data):
|
||||||
|
|
||||||
|
|
||||||
def _convert_v1_manifest_to_v2(manifest):
|
def _convert_v1_manifest_to_v2(manifest):
|
||||||
|
|
||||||
manifest = copy.deepcopy(manifest)
|
manifest = copy.deepcopy(manifest)
|
||||||
|
|
||||||
if "upstream" not in manifest:
|
if "upstream" not in manifest:
|
||||||
|
@ -2186,7 +2166,6 @@ def _convert_v1_manifest_to_v2(manifest):
|
||||||
|
|
||||||
|
|
||||||
def _set_default_ask_questions(questions, script_name="install"):
|
def _set_default_ask_questions(questions, script_name="install"):
|
||||||
|
|
||||||
# arguments is something like
|
# arguments is something like
|
||||||
# { "domain":
|
# { "domain":
|
||||||
# {
|
# {
|
||||||
|
@ -2244,7 +2223,6 @@ def _set_default_ask_questions(questions, script_name="install"):
|
||||||
|
|
||||||
|
|
||||||
def _is_app_repo_url(string: str) -> bool:
|
def _is_app_repo_url(string: str) -> bool:
|
||||||
|
|
||||||
string = string.strip()
|
string = string.strip()
|
||||||
|
|
||||||
# Dummy test for ssh-based stuff ... should probably be improved somehow
|
# Dummy test for ssh-based stuff ... should probably be improved somehow
|
||||||
|
@ -2261,7 +2239,6 @@ def _app_quality(src: str) -> str:
|
||||||
|
|
||||||
raw_app_catalog = _load_apps_catalog()["apps"]
|
raw_app_catalog = _load_apps_catalog()["apps"]
|
||||||
if src in raw_app_catalog or _is_app_repo_url(src):
|
if src in raw_app_catalog or _is_app_repo_url(src):
|
||||||
|
|
||||||
# If we got an app name directly (e.g. just "wordpress"), we gonna test this name
|
# If we got an app name directly (e.g. just "wordpress"), we gonna test this name
|
||||||
if src in raw_app_catalog:
|
if src in raw_app_catalog:
|
||||||
app_name_to_test = src
|
app_name_to_test = src
|
||||||
|
@ -2274,7 +2251,6 @@ def _app_quality(src: str) -> str:
|
||||||
return "thirdparty"
|
return "thirdparty"
|
||||||
|
|
||||||
if app_name_to_test in raw_app_catalog:
|
if app_name_to_test in raw_app_catalog:
|
||||||
|
|
||||||
state = raw_app_catalog[app_name_to_test].get("state", "notworking")
|
state = raw_app_catalog[app_name_to_test].get("state", "notworking")
|
||||||
level = raw_app_catalog[app_name_to_test].get("level", None)
|
level = raw_app_catalog[app_name_to_test].get("level", None)
|
||||||
if state in ["working", "validated"]:
|
if state in ["working", "validated"]:
|
||||||
|
@ -2385,7 +2361,6 @@ def _extract_app_from_folder(path: str) -> Tuple[Dict, str]:
|
||||||
def _extract_app_from_gitrepo(
|
def _extract_app_from_gitrepo(
|
||||||
url: str, branch: Optional[str] = None, revision: str = "HEAD", app_info: Dict = {}
|
url: str, branch: Optional[str] = None, revision: str = "HEAD", app_info: Dict = {}
|
||||||
) -> Tuple[Dict, str]:
|
) -> Tuple[Dict, str]:
|
||||||
|
|
||||||
logger.debug("Checking default branch")
|
logger.debug("Checking default branch")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -2635,7 +2610,6 @@ def _check_manifest_requirements(
|
||||||
|
|
||||||
|
|
||||||
def _guess_webapp_path_requirement(app_folder: str) -> str:
|
def _guess_webapp_path_requirement(app_folder: str) -> str:
|
||||||
|
|
||||||
# If there's only one "domain" and "path", validate that domain/path
|
# If there's only one "domain" and "path", validate that domain/path
|
||||||
# is an available url and normalize the path.
|
# is an available url and normalize the path.
|
||||||
|
|
||||||
|
@ -2681,7 +2655,6 @@ def _guess_webapp_path_requirement(app_folder: str) -> str:
|
||||||
def _validate_webpath_requirement(
|
def _validate_webpath_requirement(
|
||||||
args: Dict[str, Any], path_requirement: str, ignore_app=None
|
args: Dict[str, Any], path_requirement: str, ignore_app=None
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
domain = args.get("domain")
|
domain = args.get("domain")
|
||||||
path = args.get("path")
|
path = args.get("path")
|
||||||
|
|
||||||
|
@ -2729,7 +2702,6 @@ def _get_conflicting_apps(domain, path, ignore_app=None):
|
||||||
|
|
||||||
|
|
||||||
def _assert_no_conflicting_apps(domain, path, ignore_app=None, full_domain=False):
|
def _assert_no_conflicting_apps(domain, path, ignore_app=None, full_domain=False):
|
||||||
|
|
||||||
conflicts = _get_conflicting_apps(domain, path, ignore_app)
|
conflicts = _get_conflicting_apps(domain, path, ignore_app)
|
||||||
|
|
||||||
if conflicts:
|
if conflicts:
|
||||||
|
@ -2748,7 +2720,6 @@ def _assert_no_conflicting_apps(domain, path, ignore_app=None, full_domain=False
|
||||||
def _make_environment_for_app_script(
|
def _make_environment_for_app_script(
|
||||||
app, args={}, args_prefix="APP_ARG_", workdir=None, action=None
|
app, args={}, args_prefix="APP_ARG_", workdir=None, action=None
|
||||||
):
|
):
|
||||||
|
|
||||||
app_setting_path = os.path.join(APPS_SETTING_PATH, app)
|
app_setting_path = os.path.join(APPS_SETTING_PATH, app)
|
||||||
|
|
||||||
manifest = _get_manifest_of_app(app_setting_path)
|
manifest = _get_manifest_of_app(app_setting_path)
|
||||||
|
@ -2777,7 +2748,6 @@ def _make_environment_for_app_script(
|
||||||
if manifest["packaging_format"] >= 2:
|
if manifest["packaging_format"] >= 2:
|
||||||
env_dict["app"] = app
|
env_dict["app"] = app
|
||||||
for setting_name, setting_value in _get_app_settings(app).items():
|
for setting_name, setting_value in _get_app_settings(app).items():
|
||||||
|
|
||||||
# Ignore special internal settings like checksum__
|
# Ignore special internal settings like checksum__
|
||||||
# (not a huge deal to load them but idk...)
|
# (not a huge deal to load them but idk...)
|
||||||
if setting_name.startswith("checksum__"):
|
if setting_name.startswith("checksum__"):
|
||||||
|
@ -2822,7 +2792,6 @@ def _parse_app_instance_name(app_instance_name: str) -> Tuple[str, int]:
|
||||||
|
|
||||||
|
|
||||||
def _next_instance_number_for_app(app):
|
def _next_instance_number_for_app(app):
|
||||||
|
|
||||||
# Get list of sibling apps, such as {app}, {app}__2, {app}__4
|
# Get list of sibling apps, such as {app}, {app}__2, {app}__4
|
||||||
apps = _installed_apps()
|
apps = _installed_apps()
|
||||||
sibling_app_ids = [a for a in apps if a == app or a.startswith(f"{app}__")]
|
sibling_app_ids = [a for a in apps if a == app or a.startswith(f"{app}__")]
|
||||||
|
@ -2840,7 +2809,6 @@ def _next_instance_number_for_app(app):
|
||||||
|
|
||||||
|
|
||||||
def _make_tmp_workdir_for_app(app=None):
|
def _make_tmp_workdir_for_app(app=None):
|
||||||
|
|
||||||
# Create parent dir if it doesn't exists yet
|
# Create parent dir if it doesn't exists yet
|
||||||
if not os.path.exists(APP_TMP_WORKDIRS):
|
if not os.path.exists(APP_TMP_WORKDIRS):
|
||||||
os.makedirs(APP_TMP_WORKDIRS)
|
os.makedirs(APP_TMP_WORKDIRS)
|
||||||
|
@ -2870,12 +2838,10 @@ def _make_tmp_workdir_for_app(app=None):
|
||||||
|
|
||||||
|
|
||||||
def unstable_apps():
|
def unstable_apps():
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
deprecated_apps = ["mailman", "ffsync"]
|
deprecated_apps = ["mailman", "ffsync"]
|
||||||
|
|
||||||
for infos in app_list(full=True)["apps"]:
|
for infos in app_list(full=True)["apps"]:
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not infos.get("from_catalog")
|
not infos.get("from_catalog")
|
||||||
or infos.get("from_catalog").get("state")
|
or infos.get("from_catalog").get("state")
|
||||||
|
@ -2891,7 +2857,6 @@ def unstable_apps():
|
||||||
|
|
||||||
|
|
||||||
def _assert_system_is_sane_for_app(manifest, when):
|
def _assert_system_is_sane_for_app(manifest, when):
|
||||||
|
|
||||||
from yunohost.service import service_status
|
from yunohost.service import service_status
|
||||||
|
|
||||||
logger.debug("Checking that required services are up and running...")
|
logger.debug("Checking that required services are up and running...")
|
||||||
|
@ -2954,7 +2919,6 @@ def _assert_system_is_sane_for_app(manifest, when):
|
||||||
|
|
||||||
|
|
||||||
def app_dismiss_notification(app, name):
|
def app_dismiss_notification(app, name):
|
||||||
|
|
||||||
assert isinstance(name, str)
|
assert isinstance(name, str)
|
||||||
name = name.lower()
|
name = name.lower()
|
||||||
assert name in ["post_install", "post_upgrade"]
|
assert name in ["post_install", "post_upgrade"]
|
||||||
|
|
|
@ -157,7 +157,6 @@ def _read_apps_catalog_list():
|
||||||
|
|
||||||
|
|
||||||
def _actual_apps_catalog_api_url(base_url):
|
def _actual_apps_catalog_api_url(base_url):
|
||||||
|
|
||||||
return f"{base_url}/v{APPS_CATALOG_API_VERSION}/apps.json"
|
return f"{base_url}/v{APPS_CATALOG_API_VERSION}/apps.json"
|
||||||
|
|
||||||
|
|
||||||
|
@ -269,7 +268,6 @@ def _load_apps_catalog():
|
||||||
merged_catalog = {"apps": {}, "categories": [], "antifeatures": []}
|
merged_catalog = {"apps": {}, "categories": [], "antifeatures": []}
|
||||||
|
|
||||||
for apps_catalog_id in [L["id"] for L in _read_apps_catalog_list()]:
|
for apps_catalog_id in [L["id"] for L in _read_apps_catalog_list()]:
|
||||||
|
|
||||||
# Let's load the json from cache for this catalog
|
# Let's load the json from cache for this catalog
|
||||||
cache_file = f"{APPS_CATALOG_CACHE}/{apps_catalog_id}.json"
|
cache_file = f"{APPS_CATALOG_CACHE}/{apps_catalog_id}.json"
|
||||||
|
|
||||||
|
@ -298,7 +296,6 @@ def _load_apps_catalog():
|
||||||
|
|
||||||
# Add apps from this catalog to the output
|
# Add apps from this catalog to the output
|
||||||
for app, info in apps_catalog_content["apps"].items():
|
for app, info in apps_catalog_content["apps"].items():
|
||||||
|
|
||||||
# (N.B. : there's a small edge case where multiple apps catalog could be listing the same apps ...
|
# (N.B. : there's a small edge case where multiple apps catalog could be listing the same apps ...
|
||||||
# in which case we keep only the first one found)
|
# in which case we keep only the first one found)
|
||||||
if app in merged_catalog["apps"]:
|
if app in merged_catalog["apps"]:
|
||||||
|
|
|
@ -38,14 +38,12 @@ AUTH_DN = "uid={uid},ou=users,dc=yunohost,dc=org"
|
||||||
|
|
||||||
|
|
||||||
class Authenticator(BaseAuthenticator):
|
class Authenticator(BaseAuthenticator):
|
||||||
|
|
||||||
name = "ldap_admin"
|
name = "ldap_admin"
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _authenticate_credentials(self, credentials=None):
|
def _authenticate_credentials(self, credentials=None):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
admins = (
|
admins = (
|
||||||
_get_ldap_interface()
|
_get_ldap_interface()
|
||||||
|
@ -125,7 +123,6 @@ class Authenticator(BaseAuthenticator):
|
||||||
con.unbind_s()
|
con.unbind_s()
|
||||||
|
|
||||||
def set_session_cookie(self, infos):
|
def set_session_cookie(self, infos):
|
||||||
|
|
||||||
from bottle import response
|
from bottle import response
|
||||||
|
|
||||||
assert isinstance(infos, dict)
|
assert isinstance(infos, dict)
|
||||||
|
@ -145,7 +142,6 @@ class Authenticator(BaseAuthenticator):
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_session_cookie(self, raise_if_no_session_exists=True):
|
def get_session_cookie(self, raise_if_no_session_exists=True):
|
||||||
|
|
||||||
from bottle import request
|
from bottle import request
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -174,7 +170,6 @@ class Authenticator(BaseAuthenticator):
|
||||||
return infos
|
return infos
|
||||||
|
|
||||||
def delete_session_cookie(self):
|
def delete_session_cookie(self):
|
||||||
|
|
||||||
from bottle import response
|
from bottle import response
|
||||||
|
|
||||||
response.set_cookie("yunohost.admin", "", max_age=-1)
|
response.set_cookie("yunohost.admin", "", max_age=-1)
|
||||||
|
|
|
@ -93,7 +93,6 @@ class BackupRestoreTargetsManager:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.targets = {}
|
self.targets = {}
|
||||||
self.results = {"system": {}, "apps": {}}
|
self.results = {"system": {}, "apps": {}}
|
||||||
|
|
||||||
|
@ -349,7 +348,6 @@ class BackupManager:
|
||||||
if not os.path.isdir(self.work_dir):
|
if not os.path.isdir(self.work_dir):
|
||||||
mkdir(self.work_dir, 0o750, parents=True)
|
mkdir(self.work_dir, 0o750, parents=True)
|
||||||
elif self.is_tmp_work_dir:
|
elif self.is_tmp_work_dir:
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"temporary directory for backup '%s' already exists... attempting to clean it",
|
"temporary directory for backup '%s' already exists... attempting to clean it",
|
||||||
self.work_dir,
|
self.work_dir,
|
||||||
|
@ -887,7 +885,6 @@ class RestoreManager:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success(self):
|
def success(self):
|
||||||
|
|
||||||
successful_apps = self.targets.list("apps", include=["Success", "Warning"])
|
successful_apps = self.targets.list("apps", include=["Success", "Warning"])
|
||||||
successful_system = self.targets.list("system", include=["Success", "Warning"])
|
successful_system = self.targets.list("system", include=["Success", "Warning"])
|
||||||
|
|
||||||
|
@ -1443,7 +1440,6 @@ class RestoreManager:
|
||||||
existing_groups = user_group_list()["groups"]
|
existing_groups = user_group_list()["groups"]
|
||||||
|
|
||||||
for permission_name, permission_infos in permissions.items():
|
for permission_name, permission_infos in permissions.items():
|
||||||
|
|
||||||
if "allowed" not in permission_infos:
|
if "allowed" not in permission_infos:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"'allowed' key corresponding to allowed groups for permission {permission_name} not found when restoring app {app_instance_name} … You might have to reconfigure permissions yourself."
|
f"'allowed' key corresponding to allowed groups for permission {permission_name} not found when restoring app {app_instance_name} … You might have to reconfigure permissions yourself."
|
||||||
|
@ -1547,7 +1543,6 @@ class RestoreManager:
|
||||||
self.targets.set_result("apps", app_instance_name, "Success")
|
self.targets.set_result("apps", app_instance_name, "Success")
|
||||||
operation_logger.success()
|
operation_logger.success()
|
||||||
else:
|
else:
|
||||||
|
|
||||||
self.targets.set_result("apps", app_instance_name, "Error")
|
self.targets.set_result("apps", app_instance_name, "Error")
|
||||||
|
|
||||||
remove_script = os.path.join(app_scripts_in_archive, "remove")
|
remove_script = os.path.join(app_scripts_in_archive, "remove")
|
||||||
|
@ -1938,12 +1933,10 @@ class CopyBackupMethod(BackupMethod):
|
||||||
|
|
||||||
|
|
||||||
class TarBackupMethod(BackupMethod):
|
class TarBackupMethod(BackupMethod):
|
||||||
|
|
||||||
method_name = "tar"
|
method_name = "tar"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _archive_file(self):
|
def _archive_file(self):
|
||||||
|
|
||||||
if isinstance(self.manager, BackupManager) and settings_get(
|
if isinstance(self.manager, BackupManager) and settings_get(
|
||||||
"misc.backup.backup_compress_tar_archives"
|
"misc.backup.backup_compress_tar_archives"
|
||||||
):
|
):
|
||||||
|
@ -2430,7 +2423,6 @@ def backup_list(with_info=False, human_readable=False):
|
||||||
|
|
||||||
|
|
||||||
def backup_download(name):
|
def backup_download(name):
|
||||||
|
|
||||||
if Moulinette.interface.type != "api":
|
if Moulinette.interface.type != "api":
|
||||||
logger.error(
|
logger.error(
|
||||||
"This option is only meant for the API/webadmin and doesn't make sense for the command line."
|
"This option is only meant for the API/webadmin and doesn't make sense for the command line."
|
||||||
|
@ -2571,7 +2563,6 @@ def backup_info(name, with_details=False, human_readable=False):
|
||||||
if "size_details" in info.keys():
|
if "size_details" in info.keys():
|
||||||
for category in ["apps", "system"]:
|
for category in ["apps", "system"]:
|
||||||
for name, key_info in info[category].items():
|
for name, key_info in info[category].items():
|
||||||
|
|
||||||
if category == "system":
|
if category == "system":
|
||||||
# Stupid legacy fix for weird format between 3.5 and 3.6
|
# Stupid legacy fix for weird format between 3.5 and 3.6
|
||||||
if isinstance(key_info, dict):
|
if isinstance(key_info, dict):
|
||||||
|
|
|
@ -20,7 +20,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import glob
|
from glob import glob
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
@ -124,10 +124,8 @@ def certificate_install(domain_list, force=False, no_checks=False, self_signed=F
|
||||||
|
|
||||||
|
|
||||||
def _certificate_install_selfsigned(domain_list, force=False):
|
def _certificate_install_selfsigned(domain_list, force=False):
|
||||||
|
|
||||||
failed_cert_install = []
|
failed_cert_install = []
|
||||||
for domain in domain_list:
|
for domain in domain_list:
|
||||||
|
|
||||||
operation_logger = OperationLogger(
|
operation_logger = OperationLogger(
|
||||||
"selfsigned_cert_install", [("domain", domain)], args={"force": force}
|
"selfsigned_cert_install", [("domain", domain)], args={"force": force}
|
||||||
)
|
)
|
||||||
|
@ -238,7 +236,6 @@ def _certificate_install_letsencrypt(domains, force=False, no_checks=False):
|
||||||
# certificates
|
# certificates
|
||||||
if domains == []:
|
if domains == []:
|
||||||
for domain in domain_list()["domains"]:
|
for domain in domain_list()["domains"]:
|
||||||
|
|
||||||
status = _get_status(domain)
|
status = _get_status(domain)
|
||||||
if status["CA_type"] != "selfsigned":
|
if status["CA_type"] != "selfsigned":
|
||||||
continue
|
continue
|
||||||
|
@ -260,7 +257,6 @@ def _certificate_install_letsencrypt(domains, force=False, no_checks=False):
|
||||||
# Actual install steps
|
# Actual install steps
|
||||||
failed_cert_install = []
|
failed_cert_install = []
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
|
|
||||||
if not no_checks:
|
if not no_checks:
|
||||||
try:
|
try:
|
||||||
_check_domain_is_ready_for_ACME(domain)
|
_check_domain_is_ready_for_ACME(domain)
|
||||||
|
@ -317,7 +313,6 @@ def certificate_renew(domains, force=False, no_checks=False, email=False):
|
||||||
# certificates
|
# certificates
|
||||||
if domains == []:
|
if domains == []:
|
||||||
for domain in domain_list()["domains"]:
|
for domain in domain_list()["domains"]:
|
||||||
|
|
||||||
# Does it have a Let's Encrypt cert?
|
# Does it have a Let's Encrypt cert?
|
||||||
status = _get_status(domain)
|
status = _get_status(domain)
|
||||||
if status["CA_type"] != "letsencrypt":
|
if status["CA_type"] != "letsencrypt":
|
||||||
|
@ -342,7 +337,6 @@ def certificate_renew(domains, force=False, no_checks=False, email=False):
|
||||||
# Else, validate the domain list given
|
# Else, validate the domain list given
|
||||||
else:
|
else:
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
|
|
||||||
# Is it in Yunohost domain list?
|
# Is it in Yunohost domain list?
|
||||||
_assert_domain_exists(domain)
|
_assert_domain_exists(domain)
|
||||||
|
|
||||||
|
@ -369,7 +363,6 @@ def certificate_renew(domains, force=False, no_checks=False, email=False):
|
||||||
# Actual renew steps
|
# Actual renew steps
|
||||||
failed_cert_install = []
|
failed_cert_install = []
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
|
|
||||||
if not no_checks:
|
if not no_checks:
|
||||||
try:
|
try:
|
||||||
_check_domain_is_ready_for_ACME(domain)
|
_check_domain_is_ready_for_ACME(domain)
|
||||||
|
@ -468,13 +461,11 @@ investigate :
|
||||||
|
|
||||||
|
|
||||||
def _check_acme_challenge_configuration(domain):
|
def _check_acme_challenge_configuration(domain):
|
||||||
|
|
||||||
domain_conf = f"/etc/nginx/conf.d/{domain}.conf"
|
domain_conf = f"/etc/nginx/conf.d/{domain}.conf"
|
||||||
return "include /etc/nginx/conf.d/acme-challenge.conf.inc" in read_file(domain_conf)
|
return "include /etc/nginx/conf.d/acme-challenge.conf.inc" in read_file(domain_conf)
|
||||||
|
|
||||||
|
|
||||||
def _fetch_and_enable_new_certificate(domain, no_checks=False):
|
def _fetch_and_enable_new_certificate(domain, no_checks=False):
|
||||||
|
|
||||||
if not os.path.exists(ACCOUNT_KEY_FILE):
|
if not os.path.exists(ACCOUNT_KEY_FILE):
|
||||||
_generate_account_key()
|
_generate_account_key()
|
||||||
|
|
||||||
|
@ -628,7 +619,6 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder):
|
||||||
|
|
||||||
|
|
||||||
def _get_status(domain):
|
def _get_status(domain):
|
||||||
|
|
||||||
cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem")
|
cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem")
|
||||||
|
|
||||||
if not os.path.isfile(cert_file):
|
if not os.path.isfile(cert_file):
|
||||||
|
@ -744,10 +734,10 @@ def _enable_certificate(domain, new_cert_folder):
|
||||||
logger.debug("Restarting services...")
|
logger.debug("Restarting services...")
|
||||||
|
|
||||||
for service in ("dovecot", "metronome"):
|
for service in ("dovecot", "metronome"):
|
||||||
# Ugly trick to not restart metronome if it's not installed
|
# Ugly trick to not restart metronome if it's not installed or no domain configured for XMPP
|
||||||
if (
|
if service == "metronome" and (
|
||||||
service == "metronome"
|
os.system("dpkg --list | grep -q 'ii *metronome'") != 0
|
||||||
and os.system("dpkg --list | grep -q 'ii *metronome'") != 0
|
or not glob("/etc/metronome/conf.d/*.cfg.lua")
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
_run_service_command("restart", service)
|
_run_service_command("restart", service)
|
||||||
|
@ -777,7 +767,6 @@ def _backup_current_cert(domain):
|
||||||
|
|
||||||
|
|
||||||
def _check_domain_is_ready_for_ACME(domain):
|
def _check_domain_is_ready_for_ACME(domain):
|
||||||
|
|
||||||
from yunohost.domain import _get_parent_domain_of
|
from yunohost.domain import _get_parent_domain_of
|
||||||
from yunohost.dns import _get_dns_zone_for_domain
|
from yunohost.dns import _get_dns_zone_for_domain
|
||||||
from yunohost.utils.dns import is_yunohost_dyndns_domain
|
from yunohost.utils.dns import is_yunohost_dyndns_domain
|
||||||
|
@ -864,9 +853,8 @@ def _regen_dnsmasq_if_needed():
|
||||||
do_regen = False
|
do_regen = False
|
||||||
|
|
||||||
# For all domain files in DNSmasq conf...
|
# For all domain files in DNSmasq conf...
|
||||||
domainsconf = glob.glob("/etc/dnsmasq.d/*.*")
|
domainsconf = glob("/etc/dnsmasq.d/*.*")
|
||||||
for domainconf in domainsconf:
|
for domainconf in domainsconf:
|
||||||
|
|
||||||
# Look for the IP, it's in the lines with this format :
|
# Look for the IP, it's in the lines with this format :
|
||||||
# host-record=the.domain.tld,11.22.33.44
|
# host-record=the.domain.tld,11.22.33.44
|
||||||
for line in open(domainconf).readlines():
|
for line in open(domainconf).readlines():
|
||||||
|
|
|
@ -35,13 +35,11 @@ logger = log.getActionLogger("yunohost.diagnosis")
|
||||||
|
|
||||||
|
|
||||||
class MyDiagnoser(Diagnoser):
|
class MyDiagnoser(Diagnoser):
|
||||||
|
|
||||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||||
cache_duration = 600
|
cache_duration = 600
|
||||||
dependencies: List[str] = []
|
dependencies: List[str] = []
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
virt = system_virt()
|
virt = system_virt()
|
||||||
if virt.lower() == "none":
|
if virt.lower() == "none":
|
||||||
virt = "bare-metal"
|
virt = "bare-metal"
|
||||||
|
@ -193,7 +191,6 @@ class MyDiagnoser(Diagnoser):
|
||||||
)
|
)
|
||||||
|
|
||||||
def bad_sury_packages(self):
|
def bad_sury_packages(self):
|
||||||
|
|
||||||
packages_to_check = ["openssl", "libssl1.1", "libssl-dev"]
|
packages_to_check = ["openssl", "libssl1.1", "libssl-dev"]
|
||||||
for package in packages_to_check:
|
for package in packages_to_check:
|
||||||
cmd = "dpkg --list | grep '^ii' | grep gbp | grep -q -w %s" % package
|
cmd = "dpkg --list | grep '^ii' | grep gbp | grep -q -w %s" % package
|
||||||
|
@ -209,12 +206,10 @@ class MyDiagnoser(Diagnoser):
|
||||||
yield (package, version_to_downgrade_to)
|
yield (package, version_to_downgrade_to)
|
||||||
|
|
||||||
def backports_in_sources_list(self):
|
def backports_in_sources_list(self):
|
||||||
|
|
||||||
cmd = "grep -q -nr '^ *deb .*-backports' /etc/apt/sources.list*"
|
cmd = "grep -q -nr '^ *deb .*-backports' /etc/apt/sources.list*"
|
||||||
return os.system(cmd) == 0
|
return os.system(cmd) == 0
|
||||||
|
|
||||||
def number_of_recent_auth_failure(self):
|
def number_of_recent_auth_failure(self):
|
||||||
|
|
||||||
# Those syslog facilities correspond to auth and authpriv
|
# Those syslog facilities correspond to auth and authpriv
|
||||||
# c.f. https://unix.stackexchange.com/a/401398
|
# c.f. https://unix.stackexchange.com/a/401398
|
||||||
# and https://wiki.archlinux.org/title/Systemd/Journal#Facility
|
# and https://wiki.archlinux.org/title/Systemd/Journal#Facility
|
||||||
|
|
|
@ -28,18 +28,17 @@ from moulinette.utils.filesystem import read_file
|
||||||
|
|
||||||
from yunohost.diagnosis import Diagnoser
|
from yunohost.diagnosis import Diagnoser
|
||||||
from yunohost.utils.network import get_network_interfaces
|
from yunohost.utils.network import get_network_interfaces
|
||||||
|
from yunohost.settings import settings_get
|
||||||
|
|
||||||
logger = log.getActionLogger("yunohost.diagnosis")
|
logger = log.getActionLogger("yunohost.diagnosis")
|
||||||
|
|
||||||
|
|
||||||
class MyDiagnoser(Diagnoser):
|
class MyDiagnoser(Diagnoser):
|
||||||
|
|
||||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||||
cache_duration = 600
|
cache_duration = 600
|
||||||
dependencies: List[str] = []
|
dependencies: List[str] = []
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
# ############################################################ #
|
# ############################################################ #
|
||||||
# PING : Check that we can ping outside at least in ipv4 or v6 #
|
# PING : Check that we can ping outside at least in ipv4 or v6 #
|
||||||
# ############################################################ #
|
# ############################################################ #
|
||||||
|
@ -118,10 +117,17 @@ class MyDiagnoser(Diagnoser):
|
||||||
else:
|
else:
|
||||||
return local_ip
|
return local_ip
|
||||||
|
|
||||||
|
def is_ipvx_important(x):
|
||||||
|
return settings_get("misc.network.dns_exposure") in ["both", "ipv" + str(x)]
|
||||||
|
|
||||||
yield dict(
|
yield dict(
|
||||||
meta={"test": "ipv4"},
|
meta={"test": "ipv4"},
|
||||||
data={"global": ipv4, "local": get_local_ip("ipv4")},
|
data={"global": ipv4, "local": get_local_ip("ipv4")},
|
||||||
status="SUCCESS" if ipv4 else "ERROR",
|
status="SUCCESS"
|
||||||
|
if ipv4
|
||||||
|
else "ERROR"
|
||||||
|
if is_ipvx_important(4)
|
||||||
|
else "WARNING",
|
||||||
summary="diagnosis_ip_connected_ipv4" if ipv4 else "diagnosis_ip_no_ipv4",
|
summary="diagnosis_ip_connected_ipv4" if ipv4 else "diagnosis_ip_no_ipv4",
|
||||||
details=["diagnosis_ip_global", "diagnosis_ip_local"] if ipv4 else None,
|
details=["diagnosis_ip_global", "diagnosis_ip_local"] if ipv4 else None,
|
||||||
)
|
)
|
||||||
|
@ -129,17 +135,24 @@ class MyDiagnoser(Diagnoser):
|
||||||
yield dict(
|
yield dict(
|
||||||
meta={"test": "ipv6"},
|
meta={"test": "ipv6"},
|
||||||
data={"global": ipv6, "local": get_local_ip("ipv6")},
|
data={"global": ipv6, "local": get_local_ip("ipv6")},
|
||||||
status="SUCCESS" if ipv6 else "WARNING",
|
status="SUCCESS"
|
||||||
|
if ipv6
|
||||||
|
else "ERROR"
|
||||||
|
if settings_get("misc.network.dns_exposure") == "ipv6"
|
||||||
|
else "WARNING",
|
||||||
summary="diagnosis_ip_connected_ipv6" if ipv6 else "diagnosis_ip_no_ipv6",
|
summary="diagnosis_ip_connected_ipv6" if ipv6 else "diagnosis_ip_no_ipv6",
|
||||||
details=["diagnosis_ip_global", "diagnosis_ip_local"]
|
details=["diagnosis_ip_global", "diagnosis_ip_local"]
|
||||||
if ipv6
|
if ipv6
|
||||||
else ["diagnosis_ip_no_ipv6_tip"],
|
else [
|
||||||
|
"diagnosis_ip_no_ipv6_tip_important"
|
||||||
|
if is_ipvx_important(6)
|
||||||
|
else "diagnosis_ip_no_ipv6_tip"
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO / FIXME : add some attempt to detect ISP (using whois ?) ?
|
# TODO / FIXME : add some attempt to detect ISP (using whois ?) ?
|
||||||
|
|
||||||
def can_ping_outside(self, protocol=4):
|
def can_ping_outside(self, protocol=4):
|
||||||
|
|
||||||
assert protocol in [
|
assert protocol in [
|
||||||
4,
|
4,
|
||||||
6,
|
6,
|
||||||
|
@ -218,7 +231,6 @@ class MyDiagnoser(Diagnoser):
|
||||||
return len(content) == 1 and content[0].split() == ["nameserver", "127.0.0.1"]
|
return len(content) == 1 and content[0].split() == ["nameserver", "127.0.0.1"]
|
||||||
|
|
||||||
def get_public_ip(self, protocol=4):
|
def get_public_ip(self, protocol=4):
|
||||||
|
|
||||||
# FIXME - TODO : here we assume that DNS resolution for ip.yunohost.org is working
|
# FIXME - TODO : here we assume that DNS resolution for ip.yunohost.org is working
|
||||||
# but if we want to be able to diagnose DNS resolution issues independently from
|
# but if we want to be able to diagnose DNS resolution issues independently from
|
||||||
# internet connectivity, we gotta rely on fixed IPs first....
|
# internet connectivity, we gotta rely on fixed IPs first....
|
||||||
|
|
|
@ -43,13 +43,11 @@ logger = log.getActionLogger("yunohost.diagnosis")
|
||||||
|
|
||||||
|
|
||||||
class MyDiagnoser(Diagnoser):
|
class MyDiagnoser(Diagnoser):
|
||||||
|
|
||||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||||
cache_duration = 600
|
cache_duration = 600
|
||||||
dependencies: List[str] = ["ip"]
|
dependencies: List[str] = ["ip"]
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
main_domain = _get_maindomain()
|
main_domain = _get_maindomain()
|
||||||
|
|
||||||
major_domains = domain_list(exclude_subdomains=True)["domains"]
|
major_domains = domain_list(exclude_subdomains=True)["domains"]
|
||||||
|
@ -77,7 +75,6 @@ class MyDiagnoser(Diagnoser):
|
||||||
yield report
|
yield report
|
||||||
|
|
||||||
def check_domain(self, domain, is_main_domain):
|
def check_domain(self, domain, is_main_domain):
|
||||||
|
|
||||||
if is_special_use_tld(domain):
|
if is_special_use_tld(domain):
|
||||||
yield dict(
|
yield dict(
|
||||||
meta={"domain": domain},
|
meta={"domain": domain},
|
||||||
|
@ -97,13 +94,11 @@ class MyDiagnoser(Diagnoser):
|
||||||
categories = ["basic", "mail", "xmpp", "extra"]
|
categories = ["basic", "mail", "xmpp", "extra"]
|
||||||
|
|
||||||
for category in categories:
|
for category in categories:
|
||||||
|
|
||||||
records = expected_configuration[category]
|
records = expected_configuration[category]
|
||||||
discrepancies = []
|
discrepancies = []
|
||||||
results = {}
|
results = {}
|
||||||
|
|
||||||
for r in records:
|
for r in records:
|
||||||
|
|
||||||
id_ = r["type"] + ":" + r["name"]
|
id_ = r["type"] + ":" + r["name"]
|
||||||
fqdn = r["name"] + "." + base_dns_zone if r["name"] != "@" else domain
|
fqdn = r["name"] + "." + base_dns_zone if r["name"] != "@" else domain
|
||||||
|
|
||||||
|
@ -182,7 +177,6 @@ class MyDiagnoser(Diagnoser):
|
||||||
yield output
|
yield output
|
||||||
|
|
||||||
def get_current_record(self, fqdn, type_):
|
def get_current_record(self, fqdn, type_):
|
||||||
|
|
||||||
success, answers = dig(fqdn, type_, resolvers="force_external")
|
success, answers = dig(fqdn, type_, resolvers="force_external")
|
||||||
|
|
||||||
if success != "ok":
|
if success != "ok":
|
||||||
|
|
|
@ -21,16 +21,15 @@ from typing import List
|
||||||
|
|
||||||
from yunohost.diagnosis import Diagnoser
|
from yunohost.diagnosis import Diagnoser
|
||||||
from yunohost.service import _get_services
|
from yunohost.service import _get_services
|
||||||
|
from yunohost.settings import settings_get
|
||||||
|
|
||||||
|
|
||||||
class MyDiagnoser(Diagnoser):
|
class MyDiagnoser(Diagnoser):
|
||||||
|
|
||||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||||
cache_duration = 600
|
cache_duration = 600
|
||||||
dependencies: List[str] = ["ip"]
|
dependencies: List[str] = ["ip"]
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
# TODO: report a warning if port 53 or 5353 is exposed to the outside world...
|
# TODO: report a warning if port 53 or 5353 is exposed to the outside world...
|
||||||
|
|
||||||
# This dict is something like :
|
# This dict is something like :
|
||||||
|
@ -46,7 +45,10 @@ class MyDiagnoser(Diagnoser):
|
||||||
|
|
||||||
ipversions = []
|
ipversions = []
|
||||||
ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {}
|
ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {}
|
||||||
if ipv4.get("status") == "SUCCESS":
|
if (
|
||||||
|
ipv4.get("status") == "SUCCESS"
|
||||||
|
or settings_get("misc.network.dns_exposure") != "ipv6"
|
||||||
|
):
|
||||||
ipversions.append(4)
|
ipversions.append(4)
|
||||||
|
|
||||||
# To be discussed: we could also make this check dependent on the
|
# To be discussed: we could also make this check dependent on the
|
||||||
|
@ -120,7 +122,10 @@ class MyDiagnoser(Diagnoser):
|
||||||
for record in dnsrecords.get("items", [])
|
for record in dnsrecords.get("items", [])
|
||||||
)
|
)
|
||||||
|
|
||||||
if failed == 4 or ipv6_is_important():
|
if (
|
||||||
|
failed == 4
|
||||||
|
and settings_get("misc.network.dns_exposure") in ["both", "ipv4"]
|
||||||
|
) or (failed == 6 and ipv6_is_important()):
|
||||||
yield dict(
|
yield dict(
|
||||||
meta={"port": port},
|
meta={"port": port},
|
||||||
data={
|
data={
|
||||||
|
|
|
@ -26,22 +26,20 @@ 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
|
||||||
from yunohost.utils.dns import is_special_use_tld
|
from yunohost.utils.dns import is_special_use_tld
|
||||||
|
from yunohost.settings import settings_get
|
||||||
|
|
||||||
DIAGNOSIS_SERVER = "diagnosis.yunohost.org"
|
DIAGNOSIS_SERVER = "diagnosis.yunohost.org"
|
||||||
|
|
||||||
|
|
||||||
class MyDiagnoser(Diagnoser):
|
class MyDiagnoser(Diagnoser):
|
||||||
|
|
||||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||||
cache_duration = 600
|
cache_duration = 600
|
||||||
dependencies: List[str] = ["ip"]
|
dependencies: List[str] = ["ip"]
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
all_domains = domain_list()["domains"]
|
all_domains = domain_list()["domains"]
|
||||||
domains_to_check = []
|
domains_to_check = []
|
||||||
for domain in all_domains:
|
for domain in all_domains:
|
||||||
|
|
||||||
# If the diagnosis location ain't defined, can't do diagnosis,
|
# If the diagnosis location ain't defined, can't do diagnosis,
|
||||||
# probably because nginx conf manually modified...
|
# probably because nginx conf manually modified...
|
||||||
nginx_conf = "/etc/nginx/conf.d/%s.conf" % domain
|
nginx_conf = "/etc/nginx/conf.d/%s.conf" % domain
|
||||||
|
@ -76,7 +74,9 @@ class MyDiagnoser(Diagnoser):
|
||||||
|
|
||||||
ipversions = []
|
ipversions = []
|
||||||
ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {}
|
ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {}
|
||||||
if ipv4.get("status") == "SUCCESS":
|
if ipv4.get("status") == "SUCCESS" and settings_get(
|
||||||
|
"misc.network.dns_exposure"
|
||||||
|
) in ["both", "ipv4"]:
|
||||||
ipversions.append(4)
|
ipversions.append(4)
|
||||||
|
|
||||||
# To be discussed: we could also make this check dependent on the
|
# To be discussed: we could also make this check dependent on the
|
||||||
|
@ -96,7 +96,10 @@ class MyDiagnoser(Diagnoser):
|
||||||
# "curl --head the.global.ip" will simply timeout...
|
# "curl --head the.global.ip" will simply timeout...
|
||||||
if self.do_hairpinning_test:
|
if self.do_hairpinning_test:
|
||||||
global_ipv4 = ipv4.get("data", {}).get("global", None)
|
global_ipv4 = ipv4.get("data", {}).get("global", None)
|
||||||
if global_ipv4:
|
if global_ipv4 and settings_get("misc.network.dns_exposure") in [
|
||||||
|
"both",
|
||||||
|
"ipv4",
|
||||||
|
]:
|
||||||
try:
|
try:
|
||||||
requests.head("http://" + global_ipv4, timeout=5)
|
requests.head("http://" + global_ipv4, timeout=5)
|
||||||
except requests.exceptions.Timeout:
|
except requests.exceptions.Timeout:
|
||||||
|
@ -113,7 +116,6 @@ class MyDiagnoser(Diagnoser):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_http(self, domains, ipversions):
|
def test_http(self, domains, ipversions):
|
||||||
|
|
||||||
results = {}
|
results = {}
|
||||||
for ipversion in ipversions:
|
for ipversion in ipversions:
|
||||||
try:
|
try:
|
||||||
|
@ -138,7 +140,6 @@ class MyDiagnoser(Diagnoser):
|
||||||
return
|
return
|
||||||
|
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
|
|
||||||
# i18n: diagnosis_http_bad_status_code
|
# i18n: diagnosis_http_bad_status_code
|
||||||
# i18n: diagnosis_http_connection_error
|
# i18n: diagnosis_http_connection_error
|
||||||
# i18n: diagnosis_http_timeout
|
# i18n: diagnosis_http_timeout
|
||||||
|
@ -147,7 +148,10 @@ class MyDiagnoser(Diagnoser):
|
||||||
if all(
|
if all(
|
||||||
results[ipversion][domain]["status"] == "ok" for ipversion in ipversions
|
results[ipversion][domain]["status"] == "ok" for ipversion in ipversions
|
||||||
):
|
):
|
||||||
if 4 in ipversions:
|
if 4 in ipversions and settings_get("misc.network.dns_exposure") in [
|
||||||
|
"both",
|
||||||
|
"ipv4",
|
||||||
|
]:
|
||||||
self.do_hairpinning_test = True
|
self.do_hairpinning_test = True
|
||||||
yield dict(
|
yield dict(
|
||||||
meta={"domain": domain},
|
meta={"domain": domain},
|
||||||
|
@ -185,7 +189,9 @@ class MyDiagnoser(Diagnoser):
|
||||||
)
|
)
|
||||||
AAAA_status = dnsrecords.get("data", {}).get("AAAA:@")
|
AAAA_status = dnsrecords.get("data", {}).get("AAAA:@")
|
||||||
|
|
||||||
return AAAA_status in ["OK", "WRONG"]
|
return AAAA_status in ["OK", "WRONG"] or settings_get(
|
||||||
|
"misc.network.dns_exposure"
|
||||||
|
) in ["both", "ipv6"]
|
||||||
|
|
||||||
if failed == 4 or ipv6_is_important_for_this_domain():
|
if failed == 4 or ipv6_is_important_for_this_domain():
|
||||||
yield dict(
|
yield dict(
|
||||||
|
|
|
@ -38,13 +38,11 @@ logger = log.getActionLogger("yunohost.diagnosis")
|
||||||
|
|
||||||
|
|
||||||
class MyDiagnoser(Diagnoser):
|
class MyDiagnoser(Diagnoser):
|
||||||
|
|
||||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||||
cache_duration = 600
|
cache_duration = 600
|
||||||
dependencies: List[str] = ["ip"]
|
dependencies: List[str] = ["ip"]
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
self.ehlo_domain = _get_maindomain()
|
self.ehlo_domain = _get_maindomain()
|
||||||
self.mail_domains = domain_list()["domains"]
|
self.mail_domains = domain_list()["domains"]
|
||||||
self.ipversions, self.ips = self.get_ips_checked()
|
self.ipversions, self.ips = self.get_ips_checked()
|
||||||
|
@ -301,13 +299,17 @@ class MyDiagnoser(Diagnoser):
|
||||||
outgoing_ipversions = []
|
outgoing_ipversions = []
|
||||||
outgoing_ips = []
|
outgoing_ips = []
|
||||||
ipv4 = Diagnoser.get_cached_report("ip", {"test": "ipv4"}) or {}
|
ipv4 = Diagnoser.get_cached_report("ip", {"test": "ipv4"}) or {}
|
||||||
if ipv4.get("status") == "SUCCESS":
|
if ipv4.get("status") == "SUCCESS" and settings_get(
|
||||||
|
"misc.network.dns_exposure"
|
||||||
|
) in ["both", "ipv4"]:
|
||||||
outgoing_ipversions.append(4)
|
outgoing_ipversions.append(4)
|
||||||
global_ipv4 = ipv4.get("data", {}).get("global", {})
|
global_ipv4 = ipv4.get("data", {}).get("global", {})
|
||||||
if global_ipv4:
|
if global_ipv4:
|
||||||
outgoing_ips.append(global_ipv4)
|
outgoing_ips.append(global_ipv4)
|
||||||
|
|
||||||
if settings_get("email.smtp.smtp_allow_ipv6"):
|
if settings_get("email.smtp.smtp_allow_ipv6") or settings_get(
|
||||||
|
"misc.network.dns_exposure"
|
||||||
|
) in ["both", "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)
|
||||||
|
|
|
@ -24,17 +24,14 @@ from yunohost.service import service_status
|
||||||
|
|
||||||
|
|
||||||
class MyDiagnoser(Diagnoser):
|
class MyDiagnoser(Diagnoser):
|
||||||
|
|
||||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||||
cache_duration = 300
|
cache_duration = 300
|
||||||
dependencies: List[str] = []
|
dependencies: List[str] = []
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
all_result = service_status()
|
all_result = service_status()
|
||||||
|
|
||||||
for service, result in sorted(all_result.items()):
|
for service, result in sorted(all_result.items()):
|
||||||
|
|
||||||
item = dict(
|
item = dict(
|
||||||
meta={"service": service},
|
meta={"service": service},
|
||||||
data={
|
data={
|
||||||
|
|
|
@ -28,13 +28,11 @@ from yunohost.diagnosis import Diagnoser
|
||||||
|
|
||||||
|
|
||||||
class MyDiagnoser(Diagnoser):
|
class MyDiagnoser(Diagnoser):
|
||||||
|
|
||||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||||
cache_duration = 300
|
cache_duration = 300
|
||||||
dependencies: List[str] = []
|
dependencies: List[str] = []
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
MB = 1024**2
|
MB = 1024**2
|
||||||
GB = MB * 1024
|
GB = MB * 1024
|
||||||
|
|
||||||
|
@ -189,7 +187,6 @@ class MyDiagnoser(Diagnoser):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def analyzed_kern_log():
|
def analyzed_kern_log():
|
||||||
|
|
||||||
cmd = 'tail -n 10000 /var/log/kern.log | grep "oom_reaper: reaped process" || true'
|
cmd = 'tail -n 10000 /var/log/kern.log | grep "oom_reaper: reaped process" || true'
|
||||||
out = check_output(cmd)
|
out = check_output(cmd)
|
||||||
lines = out.split("\n") if out else []
|
lines = out.split("\n") if out else []
|
||||||
|
|
|
@ -27,13 +27,11 @@ from moulinette.utils.filesystem import read_file
|
||||||
|
|
||||||
|
|
||||||
class MyDiagnoser(Diagnoser):
|
class MyDiagnoser(Diagnoser):
|
||||||
|
|
||||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||||
cache_duration = 300
|
cache_duration = 300
|
||||||
dependencies: List[str] = []
|
dependencies: List[str] = []
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
regenconf_modified_files = list(self.manually_modified_files())
|
regenconf_modified_files = list(self.manually_modified_files())
|
||||||
|
|
||||||
if not regenconf_modified_files:
|
if not regenconf_modified_files:
|
||||||
|
@ -82,7 +80,6 @@ class MyDiagnoser(Diagnoser):
|
||||||
)
|
)
|
||||||
|
|
||||||
def manually_modified_files(self):
|
def manually_modified_files(self):
|
||||||
|
|
||||||
for category, infos in _get_regenconf_infos().items():
|
for category, infos in _get_regenconf_infos().items():
|
||||||
for path, hash_ in infos["conffiles"].items():
|
for path, hash_ in infos["conffiles"].items():
|
||||||
if hash_ != _calculate_hash(path):
|
if hash_ != _calculate_hash(path):
|
||||||
|
|
|
@ -25,13 +25,11 @@ from yunohost.diagnosis import Diagnoser
|
||||||
|
|
||||||
|
|
||||||
class MyDiagnoser(Diagnoser):
|
class MyDiagnoser(Diagnoser):
|
||||||
|
|
||||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||||
cache_duration = 300
|
cache_duration = 300
|
||||||
dependencies: List[str] = []
|
dependencies: List[str] = []
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
apps = app_list(full=True)["apps"]
|
apps = app_list(full=True)["apps"]
|
||||||
for app in apps:
|
for app in apps:
|
||||||
app["issues"] = list(self.issues(app))
|
app["issues"] = list(self.issues(app))
|
||||||
|
@ -44,7 +42,6 @@ class MyDiagnoser(Diagnoser):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
for app in apps:
|
for app in apps:
|
||||||
|
|
||||||
if not app["issues"]:
|
if not app["issues"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -62,7 +59,6 @@ class MyDiagnoser(Diagnoser):
|
||||||
)
|
)
|
||||||
|
|
||||||
def issues(self, app):
|
def issues(self, app):
|
||||||
|
|
||||||
# Check quality level in catalog
|
# Check quality level in catalog
|
||||||
|
|
||||||
if not app.get("from_catalog") or app["from_catalog"].get("state") != "working":
|
if not app.get("from_catalog") or app["from_catalog"].get("state") != "working":
|
||||||
|
|
|
@ -45,7 +45,6 @@ def diagnosis_list():
|
||||||
|
|
||||||
|
|
||||||
def diagnosis_get(category, item):
|
def diagnosis_get(category, item):
|
||||||
|
|
||||||
# Get all the categories
|
# Get all the categories
|
||||||
all_categories_names = _list_diagnosis_categories()
|
all_categories_names = _list_diagnosis_categories()
|
||||||
|
|
||||||
|
@ -69,7 +68,6 @@ def diagnosis_get(category, item):
|
||||||
def diagnosis_show(
|
def diagnosis_show(
|
||||||
categories=[], issues=False, full=False, share=False, human_readable=False
|
categories=[], issues=False, full=False, share=False, human_readable=False
|
||||||
):
|
):
|
||||||
|
|
||||||
if not os.path.exists(DIAGNOSIS_CACHE):
|
if not os.path.exists(DIAGNOSIS_CACHE):
|
||||||
logger.warning(m18n.n("diagnosis_never_ran_yet"))
|
logger.warning(m18n.n("diagnosis_never_ran_yet"))
|
||||||
return
|
return
|
||||||
|
@ -90,7 +88,6 @@ def diagnosis_show(
|
||||||
# Fetch all reports
|
# Fetch all reports
|
||||||
all_reports = []
|
all_reports = []
|
||||||
for category in categories:
|
for category in categories:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
report = Diagnoser.get_cached_report(category)
|
report = Diagnoser.get_cached_report(category)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -139,7 +136,6 @@ def diagnosis_show(
|
||||||
|
|
||||||
|
|
||||||
def _dump_human_readable_reports(reports):
|
def _dump_human_readable_reports(reports):
|
||||||
|
|
||||||
output = ""
|
output = ""
|
||||||
|
|
||||||
for report in reports:
|
for report in reports:
|
||||||
|
@ -159,7 +155,6 @@ def _dump_human_readable_reports(reports):
|
||||||
def diagnosis_run(
|
def diagnosis_run(
|
||||||
categories=[], force=False, except_if_never_ran_yet=False, email=False
|
categories=[], force=False, except_if_never_ran_yet=False, email=False
|
||||||
):
|
):
|
||||||
|
|
||||||
if (email or except_if_never_ran_yet) and not os.path.exists(DIAGNOSIS_CACHE):
|
if (email or except_if_never_ran_yet) and not os.path.exists(DIAGNOSIS_CACHE):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -263,7 +258,6 @@ def _diagnosis_ignore(add_filter=None, remove_filter=None, list=False):
|
||||||
return {"ignore_filters": configuration.get("ignore_filters", {})}
|
return {"ignore_filters": configuration.get("ignore_filters", {})}
|
||||||
|
|
||||||
def validate_filter_criterias(filter_):
|
def validate_filter_criterias(filter_):
|
||||||
|
|
||||||
# Get all the categories
|
# Get all the categories
|
||||||
all_categories_names = _list_diagnosis_categories()
|
all_categories_names = _list_diagnosis_categories()
|
||||||
|
|
||||||
|
@ -286,7 +280,6 @@ def _diagnosis_ignore(add_filter=None, remove_filter=None, list=False):
|
||||||
return category, criterias
|
return category, criterias
|
||||||
|
|
||||||
if add_filter:
|
if add_filter:
|
||||||
|
|
||||||
category, criterias = validate_filter_criterias(add_filter)
|
category, criterias = validate_filter_criterias(add_filter)
|
||||||
|
|
||||||
# Fetch current issues for the requested category
|
# Fetch current issues for the requested category
|
||||||
|
@ -320,7 +313,6 @@ def _diagnosis_ignore(add_filter=None, remove_filter=None, list=False):
|
||||||
return
|
return
|
||||||
|
|
||||||
if remove_filter:
|
if remove_filter:
|
||||||
|
|
||||||
category, criterias = validate_filter_criterias(remove_filter)
|
category, criterias = validate_filter_criterias(remove_filter)
|
||||||
|
|
||||||
# Make sure the subdicts/lists exists
|
# Make sure the subdicts/lists exists
|
||||||
|
@ -394,12 +386,10 @@ def add_ignore_flag_to_issues(report):
|
||||||
|
|
||||||
class Diagnoser:
|
class Diagnoser:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.cache_file = Diagnoser.cache_file(self.id_)
|
self.cache_file = Diagnoser.cache_file(self.id_)
|
||||||
self.description = Diagnoser.get_description(self.id_)
|
self.description = Diagnoser.get_description(self.id_)
|
||||||
|
|
||||||
def cached_time_ago(self):
|
def cached_time_ago(self):
|
||||||
|
|
||||||
if not os.path.exists(self.cache_file):
|
if not os.path.exists(self.cache_file):
|
||||||
return 99999999
|
return 99999999
|
||||||
return time.time() - os.path.getmtime(self.cache_file)
|
return time.time() - os.path.getmtime(self.cache_file)
|
||||||
|
@ -410,7 +400,6 @@ class Diagnoser:
|
||||||
return write_to_json(self.cache_file, report)
|
return write_to_json(self.cache_file, report)
|
||||||
|
|
||||||
def diagnose(self, force=False):
|
def diagnose(self, force=False):
|
||||||
|
|
||||||
if not force and self.cached_time_ago() < self.cache_duration:
|
if not force and self.cached_time_ago() < self.cache_duration:
|
||||||
logger.debug(f"Cache still valid : {self.cache_file}")
|
logger.debug(f"Cache still valid : {self.cache_file}")
|
||||||
logger.info(
|
logger.info(
|
||||||
|
@ -548,7 +537,6 @@ class Diagnoser:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def i18n(report, force_remove_html_tags=False):
|
def i18n(report, force_remove_html_tags=False):
|
||||||
|
|
||||||
# "Render" the strings with m18n.n
|
# "Render" the strings with m18n.n
|
||||||
# N.B. : we do those m18n.n right now instead of saving the already-translated report
|
# N.B. : we do those m18n.n right now instead of saving the already-translated report
|
||||||
# because we can't be sure we'll redisplay the infos with the same locale as it
|
# because we can't be sure we'll redisplay the infos with the same locale as it
|
||||||
|
@ -558,7 +546,6 @@ class Diagnoser:
|
||||||
report["description"] = Diagnoser.get_description(report["id"])
|
report["description"] = Diagnoser.get_description(report["id"])
|
||||||
|
|
||||||
for item in report["items"]:
|
for item in report["items"]:
|
||||||
|
|
||||||
# For the summary and each details, we want to call
|
# For the summary and each details, we want to call
|
||||||
# m18n() on the string, with the appropriate data for string
|
# m18n() on the string, with the appropriate data for string
|
||||||
# formatting which can come from :
|
# formatting which can come from :
|
||||||
|
@ -597,7 +584,6 @@ class Diagnoser:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def remote_diagnosis(uri, data, ipversion, timeout=30):
|
def remote_diagnosis(uri, data, ipversion, timeout=30):
|
||||||
|
|
||||||
# Lazy loading for performance
|
# Lazy loading for performance
|
||||||
import requests
|
import requests
|
||||||
import socket
|
import socket
|
||||||
|
@ -646,7 +632,6 @@ class Diagnoser:
|
||||||
|
|
||||||
|
|
||||||
def _list_diagnosis_categories():
|
def _list_diagnosis_categories():
|
||||||
|
|
||||||
paths = glob.glob(os.path.dirname(__file__) + "/diagnosers/??-*.py")
|
paths = glob.glob(os.path.dirname(__file__) + "/diagnosers/??-*.py")
|
||||||
names = [
|
names = [
|
||||||
name.split("-")[-1]
|
name.split("-")[-1]
|
||||||
|
@ -657,7 +642,6 @@ def _list_diagnosis_categories():
|
||||||
|
|
||||||
|
|
||||||
def _load_diagnoser(diagnoser_name):
|
def _load_diagnoser(diagnoser_name):
|
||||||
|
|
||||||
logger.debug(f"Loading diagnoser {diagnoser_name}")
|
logger.debug(f"Loading diagnoser {diagnoser_name}")
|
||||||
|
|
||||||
paths = glob.glob(os.path.dirname(__file__) + f"/diagnosers/??-{diagnoser_name}.py")
|
paths = glob.glob(os.path.dirname(__file__) + f"/diagnosers/??-{diagnoser_name}.py")
|
||||||
|
|
17
src/dns.py
17
src/dns.py
|
@ -38,6 +38,7 @@ from yunohost.domain import (
|
||||||
from yunohost.utils.dns import dig, is_yunohost_dyndns_domain, is_special_use_tld
|
from yunohost.utils.dns import dig, is_yunohost_dyndns_domain, is_special_use_tld
|
||||||
from yunohost.utils.error import YunohostValidationError, YunohostError
|
from yunohost.utils.error import YunohostValidationError, YunohostError
|
||||||
from yunohost.utils.network import get_public_ip
|
from yunohost.utils.network import get_public_ip
|
||||||
|
from yunohost.settings import settings_get
|
||||||
from yunohost.log import is_unit_operation
|
from yunohost.log import is_unit_operation
|
||||||
from yunohost.hook import hook_callback
|
from yunohost.hook import hook_callback
|
||||||
|
|
||||||
|
@ -168,7 +169,6 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False):
|
||||||
base_dns_zone = _get_dns_zone_for_domain(base_domain)
|
base_dns_zone = _get_dns_zone_for_domain(base_domain)
|
||||||
|
|
||||||
for domain, settings in domains_settings.items():
|
for domain, settings in domains_settings.items():
|
||||||
|
|
||||||
# Domain # Base DNS zone # Basename # Suffix #
|
# Domain # Base DNS zone # Basename # Suffix #
|
||||||
# ------------------ # ----------------- # --------- # -------- #
|
# ------------------ # ----------------- # --------- # -------- #
|
||||||
# domain.tld # domain.tld # @ # #
|
# domain.tld # domain.tld # @ # #
|
||||||
|
@ -185,7 +185,7 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False):
|
||||||
###########################
|
###########################
|
||||||
# Basic ipv4/ipv6 records #
|
# Basic ipv4/ipv6 records #
|
||||||
###########################
|
###########################
|
||||||
if ipv4:
|
if ipv4 and settings_get("misc.network.dns_exposure") in ["both", "ipv4"]:
|
||||||
basic.append([basename, ttl, "A", ipv4])
|
basic.append([basename, ttl, "A", ipv4])
|
||||||
|
|
||||||
if ipv6:
|
if ipv6:
|
||||||
|
@ -240,7 +240,7 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False):
|
||||||
|
|
||||||
# Only recommend wildcard and CAA for the top level
|
# Only recommend wildcard and CAA for the top level
|
||||||
if domain == base_domain:
|
if domain == base_domain:
|
||||||
if ipv4:
|
if ipv4 and settings_get("misc.network.dns_exposure") in ["both", "ipv4"]:
|
||||||
extra.append([f"*{suffix}", ttl, "A", ipv4])
|
extra.append([f"*{suffix}", ttl, "A", ipv4])
|
||||||
|
|
||||||
if ipv6:
|
if ipv6:
|
||||||
|
@ -461,7 +461,6 @@ def _get_dns_zone_for_domain(domain):
|
||||||
|
|
||||||
# We don't wan't to do A NS request on the tld
|
# We don't wan't to do A NS request on the tld
|
||||||
for parent in parent_list[0:-1]:
|
for parent in parent_list[0:-1]:
|
||||||
|
|
||||||
# Check if there's a NS record for that domain
|
# Check if there's a NS record for that domain
|
||||||
answer = dig(parent, rdtype="NS", full_answers=True, resolvers="force_external")
|
answer = dig(parent, rdtype="NS", full_answers=True, resolvers="force_external")
|
||||||
|
|
||||||
|
@ -502,7 +501,6 @@ def _get_relative_name_for_dns_zone(domain, base_dns_zone):
|
||||||
|
|
||||||
|
|
||||||
def _get_registrar_config_section(domain):
|
def _get_registrar_config_section(domain):
|
||||||
|
|
||||||
from lexicon.providers.auto import _relevant_provider_for_domain
|
from lexicon.providers.auto import _relevant_provider_for_domain
|
||||||
|
|
||||||
registrar_infos = {
|
registrar_infos = {
|
||||||
|
@ -516,7 +514,6 @@ def _get_registrar_config_section(domain):
|
||||||
# If parent domain exists in yunohost
|
# If parent domain exists in yunohost
|
||||||
parent_domain = _get_parent_domain_of(domain, topest=True)
|
parent_domain = _get_parent_domain_of(domain, topest=True)
|
||||||
if parent_domain:
|
if parent_domain:
|
||||||
|
|
||||||
# Dirty hack to have a link on the webadmin
|
# Dirty hack to have a link on the webadmin
|
||||||
if Moulinette.interface.type == "api":
|
if Moulinette.interface.type == "api":
|
||||||
parent_domain_link = f"[{parent_domain}](#/domains/{parent_domain}/dns)"
|
parent_domain_link = f"[{parent_domain}](#/domains/{parent_domain}/dns)"
|
||||||
|
@ -571,7 +568,6 @@ def _get_registrar_config_section(domain):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
registrar_infos["registrar"] = OrderedDict(
|
registrar_infos["registrar"] = OrderedDict(
|
||||||
{
|
{
|
||||||
"type": "alert",
|
"type": "alert",
|
||||||
|
@ -605,7 +601,6 @@ def _get_registrar_config_section(domain):
|
||||||
|
|
||||||
|
|
||||||
def _get_registar_settings(domain):
|
def _get_registar_settings(domain):
|
||||||
|
|
||||||
_assert_domain_exists(domain)
|
_assert_domain_exists(domain)
|
||||||
|
|
||||||
settings = domain_config_get(domain, key="dns.registrar", export=True)
|
settings = domain_config_get(domain, key="dns.registrar", export=True)
|
||||||
|
@ -669,7 +664,6 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge=
|
||||||
wanted_records = []
|
wanted_records = []
|
||||||
for records in _build_dns_conf(domain).values():
|
for records in _build_dns_conf(domain).values():
|
||||||
for record in records:
|
for record in records:
|
||||||
|
|
||||||
# Make sure the name is a FQDN
|
# Make sure the name is a FQDN
|
||||||
name = (
|
name = (
|
||||||
f"{record['name']}.{base_dns_zone}"
|
f"{record['name']}.{base_dns_zone}"
|
||||||
|
@ -744,7 +738,6 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge=
|
||||||
]
|
]
|
||||||
|
|
||||||
for record in current_records:
|
for record in current_records:
|
||||||
|
|
||||||
# Try to get rid of weird stuff like ".domain.tld" or "@.domain.tld"
|
# Try to get rid of weird stuff like ".domain.tld" or "@.domain.tld"
|
||||||
record["name"] = record["name"].strip("@").strip(".")
|
record["name"] = record["name"].strip("@").strip(".")
|
||||||
|
|
||||||
|
@ -794,7 +787,6 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge=
|
||||||
comparison[(record["type"], record["name"])]["wanted"].append(record)
|
comparison[(record["type"], record["name"])]["wanted"].append(record)
|
||||||
|
|
||||||
for type_and_name, records in comparison.items():
|
for type_and_name, records in comparison.items():
|
||||||
|
|
||||||
#
|
#
|
||||||
# Step 1 : compute a first "diff" where we remove records which are the same on both sides
|
# Step 1 : compute a first "diff" where we remove records which are the same on both sides
|
||||||
#
|
#
|
||||||
|
@ -938,9 +930,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge=
|
||||||
results = {"warnings": [], "errors": []}
|
results = {"warnings": [], "errors": []}
|
||||||
|
|
||||||
for action in ["delete", "create", "update"]:
|
for action in ["delete", "create", "update"]:
|
||||||
|
|
||||||
for record in changes[action]:
|
for record in changes[action]:
|
||||||
|
|
||||||
relative_name = _get_relative_name_for_dns_zone(
|
relative_name = _get_relative_name_for_dns_zone(
|
||||||
record["name"], base_dns_zone
|
record["name"], base_dns_zone
|
||||||
)
|
)
|
||||||
|
@ -1025,7 +1015,6 @@ def _set_managed_dns_records_hashes(domain: str, hashes: list) -> None:
|
||||||
|
|
||||||
|
|
||||||
def _hash_dns_record(record: dict) -> int:
|
def _hash_dns_record(record: dict) -> int:
|
||||||
|
|
||||||
fields = ["name", "type", "content"]
|
fields = ["name", "type", "content"]
|
||||||
record_ = {f: record.get(f) for f in fields}
|
record_ = {f: record.get(f) for f in fields}
|
||||||
|
|
||||||
|
|
|
@ -187,7 +187,6 @@ def _assert_domain_exists(domain):
|
||||||
|
|
||||||
|
|
||||||
def _list_subdomains_of(parent_domain):
|
def _list_subdomains_of(parent_domain):
|
||||||
|
|
||||||
_assert_domain_exists(parent_domain)
|
_assert_domain_exists(parent_domain)
|
||||||
|
|
||||||
out = []
|
out = []
|
||||||
|
@ -199,7 +198,6 @@ def _list_subdomains_of(parent_domain):
|
||||||
|
|
||||||
|
|
||||||
def _get_parent_domain_of(domain, return_self=False, topest=False):
|
def _get_parent_domain_of(domain, return_self=False, topest=False):
|
||||||
|
|
||||||
domains = _get_domains(exclude_subdomains=topest)
|
domains = _get_domains(exclude_subdomains=topest)
|
||||||
|
|
||||||
domain_ = domain
|
domain_ = domain
|
||||||
|
@ -248,7 +246,6 @@ def domain_add(operation_logger, domain, dyndns=False):
|
||||||
|
|
||||||
# DynDNS domain
|
# DynDNS domain
|
||||||
if dyndns:
|
if dyndns:
|
||||||
|
|
||||||
from yunohost.utils.dns import is_yunohost_dyndns_domain
|
from yunohost.utils.dns import is_yunohost_dyndns_domain
|
||||||
from yunohost.dyndns import _guess_current_dyndns_domain
|
from yunohost.dyndns import _guess_current_dyndns_domain
|
||||||
|
|
||||||
|
@ -589,7 +586,6 @@ class DomainConfigPanel(ConfigPanel):
|
||||||
regen_conf(names=stuff_to_regen_conf)
|
regen_conf(names=stuff_to_regen_conf)
|
||||||
|
|
||||||
def _get_toml(self):
|
def _get_toml(self):
|
||||||
|
|
||||||
toml = super()._get_toml()
|
toml = super()._get_toml()
|
||||||
|
|
||||||
toml["feature"]["xmpp"]["xmpp"]["default"] = (
|
toml["feature"]["xmpp"]["xmpp"]["default"] = (
|
||||||
|
@ -611,7 +607,6 @@ class DomainConfigPanel(ConfigPanel):
|
||||||
|
|
||||||
# Cert stuff
|
# Cert stuff
|
||||||
if not filter_key or filter_key[0] == "cert":
|
if not filter_key or filter_key[0] == "cert":
|
||||||
|
|
||||||
from yunohost.certificate import certificate_status
|
from yunohost.certificate import certificate_status
|
||||||
|
|
||||||
status = certificate_status([self.entity], full=True)["certificates"][
|
status = certificate_status([self.entity], full=True)["certificates"][
|
||||||
|
@ -638,7 +633,6 @@ class DomainConfigPanel(ConfigPanel):
|
||||||
return toml
|
return toml
|
||||||
|
|
||||||
def _load_current_values(self):
|
def _load_current_values(self):
|
||||||
|
|
||||||
# TODO add mechanism to share some settings with other domains on the same zone
|
# TODO add mechanism to share some settings with other domains on the same zone
|
||||||
super()._load_current_values()
|
super()._load_current_values()
|
||||||
|
|
||||||
|
@ -656,7 +650,6 @@ class DomainConfigPanel(ConfigPanel):
|
||||||
|
|
||||||
|
|
||||||
def domain_action_run(domain, action, args=None):
|
def domain_action_run(domain, action, args=None):
|
||||||
|
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
if action == "cert.cert.cert_install":
|
if action == "cert.cert.cert_install":
|
||||||
|
@ -671,7 +664,6 @@ def domain_action_run(domain, action, args=None):
|
||||||
|
|
||||||
|
|
||||||
def _get_domain_settings(domain: str) -> dict:
|
def _get_domain_settings(domain: str) -> dict:
|
||||||
|
|
||||||
_assert_domain_exists(domain)
|
_assert_domain_exists(domain)
|
||||||
|
|
||||||
if os.path.exists(f"{DOMAIN_SETTINGS_DIR}/{domain}.yml"):
|
if os.path.exists(f"{DOMAIN_SETTINGS_DIR}/{domain}.yml"):
|
||||||
|
@ -681,7 +673,6 @@ def _get_domain_settings(domain: str) -> dict:
|
||||||
|
|
||||||
|
|
||||||
def _set_domain_settings(domain: str, settings: dict) -> None:
|
def _set_domain_settings(domain: str, settings: dict) -> None:
|
||||||
|
|
||||||
_assert_domain_exists(domain)
|
_assert_domain_exists(domain)
|
||||||
|
|
||||||
write_to_yaml(f"{DOMAIN_SETTINGS_DIR}/{domain}.yml", settings)
|
write_to_yaml(f"{DOMAIN_SETTINGS_DIR}/{domain}.yml", settings)
|
||||||
|
|
|
@ -227,7 +227,6 @@ def dyndns_update(
|
||||||
|
|
||||||
for dns_auth in DYNDNS_DNS_AUTH:
|
for dns_auth in DYNDNS_DNS_AUTH:
|
||||||
for type_ in ["A", "AAAA"]:
|
for type_ in ["A", "AAAA"]:
|
||||||
|
|
||||||
ok, result = dig(dns_auth, type_)
|
ok, result = dig(dns_auth, type_)
|
||||||
if ok == "ok" and len(result) and result[0]:
|
if ok == "ok" and len(result) and result[0]:
|
||||||
auth_resolvers.append(result[0])
|
auth_resolvers.append(result[0])
|
||||||
|
@ -238,7 +237,6 @@ def dyndns_update(
|
||||||
)
|
)
|
||||||
|
|
||||||
def resolve_domain(domain, rdtype):
|
def resolve_domain(domain, rdtype):
|
||||||
|
|
||||||
ok, result = dig(domain, rdtype, resolvers=auth_resolvers)
|
ok, result = dig(domain, rdtype, resolvers=auth_resolvers)
|
||||||
if ok == "ok":
|
if ok == "ok":
|
||||||
return result[0] if len(result) else None
|
return result[0] if len(result) else None
|
||||||
|
|
|
@ -415,7 +415,6 @@ def firewall_upnp(action="status", no_refresh=False):
|
||||||
for protocol in ["TCP", "UDP"]:
|
for protocol in ["TCP", "UDP"]:
|
||||||
if protocol + "_TO_CLOSE" in firewall["uPnP"]:
|
if protocol + "_TO_CLOSE" in firewall["uPnP"]:
|
||||||
for port in firewall["uPnP"][protocol + "_TO_CLOSE"]:
|
for port in firewall["uPnP"][protocol + "_TO_CLOSE"]:
|
||||||
|
|
||||||
if not isinstance(port, int):
|
if not isinstance(port, int):
|
||||||
# FIXME : how should we handle port ranges ?
|
# FIXME : how should we handle port ranges ?
|
||||||
logger.warning("Can't use UPnP to close '%s'" % port)
|
logger.warning("Can't use UPnP to close '%s'" % port)
|
||||||
|
@ -430,7 +429,6 @@ def firewall_upnp(action="status", no_refresh=False):
|
||||||
firewall["uPnP"][protocol + "_TO_CLOSE"] = []
|
firewall["uPnP"][protocol + "_TO_CLOSE"] = []
|
||||||
|
|
||||||
for port in firewall["uPnP"][protocol]:
|
for port in firewall["uPnP"][protocol]:
|
||||||
|
|
||||||
if not isinstance(port, int):
|
if not isinstance(port, int):
|
||||||
# FIXME : how should we handle port ranges ?
|
# FIXME : how should we handle port ranges ?
|
||||||
logger.warning("Can't use UPnP to open '%s'" % port)
|
logger.warning("Can't use UPnP to open '%s'" % port)
|
||||||
|
|
|
@ -339,7 +339,6 @@ def hook_exec(
|
||||||
raise YunohostError("file_does_not_exist", path=path)
|
raise YunohostError("file_does_not_exist", path=path)
|
||||||
|
|
||||||
def is_relevant_warning(msg):
|
def is_relevant_warning(msg):
|
||||||
|
|
||||||
# Ignore empty warning messages...
|
# Ignore empty warning messages...
|
||||||
if not msg:
|
if not msg:
|
||||||
return False
|
return False
|
||||||
|
@ -389,7 +388,6 @@ def hook_exec(
|
||||||
|
|
||||||
|
|
||||||
def _hook_exec_bash(path, args, chdir, env, user, return_format, loggers):
|
def _hook_exec_bash(path, args, chdir, env, user, return_format, loggers):
|
||||||
|
|
||||||
from moulinette.utils.process import call_async_output
|
from moulinette.utils.process import call_async_output
|
||||||
|
|
||||||
# Construct command variables
|
# Construct command variables
|
||||||
|
@ -477,7 +475,6 @@ def _hook_exec_bash(path, args, chdir, env, user, return_format, loggers):
|
||||||
|
|
||||||
|
|
||||||
def _hook_exec_python(path, args, env, loggers):
|
def _hook_exec_python(path, args, env, loggers):
|
||||||
|
|
||||||
dir_ = os.path.dirname(path)
|
dir_ = os.path.dirname(path)
|
||||||
name = os.path.splitext(os.path.basename(path))[0]
|
name = os.path.splitext(os.path.basename(path))[0]
|
||||||
|
|
||||||
|
@ -497,7 +494,6 @@ def _hook_exec_python(path, args, env, loggers):
|
||||||
|
|
||||||
|
|
||||||
def hook_exec_with_script_debug_if_failure(*args, **kwargs):
|
def hook_exec_with_script_debug_if_failure(*args, **kwargs):
|
||||||
|
|
||||||
operation_logger = kwargs.pop("operation_logger")
|
operation_logger = kwargs.pop("operation_logger")
|
||||||
error_message_if_failed = kwargs.pop("error_message_if_failed")
|
error_message_if_failed = kwargs.pop("error_message_if_failed")
|
||||||
error_message_if_script_failed = kwargs.pop("error_message_if_script_failed")
|
error_message_if_script_failed = kwargs.pop("error_message_if_script_failed")
|
||||||
|
|
|
@ -95,7 +95,6 @@ def log_list(limit=None, with_details=False, with_suboperations=False):
|
||||||
logs = logs[: limit * 5]
|
logs = logs[: limit * 5]
|
||||||
|
|
||||||
for log in logs:
|
for log in logs:
|
||||||
|
|
||||||
base_filename = log[: -len(METADATA_FILE_EXT)]
|
base_filename = log[: -len(METADATA_FILE_EXT)]
|
||||||
md_path = os.path.join(OPERATIONS_PATH, log)
|
md_path = os.path.join(OPERATIONS_PATH, log)
|
||||||
|
|
||||||
|
@ -264,7 +263,6 @@ def log_show(
|
||||||
return
|
return
|
||||||
|
|
||||||
for filename in os.listdir(OPERATIONS_PATH):
|
for filename in os.listdir(OPERATIONS_PATH):
|
||||||
|
|
||||||
if not filename.endswith(METADATA_FILE_EXT):
|
if not filename.endswith(METADATA_FILE_EXT):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -438,7 +436,6 @@ class RedactingFormatter(Formatter):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def identify_data_to_redact(self, record):
|
def identify_data_to_redact(self, record):
|
||||||
|
|
||||||
# Wrapping this in a try/except because we don't want this to
|
# Wrapping this in a try/except because we don't want this to
|
||||||
# break everything in case it fails miserably for some reason :s
|
# break everything in case it fails miserably for some reason :s
|
||||||
try:
|
try:
|
||||||
|
@ -497,7 +494,6 @@ class OperationLogger:
|
||||||
os.makedirs(self.path)
|
os.makedirs(self.path)
|
||||||
|
|
||||||
def parent_logger(self):
|
def parent_logger(self):
|
||||||
|
|
||||||
# If there are other operation logger instances
|
# If there are other operation logger instances
|
||||||
for instance in reversed(self._instances):
|
for instance in reversed(self._instances):
|
||||||
# Is one of these operation logger started but not yet done ?
|
# Is one of these operation logger started but not yet done ?
|
||||||
|
@ -732,7 +728,6 @@ class OperationLogger:
|
||||||
self.error(m18n.n("log_operation_unit_unclosed_properly"))
|
self.error(m18n.n("log_operation_unit_unclosed_properly"))
|
||||||
|
|
||||||
def dump_script_log_extract_for_debugging(self):
|
def dump_script_log_extract_for_debugging(self):
|
||||||
|
|
||||||
with open(self.log_path, "r") as f:
|
with open(self.log_path, "r") as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
|
|
||||||
|
@ -774,7 +769,6 @@ class OperationLogger:
|
||||||
|
|
||||||
|
|
||||||
def _get_datetime_from_name(name):
|
def _get_datetime_from_name(name):
|
||||||
|
|
||||||
# Filenames are expected to follow the format:
|
# Filenames are expected to follow the format:
|
||||||
# 20200831-170740-short_description-and-stuff
|
# 20200831-170740-short_description-and-stuff
|
||||||
|
|
||||||
|
|
|
@ -72,13 +72,11 @@ def _backup_pip_freeze_for_python_app_venvs():
|
||||||
|
|
||||||
|
|
||||||
class MyMigration(Migration):
|
class MyMigration(Migration):
|
||||||
|
|
||||||
"Upgrade the system to Debian Bullseye and Yunohost 11.x"
|
"Upgrade the system to Debian Bullseye and Yunohost 11.x"
|
||||||
|
|
||||||
mode = "manual"
|
mode = "manual"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
self.check_assertions()
|
self.check_assertions()
|
||||||
|
|
||||||
logger.info(m18n.n("migration_0021_start"))
|
logger.info(m18n.n("migration_0021_start"))
|
||||||
|
@ -389,7 +387,6 @@ class MyMigration(Migration):
|
||||||
return int(get_ynh_package_version("yunohost")["version"].split(".")[0])
|
return int(get_ynh_package_version("yunohost")["version"].split(".")[0])
|
||||||
|
|
||||||
def check_assertions(self):
|
def check_assertions(self):
|
||||||
|
|
||||||
# Be on buster (10.x) and yunohost 4.x
|
# Be on buster (10.x) and yunohost 4.x
|
||||||
# NB : we do both check to cover situations where the upgrade crashed
|
# NB : we do both check to cover situations where the upgrade crashed
|
||||||
# in the middle and debian version could be > 9.x but yunohost package
|
# in the middle and debian version could be > 9.x but yunohost package
|
||||||
|
@ -453,7 +450,6 @@ class MyMigration(Migration):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def disclaimer(self):
|
def disclaimer(self):
|
||||||
|
|
||||||
# Avoid having a super long disclaimer + uncessary check if we ain't
|
# Avoid having a super long disclaimer + uncessary check if we ain't
|
||||||
# on buster / yunohost 4.x anymore
|
# on buster / yunohost 4.x anymore
|
||||||
# NB : we do both check to cover situations where the upgrade crashed
|
# NB : we do both check to cover situations where the upgrade crashed
|
||||||
|
@ -494,7 +490,6 @@ class MyMigration(Migration):
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def patch_apt_sources_list(self):
|
def patch_apt_sources_list(self):
|
||||||
|
|
||||||
sources_list = glob.glob("/etc/apt/sources.list.d/*.list")
|
sources_list = glob.glob("/etc/apt/sources.list.d/*.list")
|
||||||
if os.path.exists("/etc/apt/sources.list"):
|
if os.path.exists("/etc/apt/sources.list"):
|
||||||
sources_list.append("/etc/apt/sources.list")
|
sources_list.append("/etc/apt/sources.list")
|
||||||
|
@ -516,7 +511,6 @@ class MyMigration(Migration):
|
||||||
os.system(command)
|
os.system(command)
|
||||||
|
|
||||||
def get_apps_equivs_packages(self):
|
def get_apps_equivs_packages(self):
|
||||||
|
|
||||||
command = (
|
command = (
|
||||||
"dpkg --get-selections"
|
"dpkg --get-selections"
|
||||||
" | grep -v deinstall"
|
" | grep -v deinstall"
|
||||||
|
|
|
@ -27,7 +27,6 @@ MIGRATION_COMMENT = (
|
||||||
|
|
||||||
|
|
||||||
class MyMigration(Migration):
|
class MyMigration(Migration):
|
||||||
|
|
||||||
"Migrate php7.3-fpm 'pool' conf files to php7.4"
|
"Migrate php7.3-fpm 'pool' conf files to php7.4"
|
||||||
|
|
||||||
dependencies = ["migrate_to_bullseye"]
|
dependencies = ["migrate_to_bullseye"]
|
||||||
|
@ -43,7 +42,6 @@ class MyMigration(Migration):
|
||||||
oldphp_pool_files = [f for f in oldphp_pool_files if f != "www.conf"]
|
oldphp_pool_files = [f for f in oldphp_pool_files if f != "www.conf"]
|
||||||
|
|
||||||
for pf in oldphp_pool_files:
|
for pf in oldphp_pool_files:
|
||||||
|
|
||||||
# Copy the files to the php7.3 pool
|
# Copy the files to the php7.3 pool
|
||||||
src = "{}/{}".format(OLDPHP_POOLS, pf)
|
src = "{}/{}".format(OLDPHP_POOLS, pf)
|
||||||
dest = "{}/{}".format(NEWPHP_POOLS, pf)
|
dest = "{}/{}".format(NEWPHP_POOLS, pf)
|
||||||
|
|
|
@ -13,13 +13,11 @@ logger = getActionLogger("yunohost.migration")
|
||||||
|
|
||||||
|
|
||||||
class MyMigration(Migration):
|
class MyMigration(Migration):
|
||||||
|
|
||||||
"Migrate DBs from Postgresql 11 to 13 after migrating to Bullseye"
|
"Migrate DBs from Postgresql 11 to 13 after migrating to Bullseye"
|
||||||
|
|
||||||
dependencies = ["migrate_to_bullseye"]
|
dependencies = ["migrate_to_bullseye"]
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
if (
|
if (
|
||||||
os.system(
|
os.system(
|
||||||
'grep -A10 "ynh-deps" /var/lib/dpkg/status | grep -E "Package:|Depends:" | grep -B1 postgresql'
|
'grep -A10 "ynh-deps" /var/lib/dpkg/status | grep -E "Package:|Depends:" | grep -B1 postgresql'
|
||||||
|
@ -63,7 +61,6 @@ class MyMigration(Migration):
|
||||||
self.runcmd("systemctl start postgresql")
|
self.runcmd("systemctl start postgresql")
|
||||||
|
|
||||||
def package_is_installed(self, package_name):
|
def package_is_installed(self, package_name):
|
||||||
|
|
||||||
(returncode, out, err) = self.runcmd(
|
(returncode, out, err) = self.runcmd(
|
||||||
"dpkg --list | grep '^ii ' | grep -q -w {}".format(package_name),
|
"dpkg --list | grep '^ii ' | grep -q -w {}".format(package_name),
|
||||||
raise_on_errors=False,
|
raise_on_errors=False,
|
||||||
|
@ -71,7 +68,6 @@ class MyMigration(Migration):
|
||||||
return returncode == 0
|
return returncode == 0
|
||||||
|
|
||||||
def runcmd(self, cmd, raise_on_errors=True):
|
def runcmd(self, cmd, raise_on_errors=True):
|
||||||
|
|
||||||
logger.debug("Running command: " + cmd)
|
logger.debug("Running command: " + cmd)
|
||||||
|
|
||||||
p = subprocess.Popen(
|
p = subprocess.Popen(
|
||||||
|
|
|
@ -14,7 +14,6 @@ VENV_REQUIREMENTS_SUFFIX = ".requirements_backup_for_bullseye_upgrade.txt"
|
||||||
|
|
||||||
|
|
||||||
def extract_app_from_venv_path(venv_path):
|
def extract_app_from_venv_path(venv_path):
|
||||||
|
|
||||||
venv_path = venv_path.replace("/var/www/", "")
|
venv_path = venv_path.replace("/var/www/", "")
|
||||||
venv_path = venv_path.replace("/opt/yunohost/", "")
|
venv_path = venv_path.replace("/opt/yunohost/", "")
|
||||||
venv_path = venv_path.replace("/opt/", "")
|
venv_path = venv_path.replace("/opt/", "")
|
||||||
|
@ -137,13 +136,11 @@ class MyMigration(Migration):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
if self.mode == "auto":
|
if self.mode == "auto":
|
||||||
return
|
return
|
||||||
|
|
||||||
venvs = _get_all_venvs("/opt/") + _get_all_venvs("/var/www/")
|
venvs = _get_all_venvs("/opt/") + _get_all_venvs("/var/www/")
|
||||||
for venv in venvs:
|
for venv in venvs:
|
||||||
|
|
||||||
app_corresponding_to_venv = extract_app_from_venv_path(venv)
|
app_corresponding_to_venv = extract_app_from_venv_path(venv)
|
||||||
|
|
||||||
# Search for ignore apps
|
# Search for ignore apps
|
||||||
|
|
|
@ -14,7 +14,6 @@ OLD_SETTINGS_PATH = "/etc/yunohost/settings.json"
|
||||||
|
|
||||||
|
|
||||||
class MyMigration(Migration):
|
class MyMigration(Migration):
|
||||||
|
|
||||||
"Migrate old global settings to the new ConfigPanel global settings"
|
"Migrate old global settings to the new ConfigPanel global settings"
|
||||||
|
|
||||||
dependencies = ["migrate_to_bullseye"]
|
dependencies = ["migrate_to_bullseye"]
|
||||||
|
|
|
@ -21,7 +21,6 @@ class MyMigration(Migration):
|
||||||
|
|
||||||
@Migration.ldap_migration
|
@Migration.ldap_migration
|
||||||
def run(self, *args):
|
def run(self, *args):
|
||||||
|
|
||||||
from yunohost.user import (
|
from yunohost.user import (
|
||||||
user_list,
|
user_list,
|
||||||
user_info,
|
user_info,
|
||||||
|
|
|
@ -79,7 +79,6 @@ def user_permission_list(
|
||||||
|
|
||||||
permissions = {}
|
permissions = {}
|
||||||
for infos in permissions_infos:
|
for infos in permissions_infos:
|
||||||
|
|
||||||
name = infos["cn"][0]
|
name = infos["cn"][0]
|
||||||
app = name.split(".")[0]
|
app = name.split(".")[0]
|
||||||
|
|
||||||
|
@ -654,7 +653,6 @@ def permission_sync_to_user():
|
||||||
permissions = user_permission_list(full=True)["permissions"]
|
permissions = user_permission_list(full=True)["permissions"]
|
||||||
|
|
||||||
for permission_name, permission_infos in permissions.items():
|
for permission_name, permission_infos in permissions.items():
|
||||||
|
|
||||||
# These are the users currently allowed because there's an 'inheritPermission' object corresponding to it
|
# These are the users currently allowed because there's an 'inheritPermission' object corresponding to it
|
||||||
currently_allowed_users = set(permission_infos["corresponding_users"])
|
currently_allowed_users = set(permission_infos["corresponding_users"])
|
||||||
|
|
||||||
|
@ -740,7 +738,6 @@ def _update_ldap_group_permission(
|
||||||
update["isProtected"] = [str(protected).upper()]
|
update["isProtected"] = [str(protected).upper()]
|
||||||
|
|
||||||
if show_tile is not None:
|
if show_tile is not None:
|
||||||
|
|
||||||
if show_tile is True:
|
if show_tile is True:
|
||||||
if not existing_permission["url"]:
|
if not existing_permission["url"]:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
@ -876,7 +873,6 @@ def _validate_and_sanitize_permission_url(url, app_base_path, app):
|
||||||
raise YunohostValidationError("invalid_regex", regex=regex)
|
raise YunohostValidationError("invalid_regex", regex=regex)
|
||||||
|
|
||||||
if url.startswith("re:"):
|
if url.startswith("re:"):
|
||||||
|
|
||||||
# regex without domain
|
# regex without domain
|
||||||
# we check for the first char after 're:'
|
# we check for the first char after 're:'
|
||||||
if url[3] in ["/", "^", "\\"]:
|
if url[3] in ["/", "^", "\\"]:
|
||||||
|
|
|
@ -77,7 +77,6 @@ def regen_conf(
|
||||||
|
|
||||||
for category, conf_files in pending_conf.items():
|
for category, conf_files in pending_conf.items():
|
||||||
for system_path, pending_path in conf_files.items():
|
for system_path, pending_path in conf_files.items():
|
||||||
|
|
||||||
pending_conf[category][system_path] = {
|
pending_conf[category][system_path] = {
|
||||||
"pending_conf": pending_path,
|
"pending_conf": pending_path,
|
||||||
"diff": _get_files_diff(system_path, pending_path, True),
|
"diff": _get_files_diff(system_path, pending_path, True),
|
||||||
|
@ -595,7 +594,6 @@ def _update_conf_hashes(category, hashes):
|
||||||
|
|
||||||
|
|
||||||
def _force_clear_hashes(paths):
|
def _force_clear_hashes(paths):
|
||||||
|
|
||||||
categories = _get_regenconf_infos()
|
categories = _get_regenconf_infos()
|
||||||
for path in paths:
|
for path in paths:
|
||||||
for category in categories.keys():
|
for category in categories.keys():
|
||||||
|
@ -675,7 +673,6 @@ def _process_regen_conf(system_conf, new_conf=None, save=True):
|
||||||
|
|
||||||
|
|
||||||
def manually_modified_files():
|
def manually_modified_files():
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
regenconf_categories = _get_regenconf_infos()
|
regenconf_categories = _get_regenconf_infos()
|
||||||
for category, infos in regenconf_categories.items():
|
for category, infos in regenconf_categories.items():
|
||||||
|
@ -690,7 +687,6 @@ def manually_modified_files():
|
||||||
def manually_modified_files_compared_to_debian_default(
|
def manually_modified_files_compared_to_debian_default(
|
||||||
ignore_handled_by_regenconf=False,
|
ignore_handled_by_regenconf=False,
|
||||||
):
|
):
|
||||||
|
|
||||||
# from https://serverfault.com/a/90401
|
# from https://serverfault.com/a/90401
|
||||||
files = check_output(
|
files = check_output(
|
||||||
"dpkg-query -W -f='${Conffiles}\n' '*' \
|
"dpkg-query -W -f='${Conffiles}\n' '*' \
|
||||||
|
|
|
@ -249,12 +249,10 @@ def service_reload_or_restart(names, test_conf=True):
|
||||||
services = _get_services()
|
services = _get_services()
|
||||||
|
|
||||||
for name in names:
|
for name in names:
|
||||||
|
|
||||||
logger.debug(f"Reloading service {name}")
|
logger.debug(f"Reloading service {name}")
|
||||||
|
|
||||||
test_conf_cmd = services.get(name, {}).get("test_conf")
|
test_conf_cmd = services.get(name, {}).get("test_conf")
|
||||||
if test_conf and test_conf_cmd:
|
if test_conf and test_conf_cmd:
|
||||||
|
|
||||||
p = subprocess.Popen(
|
p = subprocess.Popen(
|
||||||
test_conf_cmd,
|
test_conf_cmd,
|
||||||
shell=True,
|
shell=True,
|
||||||
|
@ -393,7 +391,6 @@ def _get_service_information_from_systemd(service):
|
||||||
|
|
||||||
|
|
||||||
def _get_and_format_service_status(service, infos):
|
def _get_and_format_service_status(service, infos):
|
||||||
|
|
||||||
systemd_service = infos.get("actual_systemd_service", service)
|
systemd_service = infos.get("actual_systemd_service", service)
|
||||||
raw_status, raw_service = _get_service_information_from_systemd(systemd_service)
|
raw_status, raw_service = _get_service_information_from_systemd(systemd_service)
|
||||||
|
|
||||||
|
@ -414,7 +411,6 @@ def _get_and_format_service_status(service, infos):
|
||||||
|
|
||||||
# If no description was there, try to get it from the .json locales
|
# If no description was there, try to get it from the .json locales
|
||||||
if not description:
|
if not description:
|
||||||
|
|
||||||
translation_key = f"service_description_{service}"
|
translation_key = f"service_description_{service}"
|
||||||
if m18n.key_exists(translation_key):
|
if m18n.key_exists(translation_key):
|
||||||
description = m18n.n(translation_key)
|
description = m18n.n(translation_key)
|
||||||
|
@ -521,7 +517,6 @@ def service_log(name, number=50):
|
||||||
result["journalctl"] = _get_journalctl_logs(name, number).splitlines()
|
result["journalctl"] = _get_journalctl_logs(name, number).splitlines()
|
||||||
|
|
||||||
for log_path in log_list:
|
for log_path in log_list:
|
||||||
|
|
||||||
if not os.path.exists(log_path):
|
if not os.path.exists(log_path):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -620,7 +615,6 @@ def _run_service_command(action, service):
|
||||||
|
|
||||||
|
|
||||||
def _give_lock(action, service, p):
|
def _give_lock(action, service, p):
|
||||||
|
|
||||||
# Depending of the action, systemctl calls the PID differently :/
|
# Depending of the action, systemctl calls the PID differently :/
|
||||||
if action == "start" or action == "restart":
|
if action == "start" or action == "restart":
|
||||||
systemctl_PID_name = "MainPID"
|
systemctl_PID_name = "MainPID"
|
||||||
|
@ -712,6 +706,10 @@ def _get_services():
|
||||||
"category": "web",
|
"category": "web",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Ignore metronome entirely if XMPP was disabled on all domains
|
||||||
|
if "metronome" in services and not glob("/etc/metronome/conf.d/*.cfg.lua"):
|
||||||
|
del services["metronome"]
|
||||||
|
|
||||||
# Remove legacy /var/log/daemon.log and /var/log/syslog from log entries
|
# Remove legacy /var/log/daemon.log and /var/log/syslog from log entries
|
||||||
# because they are too general. Instead, now the journalctl log is
|
# because they are too general. Instead, now the journalctl log is
|
||||||
# returned by default which is more relevant.
|
# returned by default which is more relevant.
|
||||||
|
@ -740,7 +738,6 @@ def _save_services(services):
|
||||||
diff = {}
|
diff = {}
|
||||||
|
|
||||||
for service_name, service_infos in services.items():
|
for service_name, service_infos in services.items():
|
||||||
|
|
||||||
# Ignore php-fpm services, they are to be added dynamically by the core,
|
# Ignore php-fpm services, they are to be added dynamically by the core,
|
||||||
# but not actually saved
|
# but not actually saved
|
||||||
if service_name.startswith("php") and service_name.endswith("-fpm"):
|
if service_name.startswith("php") and service_name.endswith("-fpm"):
|
||||||
|
|
|
@ -59,7 +59,6 @@ def settings_get(key="", full=False, export=False):
|
||||||
|
|
||||||
|
|
||||||
def settings_list(full=False):
|
def settings_list(full=False):
|
||||||
|
|
||||||
settings = settings_get(full=full)
|
settings = settings_get(full=full)
|
||||||
|
|
||||||
if full:
|
if full:
|
||||||
|
@ -126,7 +125,6 @@ class SettingsConfigPanel(ConfigPanel):
|
||||||
super().__init__("settings")
|
super().__init__("settings")
|
||||||
|
|
||||||
def _apply(self):
|
def _apply(self):
|
||||||
|
|
||||||
root_password = self.new_values.pop("root_password", None)
|
root_password = self.new_values.pop("root_password", None)
|
||||||
root_password_confirm = self.new_values.pop("root_password_confirm", None)
|
root_password_confirm = self.new_values.pop("root_password_confirm", None)
|
||||||
passwordless_sudo = self.new_values.pop("passwordless_sudo", None)
|
passwordless_sudo = self.new_values.pop("passwordless_sudo", None)
|
||||||
|
@ -141,7 +139,6 @@ class SettingsConfigPanel(ConfigPanel):
|
||||||
assert all(v not in self.future_values for v in self.virtual_settings)
|
assert all(v not in self.future_values for v in self.virtual_settings)
|
||||||
|
|
||||||
if root_password and root_password.strip():
|
if root_password and root_password.strip():
|
||||||
|
|
||||||
if root_password != root_password_confirm:
|
if root_password != root_password_confirm:
|
||||||
raise YunohostValidationError("password_confirmation_not_the_same")
|
raise YunohostValidationError("password_confirmation_not_the_same")
|
||||||
|
|
||||||
|
@ -173,7 +170,6 @@ class SettingsConfigPanel(ConfigPanel):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def _get_toml(self):
|
def _get_toml(self):
|
||||||
|
|
||||||
toml = super()._get_toml()
|
toml = super()._get_toml()
|
||||||
|
|
||||||
# Dynamic choice list for portal themes
|
# Dynamic choice list for portal themes
|
||||||
|
@ -187,7 +183,6 @@ class SettingsConfigPanel(ConfigPanel):
|
||||||
return toml
|
return toml
|
||||||
|
|
||||||
def _load_current_values(self):
|
def _load_current_values(self):
|
||||||
|
|
||||||
super()._load_current_values()
|
super()._load_current_values()
|
||||||
|
|
||||||
# Specific logic for those settings who are "virtual" settings
|
# Specific logic for those settings who are "virtual" settings
|
||||||
|
@ -203,11 +198,10 @@ class SettingsConfigPanel(ConfigPanel):
|
||||||
self.values["passwordless_sudo"] = "!authenticate" in ldap.search(
|
self.values["passwordless_sudo"] = "!authenticate" in ldap.search(
|
||||||
"ou=sudo", "cn=admins", ["sudoOption"]
|
"ou=sudo", "cn=admins", ["sudoOption"]
|
||||||
)[0].get("sudoOption", [])
|
)[0].get("sudoOption", [])
|
||||||
except:
|
except Exception:
|
||||||
self.values["passwordless_sudo"] = False
|
self.values["passwordless_sudo"] = False
|
||||||
|
|
||||||
def get(self, key="", mode="classic"):
|
def get(self, key="", mode="classic"):
|
||||||
|
|
||||||
result = super().get(key=key, mode=mode)
|
result = super().get(key=key, mode=mode)
|
||||||
|
|
||||||
if mode == "full":
|
if mode == "full":
|
||||||
|
|
|
@ -51,7 +51,6 @@ old_translate = moulinette.core.Translator.translate
|
||||||
|
|
||||||
|
|
||||||
def new_translate(self, key, *args, **kwargs):
|
def new_translate(self, key, *args, **kwargs):
|
||||||
|
|
||||||
if key not in self._translations[self.default_locale].keys():
|
if key not in self._translations[self.default_locale].keys():
|
||||||
raise KeyError("Unable to retrieve key %s for default locale !" % key)
|
raise KeyError("Unable to retrieve key %s for default locale !" % key)
|
||||||
|
|
||||||
|
@ -67,7 +66,6 @@ moulinette.core.Translator.translate = new_translate
|
||||||
|
|
||||||
|
|
||||||
def pytest_cmdline_main(config):
|
def pytest_cmdline_main(config):
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, "/usr/lib/moulinette/")
|
sys.path.insert(0, "/usr/lib/moulinette/")
|
||||||
|
@ -76,7 +74,6 @@ def pytest_cmdline_main(config):
|
||||||
yunohost.init(debug=config.option.yunodebug)
|
yunohost.init(debug=config.option.yunodebug)
|
||||||
|
|
||||||
class DummyInterface:
|
class DummyInterface:
|
||||||
|
|
||||||
type = "cli"
|
type = "cli"
|
||||||
|
|
||||||
def prompt(self, *args, **kwargs):
|
def prompt(self, *args, **kwargs):
|
||||||
|
|
|
@ -44,7 +44,6 @@ class AnyStringWith(str):
|
||||||
|
|
||||||
|
|
||||||
def setup_function(function):
|
def setup_function(function):
|
||||||
|
|
||||||
# Clear apps catalog cache
|
# Clear apps catalog cache
|
||||||
shutil.rmtree(APPS_CATALOG_CACHE, ignore_errors=True)
|
shutil.rmtree(APPS_CATALOG_CACHE, ignore_errors=True)
|
||||||
|
|
||||||
|
@ -54,7 +53,6 @@ def setup_function(function):
|
||||||
|
|
||||||
|
|
||||||
def teardown_function(function):
|
def teardown_function(function):
|
||||||
|
|
||||||
# Clear apps catalog cache
|
# Clear apps catalog cache
|
||||||
# Otherwise when using apps stuff after running the test,
|
# Otherwise when using apps stuff after running the test,
|
||||||
# we'll still have the dummy unusable list
|
# we'll still have the dummy unusable list
|
||||||
|
@ -67,7 +65,6 @@ def teardown_function(function):
|
||||||
|
|
||||||
|
|
||||||
def test_apps_catalog_init(mocker):
|
def test_apps_catalog_init(mocker):
|
||||||
|
|
||||||
# Cache is empty
|
# Cache is empty
|
||||||
assert not glob.glob(APPS_CATALOG_CACHE + "/*")
|
assert not glob.glob(APPS_CATALOG_CACHE + "/*")
|
||||||
# Conf doesn't exist yet
|
# Conf doesn't exist yet
|
||||||
|
@ -91,7 +88,6 @@ def test_apps_catalog_init(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_apps_catalog_emptylist():
|
def test_apps_catalog_emptylist():
|
||||||
|
|
||||||
# Initialize ...
|
# Initialize ...
|
||||||
_initialize_apps_catalog_system()
|
_initialize_apps_catalog_system()
|
||||||
|
|
||||||
|
@ -104,7 +100,6 @@ def test_apps_catalog_emptylist():
|
||||||
|
|
||||||
|
|
||||||
def test_apps_catalog_update_nominal(mocker):
|
def test_apps_catalog_update_nominal(mocker):
|
||||||
|
|
||||||
# Initialize ...
|
# Initialize ...
|
||||||
_initialize_apps_catalog_system()
|
_initialize_apps_catalog_system()
|
||||||
|
|
||||||
|
@ -113,7 +108,6 @@ def test_apps_catalog_update_nominal(mocker):
|
||||||
|
|
||||||
# Update
|
# Update
|
||||||
with requests_mock.Mocker() as m:
|
with requests_mock.Mocker() as m:
|
||||||
|
|
||||||
_actual_apps_catalog_api_url,
|
_actual_apps_catalog_api_url,
|
||||||
# Mock the server response with a dummy apps catalog
|
# Mock the server response with a dummy apps catalog
|
||||||
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
||||||
|
@ -139,12 +133,10 @@ def test_apps_catalog_update_nominal(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_apps_catalog_update_404(mocker):
|
def test_apps_catalog_update_404(mocker):
|
||||||
|
|
||||||
# Initialize ...
|
# Initialize ...
|
||||||
_initialize_apps_catalog_system()
|
_initialize_apps_catalog_system()
|
||||||
|
|
||||||
with requests_mock.Mocker() as m:
|
with requests_mock.Mocker() as m:
|
||||||
|
|
||||||
# 404 error
|
# 404 error
|
||||||
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, status_code=404)
|
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, status_code=404)
|
||||||
|
|
||||||
|
@ -155,12 +147,10 @@ def test_apps_catalog_update_404(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_apps_catalog_update_timeout(mocker):
|
def test_apps_catalog_update_timeout(mocker):
|
||||||
|
|
||||||
# Initialize ...
|
# Initialize ...
|
||||||
_initialize_apps_catalog_system()
|
_initialize_apps_catalog_system()
|
||||||
|
|
||||||
with requests_mock.Mocker() as m:
|
with requests_mock.Mocker() as m:
|
||||||
|
|
||||||
# Timeout
|
# Timeout
|
||||||
m.register_uri(
|
m.register_uri(
|
||||||
"GET", APPS_CATALOG_DEFAULT_URL_FULL, exc=requests.exceptions.ConnectTimeout
|
"GET", APPS_CATALOG_DEFAULT_URL_FULL, exc=requests.exceptions.ConnectTimeout
|
||||||
|
@ -173,12 +163,10 @@ def test_apps_catalog_update_timeout(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_apps_catalog_update_sslerror(mocker):
|
def test_apps_catalog_update_sslerror(mocker):
|
||||||
|
|
||||||
# Initialize ...
|
# Initialize ...
|
||||||
_initialize_apps_catalog_system()
|
_initialize_apps_catalog_system()
|
||||||
|
|
||||||
with requests_mock.Mocker() as m:
|
with requests_mock.Mocker() as m:
|
||||||
|
|
||||||
# SSL error
|
# SSL error
|
||||||
m.register_uri(
|
m.register_uri(
|
||||||
"GET", APPS_CATALOG_DEFAULT_URL_FULL, exc=requests.exceptions.SSLError
|
"GET", APPS_CATALOG_DEFAULT_URL_FULL, exc=requests.exceptions.SSLError
|
||||||
|
@ -191,12 +179,10 @@ def test_apps_catalog_update_sslerror(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_apps_catalog_update_corrupted(mocker):
|
def test_apps_catalog_update_corrupted(mocker):
|
||||||
|
|
||||||
# Initialize ...
|
# Initialize ...
|
||||||
_initialize_apps_catalog_system()
|
_initialize_apps_catalog_system()
|
||||||
|
|
||||||
with requests_mock.Mocker() as m:
|
with requests_mock.Mocker() as m:
|
||||||
|
|
||||||
# Corrupted json
|
# Corrupted json
|
||||||
m.register_uri(
|
m.register_uri(
|
||||||
"GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG[:-2]
|
"GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG[:-2]
|
||||||
|
@ -209,7 +195,6 @@ def test_apps_catalog_update_corrupted(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_apps_catalog_load_with_empty_cache(mocker):
|
def test_apps_catalog_load_with_empty_cache(mocker):
|
||||||
|
|
||||||
# Initialize ...
|
# Initialize ...
|
||||||
_initialize_apps_catalog_system()
|
_initialize_apps_catalog_system()
|
||||||
|
|
||||||
|
@ -218,7 +203,6 @@ def test_apps_catalog_load_with_empty_cache(mocker):
|
||||||
|
|
||||||
# Update
|
# Update
|
||||||
with requests_mock.Mocker() as m:
|
with requests_mock.Mocker() as m:
|
||||||
|
|
||||||
# Mock the server response with a dummy apps catalog
|
# Mock the server response with a dummy apps catalog
|
||||||
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
||||||
|
|
||||||
|
@ -237,7 +221,6 @@ def test_apps_catalog_load_with_empty_cache(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_apps_catalog_load_with_conflicts_between_lists(mocker):
|
def test_apps_catalog_load_with_conflicts_between_lists(mocker):
|
||||||
|
|
||||||
# Initialize ...
|
# Initialize ...
|
||||||
_initialize_apps_catalog_system()
|
_initialize_apps_catalog_system()
|
||||||
|
|
||||||
|
@ -253,7 +236,6 @@ def test_apps_catalog_load_with_conflicts_between_lists(mocker):
|
||||||
|
|
||||||
# Update
|
# Update
|
||||||
with requests_mock.Mocker() as m:
|
with requests_mock.Mocker() as m:
|
||||||
|
|
||||||
# Mock the server response with a dummy apps catalog
|
# Mock the server response with a dummy apps catalog
|
||||||
# + the same apps catalog for the second list
|
# + the same apps catalog for the second list
|
||||||
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
||||||
|
@ -277,13 +259,11 @@ def test_apps_catalog_load_with_conflicts_between_lists(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_apps_catalog_load_with_oudated_api_version(mocker):
|
def test_apps_catalog_load_with_oudated_api_version(mocker):
|
||||||
|
|
||||||
# Initialize ...
|
# Initialize ...
|
||||||
_initialize_apps_catalog_system()
|
_initialize_apps_catalog_system()
|
||||||
|
|
||||||
# Update
|
# Update
|
||||||
with requests_mock.Mocker() as m:
|
with requests_mock.Mocker() as m:
|
||||||
|
|
||||||
mocker.spy(m18n, "n")
|
mocker.spy(m18n, "n")
|
||||||
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
||||||
_update_apps_catalog()
|
_update_apps_catalog()
|
||||||
|
@ -300,7 +280,6 @@ def test_apps_catalog_load_with_oudated_api_version(mocker):
|
||||||
|
|
||||||
# Update
|
# Update
|
||||||
with requests_mock.Mocker() as m:
|
with requests_mock.Mocker() as m:
|
||||||
|
|
||||||
# Mock the server response with a dummy apps catalog
|
# Mock the server response with a dummy apps catalog
|
||||||
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
||||||
|
|
||||||
|
|
|
@ -25,17 +25,14 @@ from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||||
|
|
||||||
|
|
||||||
def setup_function(function):
|
def setup_function(function):
|
||||||
|
|
||||||
clean()
|
clean()
|
||||||
|
|
||||||
|
|
||||||
def teardown_function(function):
|
def teardown_function(function):
|
||||||
|
|
||||||
clean()
|
clean()
|
||||||
|
|
||||||
|
|
||||||
def clean():
|
def clean():
|
||||||
|
|
||||||
# Make sure we have a ssowat
|
# Make sure we have a ssowat
|
||||||
os.system("mkdir -p /etc/ssowat/")
|
os.system("mkdir -p /etc/ssowat/")
|
||||||
app_ssowatconf()
|
app_ssowatconf()
|
||||||
|
@ -43,7 +40,6 @@ def clean():
|
||||||
test_apps = ["config_app", "legacy_app"]
|
test_apps = ["config_app", "legacy_app"]
|
||||||
|
|
||||||
for test_app in test_apps:
|
for test_app in test_apps:
|
||||||
|
|
||||||
if _is_installed(test_app):
|
if _is_installed(test_app):
|
||||||
app_remove(test_app)
|
app_remove(test_app)
|
||||||
|
|
||||||
|
@ -66,7 +62,6 @@ def clean():
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def legacy_app(request):
|
def legacy_app(request):
|
||||||
|
|
||||||
main_domain = _get_maindomain()
|
main_domain = _get_maindomain()
|
||||||
|
|
||||||
app_install(
|
app_install(
|
||||||
|
@ -85,7 +80,6 @@ def legacy_app(request):
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def config_app(request):
|
def config_app(request):
|
||||||
|
|
||||||
app_install(
|
app_install(
|
||||||
os.path.join(get_test_apps_dir(), "config_app_ynh"),
|
os.path.join(get_test_apps_dir(), "config_app_ynh"),
|
||||||
args="",
|
args="",
|
||||||
|
@ -101,7 +95,6 @@ def config_app(request):
|
||||||
|
|
||||||
|
|
||||||
def test_app_config_get(config_app):
|
def test_app_config_get(config_app):
|
||||||
|
|
||||||
user_create("alice", _get_maindomain(), "test123Ynh", fullname="Alice White")
|
user_create("alice", _get_maindomain(), "test123Ynh", fullname="Alice White")
|
||||||
|
|
||||||
assert isinstance(app_config_get(config_app), dict)
|
assert isinstance(app_config_get(config_app), dict)
|
||||||
|
@ -115,13 +108,11 @@ def test_app_config_get(config_app):
|
||||||
|
|
||||||
|
|
||||||
def test_app_config_nopanel(legacy_app):
|
def test_app_config_nopanel(legacy_app):
|
||||||
|
|
||||||
with pytest.raises(YunohostValidationError):
|
with pytest.raises(YunohostValidationError):
|
||||||
app_config_get(legacy_app)
|
app_config_get(legacy_app)
|
||||||
|
|
||||||
|
|
||||||
def test_app_config_get_nonexistentstuff(config_app):
|
def test_app_config_get_nonexistentstuff(config_app):
|
||||||
|
|
||||||
with pytest.raises(YunohostValidationError):
|
with pytest.raises(YunohostValidationError):
|
||||||
app_config_get("nonexistent")
|
app_config_get("nonexistent")
|
||||||
|
|
||||||
|
@ -140,7 +131,6 @@ def test_app_config_get_nonexistentstuff(config_app):
|
||||||
|
|
||||||
|
|
||||||
def test_app_config_regular_setting(config_app):
|
def test_app_config_regular_setting(config_app):
|
||||||
|
|
||||||
assert app_config_get(config_app, "main.components.boolean") == 0
|
assert app_config_get(config_app, "main.components.boolean") == 0
|
||||||
|
|
||||||
app_config_set(config_app, "main.components.boolean", "no")
|
app_config_set(config_app, "main.components.boolean", "no")
|
||||||
|
@ -160,7 +150,6 @@ def test_app_config_regular_setting(config_app):
|
||||||
|
|
||||||
|
|
||||||
def test_app_config_bind_on_file(config_app):
|
def test_app_config_bind_on_file(config_app):
|
||||||
|
|
||||||
# c.f. conf/test.php in the config app
|
# c.f. conf/test.php in the config app
|
||||||
assert '$arg5= "Arg5 value";' in read_file("/var/www/config_app/test.php")
|
assert '$arg5= "Arg5 value";' in read_file("/var/www/config_app/test.php")
|
||||||
assert app_config_get(config_app, "bind.variable.arg5") == "Arg5 value"
|
assert app_config_get(config_app, "bind.variable.arg5") == "Arg5 value"
|
||||||
|
@ -184,7 +173,6 @@ def test_app_config_bind_on_file(config_app):
|
||||||
|
|
||||||
|
|
||||||
def test_app_config_custom_validator(config_app):
|
def test_app_config_custom_validator(config_app):
|
||||||
|
|
||||||
# c.f. the config script
|
# c.f. the config script
|
||||||
# arg8 is a password that must be at least 8 chars
|
# arg8 is a password that must be at least 8 chars
|
||||||
assert not os.path.exists("/var/www/config_app/password")
|
assert not os.path.exists("/var/www/config_app/password")
|
||||||
|
@ -198,7 +186,6 @@ def test_app_config_custom_validator(config_app):
|
||||||
|
|
||||||
|
|
||||||
def test_app_config_custom_set(config_app):
|
def test_app_config_custom_set(config_app):
|
||||||
|
|
||||||
assert not os.path.exists("/var/www/config_app/password")
|
assert not os.path.exists("/var/www/config_app/password")
|
||||||
assert app_setting(config_app, "arg8") is None
|
assert app_setting(config_app, "arg8") is None
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ dummyfile = "/tmp/dummyappresource-testapp"
|
||||||
|
|
||||||
|
|
||||||
class DummyAppResource(AppResource):
|
class DummyAppResource(AppResource):
|
||||||
|
|
||||||
type = "dummy"
|
type = "dummy"
|
||||||
|
|
||||||
default_properties = {
|
default_properties = {
|
||||||
|
@ -26,14 +25,12 @@ class DummyAppResource(AppResource):
|
||||||
}
|
}
|
||||||
|
|
||||||
def provision_or_update(self, context):
|
def provision_or_update(self, context):
|
||||||
|
|
||||||
open(self.file, "w").write(self.content)
|
open(self.file, "w").write(self.content)
|
||||||
|
|
||||||
if self.content == "forbiddenvalue":
|
if self.content == "forbiddenvalue":
|
||||||
raise Exception("Emeged you used the forbidden value!1!£&")
|
raise Exception("Emeged you used the forbidden value!1!£&")
|
||||||
|
|
||||||
def deprovision(self, context):
|
def deprovision(self, context):
|
||||||
|
|
||||||
os.system(f"rm -f {self.file}")
|
os.system(f"rm -f {self.file}")
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,7 +38,6 @@ AppResourceClassesByType["dummy"] = DummyAppResource
|
||||||
|
|
||||||
|
|
||||||
def setup_function(function):
|
def setup_function(function):
|
||||||
|
|
||||||
clean()
|
clean()
|
||||||
|
|
||||||
os.system("mkdir /etc/yunohost/apps/testapp")
|
os.system("mkdir /etc/yunohost/apps/testapp")
|
||||||
|
@ -51,12 +47,10 @@ def setup_function(function):
|
||||||
|
|
||||||
|
|
||||||
def teardown_function(function):
|
def teardown_function(function):
|
||||||
|
|
||||||
clean()
|
clean()
|
||||||
|
|
||||||
|
|
||||||
def clean():
|
def clean():
|
||||||
|
|
||||||
os.system(f"rm -f {dummyfile}")
|
os.system(f"rm -f {dummyfile}")
|
||||||
os.system("rm -rf /etc/yunohost/apps/testapp")
|
os.system("rm -rf /etc/yunohost/apps/testapp")
|
||||||
os.system("rm -rf /var/www/testapp")
|
os.system("rm -rf /var/www/testapp")
|
||||||
|
@ -70,7 +64,6 @@ def clean():
|
||||||
|
|
||||||
|
|
||||||
def test_provision_dummy():
|
def test_provision_dummy():
|
||||||
|
|
||||||
current = {"resources": {}}
|
current = {"resources": {}}
|
||||||
wanted = {"resources": {"dummy": {}}}
|
wanted = {"resources": {"dummy": {}}}
|
||||||
|
|
||||||
|
@ -82,7 +75,6 @@ def test_provision_dummy():
|
||||||
|
|
||||||
|
|
||||||
def test_deprovision_dummy():
|
def test_deprovision_dummy():
|
||||||
|
|
||||||
current = {"resources": {"dummy": {}}}
|
current = {"resources": {"dummy": {}}}
|
||||||
wanted = {"resources": {}}
|
wanted = {"resources": {}}
|
||||||
|
|
||||||
|
@ -96,7 +88,6 @@ def test_deprovision_dummy():
|
||||||
|
|
||||||
|
|
||||||
def test_provision_dummy_nondefaultvalue():
|
def test_provision_dummy_nondefaultvalue():
|
||||||
|
|
||||||
current = {"resources": {}}
|
current = {"resources": {}}
|
||||||
wanted = {"resources": {"dummy": {"content": "bar"}}}
|
wanted = {"resources": {"dummy": {"content": "bar"}}}
|
||||||
|
|
||||||
|
@ -108,7 +99,6 @@ def test_provision_dummy_nondefaultvalue():
|
||||||
|
|
||||||
|
|
||||||
def test_update_dummy():
|
def test_update_dummy():
|
||||||
|
|
||||||
current = {"resources": {"dummy": {}}}
|
current = {"resources": {"dummy": {}}}
|
||||||
wanted = {"resources": {"dummy": {"content": "bar"}}}
|
wanted = {"resources": {"dummy": {"content": "bar"}}}
|
||||||
|
|
||||||
|
@ -122,7 +112,6 @@ def test_update_dummy():
|
||||||
|
|
||||||
|
|
||||||
def test_update_dummy_failwithrollback():
|
def test_update_dummy_failwithrollback():
|
||||||
|
|
||||||
current = {"resources": {"dummy": {}}}
|
current = {"resources": {"dummy": {}}}
|
||||||
wanted = {"resources": {"dummy": {"content": "forbiddenvalue"}}}
|
wanted = {"resources": {"dummy": {"content": "forbiddenvalue"}}}
|
||||||
|
|
||||||
|
@ -137,7 +126,6 @@ def test_update_dummy_failwithrollback():
|
||||||
|
|
||||||
|
|
||||||
def test_resource_system_user():
|
def test_resource_system_user():
|
||||||
|
|
||||||
r = AppResourceClassesByType["system_user"]
|
r = AppResourceClassesByType["system_user"]
|
||||||
|
|
||||||
conf = {}
|
conf = {}
|
||||||
|
@ -161,7 +149,6 @@ def test_resource_system_user():
|
||||||
|
|
||||||
|
|
||||||
def test_resource_install_dir():
|
def test_resource_install_dir():
|
||||||
|
|
||||||
r = AppResourceClassesByType["install_dir"]
|
r = AppResourceClassesByType["install_dir"]
|
||||||
conf = {"owner": "nobody:rx", "group": "nogroup:rx"}
|
conf = {"owner": "nobody:rx", "group": "nogroup:rx"}
|
||||||
|
|
||||||
|
@ -196,7 +183,6 @@ def test_resource_install_dir():
|
||||||
|
|
||||||
|
|
||||||
def test_resource_data_dir():
|
def test_resource_data_dir():
|
||||||
|
|
||||||
r = AppResourceClassesByType["data_dir"]
|
r = AppResourceClassesByType["data_dir"]
|
||||||
conf = {"owner": "nobody:rx", "group": "nogroup:rx"}
|
conf = {"owner": "nobody:rx", "group": "nogroup:rx"}
|
||||||
|
|
||||||
|
@ -228,7 +214,6 @@ def test_resource_data_dir():
|
||||||
|
|
||||||
|
|
||||||
def test_resource_ports():
|
def test_resource_ports():
|
||||||
|
|
||||||
r = AppResourceClassesByType["ports"]
|
r = AppResourceClassesByType["ports"]
|
||||||
conf = {}
|
conf = {}
|
||||||
|
|
||||||
|
@ -244,7 +229,6 @@ def test_resource_ports():
|
||||||
|
|
||||||
|
|
||||||
def test_resource_ports_several():
|
def test_resource_ports_several():
|
||||||
|
|
||||||
r = AppResourceClassesByType["ports"]
|
r = AppResourceClassesByType["ports"]
|
||||||
conf = {"main": {"default": 12345}, "foobar": {"default": 23456}}
|
conf = {"main": {"default": 12345}, "foobar": {"default": 23456}}
|
||||||
|
|
||||||
|
@ -263,7 +247,6 @@ def test_resource_ports_several():
|
||||||
|
|
||||||
|
|
||||||
def test_resource_ports_firewall():
|
def test_resource_ports_firewall():
|
||||||
|
|
||||||
r = AppResourceClassesByType["ports"]
|
r = AppResourceClassesByType["ports"]
|
||||||
conf = {"main": {"default": 12345}}
|
conf = {"main": {"default": 12345}}
|
||||||
|
|
||||||
|
@ -283,7 +266,6 @@ def test_resource_ports_firewall():
|
||||||
|
|
||||||
|
|
||||||
def test_resource_database():
|
def test_resource_database():
|
||||||
|
|
||||||
r = AppResourceClassesByType["database"]
|
r = AppResourceClassesByType["database"]
|
||||||
conf = {"type": "mysql"}
|
conf = {"type": "mysql"}
|
||||||
|
|
||||||
|
@ -308,7 +290,6 @@ def test_resource_database():
|
||||||
|
|
||||||
|
|
||||||
def test_resource_apt():
|
def test_resource_apt():
|
||||||
|
|
||||||
r = AppResourceClassesByType["apt"]
|
r = AppResourceClassesByType["apt"]
|
||||||
conf = {
|
conf = {
|
||||||
"packages": "nyancat, sl",
|
"packages": "nyancat, sl",
|
||||||
|
@ -356,7 +337,6 @@ def test_resource_apt():
|
||||||
|
|
||||||
|
|
||||||
def test_resource_permissions():
|
def test_resource_permissions():
|
||||||
|
|
||||||
maindomain = _get_maindomain()
|
maindomain = _get_maindomain()
|
||||||
os.system(f"echo 'domain: {maindomain}' >> /etc/yunohost/apps/testapp/settings.yml")
|
os.system(f"echo 'domain: {maindomain}' >> /etc/yunohost/apps/testapp/settings.yml")
|
||||||
os.system("echo 'path: /testapp' >> /etc/yunohost/apps/testapp/settings.yml")
|
os.system("echo 'path: /testapp' >> /etc/yunohost/apps/testapp/settings.yml")
|
||||||
|
|
|
@ -28,17 +28,14 @@ from yunohost.permission import user_permission_list, permission_delete
|
||||||
|
|
||||||
|
|
||||||
def setup_function(function):
|
def setup_function(function):
|
||||||
|
|
||||||
clean()
|
clean()
|
||||||
|
|
||||||
|
|
||||||
def teardown_function(function):
|
def teardown_function(function):
|
||||||
|
|
||||||
clean()
|
clean()
|
||||||
|
|
||||||
|
|
||||||
def clean():
|
def clean():
|
||||||
|
|
||||||
# Make sure we have a ssowat
|
# Make sure we have a ssowat
|
||||||
os.system("mkdir -p /etc/ssowat/")
|
os.system("mkdir -p /etc/ssowat/")
|
||||||
app_ssowatconf()
|
app_ssowatconf()
|
||||||
|
@ -53,7 +50,6 @@ def clean():
|
||||||
]
|
]
|
||||||
|
|
||||||
for test_app in test_apps:
|
for test_app in test_apps:
|
||||||
|
|
||||||
if _is_installed(test_app):
|
if _is_installed(test_app):
|
||||||
app_remove(test_app)
|
app_remove(test_app)
|
||||||
|
|
||||||
|
@ -95,7 +91,6 @@ def check_permission_for_apps_call():
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def secondary_domain(request):
|
def secondary_domain(request):
|
||||||
|
|
||||||
if "example.test" not in domain_list()["domains"]:
|
if "example.test" not in domain_list()["domains"]:
|
||||||
domain_add("example.test")
|
domain_add("example.test")
|
||||||
|
|
||||||
|
@ -113,7 +108,6 @@ def secondary_domain(request):
|
||||||
|
|
||||||
|
|
||||||
def app_expected_files(domain, app):
|
def app_expected_files(domain, app):
|
||||||
|
|
||||||
yield "/etc/nginx/conf.d/{}.d/{}.conf".format(domain, app)
|
yield "/etc/nginx/conf.d/{}.d/{}.conf".format(domain, app)
|
||||||
if app.startswith("legacy_app"):
|
if app.startswith("legacy_app"):
|
||||||
yield "/var/www/%s/index.html" % app
|
yield "/var/www/%s/index.html" % app
|
||||||
|
@ -127,21 +121,18 @@ def app_expected_files(domain, app):
|
||||||
|
|
||||||
|
|
||||||
def app_is_installed(domain, app):
|
def app_is_installed(domain, app):
|
||||||
|
|
||||||
return _is_installed(app) and all(
|
return _is_installed(app) and all(
|
||||||
os.path.exists(f) for f in app_expected_files(domain, app)
|
os.path.exists(f) for f in app_expected_files(domain, app)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def app_is_not_installed(domain, app):
|
def app_is_not_installed(domain, app):
|
||||||
|
|
||||||
return not _is_installed(app) and not all(
|
return not _is_installed(app) and not all(
|
||||||
os.path.exists(f) for f in app_expected_files(domain, app)
|
os.path.exists(f) for f in app_expected_files(domain, app)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def app_is_exposed_on_http(domain, path, message_in_page):
|
def app_is_exposed_on_http(domain, path, message_in_page):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = requests.get(
|
r = requests.get(
|
||||||
"https://127.0.0.1" + path + "/",
|
"https://127.0.0.1" + path + "/",
|
||||||
|
@ -155,7 +146,6 @@ def app_is_exposed_on_http(domain, path, message_in_page):
|
||||||
|
|
||||||
|
|
||||||
def install_legacy_app(domain, path, public=True):
|
def install_legacy_app(domain, path, public=True):
|
||||||
|
|
||||||
app_install(
|
app_install(
|
||||||
os.path.join(get_test_apps_dir(), "legacy_app_ynh"),
|
os.path.join(get_test_apps_dir(), "legacy_app_ynh"),
|
||||||
args="domain={}&path={}&is_public={}".format(domain, path, 1 if public else 0),
|
args="domain={}&path={}&is_public={}".format(domain, path, 1 if public else 0),
|
||||||
|
@ -164,7 +154,6 @@ def install_legacy_app(domain, path, public=True):
|
||||||
|
|
||||||
|
|
||||||
def install_manifestv2_app(domain, path, public=True):
|
def install_manifestv2_app(domain, path, public=True):
|
||||||
|
|
||||||
app_install(
|
app_install(
|
||||||
os.path.join(get_test_apps_dir(), "manifestv2_app_ynh"),
|
os.path.join(get_test_apps_dir(), "manifestv2_app_ynh"),
|
||||||
args="domain={}&path={}&init_main_permission={}".format(
|
args="domain={}&path={}&init_main_permission={}".format(
|
||||||
|
@ -175,7 +164,6 @@ def install_manifestv2_app(domain, path, public=True):
|
||||||
|
|
||||||
|
|
||||||
def install_full_domain_app(domain):
|
def install_full_domain_app(domain):
|
||||||
|
|
||||||
app_install(
|
app_install(
|
||||||
os.path.join(get_test_apps_dir(), "full_domain_app_ynh"),
|
os.path.join(get_test_apps_dir(), "full_domain_app_ynh"),
|
||||||
args="domain=%s" % domain,
|
args="domain=%s" % domain,
|
||||||
|
@ -184,7 +172,6 @@ def install_full_domain_app(domain):
|
||||||
|
|
||||||
|
|
||||||
def install_break_yo_system(domain, breakwhat):
|
def install_break_yo_system(domain, breakwhat):
|
||||||
|
|
||||||
app_install(
|
app_install(
|
||||||
os.path.join(get_test_apps_dir(), "break_yo_system_ynh"),
|
os.path.join(get_test_apps_dir(), "break_yo_system_ynh"),
|
||||||
args="domain={}&breakwhat={}".format(domain, breakwhat),
|
args="domain={}&breakwhat={}".format(domain, breakwhat),
|
||||||
|
@ -193,7 +180,6 @@ def install_break_yo_system(domain, breakwhat):
|
||||||
|
|
||||||
|
|
||||||
def test_legacy_app_install_main_domain():
|
def test_legacy_app_install_main_domain():
|
||||||
|
|
||||||
main_domain = _get_maindomain()
|
main_domain = _get_maindomain()
|
||||||
|
|
||||||
install_legacy_app(main_domain, "/legacy")
|
install_legacy_app(main_domain, "/legacy")
|
||||||
|
@ -213,7 +199,6 @@ def test_legacy_app_install_main_domain():
|
||||||
|
|
||||||
|
|
||||||
def test_legacy_app_manifest_preinstall():
|
def test_legacy_app_manifest_preinstall():
|
||||||
|
|
||||||
m = app_manifest(os.path.join(get_test_apps_dir(), "legacy_app_ynh"))
|
m = app_manifest(os.path.join(get_test_apps_dir(), "legacy_app_ynh"))
|
||||||
# v1 manifesto are expected to have been autoconverted to v2
|
# v1 manifesto are expected to have been autoconverted to v2
|
||||||
|
|
||||||
|
@ -231,7 +216,6 @@ def test_legacy_app_manifest_preinstall():
|
||||||
|
|
||||||
|
|
||||||
def test_manifestv2_app_manifest_preinstall():
|
def test_manifestv2_app_manifest_preinstall():
|
||||||
|
|
||||||
m = app_manifest(os.path.join(get_test_apps_dir(), "manifestv2_app_ynh"))
|
m = app_manifest(os.path.join(get_test_apps_dir(), "manifestv2_app_ynh"))
|
||||||
|
|
||||||
assert "id" in m
|
assert "id" in m
|
||||||
|
@ -258,7 +242,6 @@ def test_manifestv2_app_manifest_preinstall():
|
||||||
|
|
||||||
|
|
||||||
def test_manifestv2_app_install_main_domain():
|
def test_manifestv2_app_install_main_domain():
|
||||||
|
|
||||||
main_domain = _get_maindomain()
|
main_domain = _get_maindomain()
|
||||||
|
|
||||||
install_manifestv2_app(main_domain, "/manifestv2")
|
install_manifestv2_app(main_domain, "/manifestv2")
|
||||||
|
@ -278,7 +261,6 @@ def test_manifestv2_app_install_main_domain():
|
||||||
|
|
||||||
|
|
||||||
def test_manifestv2_app_info_postinstall():
|
def test_manifestv2_app_info_postinstall():
|
||||||
|
|
||||||
main_domain = _get_maindomain()
|
main_domain = _get_maindomain()
|
||||||
install_manifestv2_app(main_domain, "/manifestv2")
|
install_manifestv2_app(main_domain, "/manifestv2")
|
||||||
m = app_info("manifestv2_app", full=True)["manifest"]
|
m = app_info("manifestv2_app", full=True)["manifest"]
|
||||||
|
@ -308,13 +290,11 @@ def test_manifestv2_app_info_postinstall():
|
||||||
|
|
||||||
|
|
||||||
def test_manifestv2_app_info_preupgrade(monkeypatch):
|
def test_manifestv2_app_info_preupgrade(monkeypatch):
|
||||||
|
|
||||||
manifest = app_manifest(os.path.join(get_test_apps_dir(), "manifestv2_app_ynh"))
|
manifest = app_manifest(os.path.join(get_test_apps_dir(), "manifestv2_app_ynh"))
|
||||||
|
|
||||||
from yunohost.app_catalog import _load_apps_catalog as original_load_apps_catalog
|
from yunohost.app_catalog import _load_apps_catalog as original_load_apps_catalog
|
||||||
|
|
||||||
def custom_load_apps_catalog(*args, **kwargs):
|
def custom_load_apps_catalog(*args, **kwargs):
|
||||||
|
|
||||||
res = original_load_apps_catalog(*args, **kwargs)
|
res = original_load_apps_catalog(*args, **kwargs)
|
||||||
res["apps"]["manifestv2_app"] = {
|
res["apps"]["manifestv2_app"] = {
|
||||||
"id": "manifestv2_app",
|
"id": "manifestv2_app",
|
||||||
|
@ -372,7 +352,6 @@ def test_app_from_catalog():
|
||||||
|
|
||||||
|
|
||||||
def test_legacy_app_install_secondary_domain(secondary_domain):
|
def test_legacy_app_install_secondary_domain(secondary_domain):
|
||||||
|
|
||||||
install_legacy_app(secondary_domain, "/legacy")
|
install_legacy_app(secondary_domain, "/legacy")
|
||||||
|
|
||||||
assert app_is_installed(secondary_domain, "legacy_app")
|
assert app_is_installed(secondary_domain, "legacy_app")
|
||||||
|
@ -384,7 +363,6 @@ def test_legacy_app_install_secondary_domain(secondary_domain):
|
||||||
|
|
||||||
|
|
||||||
def test_legacy_app_install_secondary_domain_on_root(secondary_domain):
|
def test_legacy_app_install_secondary_domain_on_root(secondary_domain):
|
||||||
|
|
||||||
install_legacy_app(secondary_domain, "/")
|
install_legacy_app(secondary_domain, "/")
|
||||||
|
|
||||||
app_map_ = app_map(raw=True)
|
app_map_ = app_map(raw=True)
|
||||||
|
@ -402,7 +380,6 @@ def test_legacy_app_install_secondary_domain_on_root(secondary_domain):
|
||||||
|
|
||||||
|
|
||||||
def test_legacy_app_install_private(secondary_domain):
|
def test_legacy_app_install_private(secondary_domain):
|
||||||
|
|
||||||
install_legacy_app(secondary_domain, "/legacy", public=False)
|
install_legacy_app(secondary_domain, "/legacy", public=False)
|
||||||
|
|
||||||
assert app_is_installed(secondary_domain, "legacy_app")
|
assert app_is_installed(secondary_domain, "legacy_app")
|
||||||
|
@ -416,7 +393,6 @@ def test_legacy_app_install_private(secondary_domain):
|
||||||
|
|
||||||
|
|
||||||
def test_legacy_app_install_unknown_domain(mocker):
|
def test_legacy_app_install_unknown_domain(mocker):
|
||||||
|
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
with message(mocker, "app_argument_invalid"):
|
with message(mocker, "app_argument_invalid"):
|
||||||
install_legacy_app("whatever.nope", "/legacy")
|
install_legacy_app("whatever.nope", "/legacy")
|
||||||
|
@ -425,7 +401,6 @@ def test_legacy_app_install_unknown_domain(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_legacy_app_install_multiple_instances(secondary_domain):
|
def test_legacy_app_install_multiple_instances(secondary_domain):
|
||||||
|
|
||||||
install_legacy_app(secondary_domain, "/foo")
|
install_legacy_app(secondary_domain, "/foo")
|
||||||
install_legacy_app(secondary_domain, "/bar")
|
install_legacy_app(secondary_domain, "/bar")
|
||||||
|
|
||||||
|
@ -447,7 +422,6 @@ def test_legacy_app_install_multiple_instances(secondary_domain):
|
||||||
|
|
||||||
|
|
||||||
def test_legacy_app_install_path_unavailable(mocker, secondary_domain):
|
def test_legacy_app_install_path_unavailable(mocker, secondary_domain):
|
||||||
|
|
||||||
# These will be removed in teardown
|
# These will be removed in teardown
|
||||||
install_legacy_app(secondary_domain, "/legacy")
|
install_legacy_app(secondary_domain, "/legacy")
|
||||||
|
|
||||||
|
@ -460,7 +434,6 @@ def test_legacy_app_install_path_unavailable(mocker, secondary_domain):
|
||||||
|
|
||||||
|
|
||||||
def test_legacy_app_install_with_nginx_down(mocker, secondary_domain):
|
def test_legacy_app_install_with_nginx_down(mocker, secondary_domain):
|
||||||
|
|
||||||
os.system("systemctl stop nginx")
|
os.system("systemctl stop nginx")
|
||||||
|
|
||||||
with raiseYunohostError(
|
with raiseYunohostError(
|
||||||
|
@ -470,7 +443,6 @@ def test_legacy_app_install_with_nginx_down(mocker, secondary_domain):
|
||||||
|
|
||||||
|
|
||||||
def test_legacy_app_failed_install(mocker, secondary_domain):
|
def test_legacy_app_failed_install(mocker, secondary_domain):
|
||||||
|
|
||||||
# This will conflict with the folder that the app
|
# This will conflict with the folder that the app
|
||||||
# attempts to create, making the install fail
|
# attempts to create, making the install fail
|
||||||
mkdir("/var/www/legacy_app/", 0o750)
|
mkdir("/var/www/legacy_app/", 0o750)
|
||||||
|
@ -483,7 +455,6 @@ def test_legacy_app_failed_install(mocker, secondary_domain):
|
||||||
|
|
||||||
|
|
||||||
def test_legacy_app_failed_remove(mocker, secondary_domain):
|
def test_legacy_app_failed_remove(mocker, secondary_domain):
|
||||||
|
|
||||||
install_legacy_app(secondary_domain, "/legacy")
|
install_legacy_app(secondary_domain, "/legacy")
|
||||||
|
|
||||||
# The remove script runs with set -eu and attempt to remove this
|
# The remove script runs with set -eu and attempt to remove this
|
||||||
|
@ -503,14 +474,12 @@ def test_legacy_app_failed_remove(mocker, secondary_domain):
|
||||||
|
|
||||||
|
|
||||||
def test_full_domain_app(secondary_domain):
|
def test_full_domain_app(secondary_domain):
|
||||||
|
|
||||||
install_full_domain_app(secondary_domain)
|
install_full_domain_app(secondary_domain)
|
||||||
|
|
||||||
assert app_is_exposed_on_http(secondary_domain, "/", "This is a dummy app")
|
assert app_is_exposed_on_http(secondary_domain, "/", "This is a dummy app")
|
||||||
|
|
||||||
|
|
||||||
def test_full_domain_app_with_conflicts(mocker, secondary_domain):
|
def test_full_domain_app_with_conflicts(mocker, secondary_domain):
|
||||||
|
|
||||||
install_legacy_app(secondary_domain, "/legacy")
|
install_legacy_app(secondary_domain, "/legacy")
|
||||||
|
|
||||||
with raiseYunohostError(mocker, "app_full_domain_unavailable"):
|
with raiseYunohostError(mocker, "app_full_domain_unavailable"):
|
||||||
|
@ -518,7 +487,6 @@ def test_full_domain_app_with_conflicts(mocker, secondary_domain):
|
||||||
|
|
||||||
|
|
||||||
def test_systemfuckedup_during_app_install(mocker, secondary_domain):
|
def test_systemfuckedup_during_app_install(mocker, secondary_domain):
|
||||||
|
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
with message(mocker, "app_install_failed"):
|
with message(mocker, "app_install_failed"):
|
||||||
with message(mocker, "app_action_broke_system"):
|
with message(mocker, "app_action_broke_system"):
|
||||||
|
@ -528,7 +496,6 @@ def test_systemfuckedup_during_app_install(mocker, secondary_domain):
|
||||||
|
|
||||||
|
|
||||||
def test_systemfuckedup_during_app_remove(mocker, secondary_domain):
|
def test_systemfuckedup_during_app_remove(mocker, secondary_domain):
|
||||||
|
|
||||||
install_break_yo_system(secondary_domain, breakwhat="remove")
|
install_break_yo_system(secondary_domain, breakwhat="remove")
|
||||||
|
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
|
@ -540,7 +507,6 @@ def test_systemfuckedup_during_app_remove(mocker, secondary_domain):
|
||||||
|
|
||||||
|
|
||||||
def test_systemfuckedup_during_app_install_and_remove(mocker, secondary_domain):
|
def test_systemfuckedup_during_app_install_and_remove(mocker, secondary_domain):
|
||||||
|
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
with message(mocker, "app_install_failed"):
|
with message(mocker, "app_install_failed"):
|
||||||
with message(mocker, "app_action_broke_system"):
|
with message(mocker, "app_action_broke_system"):
|
||||||
|
@ -550,7 +516,6 @@ def test_systemfuckedup_during_app_install_and_remove(mocker, secondary_domain):
|
||||||
|
|
||||||
|
|
||||||
def test_systemfuckedup_during_app_upgrade(mocker, secondary_domain):
|
def test_systemfuckedup_during_app_upgrade(mocker, secondary_domain):
|
||||||
|
|
||||||
install_break_yo_system(secondary_domain, breakwhat="upgrade")
|
install_break_yo_system(secondary_domain, breakwhat="upgrade")
|
||||||
|
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
|
@ -562,7 +527,6 @@ def test_systemfuckedup_during_app_upgrade(mocker, secondary_domain):
|
||||||
|
|
||||||
|
|
||||||
def test_failed_multiple_app_upgrade(mocker, secondary_domain):
|
def test_failed_multiple_app_upgrade(mocker, secondary_domain):
|
||||||
|
|
||||||
install_legacy_app(secondary_domain, "/legacy")
|
install_legacy_app(secondary_domain, "/legacy")
|
||||||
install_break_yo_system(secondary_domain, breakwhat="upgrade")
|
install_break_yo_system(secondary_domain, breakwhat="upgrade")
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ maindomain = _get_maindomain()
|
||||||
|
|
||||||
|
|
||||||
def setup_function(function):
|
def setup_function(function):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
app_remove("register_url_app")
|
app_remove("register_url_app")
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -26,7 +25,6 @@ def setup_function(function):
|
||||||
|
|
||||||
|
|
||||||
def teardown_function(function):
|
def teardown_function(function):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
app_remove("register_url_app")
|
app_remove("register_url_app")
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -34,7 +32,6 @@ def teardown_function(function):
|
||||||
|
|
||||||
|
|
||||||
def test_parse_app_instance_name():
|
def test_parse_app_instance_name():
|
||||||
|
|
||||||
assert _parse_app_instance_name("yolo") == ("yolo", 1)
|
assert _parse_app_instance_name("yolo") == ("yolo", 1)
|
||||||
assert _parse_app_instance_name("yolo1") == ("yolo1", 1)
|
assert _parse_app_instance_name("yolo1") == ("yolo1", 1)
|
||||||
assert _parse_app_instance_name("yolo__0") == ("yolo__0", 1)
|
assert _parse_app_instance_name("yolo__0") == ("yolo__0", 1)
|
||||||
|
@ -86,7 +83,6 @@ def test_repo_url_definition():
|
||||||
|
|
||||||
|
|
||||||
def test_urlavailable():
|
def test_urlavailable():
|
||||||
|
|
||||||
# Except the maindomain/macnuggets to be available
|
# Except the maindomain/macnuggets to be available
|
||||||
assert domain_url_available(maindomain, "/macnuggets")
|
assert domain_url_available(maindomain, "/macnuggets")
|
||||||
|
|
||||||
|
@ -96,7 +92,6 @@ def test_urlavailable():
|
||||||
|
|
||||||
|
|
||||||
def test_registerurl():
|
def test_registerurl():
|
||||||
|
|
||||||
app_install(
|
app_install(
|
||||||
os.path.join(get_test_apps_dir(), "register_url_app_ynh"),
|
os.path.join(get_test_apps_dir(), "register_url_app_ynh"),
|
||||||
args="domain={}&path={}".format(maindomain, "/urlregisterapp"),
|
args="domain={}&path={}".format(maindomain, "/urlregisterapp"),
|
||||||
|
@ -115,7 +110,6 @@ def test_registerurl():
|
||||||
|
|
||||||
|
|
||||||
def test_registerurl_baddomain():
|
def test_registerurl_baddomain():
|
||||||
|
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
app_install(
|
app_install(
|
||||||
os.path.join(get_test_apps_dir(), "register_url_app_ynh"),
|
os.path.join(get_test_apps_dir(), "register_url_app_ynh"),
|
||||||
|
|
|
@ -30,7 +30,6 @@ maindomain = ""
|
||||||
|
|
||||||
|
|
||||||
def setup_function(function):
|
def setup_function(function):
|
||||||
|
|
||||||
global maindomain
|
global maindomain
|
||||||
maindomain = _get_maindomain()
|
maindomain = _get_maindomain()
|
||||||
|
|
||||||
|
@ -89,7 +88,6 @@ def setup_function(function):
|
||||||
|
|
||||||
|
|
||||||
def teardown_function(function):
|
def teardown_function(function):
|
||||||
|
|
||||||
assert tmp_backup_directory_is_empty()
|
assert tmp_backup_directory_is_empty()
|
||||||
|
|
||||||
reset_ssowat_conf()
|
reset_ssowat_conf()
|
||||||
|
@ -133,7 +131,6 @@ def check_permission_for_apps_call():
|
||||||
|
|
||||||
|
|
||||||
def app_is_installed(app):
|
def app_is_installed(app):
|
||||||
|
|
||||||
if app == "permissions_app":
|
if app == "permissions_app":
|
||||||
return _is_installed(app)
|
return _is_installed(app)
|
||||||
|
|
||||||
|
@ -147,7 +144,6 @@ def app_is_installed(app):
|
||||||
|
|
||||||
|
|
||||||
def backup_test_dependencies_are_met():
|
def backup_test_dependencies_are_met():
|
||||||
|
|
||||||
# Dummy test apps (or backup archives)
|
# Dummy test apps (or backup archives)
|
||||||
assert os.path.exists(
|
assert os.path.exists(
|
||||||
os.path.join(get_test_apps_dir(), "backup_wordpress_from_4p2")
|
os.path.join(get_test_apps_dir(), "backup_wordpress_from_4p2")
|
||||||
|
@ -161,7 +157,6 @@ def backup_test_dependencies_are_met():
|
||||||
|
|
||||||
|
|
||||||
def tmp_backup_directory_is_empty():
|
def tmp_backup_directory_is_empty():
|
||||||
|
|
||||||
if not os.path.exists("/home/yunohost.backup/tmp/"):
|
if not os.path.exists("/home/yunohost.backup/tmp/"):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
@ -169,7 +164,6 @@ def tmp_backup_directory_is_empty():
|
||||||
|
|
||||||
|
|
||||||
def clean_tmp_backup_directory():
|
def clean_tmp_backup_directory():
|
||||||
|
|
||||||
if tmp_backup_directory_is_empty():
|
if tmp_backup_directory_is_empty():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -191,27 +185,23 @@ def clean_tmp_backup_directory():
|
||||||
|
|
||||||
|
|
||||||
def reset_ssowat_conf():
|
def reset_ssowat_conf():
|
||||||
|
|
||||||
# Make sure we have a ssowat
|
# Make sure we have a ssowat
|
||||||
os.system("mkdir -p /etc/ssowat/")
|
os.system("mkdir -p /etc/ssowat/")
|
||||||
app_ssowatconf()
|
app_ssowatconf()
|
||||||
|
|
||||||
|
|
||||||
def delete_all_backups():
|
def delete_all_backups():
|
||||||
|
|
||||||
for archive in backup_list()["archives"]:
|
for archive in backup_list()["archives"]:
|
||||||
backup_delete(archive)
|
backup_delete(archive)
|
||||||
|
|
||||||
|
|
||||||
def uninstall_test_apps_if_needed():
|
def uninstall_test_apps_if_needed():
|
||||||
|
|
||||||
for app in ["legacy_app", "backup_recommended_app", "wordpress", "permissions_app"]:
|
for app in ["legacy_app", "backup_recommended_app", "wordpress", "permissions_app"]:
|
||||||
if _is_installed(app):
|
if _is_installed(app):
|
||||||
app_remove(app)
|
app_remove(app)
|
||||||
|
|
||||||
|
|
||||||
def install_app(app, path, additionnal_args=""):
|
def install_app(app, path, additionnal_args=""):
|
||||||
|
|
||||||
app_install(
|
app_install(
|
||||||
os.path.join(get_test_apps_dir(), app),
|
os.path.join(get_test_apps_dir(), app),
|
||||||
args="domain={}&path={}{}".format(maindomain, path, additionnal_args),
|
args="domain={}&path={}{}".format(maindomain, path, additionnal_args),
|
||||||
|
@ -220,7 +210,6 @@ def install_app(app, path, additionnal_args=""):
|
||||||
|
|
||||||
|
|
||||||
def add_archive_wordpress_from_4p2():
|
def add_archive_wordpress_from_4p2():
|
||||||
|
|
||||||
os.system("mkdir -p /home/yunohost.backup/archives")
|
os.system("mkdir -p /home/yunohost.backup/archives")
|
||||||
|
|
||||||
os.system(
|
os.system(
|
||||||
|
@ -231,7 +220,6 @@ def add_archive_wordpress_from_4p2():
|
||||||
|
|
||||||
|
|
||||||
def add_archive_system_from_4p2():
|
def add_archive_system_from_4p2():
|
||||||
|
|
||||||
os.system("mkdir -p /home/yunohost.backup/archives")
|
os.system("mkdir -p /home/yunohost.backup/archives")
|
||||||
|
|
||||||
os.system(
|
os.system(
|
||||||
|
@ -247,7 +235,6 @@ def add_archive_system_from_4p2():
|
||||||
|
|
||||||
|
|
||||||
def test_backup_only_ldap(mocker):
|
def test_backup_only_ldap(mocker):
|
||||||
|
|
||||||
# Create the backup
|
# Create the backup
|
||||||
with message(mocker, "backup_created"):
|
with message(mocker, "backup_created"):
|
||||||
backup_create(system=["conf_ldap"], apps=None)
|
backup_create(system=["conf_ldap"], apps=None)
|
||||||
|
@ -262,7 +249,6 @@ def test_backup_only_ldap(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_backup_system_part_that_does_not_exists(mocker):
|
def test_backup_system_part_that_does_not_exists(mocker):
|
||||||
|
|
||||||
# Create the backup
|
# Create the backup
|
||||||
with message(mocker, "backup_hook_unknown", hook="doesnt_exist"):
|
with message(mocker, "backup_hook_unknown", hook="doesnt_exist"):
|
||||||
with raiseYunohostError(mocker, "backup_nothings_done"):
|
with raiseYunohostError(mocker, "backup_nothings_done"):
|
||||||
|
@ -275,7 +261,6 @@ def test_backup_system_part_that_does_not_exists(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_backup_and_restore_all_sys(mocker):
|
def test_backup_and_restore_all_sys(mocker):
|
||||||
|
|
||||||
# Create the backup
|
# Create the backup
|
||||||
with message(mocker, "backup_created"):
|
with message(mocker, "backup_created"):
|
||||||
backup_create(system=[], apps=None)
|
backup_create(system=[], apps=None)
|
||||||
|
@ -309,7 +294,6 @@ def test_backup_and_restore_all_sys(mocker):
|
||||||
|
|
||||||
@pytest.mark.with_system_archive_from_4p2
|
@pytest.mark.with_system_archive_from_4p2
|
||||||
def test_restore_system_from_Ynh4p2(monkeypatch, mocker):
|
def test_restore_system_from_Ynh4p2(monkeypatch, mocker):
|
||||||
|
|
||||||
# Backup current system
|
# Backup current system
|
||||||
with message(mocker, "backup_created"):
|
with message(mocker, "backup_created"):
|
||||||
backup_create(system=[], apps=None)
|
backup_create(system=[], apps=None)
|
||||||
|
@ -337,7 +321,6 @@ def test_restore_system_from_Ynh4p2(monkeypatch, mocker):
|
||||||
@pytest.mark.with_backup_recommended_app_installed
|
@pytest.mark.with_backup_recommended_app_installed
|
||||||
def test_backup_script_failure_handling(monkeypatch, mocker):
|
def test_backup_script_failure_handling(monkeypatch, mocker):
|
||||||
def custom_hook_exec(name, *args, **kwargs):
|
def custom_hook_exec(name, *args, **kwargs):
|
||||||
|
|
||||||
if os.path.basename(name).startswith("backup_"):
|
if os.path.basename(name).startswith("backup_"):
|
||||||
raise Exception
|
raise Exception
|
||||||
else:
|
else:
|
||||||
|
@ -373,7 +356,6 @@ def test_backup_not_enough_free_space(monkeypatch, mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_backup_app_not_installed(mocker):
|
def test_backup_app_not_installed(mocker):
|
||||||
|
|
||||||
assert not _is_installed("wordpress")
|
assert not _is_installed("wordpress")
|
||||||
|
|
||||||
with message(mocker, "unbackup_app", app="wordpress"):
|
with message(mocker, "unbackup_app", app="wordpress"):
|
||||||
|
@ -383,7 +365,6 @@ def test_backup_app_not_installed(mocker):
|
||||||
|
|
||||||
@pytest.mark.with_backup_recommended_app_installed
|
@pytest.mark.with_backup_recommended_app_installed
|
||||||
def test_backup_app_with_no_backup_script(mocker):
|
def test_backup_app_with_no_backup_script(mocker):
|
||||||
|
|
||||||
backup_script = "/etc/yunohost/apps/backup_recommended_app/scripts/backup"
|
backup_script = "/etc/yunohost/apps/backup_recommended_app/scripts/backup"
|
||||||
os.system("rm %s" % backup_script)
|
os.system("rm %s" % backup_script)
|
||||||
assert not os.path.exists(backup_script)
|
assert not os.path.exists(backup_script)
|
||||||
|
@ -397,7 +378,6 @@ def test_backup_app_with_no_backup_script(mocker):
|
||||||
|
|
||||||
@pytest.mark.with_backup_recommended_app_installed
|
@pytest.mark.with_backup_recommended_app_installed
|
||||||
def test_backup_app_with_no_restore_script(mocker):
|
def test_backup_app_with_no_restore_script(mocker):
|
||||||
|
|
||||||
restore_script = "/etc/yunohost/apps/backup_recommended_app/scripts/restore"
|
restore_script = "/etc/yunohost/apps/backup_recommended_app/scripts/restore"
|
||||||
os.system("rm %s" % restore_script)
|
os.system("rm %s" % restore_script)
|
||||||
assert not os.path.exists(restore_script)
|
assert not os.path.exists(restore_script)
|
||||||
|
@ -413,7 +393,6 @@ def test_backup_app_with_no_restore_script(mocker):
|
||||||
|
|
||||||
@pytest.mark.clean_opt_dir
|
@pytest.mark.clean_opt_dir
|
||||||
def test_backup_with_different_output_directory(mocker):
|
def test_backup_with_different_output_directory(mocker):
|
||||||
|
|
||||||
# Create the backup
|
# Create the backup
|
||||||
with message(mocker, "backup_created"):
|
with message(mocker, "backup_created"):
|
||||||
backup_create(
|
backup_create(
|
||||||
|
@ -436,7 +415,6 @@ def test_backup_with_different_output_directory(mocker):
|
||||||
|
|
||||||
@pytest.mark.clean_opt_dir
|
@pytest.mark.clean_opt_dir
|
||||||
def test_backup_using_copy_method(mocker):
|
def test_backup_using_copy_method(mocker):
|
||||||
|
|
||||||
# Create the backup
|
# Create the backup
|
||||||
with message(mocker, "backup_created"):
|
with message(mocker, "backup_created"):
|
||||||
backup_create(
|
backup_create(
|
||||||
|
@ -458,7 +436,6 @@ def test_backup_using_copy_method(mocker):
|
||||||
@pytest.mark.with_wordpress_archive_from_4p2
|
@pytest.mark.with_wordpress_archive_from_4p2
|
||||||
@pytest.mark.with_custom_domain("yolo.test")
|
@pytest.mark.with_custom_domain("yolo.test")
|
||||||
def test_restore_app_wordpress_from_Ynh4p2(mocker):
|
def test_restore_app_wordpress_from_Ynh4p2(mocker):
|
||||||
|
|
||||||
with message(mocker, "restore_complete"):
|
with message(mocker, "restore_complete"):
|
||||||
backup_restore(
|
backup_restore(
|
||||||
system=None, name=backup_list()["archives"][0], apps=["wordpress"]
|
system=None, name=backup_list()["archives"][0], apps=["wordpress"]
|
||||||
|
@ -507,7 +484,6 @@ def test_restore_app_not_enough_free_space(monkeypatch, mocker):
|
||||||
|
|
||||||
@pytest.mark.with_wordpress_archive_from_4p2
|
@pytest.mark.with_wordpress_archive_from_4p2
|
||||||
def test_restore_app_not_in_backup(mocker):
|
def test_restore_app_not_in_backup(mocker):
|
||||||
|
|
||||||
assert not _is_installed("wordpress")
|
assert not _is_installed("wordpress")
|
||||||
assert not _is_installed("yoloswag")
|
assert not _is_installed("yoloswag")
|
||||||
|
|
||||||
|
@ -524,7 +500,6 @@ def test_restore_app_not_in_backup(mocker):
|
||||||
@pytest.mark.with_wordpress_archive_from_4p2
|
@pytest.mark.with_wordpress_archive_from_4p2
|
||||||
@pytest.mark.with_custom_domain("yolo.test")
|
@pytest.mark.with_custom_domain("yolo.test")
|
||||||
def test_restore_app_already_installed(mocker):
|
def test_restore_app_already_installed(mocker):
|
||||||
|
|
||||||
assert not _is_installed("wordpress")
|
assert not _is_installed("wordpress")
|
||||||
|
|
||||||
with message(mocker, "restore_complete"):
|
with message(mocker, "restore_complete"):
|
||||||
|
@ -544,25 +519,21 @@ def test_restore_app_already_installed(mocker):
|
||||||
|
|
||||||
@pytest.mark.with_legacy_app_installed
|
@pytest.mark.with_legacy_app_installed
|
||||||
def test_backup_and_restore_legacy_app(mocker):
|
def test_backup_and_restore_legacy_app(mocker):
|
||||||
|
|
||||||
_test_backup_and_restore_app(mocker, "legacy_app")
|
_test_backup_and_restore_app(mocker, "legacy_app")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.with_backup_recommended_app_installed
|
@pytest.mark.with_backup_recommended_app_installed
|
||||||
def test_backup_and_restore_recommended_app(mocker):
|
def test_backup_and_restore_recommended_app(mocker):
|
||||||
|
|
||||||
_test_backup_and_restore_app(mocker, "backup_recommended_app")
|
_test_backup_and_restore_app(mocker, "backup_recommended_app")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.with_backup_recommended_app_installed_with_ynh_restore
|
@pytest.mark.with_backup_recommended_app_installed_with_ynh_restore
|
||||||
def test_backup_and_restore_with_ynh_restore(mocker):
|
def test_backup_and_restore_with_ynh_restore(mocker):
|
||||||
|
|
||||||
_test_backup_and_restore_app(mocker, "backup_recommended_app")
|
_test_backup_and_restore_app(mocker, "backup_recommended_app")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.with_permission_app_installed
|
@pytest.mark.with_permission_app_installed
|
||||||
def test_backup_and_restore_permission_app(mocker):
|
def test_backup_and_restore_permission_app(mocker):
|
||||||
|
|
||||||
res = user_permission_list(full=True)["permissions"]
|
res = user_permission_list(full=True)["permissions"]
|
||||||
assert "permissions_app.main" in res
|
assert "permissions_app.main" in res
|
||||||
assert "permissions_app.admin" in res
|
assert "permissions_app.admin" in res
|
||||||
|
@ -593,7 +564,6 @@ def test_backup_and_restore_permission_app(mocker):
|
||||||
|
|
||||||
|
|
||||||
def _test_backup_and_restore_app(mocker, app):
|
def _test_backup_and_restore_app(mocker, app):
|
||||||
|
|
||||||
# Create a backup of this app
|
# Create a backup of this app
|
||||||
with message(mocker, "backup_created"):
|
with message(mocker, "backup_created"):
|
||||||
backup_create(system=None, apps=[app])
|
backup_create(system=None, apps=[app])
|
||||||
|
@ -628,7 +598,6 @@ def _test_backup_and_restore_app(mocker, app):
|
||||||
|
|
||||||
|
|
||||||
def test_restore_archive_with_no_json(mocker):
|
def test_restore_archive_with_no_json(mocker):
|
||||||
|
|
||||||
# Create a backup with no info.json associated
|
# Create a backup with no info.json associated
|
||||||
os.system("touch /tmp/afile")
|
os.system("touch /tmp/afile")
|
||||||
os.system("tar -cvf /home/yunohost.backup/archives/badbackup.tar /tmp/afile")
|
os.system("tar -cvf /home/yunohost.backup/archives/badbackup.tar /tmp/afile")
|
||||||
|
@ -641,7 +610,6 @@ def test_restore_archive_with_no_json(mocker):
|
||||||
|
|
||||||
@pytest.mark.with_wordpress_archive_from_4p2
|
@pytest.mark.with_wordpress_archive_from_4p2
|
||||||
def test_restore_archive_with_bad_archive(mocker):
|
def test_restore_archive_with_bad_archive(mocker):
|
||||||
|
|
||||||
# Break the archive
|
# Break the archive
|
||||||
os.system(
|
os.system(
|
||||||
"head -n 1000 /home/yunohost.backup/archives/backup_wordpress_from_4p2.tar > /home/yunohost.backup/archives/backup_wordpress_from_4p2_bad.tar"
|
"head -n 1000 /home/yunohost.backup/archives/backup_wordpress_from_4p2.tar > /home/yunohost.backup/archives/backup_wordpress_from_4p2_bad.tar"
|
||||||
|
@ -656,7 +624,6 @@ def test_restore_archive_with_bad_archive(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_restore_archive_with_custom_hook(mocker):
|
def test_restore_archive_with_custom_hook(mocker):
|
||||||
|
|
||||||
custom_restore_hook_folder = os.path.join(CUSTOM_HOOK_FOLDER, "restore")
|
custom_restore_hook_folder = os.path.join(CUSTOM_HOOK_FOLDER, "restore")
|
||||||
os.system("touch %s/99-yolo" % custom_restore_hook_folder)
|
os.system("touch %s/99-yolo" % custom_restore_hook_folder)
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,10 @@ from yunohost.dns import (
|
||||||
|
|
||||||
|
|
||||||
def setup_function(function):
|
def setup_function(function):
|
||||||
|
|
||||||
clean()
|
clean()
|
||||||
|
|
||||||
|
|
||||||
def teardown_function(function):
|
def teardown_function(function):
|
||||||
|
|
||||||
clean()
|
clean()
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,7 +74,6 @@ def example_domain():
|
||||||
|
|
||||||
|
|
||||||
def test_domain_dns_suggest(example_domain):
|
def test_domain_dns_suggest(example_domain):
|
||||||
|
|
||||||
assert _build_dns_conf(example_domain)
|
assert _build_dns_conf(example_domain)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ TEST_DOMAINS = ["example.tld", "sub.example.tld", "other-example.com"]
|
||||||
|
|
||||||
|
|
||||||
def setup_function(function):
|
def setup_function(function):
|
||||||
|
|
||||||
# Save domain list in variable to avoid multiple calls to domain_list()
|
# Save domain list in variable to avoid multiple calls to domain_list()
|
||||||
domains = domain_list()["domains"]
|
domains = domain_list()["domains"]
|
||||||
|
|
||||||
|
@ -52,7 +51,6 @@ def setup_function(function):
|
||||||
|
|
||||||
|
|
||||||
def teardown_function(function):
|
def teardown_function(function):
|
||||||
|
|
||||||
clean()
|
clean()
|
||||||
|
|
||||||
|
|
||||||
|
@ -102,7 +100,6 @@ def test_domain_config_get_default():
|
||||||
|
|
||||||
|
|
||||||
def test_domain_config_get_export():
|
def test_domain_config_get_export():
|
||||||
|
|
||||||
assert domain_config_get(TEST_DOMAINS[0], export=True)["xmpp"] == 1
|
assert domain_config_get(TEST_DOMAINS[0], export=True)["xmpp"] == 1
|
||||||
assert domain_config_get(TEST_DOMAINS[1], export=True)["xmpp"] == 0
|
assert domain_config_get(TEST_DOMAINS[1], export=True)["xmpp"] == 0
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ from moulinette.core import MoulinetteError
|
||||||
|
|
||||||
|
|
||||||
def setup_function(function):
|
def setup_function(function):
|
||||||
|
|
||||||
for u in user_list()["users"]:
|
for u in user_list()["users"]:
|
||||||
user_delete(u, purge=True)
|
user_delete(u, purge=True)
|
||||||
|
|
||||||
|
@ -24,7 +23,6 @@ def setup_function(function):
|
||||||
|
|
||||||
|
|
||||||
def teardown_function():
|
def teardown_function():
|
||||||
|
|
||||||
os.system("systemctl is-active slapd >/dev/null || systemctl start slapd; sleep 5")
|
os.system("systemctl is-active slapd >/dev/null || systemctl start slapd; sleep 5")
|
||||||
|
|
||||||
for u in user_list()["users"]:
|
for u in user_list()["users"]:
|
||||||
|
@ -36,7 +34,6 @@ def test_authenticate():
|
||||||
|
|
||||||
|
|
||||||
def test_authenticate_with_no_user():
|
def test_authenticate_with_no_user():
|
||||||
|
|
||||||
with pytest.raises(MoulinetteError):
|
with pytest.raises(MoulinetteError):
|
||||||
LDAPAuth().authenticate_credentials(credentials="Yunohost")
|
LDAPAuth().authenticate_credentials(credentials="Yunohost")
|
||||||
|
|
||||||
|
@ -45,7 +42,6 @@ def test_authenticate_with_no_user():
|
||||||
|
|
||||||
|
|
||||||
def test_authenticate_with_user_who_is_not_admin():
|
def test_authenticate_with_user_who_is_not_admin():
|
||||||
|
|
||||||
with pytest.raises(MoulinetteError) as exception:
|
with pytest.raises(MoulinetteError) as exception:
|
||||||
LDAPAuth().authenticate_credentials(credentials="bob:test123Ynh")
|
LDAPAuth().authenticate_credentials(credentials="bob:test123Ynh")
|
||||||
|
|
||||||
|
@ -70,7 +66,6 @@ def test_authenticate_server_down(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_authenticate_change_password():
|
def test_authenticate_change_password():
|
||||||
|
|
||||||
LDAPAuth().authenticate_credentials(credentials="alice:Yunohost")
|
LDAPAuth().authenticate_credentials(credentials="alice:Yunohost")
|
||||||
|
|
||||||
user_update("alice", change_password="plopette")
|
user_update("alice", change_password="plopette")
|
||||||
|
|
|
@ -354,7 +354,6 @@ def check_permission_for_apps():
|
||||||
|
|
||||||
|
|
||||||
def can_access_webpage(webpath, logged_as=None):
|
def can_access_webpage(webpath, logged_as=None):
|
||||||
|
|
||||||
webpath = webpath.rstrip("/")
|
webpath = webpath.rstrip("/")
|
||||||
sso_url = "https://" + maindomain + "/yunohost/sso/"
|
sso_url = "https://" + maindomain + "/yunohost/sso/"
|
||||||
|
|
||||||
|
@ -1094,7 +1093,6 @@ def test_permission_protection_management_by_helper():
|
||||||
|
|
||||||
@pytest.mark.other_domains(number=1)
|
@pytest.mark.other_domains(number=1)
|
||||||
def test_permission_app_propagation_on_ssowat():
|
def test_permission_app_propagation_on_ssowat():
|
||||||
|
|
||||||
app_install(
|
app_install(
|
||||||
os.path.join(get_test_apps_dir(), "permissions_app_ynh"),
|
os.path.join(get_test_apps_dir(), "permissions_app_ynh"),
|
||||||
args="domain=%s&domain_2=%s&path=%s&is_public=1&admin=%s"
|
args="domain=%s&domain_2=%s&path=%s&is_public=1&admin=%s"
|
||||||
|
@ -1131,7 +1129,6 @@ def test_permission_app_propagation_on_ssowat():
|
||||||
|
|
||||||
@pytest.mark.other_domains(number=1)
|
@pytest.mark.other_domains(number=1)
|
||||||
def test_permission_legacy_app_propagation_on_ssowat():
|
def test_permission_legacy_app_propagation_on_ssowat():
|
||||||
|
|
||||||
app_install(
|
app_install(
|
||||||
os.path.join(get_test_apps_dir(), "legacy_app_ynh"),
|
os.path.join(get_test_apps_dir(), "legacy_app_ynh"),
|
||||||
args="domain=%s&domain_2=%s&path=%s&is_public=1"
|
args="domain=%s&domain_2=%s&path=%s&is_public=1"
|
||||||
|
|
|
@ -49,7 +49,6 @@ def test_question_empty():
|
||||||
|
|
||||||
|
|
||||||
def test_question_string():
|
def test_question_string():
|
||||||
|
|
||||||
questions = {
|
questions = {
|
||||||
"some_string": {
|
"some_string": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -65,7 +64,6 @@ def test_question_string():
|
||||||
|
|
||||||
|
|
||||||
def test_question_string_from_query_string():
|
def test_question_string_from_query_string():
|
||||||
|
|
||||||
questions = {
|
questions = {
|
||||||
"some_string": {
|
"some_string": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -1539,7 +1537,6 @@ def test_question_user_two_users_default_input():
|
||||||
os, "isatty", return_value=True
|
os, "isatty", return_value=True
|
||||||
):
|
):
|
||||||
with patch.object(user, "user_info", return_value={}):
|
with patch.object(user, "user_info", return_value={}):
|
||||||
|
|
||||||
with patch.object(Moulinette, "prompt", return_value=username):
|
with patch.object(Moulinette, "prompt", return_value=username):
|
||||||
out = ask_questions_and_parse_answers(questions, answers)[0]
|
out = ask_questions_and_parse_answers(questions, answers)[0]
|
||||||
|
|
||||||
|
@ -1843,7 +1840,6 @@ def test_question_display_text():
|
||||||
|
|
||||||
|
|
||||||
def test_question_file_from_cli():
|
def test_question_file_from_cli():
|
||||||
|
|
||||||
FileQuestion.clean_upload_dirs()
|
FileQuestion.clean_upload_dirs()
|
||||||
|
|
||||||
filename = "/tmp/ynh_test_question_file"
|
filename = "/tmp/ynh_test_question_file"
|
||||||
|
@ -1874,7 +1870,6 @@ def test_question_file_from_cli():
|
||||||
|
|
||||||
|
|
||||||
def test_question_file_from_api():
|
def test_question_file_from_api():
|
||||||
|
|
||||||
FileQuestion.clean_upload_dirs()
|
FileQuestion.clean_upload_dirs()
|
||||||
|
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
@ -1907,7 +1902,6 @@ def test_question_file_from_api():
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_boolean_nominal():
|
def test_normalize_boolean_nominal():
|
||||||
|
|
||||||
assert BooleanQuestion.normalize("yes") == 1
|
assert BooleanQuestion.normalize("yes") == 1
|
||||||
assert BooleanQuestion.normalize("Yes") == 1
|
assert BooleanQuestion.normalize("Yes") == 1
|
||||||
assert BooleanQuestion.normalize(" yes ") == 1
|
assert BooleanQuestion.normalize(" yes ") == 1
|
||||||
|
@ -1937,7 +1931,6 @@ def test_normalize_boolean_nominal():
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_boolean_humanize():
|
def test_normalize_boolean_humanize():
|
||||||
|
|
||||||
assert BooleanQuestion.humanize("yes") == "yes"
|
assert BooleanQuestion.humanize("yes") == "yes"
|
||||||
assert BooleanQuestion.humanize("true") == "yes"
|
assert BooleanQuestion.humanize("true") == "yes"
|
||||||
assert BooleanQuestion.humanize("on") == "yes"
|
assert BooleanQuestion.humanize("on") == "yes"
|
||||||
|
@ -1948,7 +1941,6 @@ def test_normalize_boolean_humanize():
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_boolean_invalid():
|
def test_normalize_boolean_invalid():
|
||||||
|
|
||||||
with pytest.raises(YunohostValidationError):
|
with pytest.raises(YunohostValidationError):
|
||||||
BooleanQuestion.normalize("yesno")
|
BooleanQuestion.normalize("yesno")
|
||||||
with pytest.raises(YunohostValidationError):
|
with pytest.raises(YunohostValidationError):
|
||||||
|
@ -1958,7 +1950,6 @@ def test_normalize_boolean_invalid():
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_boolean_special_yesno():
|
def test_normalize_boolean_special_yesno():
|
||||||
|
|
||||||
customyesno = {"yes": "enabled", "no": "disabled"}
|
customyesno = {"yes": "enabled", "no": "disabled"}
|
||||||
|
|
||||||
assert BooleanQuestion.normalize("yes", customyesno) == "enabled"
|
assert BooleanQuestion.normalize("yes", customyesno) == "enabled"
|
||||||
|
@ -1977,14 +1968,12 @@ def test_normalize_boolean_special_yesno():
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_domain():
|
def test_normalize_domain():
|
||||||
|
|
||||||
assert DomainQuestion.normalize("https://yolo.swag/") == "yolo.swag"
|
assert DomainQuestion.normalize("https://yolo.swag/") == "yolo.swag"
|
||||||
assert DomainQuestion.normalize("http://yolo.swag") == "yolo.swag"
|
assert DomainQuestion.normalize("http://yolo.swag") == "yolo.swag"
|
||||||
assert DomainQuestion.normalize("yolo.swag/") == "yolo.swag"
|
assert DomainQuestion.normalize("yolo.swag/") == "yolo.swag"
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_path():
|
def test_normalize_path():
|
||||||
|
|
||||||
assert PathQuestion.normalize("") == "/"
|
assert PathQuestion.normalize("") == "/"
|
||||||
assert PathQuestion.normalize("") == "/"
|
assert PathQuestion.normalize("") == "/"
|
||||||
assert PathQuestion.normalize("macnuggets") == "/macnuggets"
|
assert PathQuestion.normalize("macnuggets") == "/macnuggets"
|
||||||
|
|
|
@ -16,19 +16,16 @@ SSHD_CONFIG = "/etc/ssh/sshd_config"
|
||||||
|
|
||||||
|
|
||||||
def setup_function(function):
|
def setup_function(function):
|
||||||
|
|
||||||
_force_clear_hashes([TEST_DOMAIN_NGINX_CONFIG])
|
_force_clear_hashes([TEST_DOMAIN_NGINX_CONFIG])
|
||||||
clean()
|
clean()
|
||||||
|
|
||||||
|
|
||||||
def teardown_function(function):
|
def teardown_function(function):
|
||||||
|
|
||||||
clean()
|
clean()
|
||||||
_force_clear_hashes([TEST_DOMAIN_NGINX_CONFIG])
|
_force_clear_hashes([TEST_DOMAIN_NGINX_CONFIG])
|
||||||
|
|
||||||
|
|
||||||
def clean():
|
def clean():
|
||||||
|
|
||||||
assert os.system("pgrep slapd >/dev/null") == 0
|
assert os.system("pgrep slapd >/dev/null") == 0
|
||||||
assert os.system("pgrep nginx >/dev/null") == 0
|
assert os.system("pgrep nginx >/dev/null") == 0
|
||||||
|
|
||||||
|
@ -48,7 +45,6 @@ def clean():
|
||||||
|
|
||||||
|
|
||||||
def test_add_domain():
|
def test_add_domain():
|
||||||
|
|
||||||
domain_add(TEST_DOMAIN)
|
domain_add(TEST_DOMAIN)
|
||||||
|
|
||||||
assert TEST_DOMAIN in domain_list()["domains"]
|
assert TEST_DOMAIN in domain_list()["domains"]
|
||||||
|
@ -60,7 +56,6 @@ def test_add_domain():
|
||||||
|
|
||||||
|
|
||||||
def test_add_and_edit_domain_conf():
|
def test_add_and_edit_domain_conf():
|
||||||
|
|
||||||
domain_add(TEST_DOMAIN)
|
domain_add(TEST_DOMAIN)
|
||||||
|
|
||||||
assert os.path.exists(TEST_DOMAIN_NGINX_CONFIG)
|
assert os.path.exists(TEST_DOMAIN_NGINX_CONFIG)
|
||||||
|
@ -73,7 +68,6 @@ def test_add_and_edit_domain_conf():
|
||||||
|
|
||||||
|
|
||||||
def test_add_domain_conf_already_exists():
|
def test_add_domain_conf_already_exists():
|
||||||
|
|
||||||
os.system("echo ' ' >> %s" % TEST_DOMAIN_NGINX_CONFIG)
|
os.system("echo ' ' >> %s" % TEST_DOMAIN_NGINX_CONFIG)
|
||||||
|
|
||||||
domain_add(TEST_DOMAIN)
|
domain_add(TEST_DOMAIN)
|
||||||
|
@ -84,7 +78,6 @@ def test_add_domain_conf_already_exists():
|
||||||
|
|
||||||
|
|
||||||
def test_ssh_conf_unmanaged():
|
def test_ssh_conf_unmanaged():
|
||||||
|
|
||||||
_force_clear_hashes([SSHD_CONFIG])
|
_force_clear_hashes([SSHD_CONFIG])
|
||||||
|
|
||||||
assert SSHD_CONFIG not in _get_conf_hashes("ssh")
|
assert SSHD_CONFIG not in _get_conf_hashes("ssh")
|
||||||
|
@ -95,7 +88,6 @@ def test_ssh_conf_unmanaged():
|
||||||
|
|
||||||
|
|
||||||
def test_ssh_conf_unmanaged_and_manually_modified(mocker):
|
def test_ssh_conf_unmanaged_and_manually_modified(mocker):
|
||||||
|
|
||||||
_force_clear_hashes([SSHD_CONFIG])
|
_force_clear_hashes([SSHD_CONFIG])
|
||||||
os.system("echo ' ' >> %s" % SSHD_CONFIG)
|
os.system("echo ' ' >> %s" % SSHD_CONFIG)
|
||||||
|
|
||||||
|
|
|
@ -14,17 +14,14 @@ from yunohost.service import (
|
||||||
|
|
||||||
|
|
||||||
def setup_function(function):
|
def setup_function(function):
|
||||||
|
|
||||||
clean()
|
clean()
|
||||||
|
|
||||||
|
|
||||||
def teardown_function(function):
|
def teardown_function(function):
|
||||||
|
|
||||||
clean()
|
clean()
|
||||||
|
|
||||||
|
|
||||||
def clean():
|
def clean():
|
||||||
|
|
||||||
# To run these tests, we assume ssh(d) service exists and is running
|
# To run these tests, we assume ssh(d) service exists and is running
|
||||||
assert os.system("pgrep sshd >/dev/null") == 0
|
assert os.system("pgrep sshd >/dev/null") == 0
|
||||||
|
|
||||||
|
@ -45,46 +42,39 @@ def clean():
|
||||||
|
|
||||||
|
|
||||||
def test_service_status_all():
|
def test_service_status_all():
|
||||||
|
|
||||||
status = service_status()
|
status = service_status()
|
||||||
assert "ssh" in status.keys()
|
assert "ssh" in status.keys()
|
||||||
assert status["ssh"]["status"] == "running"
|
assert status["ssh"]["status"] == "running"
|
||||||
|
|
||||||
|
|
||||||
def test_service_status_single():
|
def test_service_status_single():
|
||||||
|
|
||||||
status = service_status("ssh")
|
status = service_status("ssh")
|
||||||
assert "status" in status.keys()
|
assert "status" in status.keys()
|
||||||
assert status["status"] == "running"
|
assert status["status"] == "running"
|
||||||
|
|
||||||
|
|
||||||
def test_service_log():
|
def test_service_log():
|
||||||
|
|
||||||
logs = service_log("ssh")
|
logs = service_log("ssh")
|
||||||
assert "journalctl" in logs.keys()
|
assert "journalctl" in logs.keys()
|
||||||
assert "/var/log/auth.log" in logs.keys()
|
assert "/var/log/auth.log" in logs.keys()
|
||||||
|
|
||||||
|
|
||||||
def test_service_status_unknown_service(mocker):
|
def test_service_status_unknown_service(mocker):
|
||||||
|
|
||||||
with raiseYunohostError(mocker, "service_unknown"):
|
with raiseYunohostError(mocker, "service_unknown"):
|
||||||
service_status(["ssh", "doesnotexists"])
|
service_status(["ssh", "doesnotexists"])
|
||||||
|
|
||||||
|
|
||||||
def test_service_add():
|
def test_service_add():
|
||||||
|
|
||||||
service_add("dummyservice", description="A dummy service to run tests")
|
service_add("dummyservice", description="A dummy service to run tests")
|
||||||
assert "dummyservice" in service_status().keys()
|
assert "dummyservice" in service_status().keys()
|
||||||
|
|
||||||
|
|
||||||
def test_service_add_real_service():
|
def test_service_add_real_service():
|
||||||
|
|
||||||
service_add("networking")
|
service_add("networking")
|
||||||
assert "networking" in service_status().keys()
|
assert "networking" in service_status().keys()
|
||||||
|
|
||||||
|
|
||||||
def test_service_remove():
|
def test_service_remove():
|
||||||
|
|
||||||
service_add("dummyservice", description="A dummy service to run tests")
|
service_add("dummyservice", description="A dummy service to run tests")
|
||||||
assert "dummyservice" in service_status().keys()
|
assert "dummyservice" in service_status().keys()
|
||||||
service_remove("dummyservice")
|
service_remove("dummyservice")
|
||||||
|
@ -92,7 +82,6 @@ def test_service_remove():
|
||||||
|
|
||||||
|
|
||||||
def test_service_remove_service_that_doesnt_exists(mocker):
|
def test_service_remove_service_that_doesnt_exists(mocker):
|
||||||
|
|
||||||
assert "dummyservice" not in service_status().keys()
|
assert "dummyservice" not in service_status().keys()
|
||||||
|
|
||||||
with raiseYunohostError(mocker, "service_unknown"):
|
with raiseYunohostError(mocker, "service_unknown"):
|
||||||
|
@ -102,7 +91,6 @@ def test_service_remove_service_that_doesnt_exists(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_service_update_to_add_properties():
|
def test_service_update_to_add_properties():
|
||||||
|
|
||||||
service_add("dummyservice", description="dummy")
|
service_add("dummyservice", description="dummy")
|
||||||
assert not _get_services()["dummyservice"].get("test_status")
|
assert not _get_services()["dummyservice"].get("test_status")
|
||||||
service_add("dummyservice", description="dummy", test_status="true")
|
service_add("dummyservice", description="dummy", test_status="true")
|
||||||
|
@ -110,7 +98,6 @@ def test_service_update_to_add_properties():
|
||||||
|
|
||||||
|
|
||||||
def test_service_update_to_change_properties():
|
def test_service_update_to_change_properties():
|
||||||
|
|
||||||
service_add("dummyservice", description="dummy", test_status="false")
|
service_add("dummyservice", description="dummy", test_status="false")
|
||||||
assert _get_services()["dummyservice"].get("test_status") == "false"
|
assert _get_services()["dummyservice"].get("test_status") == "false"
|
||||||
service_add("dummyservice", description="dummy", test_status="true")
|
service_add("dummyservice", description="dummy", test_status="true")
|
||||||
|
@ -118,7 +105,6 @@ def test_service_update_to_change_properties():
|
||||||
|
|
||||||
|
|
||||||
def test_service_update_to_remove_properties():
|
def test_service_update_to_remove_properties():
|
||||||
|
|
||||||
service_add("dummyservice", description="dummy", test_status="false")
|
service_add("dummyservice", description="dummy", test_status="false")
|
||||||
assert _get_services()["dummyservice"].get("test_status") == "false"
|
assert _get_services()["dummyservice"].get("test_status") == "false"
|
||||||
service_add("dummyservice", description="dummy", test_status="")
|
service_add("dummyservice", description="dummy", test_status="")
|
||||||
|
@ -126,7 +112,6 @@ def test_service_update_to_remove_properties():
|
||||||
|
|
||||||
|
|
||||||
def test_service_conf_broken():
|
def test_service_conf_broken():
|
||||||
|
|
||||||
os.system("echo pwet > /etc/nginx/conf.d/broken.conf")
|
os.system("echo pwet > /etc/nginx/conf.d/broken.conf")
|
||||||
|
|
||||||
status = service_status("nginx")
|
status = service_status("nginx")
|
||||||
|
|
|
@ -65,7 +65,6 @@ old_translate = moulinette.core.Translator.translate
|
||||||
|
|
||||||
|
|
||||||
def _monkeypatch_translator(self, key, *args, **kwargs):
|
def _monkeypatch_translator(self, key, *args, **kwargs):
|
||||||
|
|
||||||
if key.startswith("global_settings_setting_"):
|
if key.startswith("global_settings_setting_"):
|
||||||
return f"Dummy translation for {key}"
|
return f"Dummy translation for {key}"
|
||||||
|
|
||||||
|
@ -175,7 +174,6 @@ def test_settings_set_doesexit():
|
||||||
|
|
||||||
|
|
||||||
def test_settings_set_bad_type_bool():
|
def test_settings_set_bad_type_bool():
|
||||||
|
|
||||||
with patch.object(os, "isatty", return_value=False):
|
with patch.object(os, "isatty", return_value=False):
|
||||||
with pytest.raises(YunohostError):
|
with pytest.raises(YunohostError):
|
||||||
settings_set("example.example.boolean", 42)
|
settings_set("example.example.boolean", 42)
|
||||||
|
|
|
@ -92,7 +92,6 @@ def test_list_groups():
|
||||||
|
|
||||||
|
|
||||||
def test_create_user(mocker):
|
def test_create_user(mocker):
|
||||||
|
|
||||||
with message(mocker, "user_created"):
|
with message(mocker, "user_created"):
|
||||||
user_create("albert", maindomain, "test123Ynh", fullname="Albert Good")
|
user_create("albert", maindomain, "test123Ynh", fullname="Albert Good")
|
||||||
|
|
||||||
|
@ -104,7 +103,6 @@ def test_create_user(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_del_user(mocker):
|
def test_del_user(mocker):
|
||||||
|
|
||||||
with message(mocker, "user_deleted"):
|
with message(mocker, "user_deleted"):
|
||||||
user_delete("alice")
|
user_delete("alice")
|
||||||
|
|
||||||
|
@ -185,7 +183,6 @@ def test_export_user(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_create_group(mocker):
|
def test_create_group(mocker):
|
||||||
|
|
||||||
with message(mocker, "group_created", group="adminsys"):
|
with message(mocker, "group_created", group="adminsys"):
|
||||||
user_group_create("adminsys")
|
user_group_create("adminsys")
|
||||||
|
|
||||||
|
@ -196,7 +193,6 @@ def test_create_group(mocker):
|
||||||
|
|
||||||
|
|
||||||
def test_del_group(mocker):
|
def test_del_group(mocker):
|
||||||
|
|
||||||
with message(mocker, "group_deleted", group="dev"):
|
with message(mocker, "group_deleted", group="dev"):
|
||||||
user_group_delete("dev")
|
user_group_delete("dev")
|
||||||
|
|
||||||
|
|
17
src/tools.py
17
src/tools.py
|
@ -62,7 +62,6 @@ def tools_versions():
|
||||||
|
|
||||||
|
|
||||||
def tools_rootpw(new_password, check_strength=True):
|
def tools_rootpw(new_password, check_strength=True):
|
||||||
|
|
||||||
from yunohost.user import _hash_user_password
|
from yunohost.user import _hash_user_password
|
||||||
from yunohost.utils.password import (
|
from yunohost.utils.password import (
|
||||||
assert_password_is_strong_enough,
|
assert_password_is_strong_enough,
|
||||||
|
@ -154,7 +153,6 @@ def tools_postinstall(
|
||||||
ignore_dyndns=False,
|
ignore_dyndns=False,
|
||||||
force_diskspace=False,
|
force_diskspace=False,
|
||||||
):
|
):
|
||||||
|
|
||||||
from yunohost.dyndns import _dyndns_available
|
from yunohost.dyndns import _dyndns_available
|
||||||
from yunohost.utils.dns import is_yunohost_dyndns_domain
|
from yunohost.utils.dns import is_yunohost_dyndns_domain
|
||||||
from yunohost.utils.password import (
|
from yunohost.utils.password import (
|
||||||
|
@ -193,7 +191,6 @@ def tools_postinstall(
|
||||||
|
|
||||||
# If this is a nohost.me/noho.st, actually check for availability
|
# If this is a nohost.me/noho.st, actually check for availability
|
||||||
if not ignore_dyndns and is_yunohost_dyndns_domain(domain):
|
if not ignore_dyndns and is_yunohost_dyndns_domain(domain):
|
||||||
|
|
||||||
available = None
|
available = None
|
||||||
|
|
||||||
# Check if the domain is available...
|
# Check if the domain is available...
|
||||||
|
@ -281,7 +278,6 @@ def tools_postinstall(
|
||||||
def tools_regen_conf(
|
def tools_regen_conf(
|
||||||
names=[], with_diff=False, force=False, dry_run=False, list_pending=False
|
names=[], with_diff=False, force=False, dry_run=False, list_pending=False
|
||||||
):
|
):
|
||||||
|
|
||||||
# Make sure the settings are migrated before running the migration,
|
# Make sure the settings are migrated before running the migration,
|
||||||
# which may otherwise fuck things up such as the ssh config ...
|
# which may otherwise fuck things up such as the ssh config ...
|
||||||
# We do this here because the regen-conf is called before the migration in debian/postinst
|
# We do this here because the regen-conf is called before the migration in debian/postinst
|
||||||
|
@ -312,7 +308,6 @@ def tools_update(target=None):
|
||||||
|
|
||||||
upgradable_system_packages = []
|
upgradable_system_packages = []
|
||||||
if target in ["system", "all"]:
|
if target in ["system", "all"]:
|
||||||
|
|
||||||
# Update APT cache
|
# Update APT cache
|
||||||
# LC_ALL=C is here to make sure the results are in english
|
# LC_ALL=C is here to make sure the results are in english
|
||||||
command = (
|
command = (
|
||||||
|
@ -426,7 +421,6 @@ def tools_upgrade(operation_logger, target=None):
|
||||||
#
|
#
|
||||||
|
|
||||||
if target == "apps":
|
if target == "apps":
|
||||||
|
|
||||||
# Make sure there's actually something to upgrade
|
# Make sure there's actually something to upgrade
|
||||||
|
|
||||||
upgradable_apps = [app["id"] for app in app_list(upgradable=True)["apps"]]
|
upgradable_apps = [app["id"] for app in app_list(upgradable=True)["apps"]]
|
||||||
|
@ -450,7 +444,6 @@ def tools_upgrade(operation_logger, target=None):
|
||||||
#
|
#
|
||||||
|
|
||||||
if target == "system":
|
if target == "system":
|
||||||
|
|
||||||
# Check that there's indeed some packages to upgrade
|
# Check that there's indeed some packages to upgrade
|
||||||
upgradables = list(_list_upgradable_apt_packages())
|
upgradables = list(_list_upgradable_apt_packages())
|
||||||
if not upgradables:
|
if not upgradables:
|
||||||
|
@ -498,7 +491,6 @@ def tools_upgrade(operation_logger, target=None):
|
||||||
any(p["name"] == "yunohost" for p in upgradables)
|
any(p["name"] == "yunohost" for p in upgradables)
|
||||||
and Moulinette.interface.type == "api"
|
and Moulinette.interface.type == "api"
|
||||||
):
|
):
|
||||||
|
|
||||||
# Restart the API after 10 sec (at now doesn't support sub-minute times...)
|
# Restart the API after 10 sec (at now doesn't support sub-minute times...)
|
||||||
# We do this so that the API / webadmin still gets the proper HTTP response
|
# We do this so that the API / webadmin still gets the proper HTTP response
|
||||||
# It's then up to the webadmin to implement a proper UX process to wait 10 sec and then auto-fresh the webadmin
|
# It's then up to the webadmin to implement a proper UX process to wait 10 sec and then auto-fresh the webadmin
|
||||||
|
@ -722,7 +714,6 @@ def tools_migrations_run(
|
||||||
|
|
||||||
# Actually run selected migrations
|
# Actually run selected migrations
|
||||||
for migration in targets:
|
for migration in targets:
|
||||||
|
|
||||||
# If we are migrating in "automatic mode" (i.e. from debian configure
|
# If we are migrating in "automatic mode" (i.e. from debian configure
|
||||||
# during an upgrade of the package) but we are asked for running
|
# during an upgrade of the package) but we are asked for running
|
||||||
# migrations to be ran manually by the user, stop there and ask the
|
# migrations to be ran manually by the user, stop there and ask the
|
||||||
|
@ -778,7 +769,6 @@ def tools_migrations_run(
|
||||||
_write_migration_state(migration.id, "skipped")
|
_write_migration_state(migration.id, "skipped")
|
||||||
operation_logger.success()
|
operation_logger.success()
|
||||||
else:
|
else:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
migration.operation_logger = operation_logger
|
migration.operation_logger = operation_logger
|
||||||
logger.info(m18n.n("migrations_running_forward", id=migration.id))
|
logger.info(m18n.n("migrations_running_forward", id=migration.id))
|
||||||
|
@ -810,14 +800,12 @@ def tools_migrations_state():
|
||||||
|
|
||||||
|
|
||||||
def _write_migration_state(migration_id, state):
|
def _write_migration_state(migration_id, state):
|
||||||
|
|
||||||
current_states = tools_migrations_state()
|
current_states = tools_migrations_state()
|
||||||
current_states["migrations"][migration_id] = state
|
current_states["migrations"][migration_id] = state
|
||||||
write_to_yaml(MIGRATIONS_STATE_PATH, current_states)
|
write_to_yaml(MIGRATIONS_STATE_PATH, current_states)
|
||||||
|
|
||||||
|
|
||||||
def _get_migrations_list():
|
def _get_migrations_list():
|
||||||
|
|
||||||
# states is a datastructure that represents the last run migration
|
# states is a datastructure that represents the last run migration
|
||||||
# it has this form:
|
# it has this form:
|
||||||
# {
|
# {
|
||||||
|
@ -868,7 +856,6 @@ def _get_migration_by_name(migration_name):
|
||||||
|
|
||||||
|
|
||||||
def _load_migration(migration_file):
|
def _load_migration(migration_file):
|
||||||
|
|
||||||
migration_id = migration_file[: -len(".py")]
|
migration_id = migration_file[: -len(".py")]
|
||||||
|
|
||||||
logger.debug(m18n.n("migrations_loading_migration", id=migration_id))
|
logger.debug(m18n.n("migrations_loading_migration", id=migration_id))
|
||||||
|
@ -903,7 +890,6 @@ def _skip_all_migrations():
|
||||||
|
|
||||||
|
|
||||||
def _tools_migrations_run_after_system_restore(backup_version):
|
def _tools_migrations_run_after_system_restore(backup_version):
|
||||||
|
|
||||||
all_migrations = _get_migrations_list()
|
all_migrations = _get_migrations_list()
|
||||||
|
|
||||||
current_version = version.parse(ynh_packages_version()["yunohost"]["version"])
|
current_version = version.parse(ynh_packages_version()["yunohost"]["version"])
|
||||||
|
@ -930,7 +916,6 @@ def _tools_migrations_run_after_system_restore(backup_version):
|
||||||
|
|
||||||
|
|
||||||
def _tools_migrations_run_before_app_restore(backup_version, app_id):
|
def _tools_migrations_run_before_app_restore(backup_version, app_id):
|
||||||
|
|
||||||
all_migrations = _get_migrations_list()
|
all_migrations = _get_migrations_list()
|
||||||
|
|
||||||
current_version = version.parse(ynh_packages_version()["yunohost"]["version"])
|
current_version = version.parse(ynh_packages_version()["yunohost"]["version"])
|
||||||
|
@ -957,7 +942,6 @@ def _tools_migrations_run_before_app_restore(backup_version, app_id):
|
||||||
|
|
||||||
|
|
||||||
class Migration:
|
class Migration:
|
||||||
|
|
||||||
# Those are to be implemented by daughter classes
|
# Those are to be implemented by daughter classes
|
||||||
|
|
||||||
mode = "auto"
|
mode = "auto"
|
||||||
|
@ -985,7 +969,6 @@ class Migration:
|
||||||
|
|
||||||
def ldap_migration(run):
|
def ldap_migration(run):
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
||||||
# Backup LDAP before the migration
|
# Backup LDAP before the migration
|
||||||
logger.info(m18n.n("migration_ldap_backup_before_migration"))
|
logger.info(m18n.n("migration_ldap_backup_before_migration"))
|
||||||
try:
|
try:
|
||||||
|
|
46
src/user.py
46
src/user.py
|
@ -53,7 +53,6 @@ ADMIN_ALIASES = ["root", "admin", "admins", "webmaster", "postmaster", "abuse"]
|
||||||
|
|
||||||
|
|
||||||
def user_list(fields=None):
|
def user_list(fields=None):
|
||||||
|
|
||||||
from yunohost.utils.ldap import _get_ldap_interface
|
from yunohost.utils.ldap import _get_ldap_interface
|
||||||
|
|
||||||
ldap_attrs = {
|
ldap_attrs = {
|
||||||
|
@ -123,6 +122,18 @@ def user_list(fields=None):
|
||||||
return {"users": users}
|
return {"users": users}
|
||||||
|
|
||||||
|
|
||||||
|
def list_shells():
|
||||||
|
with open("/etc/shells", "r") as f:
|
||||||
|
content = f.readlines()
|
||||||
|
|
||||||
|
return [line.strip() for line in content if line.startswith("/")]
|
||||||
|
|
||||||
|
|
||||||
|
def shellexists(shell):
|
||||||
|
"""Check if the provided shell exists and is executable."""
|
||||||
|
return os.path.isfile(shell) and os.access(shell, os.X_OK)
|
||||||
|
|
||||||
|
|
||||||
@is_unit_operation([("username", "user")])
|
@is_unit_operation([("username", "user")])
|
||||||
def user_create(
|
def user_create(
|
||||||
operation_logger,
|
operation_logger,
|
||||||
|
@ -135,8 +146,8 @@ def user_create(
|
||||||
mailbox_quota="0",
|
mailbox_quota="0",
|
||||||
admin=False,
|
admin=False,
|
||||||
from_import=False,
|
from_import=False,
|
||||||
|
loginShell=None,
|
||||||
):
|
):
|
||||||
|
|
||||||
if firstname or lastname:
|
if firstname or lastname:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Options --firstname / --lastname of 'yunohost user create' are deprecated. We recommend using --fullname instead."
|
"Options --firstname / --lastname of 'yunohost user create' are deprecated. We recommend using --fullname instead."
|
||||||
|
@ -230,6 +241,12 @@ def user_create(
|
||||||
uid = str(random.randint(1001, 65000))
|
uid = str(random.randint(1001, 65000))
|
||||||
uid_guid_found = uid not in all_uid and uid not in all_gid
|
uid_guid_found = uid not in all_uid and uid not in all_gid
|
||||||
|
|
||||||
|
if not loginShell:
|
||||||
|
loginShell = "/bin/bash"
|
||||||
|
else:
|
||||||
|
if not shellexists(loginShell) or loginShell not in list_shells():
|
||||||
|
raise YunohostValidationError("invalid_shell", shell=loginShell)
|
||||||
|
|
||||||
attr_dict = {
|
attr_dict = {
|
||||||
"objectClass": [
|
"objectClass": [
|
||||||
"mailAccount",
|
"mailAccount",
|
||||||
|
@ -249,7 +266,7 @@ def user_create(
|
||||||
"gidNumber": [uid],
|
"gidNumber": [uid],
|
||||||
"uidNumber": [uid],
|
"uidNumber": [uid],
|
||||||
"homeDirectory": ["/home/" + username],
|
"homeDirectory": ["/home/" + username],
|
||||||
"loginShell": ["/bin/bash"],
|
"loginShell": [loginShell],
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -300,7 +317,6 @@ def user_create(
|
||||||
|
|
||||||
@is_unit_operation([("username", "user")])
|
@is_unit_operation([("username", "user")])
|
||||||
def user_delete(operation_logger, username, purge=False, from_import=False):
|
def user_delete(operation_logger, username, purge=False, from_import=False):
|
||||||
|
|
||||||
from yunohost.hook import hook_callback
|
from yunohost.hook import hook_callback
|
||||||
from yunohost.utils.ldap import _get_ldap_interface
|
from yunohost.utils.ldap import _get_ldap_interface
|
||||||
|
|
||||||
|
@ -359,8 +375,8 @@ def user_update(
|
||||||
mailbox_quota=None,
|
mailbox_quota=None,
|
||||||
from_import=False,
|
from_import=False,
|
||||||
fullname=None,
|
fullname=None,
|
||||||
|
loginShell=None,
|
||||||
):
|
):
|
||||||
|
|
||||||
if firstname or lastname:
|
if firstname or lastname:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Options --firstname / --lastname of 'yunohost user create' are deprecated. We recommend using --fullname instead."
|
"Options --firstname / --lastname of 'yunohost user create' are deprecated. We recommend using --fullname instead."
|
||||||
|
@ -519,6 +535,12 @@ def user_update(
|
||||||
new_attr_dict["mailuserquota"] = [mailbox_quota]
|
new_attr_dict["mailuserquota"] = [mailbox_quota]
|
||||||
env_dict["YNH_USER_MAILQUOTA"] = mailbox_quota
|
env_dict["YNH_USER_MAILQUOTA"] = mailbox_quota
|
||||||
|
|
||||||
|
if loginShell is not None:
|
||||||
|
if not shellexists(loginShell) or loginShell not in list_shells():
|
||||||
|
raise YunohostValidationError("invalid_shell", shell=loginShell)
|
||||||
|
new_attr_dict["loginShell"] = [loginShell]
|
||||||
|
env_dict["YNH_USER_LOGINSHELL"] = loginShell
|
||||||
|
|
||||||
if not from_import:
|
if not from_import:
|
||||||
operation_logger.start()
|
operation_logger.start()
|
||||||
|
|
||||||
|
@ -527,6 +549,10 @@ def user_update(
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError("user_update_failed", user=username, error=e)
|
raise YunohostError("user_update_failed", user=username, error=e)
|
||||||
|
|
||||||
|
# Invalidate passwd and group to update the loginShell
|
||||||
|
subprocess.call(["nscd", "-i", "passwd"])
|
||||||
|
subprocess.call(["nscd", "-i", "group"])
|
||||||
|
|
||||||
# Trigger post_user_update hooks
|
# Trigger post_user_update hooks
|
||||||
hook_callback("post_user_update", env=env_dict)
|
hook_callback("post_user_update", env=env_dict)
|
||||||
|
|
||||||
|
@ -548,7 +574,7 @@ def user_info(username):
|
||||||
|
|
||||||
ldap = _get_ldap_interface()
|
ldap = _get_ldap_interface()
|
||||||
|
|
||||||
user_attrs = ["cn", "mail", "uid", "maildrop", "mailuserquota"]
|
user_attrs = ["cn", "mail", "uid", "maildrop", "mailuserquota", "loginShell"]
|
||||||
|
|
||||||
if len(username.split("@")) == 2:
|
if len(username.split("@")) == 2:
|
||||||
filter = "mail=" + username
|
filter = "mail=" + username
|
||||||
|
@ -566,6 +592,7 @@ def user_info(username):
|
||||||
"username": user["uid"][0],
|
"username": user["uid"][0],
|
||||||
"fullname": user["cn"][0],
|
"fullname": user["cn"][0],
|
||||||
"mail": user["mail"][0],
|
"mail": user["mail"][0],
|
||||||
|
"loginShell": user["loginShell"][0],
|
||||||
"mail-aliases": [],
|
"mail-aliases": [],
|
||||||
"mail-forward": [],
|
"mail-forward": [],
|
||||||
}
|
}
|
||||||
|
@ -704,7 +731,6 @@ def user_import(operation_logger, csvfile, update=False, delete=False):
|
||||||
)
|
)
|
||||||
|
|
||||||
for user in reader:
|
for user in reader:
|
||||||
|
|
||||||
# Validate column values against regexes
|
# Validate column values against regexes
|
||||||
format_errors = [
|
format_errors = [
|
||||||
f"{key}: '{user[key]}' doesn't match the expected format"
|
f"{key}: '{user[key]}' doesn't match the expected format"
|
||||||
|
@ -960,7 +986,6 @@ def user_group_list(short=False, full=False, include_primary_groups=True):
|
||||||
users = user_list()["users"]
|
users = user_list()["users"]
|
||||||
groups = {}
|
groups = {}
|
||||||
for infos in groups_infos:
|
for infos in groups_infos:
|
||||||
|
|
||||||
name = infos["cn"][0]
|
name = infos["cn"][0]
|
||||||
|
|
||||||
if not include_primary_groups and name in users:
|
if not include_primary_groups and name in users:
|
||||||
|
@ -1110,7 +1135,6 @@ def user_group_update(
|
||||||
sync_perm=True,
|
sync_perm=True,
|
||||||
from_import=False,
|
from_import=False,
|
||||||
):
|
):
|
||||||
|
|
||||||
from yunohost.permission import permission_sync_to_user
|
from yunohost.permission import permission_sync_to_user
|
||||||
from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract
|
from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract
|
||||||
|
|
||||||
|
@ -1153,7 +1177,6 @@ def user_group_update(
|
||||||
new_attr_dict = {}
|
new_attr_dict = {}
|
||||||
|
|
||||||
if add:
|
if add:
|
||||||
|
|
||||||
users_to_add = [add] if not isinstance(add, list) else add
|
users_to_add = [add] if not isinstance(add, list) else add
|
||||||
|
|
||||||
for user in users_to_add:
|
for user in users_to_add:
|
||||||
|
@ -1194,7 +1217,6 @@ def user_group_update(
|
||||||
|
|
||||||
# Check the whole alias situation
|
# Check the whole alias situation
|
||||||
if add_mailalias:
|
if add_mailalias:
|
||||||
|
|
||||||
from yunohost.domain import domain_list
|
from yunohost.domain import domain_list
|
||||||
|
|
||||||
domains = domain_list()["domains"]
|
domains = domain_list()["domains"]
|
||||||
|
@ -1238,7 +1260,6 @@ def user_group_update(
|
||||||
raise YunohostValidationError("mail_alias_remove_failed", mail=mail)
|
raise YunohostValidationError("mail_alias_remove_failed", mail=mail)
|
||||||
|
|
||||||
if set(new_group_mail) != set(current_group_mail):
|
if set(new_group_mail) != set(current_group_mail):
|
||||||
|
|
||||||
logger.info(m18n.n("group_update_aliases", group=groupname))
|
logger.info(m18n.n("group_update_aliases", group=groupname))
|
||||||
new_attr_dict["mail"] = set(new_group_mail)
|
new_attr_dict["mail"] = set(new_group_mail)
|
||||||
|
|
||||||
|
@ -1446,7 +1467,6 @@ def _hash_user_password(password):
|
||||||
|
|
||||||
|
|
||||||
def _update_admins_group_aliases(old_main_domain, new_main_domain):
|
def _update_admins_group_aliases(old_main_domain, new_main_domain):
|
||||||
|
|
||||||
current_admin_aliases = user_group_info("admins")["mail-aliases"]
|
current_admin_aliases = user_group_info("admins")["mail-aliases"]
|
||||||
|
|
||||||
aliases_to_remove = [
|
aliases_to_remove = [
|
||||||
|
|
|
@ -264,7 +264,6 @@ class ConfigPanel:
|
||||||
|
|
||||||
# In 'classic' mode, we display the current value if key refer to an option
|
# In 'classic' mode, we display the current value if key refer to an option
|
||||||
if self.filter_key.count(".") == 2 and mode == "classic":
|
if self.filter_key.count(".") == 2 and mode == "classic":
|
||||||
|
|
||||||
option = self.filter_key.split(".")[-1]
|
option = self.filter_key.split(".")[-1]
|
||||||
value = self.values.get(option, None)
|
value = self.values.get(option, None)
|
||||||
|
|
||||||
|
@ -280,7 +279,6 @@ class ConfigPanel:
|
||||||
logger.debug(f"Formating result in '{mode}' mode")
|
logger.debug(f"Formating result in '{mode}' mode")
|
||||||
result = {}
|
result = {}
|
||||||
for panel, section, option in self._iterate():
|
for panel, section, option in self._iterate():
|
||||||
|
|
||||||
if section["is_action_section"] and mode != "full":
|
if section["is_action_section"] and mode != "full":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -323,7 +321,6 @@ class ConfigPanel:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def list_actions(self):
|
def list_actions(self):
|
||||||
|
|
||||||
actions = {}
|
actions = {}
|
||||||
|
|
||||||
# FIXME : meh, loading the entire config panel is again going to cause
|
# FIXME : meh, loading the entire config panel is again going to cause
|
||||||
|
@ -462,7 +459,6 @@ class ConfigPanel:
|
||||||
return read_toml(self.config_path)
|
return read_toml(self.config_path)
|
||||||
|
|
||||||
def _get_config_panel(self):
|
def _get_config_panel(self):
|
||||||
|
|
||||||
# Split filter_key
|
# Split filter_key
|
||||||
filter_key = self.filter_key.split(".") if self.filter_key != "" else []
|
filter_key = self.filter_key.split(".") if self.filter_key != "" else []
|
||||||
if len(filter_key) > 3:
|
if len(filter_key) > 3:
|
||||||
|
@ -639,7 +635,6 @@ class ConfigPanel:
|
||||||
# Hydrating config panel with current value
|
# Hydrating config panel with current value
|
||||||
for _, section, option in self._iterate():
|
for _, section, option in self._iterate():
|
||||||
if option["id"] not in self.values:
|
if option["id"] not in self.values:
|
||||||
|
|
||||||
allowed_empty_types = [
|
allowed_empty_types = [
|
||||||
"alert",
|
"alert",
|
||||||
"display_text",
|
"display_text",
|
||||||
|
@ -701,7 +696,6 @@ class ConfigPanel:
|
||||||
Moulinette.display(colorize(message, "purple"))
|
Moulinette.display(colorize(message, "purple"))
|
||||||
|
|
||||||
for panel, section, obj in self._iterate(["panel", "section"]):
|
for panel, section, obj in self._iterate(["panel", "section"]):
|
||||||
|
|
||||||
if (
|
if (
|
||||||
section
|
section
|
||||||
and section.get("visible")
|
and section.get("visible")
|
||||||
|
@ -814,7 +808,6 @@ class ConfigPanel:
|
||||||
write_to_yaml(self.save_path, values_to_save)
|
write_to_yaml(self.save_path, values_to_save)
|
||||||
|
|
||||||
def _reload_services(self):
|
def _reload_services(self):
|
||||||
|
|
||||||
from yunohost.service import service_reload_or_restart
|
from yunohost.service import service_reload_or_restart
|
||||||
|
|
||||||
services_to_reload = set()
|
services_to_reload = set()
|
||||||
|
@ -905,7 +898,6 @@ class Question:
|
||||||
)
|
)
|
||||||
|
|
||||||
def ask_if_needed(self):
|
def ask_if_needed(self):
|
||||||
|
|
||||||
if self.visible and not evaluate_simple_js_expression(
|
if self.visible and not evaluate_simple_js_expression(
|
||||||
self.visible, context=self.context
|
self.visible, context=self.context
|
||||||
):
|
):
|
||||||
|
@ -980,7 +972,6 @@ class Question:
|
||||||
)
|
)
|
||||||
|
|
||||||
def _format_text_for_user_input_in_cli(self):
|
def _format_text_for_user_input_in_cli(self):
|
||||||
|
|
||||||
text_for_user_input_in_cli = _value_for_locale(self.ask)
|
text_for_user_input_in_cli = _value_for_locale(self.ask)
|
||||||
|
|
||||||
if self.readonly:
|
if self.readonly:
|
||||||
|
@ -991,7 +982,6 @@ class Question:
|
||||||
)
|
)
|
||||||
return text_for_user_input_in_cli + f" {self.humanize(self.current_value)}"
|
return text_for_user_input_in_cli + f" {self.humanize(self.current_value)}"
|
||||||
elif self.choices:
|
elif self.choices:
|
||||||
|
|
||||||
# Prevent displaying a shitload of choices
|
# Prevent displaying a shitload of choices
|
||||||
# (e.g. 100+ available users when choosing an app admin...)
|
# (e.g. 100+ available users when choosing an app admin...)
|
||||||
choices = (
|
choices = (
|
||||||
|
@ -1160,7 +1150,6 @@ class PathQuestion(Question):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def normalize(value, option={}):
|
def normalize(value, option={}):
|
||||||
|
|
||||||
option = option.__dict__ if isinstance(option, Question) else option
|
option = option.__dict__ if isinstance(option, Question) else option
|
||||||
|
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
@ -1187,7 +1176,6 @@ class BooleanQuestion(Question):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def humanize(value, option={}):
|
def humanize(value, option={}):
|
||||||
|
|
||||||
option = option.__dict__ if isinstance(option, Question) else option
|
option = option.__dict__ if isinstance(option, Question) else option
|
||||||
|
|
||||||
yes = option.get("yes", 1)
|
yes = option.get("yes", 1)
|
||||||
|
@ -1211,7 +1199,6 @@ class BooleanQuestion(Question):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def normalize(value, option={}):
|
def normalize(value, option={}):
|
||||||
|
|
||||||
option = option.__dict__ if isinstance(option, Question) else option
|
option = option.__dict__ if isinstance(option, Question) else option
|
||||||
|
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
|
@ -1368,7 +1355,6 @@ class GroupQuestion(Question):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {}
|
self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {}
|
||||||
):
|
):
|
||||||
|
|
||||||
from yunohost.user import user_group_list
|
from yunohost.user import user_group_list
|
||||||
|
|
||||||
super().__init__(question, context)
|
super().__init__(question, context)
|
||||||
|
@ -1401,7 +1387,6 @@ class NumberQuestion(Question):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def normalize(value, option={}):
|
def normalize(value, option={}):
|
||||||
|
|
||||||
if isinstance(value, int):
|
if isinstance(value, int):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
|
@ -31,19 +31,16 @@ external_resolvers_: List[str] = []
|
||||||
|
|
||||||
|
|
||||||
def is_yunohost_dyndns_domain(domain):
|
def is_yunohost_dyndns_domain(domain):
|
||||||
|
|
||||||
return any(
|
return any(
|
||||||
domain.endswith(f".{dyndns_domain}") for dyndns_domain in YNH_DYNDNS_DOMAINS
|
domain.endswith(f".{dyndns_domain}") for dyndns_domain in YNH_DYNDNS_DOMAINS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def is_special_use_tld(domain):
|
def is_special_use_tld(domain):
|
||||||
|
|
||||||
return any(domain.endswith(f".{tld}") for tld in SPECIAL_USE_TLDS)
|
return any(domain.endswith(f".{tld}") for tld in SPECIAL_USE_TLDS)
|
||||||
|
|
||||||
|
|
||||||
def external_resolvers():
|
def external_resolvers():
|
||||||
|
|
||||||
global external_resolvers_
|
global external_resolvers_
|
||||||
|
|
||||||
if not external_resolvers_:
|
if not external_resolvers_:
|
||||||
|
|
|
@ -21,7 +21,6 @@ from moulinette import m18n
|
||||||
|
|
||||||
|
|
||||||
class YunohostError(MoulinetteError):
|
class YunohostError(MoulinetteError):
|
||||||
|
|
||||||
http_code = 500
|
http_code = 500
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -43,7 +42,6 @@ class YunohostError(MoulinetteError):
|
||||||
super(YunohostError, self).__init__(msg, raw_msg=True)
|
super(YunohostError, self).__init__(msg, raw_msg=True)
|
||||||
|
|
||||||
def content(self):
|
def content(self):
|
||||||
|
|
||||||
if not self.log_ref:
|
if not self.log_ref:
|
||||||
return super().content()
|
return super().content()
|
||||||
else:
|
else:
|
||||||
|
@ -51,14 +49,11 @@ class YunohostError(MoulinetteError):
|
||||||
|
|
||||||
|
|
||||||
class YunohostValidationError(YunohostError):
|
class YunohostValidationError(YunohostError):
|
||||||
|
|
||||||
http_code = 400
|
http_code = 400
|
||||||
|
|
||||||
def content(self):
|
def content(self):
|
||||||
|
|
||||||
return {"error": self.strerror, "error_key": self.key, **self.kwargs}
|
return {"error": self.strerror, "error_key": self.key, **self.kwargs}
|
||||||
|
|
||||||
|
|
||||||
class YunohostAuthenticationError(MoulinetteAuthenticationError):
|
class YunohostAuthenticationError(MoulinetteAuthenticationError):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -36,7 +36,6 @@ _ldap_interface = None
|
||||||
|
|
||||||
|
|
||||||
def _get_ldap_interface():
|
def _get_ldap_interface():
|
||||||
|
|
||||||
global _ldap_interface
|
global _ldap_interface
|
||||||
|
|
||||||
if _ldap_interface is None:
|
if _ldap_interface is None:
|
||||||
|
|
|
@ -193,7 +193,6 @@ LEGACY_PHP_VERSION_REPLACEMENTS = [
|
||||||
|
|
||||||
|
|
||||||
def _patch_legacy_php_versions(app_folder):
|
def _patch_legacy_php_versions(app_folder):
|
||||||
|
|
||||||
files_to_patch = []
|
files_to_patch = []
|
||||||
files_to_patch.extend(glob.glob("%s/conf/*" % app_folder))
|
files_to_patch.extend(glob.glob("%s/conf/*" % app_folder))
|
||||||
files_to_patch.extend(glob.glob("%s/scripts/*" % app_folder))
|
files_to_patch.extend(glob.glob("%s/scripts/*" % app_folder))
|
||||||
|
@ -203,7 +202,6 @@ def _patch_legacy_php_versions(app_folder):
|
||||||
files_to_patch.append("%s/manifest.toml" % app_folder)
|
files_to_patch.append("%s/manifest.toml" % app_folder)
|
||||||
|
|
||||||
for filename in files_to_patch:
|
for filename in files_to_patch:
|
||||||
|
|
||||||
# Ignore non-regular files
|
# Ignore non-regular files
|
||||||
if not os.path.isfile(filename):
|
if not os.path.isfile(filename):
|
||||||
continue
|
continue
|
||||||
|
@ -217,7 +215,6 @@ def _patch_legacy_php_versions(app_folder):
|
||||||
|
|
||||||
|
|
||||||
def _patch_legacy_php_versions_in_settings(app_folder):
|
def _patch_legacy_php_versions_in_settings(app_folder):
|
||||||
|
|
||||||
settings = read_yaml(os.path.join(app_folder, "settings.yml"))
|
settings = read_yaml(os.path.join(app_folder, "settings.yml"))
|
||||||
|
|
||||||
if settings.get("fpm_config_dir") in ["/etc/php/7.0/fpm", "/etc/php/7.3/fpm"]:
|
if settings.get("fpm_config_dir") in ["/etc/php/7.0/fpm", "/etc/php/7.3/fpm"]:
|
||||||
|
@ -243,7 +240,6 @@ def _patch_legacy_php_versions_in_settings(app_folder):
|
||||||
|
|
||||||
|
|
||||||
def _patch_legacy_helpers(app_folder):
|
def _patch_legacy_helpers(app_folder):
|
||||||
|
|
||||||
files_to_patch = []
|
files_to_patch = []
|
||||||
files_to_patch.extend(glob.glob("%s/scripts/*" % app_folder))
|
files_to_patch.extend(glob.glob("%s/scripts/*" % app_folder))
|
||||||
files_to_patch.extend(glob.glob("%s/scripts/.*" % app_folder))
|
files_to_patch.extend(glob.glob("%s/scripts/.*" % app_folder))
|
||||||
|
@ -291,7 +287,6 @@ def _patch_legacy_helpers(app_folder):
|
||||||
infos["replace"] = infos.get("replace")
|
infos["replace"] = infos.get("replace")
|
||||||
|
|
||||||
for filename in files_to_patch:
|
for filename in files_to_patch:
|
||||||
|
|
||||||
# Ignore non-regular files
|
# Ignore non-regular files
|
||||||
if not os.path.isfile(filename):
|
if not os.path.isfile(filename):
|
||||||
continue
|
continue
|
||||||
|
@ -305,7 +300,6 @@ def _patch_legacy_helpers(app_folder):
|
||||||
show_warning = False
|
show_warning = False
|
||||||
|
|
||||||
for helper, infos in stuff_to_replace.items():
|
for helper, infos in stuff_to_replace.items():
|
||||||
|
|
||||||
# Ignore if not relevant for this file
|
# Ignore if not relevant for this file
|
||||||
if infos.get("only_for") and not any(
|
if infos.get("only_for") and not any(
|
||||||
filename.endswith(f) for f in infos["only_for"]
|
filename.endswith(f) for f in infos["only_for"]
|
||||||
|
@ -329,7 +323,6 @@ def _patch_legacy_helpers(app_folder):
|
||||||
)
|
)
|
||||||
|
|
||||||
if replaced_stuff:
|
if replaced_stuff:
|
||||||
|
|
||||||
# Check the app do load the helper
|
# Check the app do load the helper
|
||||||
# If it doesn't, add the instruction ourselve (making sure it's after the #!/bin/bash if it's there...
|
# If it doesn't, add the instruction ourselve (making sure it's after the #!/bin/bash if it's there...
|
||||||
if filename.split("/")[-1] in [
|
if filename.split("/")[-1] in [
|
||||||
|
|
|
@ -29,7 +29,6 @@ logger = logging.getLogger("yunohost.utils.network")
|
||||||
|
|
||||||
|
|
||||||
def get_public_ip(protocol=4):
|
def get_public_ip(protocol=4):
|
||||||
|
|
||||||
assert protocol in [4, 6], (
|
assert protocol in [4, 6], (
|
||||||
"Invalid protocol version for get_public_ip: %s, expected 4 or 6" % protocol
|
"Invalid protocol version for get_public_ip: %s, expected 4 or 6" % protocol
|
||||||
)
|
)
|
||||||
|
@ -90,7 +89,6 @@ def get_public_ip_from_remote_server(protocol=4):
|
||||||
|
|
||||||
|
|
||||||
def get_network_interfaces():
|
def get_network_interfaces():
|
||||||
|
|
||||||
# Get network devices and their addresses (raw infos from 'ip addr')
|
# Get network devices and their addresses (raw infos from 'ip addr')
|
||||||
devices_raw = {}
|
devices_raw = {}
|
||||||
output = check_output("ip addr show")
|
output = check_output("ip addr show")
|
||||||
|
@ -111,7 +109,6 @@ def get_network_interfaces():
|
||||||
|
|
||||||
|
|
||||||
def get_gateway():
|
def get_gateway():
|
||||||
|
|
||||||
output = check_output("ip route show")
|
output = check_output("ip route show")
|
||||||
m = re.search(r"default via (.*) dev ([a-z]+[0-9]?)", output)
|
m = re.search(r"default via (.*) dev ([a-z]+[0-9]?)", output)
|
||||||
if not m:
|
if not m:
|
||||||
|
|
|
@ -58,7 +58,6 @@ def assert_password_is_compatible(password):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if len(password) >= 127:
|
if len(password) >= 127:
|
||||||
|
|
||||||
# Note that those imports are made here and can't be put
|
# Note that those imports are made here and can't be put
|
||||||
# on top (at least not the moulinette ones)
|
# on top (at least not the moulinette ones)
|
||||||
# because the moulinette needs to be correctly initialized
|
# because the moulinette needs to be correctly initialized
|
||||||
|
@ -69,7 +68,6 @@ def assert_password_is_compatible(password):
|
||||||
|
|
||||||
|
|
||||||
def assert_password_is_strong_enough(profile, password):
|
def assert_password_is_strong_enough(profile, password):
|
||||||
|
|
||||||
PasswordValidator(profile).validate(password)
|
PasswordValidator(profile).validate(password)
|
||||||
|
|
||||||
|
|
||||||
|
@ -197,7 +195,6 @@ class PasswordValidator:
|
||||||
return strength_level
|
return strength_level
|
||||||
|
|
||||||
def is_in_most_used_list(self, password):
|
def is_in_most_used_list(self, password):
|
||||||
|
|
||||||
# Decompress file if compressed
|
# Decompress file if compressed
|
||||||
if os.path.exists("%s.gz" % MOST_USED_PASSWORDS):
|
if os.path.exists("%s.gz" % MOST_USED_PASSWORDS):
|
||||||
os.system("gzip -fd %s.gz" % MOST_USED_PASSWORDS)
|
os.system("gzip -fd %s.gz" % MOST_USED_PASSWORDS)
|
||||||
|
|
|
@ -37,7 +37,6 @@ logger = getActionLogger("yunohost.app_resources")
|
||||||
|
|
||||||
class AppResourceManager:
|
class AppResourceManager:
|
||||||
def __init__(self, app: str, current: Dict, wanted: Dict):
|
def __init__(self, app: str, current: Dict, wanted: Dict):
|
||||||
|
|
||||||
self.app = app
|
self.app = app
|
||||||
self.current = current
|
self.current = current
|
||||||
self.wanted = wanted
|
self.wanted = wanted
|
||||||
|
@ -50,7 +49,6 @@ class AppResourceManager:
|
||||||
def apply(
|
def apply(
|
||||||
self, rollback_and_raise_exception_if_failure, operation_logger=None, **context
|
self, rollback_and_raise_exception_if_failure, operation_logger=None, **context
|
||||||
):
|
):
|
||||||
|
|
||||||
todos = list(self.compute_todos())
|
todos = list(self.compute_todos())
|
||||||
completed = []
|
completed = []
|
||||||
rollback = False
|
rollback = False
|
||||||
|
@ -121,7 +119,6 @@ class AppResourceManager:
|
||||||
logger.error(exception)
|
logger.error(exception)
|
||||||
|
|
||||||
def compute_todos(self):
|
def compute_todos(self):
|
||||||
|
|
||||||
for name, infos in reversed(self.current["resources"].items()):
|
for name, infos in reversed(self.current["resources"].items()):
|
||||||
if name not in self.wanted["resources"].keys():
|
if name not in self.wanted["resources"].keys():
|
||||||
resource = AppResourceClassesByType[name](infos, self.app, self)
|
resource = AppResourceClassesByType[name](infos, self.app, self)
|
||||||
|
@ -140,12 +137,10 @@ class AppResourceManager:
|
||||||
|
|
||||||
|
|
||||||
class AppResource:
|
class AppResource:
|
||||||
|
|
||||||
type: str = ""
|
type: str = ""
|
||||||
default_properties: Dict[str, Any] = {}
|
default_properties: Dict[str, Any] = {}
|
||||||
|
|
||||||
def __init__(self, properties: Dict[str, Any], app: str, manager=None):
|
def __init__(self, properties: Dict[str, Any], app: str, manager=None):
|
||||||
|
|
||||||
self.app = app
|
self.app = app
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
|
|
||||||
|
@ -175,7 +170,6 @@ class AppResource:
|
||||||
app_setting(self.app, key, delete=True)
|
app_setting(self.app, key, delete=True)
|
||||||
|
|
||||||
def _run_script(self, action, script, env={}, user="root"):
|
def _run_script(self, action, script, env={}, user="root"):
|
||||||
|
|
||||||
from yunohost.app import (
|
from yunohost.app import (
|
||||||
_make_tmp_workdir_for_app,
|
_make_tmp_workdir_for_app,
|
||||||
_make_environment_for_app_script,
|
_make_environment_for_app_script,
|
||||||
|
@ -295,7 +289,6 @@ class PermissionsResource(AppResource):
|
||||||
permissions: Dict[str, Dict[str, Any]] = {}
|
permissions: Dict[str, Dict[str, Any]] = {}
|
||||||
|
|
||||||
def __init__(self, properties: Dict[str, Any], *args, **kwargs):
|
def __init__(self, properties: Dict[str, Any], *args, **kwargs):
|
||||||
|
|
||||||
# FIXME : if url != None, we should check that there's indeed a domain/path defined ? ie that app is a webapp
|
# FIXME : if url != None, we should check that there's indeed a domain/path defined ? ie that app is a webapp
|
||||||
|
|
||||||
for perm, infos in properties.items():
|
for perm, infos in properties.items():
|
||||||
|
@ -315,7 +308,6 @@ class PermissionsResource(AppResource):
|
||||||
super().__init__({"permissions": properties}, *args, **kwargs)
|
super().__init__({"permissions": properties}, *args, **kwargs)
|
||||||
|
|
||||||
def provision_or_update(self, context: Dict = {}):
|
def provision_or_update(self, context: Dict = {}):
|
||||||
|
|
||||||
from yunohost.permission import (
|
from yunohost.permission import (
|
||||||
permission_create,
|
permission_create,
|
||||||
permission_url,
|
permission_url,
|
||||||
|
@ -375,7 +367,6 @@ class PermissionsResource(AppResource):
|
||||||
permission_sync_to_user()
|
permission_sync_to_user()
|
||||||
|
|
||||||
def deprovision(self, context: Dict = {}):
|
def deprovision(self, context: Dict = {}):
|
||||||
|
|
||||||
from yunohost.permission import (
|
from yunohost.permission import (
|
||||||
permission_delete,
|
permission_delete,
|
||||||
user_permission_list,
|
user_permission_list,
|
||||||
|
@ -432,7 +423,6 @@ class SystemuserAppResource(AppResource):
|
||||||
allow_sftp: bool = False
|
allow_sftp: bool = False
|
||||||
|
|
||||||
def provision_or_update(self, context: Dict = {}):
|
def provision_or_update(self, context: Dict = {}):
|
||||||
|
|
||||||
# FIXME : validate that no yunohost user exists with that name?
|
# FIXME : validate that no yunohost user exists with that name?
|
||||||
# and/or that no system user exists during install ?
|
# and/or that no system user exists during install ?
|
||||||
|
|
||||||
|
@ -462,7 +452,6 @@ class SystemuserAppResource(AppResource):
|
||||||
os.system(f"usermod -G {','.join(groups)} {self.app}")
|
os.system(f"usermod -G {','.join(groups)} {self.app}")
|
||||||
|
|
||||||
def deprovision(self, context: Dict = {}):
|
def deprovision(self, context: Dict = {}):
|
||||||
|
|
||||||
if check_output(f"getent passwd {self.app} &>/dev/null || true").strip():
|
if check_output(f"getent passwd {self.app} &>/dev/null || true").strip():
|
||||||
os.system(f"deluser {self.app} >/dev/null")
|
os.system(f"deluser {self.app} >/dev/null")
|
||||||
if check_output(f"getent passwd {self.app} &>/dev/null || true").strip():
|
if check_output(f"getent passwd {self.app} &>/dev/null || true").strip():
|
||||||
|
@ -528,7 +517,6 @@ class InstalldirAppResource(AppResource):
|
||||||
# FIXME: change default dir to /opt/stuff if app ain't a webapp ...
|
# FIXME: change default dir to /opt/stuff if app ain't a webapp ...
|
||||||
|
|
||||||
def provision_or_update(self, context: Dict = {}):
|
def provision_or_update(self, context: Dict = {}):
|
||||||
|
|
||||||
assert self.dir.strip() # Be paranoid about self.dir being empty...
|
assert self.dir.strip() # Be paranoid about self.dir being empty...
|
||||||
assert self.owner.strip()
|
assert self.owner.strip()
|
||||||
assert self.group.strip()
|
assert self.group.strip()
|
||||||
|
@ -582,7 +570,6 @@ class InstalldirAppResource(AppResource):
|
||||||
self.delete_setting("final_path") # Legacy
|
self.delete_setting("final_path") # Legacy
|
||||||
|
|
||||||
def deprovision(self, context: Dict = {}):
|
def deprovision(self, context: Dict = {}):
|
||||||
|
|
||||||
assert self.dir.strip() # Be paranoid about self.dir being empty...
|
assert self.dir.strip() # Be paranoid about self.dir being empty...
|
||||||
assert self.owner.strip()
|
assert self.owner.strip()
|
||||||
assert self.group.strip()
|
assert self.group.strip()
|
||||||
|
@ -643,7 +630,6 @@ class DatadirAppResource(AppResource):
|
||||||
group: str = ""
|
group: str = ""
|
||||||
|
|
||||||
def provision_or_update(self, context: Dict = {}):
|
def provision_or_update(self, context: Dict = {}):
|
||||||
|
|
||||||
assert self.dir.strip() # Be paranoid about self.dir being empty...
|
assert self.dir.strip() # Be paranoid about self.dir being empty...
|
||||||
assert self.owner.strip()
|
assert self.owner.strip()
|
||||||
assert self.group.strip()
|
assert self.group.strip()
|
||||||
|
@ -686,7 +672,6 @@ class DatadirAppResource(AppResource):
|
||||||
self.delete_setting("datadir") # Legacy
|
self.delete_setting("datadir") # Legacy
|
||||||
|
|
||||||
def deprovision(self, context: Dict = {}):
|
def deprovision(self, context: Dict = {}):
|
||||||
|
|
||||||
assert self.dir.strip() # Be paranoid about self.dir being empty...
|
assert self.dir.strip() # Be paranoid about self.dir being empty...
|
||||||
assert self.owner.strip()
|
assert self.owner.strip()
|
||||||
assert self.group.strip()
|
assert self.group.strip()
|
||||||
|
@ -737,7 +722,6 @@ class AptDependenciesAppResource(AppResource):
|
||||||
extras: Dict[str, Dict[str, str]] = {}
|
extras: Dict[str, Dict[str, str]] = {}
|
||||||
|
|
||||||
def __init__(self, properties: Dict[str, Any], *args, **kwargs):
|
def __init__(self, properties: Dict[str, Any], *args, **kwargs):
|
||||||
|
|
||||||
for key, values in properties.get("extras", {}).items():
|
for key, values in properties.get("extras", {}).items():
|
||||||
if not all(
|
if not all(
|
||||||
isinstance(values.get(k), str) for k in ["repo", "key", "packages"]
|
isinstance(values.get(k), str) for k in ["repo", "key", "packages"]
|
||||||
|
@ -749,7 +733,6 @@ class AptDependenciesAppResource(AppResource):
|
||||||
super().__init__(properties, *args, **kwargs)
|
super().__init__(properties, *args, **kwargs)
|
||||||
|
|
||||||
def provision_or_update(self, context: Dict = {}):
|
def provision_or_update(self, context: Dict = {}):
|
||||||
|
|
||||||
script = [f"ynh_install_app_dependencies {self.packages}"]
|
script = [f"ynh_install_app_dependencies {self.packages}"]
|
||||||
for repo, values in self.extras.items():
|
for repo, values in self.extras.items():
|
||||||
script += [
|
script += [
|
||||||
|
@ -760,7 +743,6 @@ class AptDependenciesAppResource(AppResource):
|
||||||
self._run_script("provision_or_update", "\n".join(script))
|
self._run_script("provision_or_update", "\n".join(script))
|
||||||
|
|
||||||
def deprovision(self, context: Dict = {}):
|
def deprovision(self, context: Dict = {}):
|
||||||
|
|
||||||
self._run_script("deprovision", "ynh_remove_app_dependencies")
|
self._run_script("deprovision", "ynh_remove_app_dependencies")
|
||||||
|
|
||||||
|
|
||||||
|
@ -818,7 +800,6 @@ class PortsResource(AppResource):
|
||||||
ports: Dict[str, Dict[str, Any]]
|
ports: Dict[str, Dict[str, Any]]
|
||||||
|
|
||||||
def __init__(self, properties: Dict[str, Any], *args, **kwargs):
|
def __init__(self, properties: Dict[str, Any], *args, **kwargs):
|
||||||
|
|
||||||
if "main" not in properties:
|
if "main" not in properties:
|
||||||
properties["main"] = {}
|
properties["main"] = {}
|
||||||
|
|
||||||
|
@ -832,7 +813,6 @@ class PortsResource(AppResource):
|
||||||
super().__init__({"ports": properties}, *args, **kwargs)
|
super().__init__({"ports": properties}, *args, **kwargs)
|
||||||
|
|
||||||
def _port_is_used(self, port):
|
def _port_is_used(self, port):
|
||||||
|
|
||||||
# FIXME : this could be less brutal than two os.system ...
|
# FIXME : this could be less brutal than two os.system ...
|
||||||
cmd1 = (
|
cmd1 = (
|
||||||
"ss --numeric --listening --tcp --udp | awk '{print$5}' | grep --quiet --extended-regexp ':%s$'"
|
"ss --numeric --listening --tcp --udp | awk '{print$5}' | grep --quiet --extended-regexp ':%s$'"
|
||||||
|
@ -843,11 +823,9 @@ class PortsResource(AppResource):
|
||||||
return os.system(cmd1) == 0 and os.system(cmd2) == 0
|
return os.system(cmd1) == 0 and os.system(cmd2) == 0
|
||||||
|
|
||||||
def provision_or_update(self, context: Dict = {}):
|
def provision_or_update(self, context: Dict = {}):
|
||||||
|
|
||||||
from yunohost.firewall import firewall_allow, firewall_disallow
|
from yunohost.firewall import firewall_allow, firewall_disallow
|
||||||
|
|
||||||
for name, infos in self.ports.items():
|
for name, infos in self.ports.items():
|
||||||
|
|
||||||
setting_name = f"port_{name}" if name != "main" else "port"
|
setting_name = f"port_{name}" if name != "main" else "port"
|
||||||
port_value = self.get_setting(setting_name)
|
port_value = self.get_setting(setting_name)
|
||||||
if not port_value and name != "main":
|
if not port_value and name != "main":
|
||||||
|
@ -881,7 +859,6 @@ class PortsResource(AppResource):
|
||||||
)
|
)
|
||||||
|
|
||||||
def deprovision(self, context: Dict = {}):
|
def deprovision(self, context: Dict = {}):
|
||||||
|
|
||||||
from yunohost.firewall import firewall_disallow
|
from yunohost.firewall import firewall_disallow
|
||||||
|
|
||||||
for name, infos in self.ports.items():
|
for name, infos in self.ports.items():
|
||||||
|
@ -938,7 +915,6 @@ class DatabaseAppResource(AppResource):
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, properties: Dict[str, Any], *args, **kwargs):
|
def __init__(self, properties: Dict[str, Any], *args, **kwargs):
|
||||||
|
|
||||||
if "type" not in properties or properties["type"] not in [
|
if "type" not in properties or properties["type"] not in [
|
||||||
"mysql",
|
"mysql",
|
||||||
"postgresql",
|
"postgresql",
|
||||||
|
@ -956,7 +932,6 @@ class DatabaseAppResource(AppResource):
|
||||||
super().__init__(properties, *args, **kwargs)
|
super().__init__(properties, *args, **kwargs)
|
||||||
|
|
||||||
def db_exists(self, db_name):
|
def db_exists(self, db_name):
|
||||||
|
|
||||||
if self.dbtype == "mysql":
|
if self.dbtype == "mysql":
|
||||||
return os.system(f"mysqlshow '{db_name}' >/dev/null 2>/dev/null") == 0
|
return os.system(f"mysqlshow '{db_name}' >/dev/null 2>/dev/null") == 0
|
||||||
elif self.dbtype == "postgresql":
|
elif self.dbtype == "postgresql":
|
||||||
|
@ -970,7 +945,6 @@ class DatabaseAppResource(AppResource):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def provision_or_update(self, context: Dict = {}):
|
def provision_or_update(self, context: Dict = {}):
|
||||||
|
|
||||||
# This is equivalent to ynh_sanitize_dbid
|
# This is equivalent to ynh_sanitize_dbid
|
||||||
db_name = self.app.replace("-", "_").replace(".", "_")
|
db_name = self.app.replace("-", "_").replace(".", "_")
|
||||||
db_user = db_name
|
db_user = db_name
|
||||||
|
@ -997,7 +971,6 @@ class DatabaseAppResource(AppResource):
|
||||||
self.set_setting("db_pwd", db_pwd)
|
self.set_setting("db_pwd", db_pwd)
|
||||||
|
|
||||||
if not self.db_exists(db_name):
|
if not self.db_exists(db_name):
|
||||||
|
|
||||||
if self.dbtype == "mysql":
|
if self.dbtype == "mysql":
|
||||||
self._run_script(
|
self._run_script(
|
||||||
"provision",
|
"provision",
|
||||||
|
@ -1010,7 +983,6 @@ class DatabaseAppResource(AppResource):
|
||||||
)
|
)
|
||||||
|
|
||||||
def deprovision(self, context: Dict = {}):
|
def deprovision(self, context: Dict = {}):
|
||||||
|
|
||||||
db_name = self.app.replace("-", "_").replace(".", "_")
|
db_name = self.app.replace("-", "_").replace(".", "_")
|
||||||
db_user = db_name
|
db_user = db_name
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,6 @@ def free_space_in_directory(dirpath):
|
||||||
|
|
||||||
|
|
||||||
def space_used_by_directory(dirpath, follow_symlinks=True):
|
def space_used_by_directory(dirpath, follow_symlinks=True):
|
||||||
|
|
||||||
if not follow_symlinks:
|
if not follow_symlinks:
|
||||||
du_output = check_output(["du", "-sb", dirpath], shell=False)
|
du_output = check_output(["du", "-sb", dirpath], shell=False)
|
||||||
return int(du_output.split()[0])
|
return int(du_output.split()[0])
|
||||||
|
@ -61,7 +60,6 @@ def space_used_by_directory(dirpath, follow_symlinks=True):
|
||||||
|
|
||||||
|
|
||||||
def human_to_binary(size: str) -> int:
|
def human_to_binary(size: str) -> int:
|
||||||
|
|
||||||
symbols = ("K", "M", "G", "T", "P", "E", "Z", "Y")
|
symbols = ("K", "M", "G", "T", "P", "E", "Z", "Y")
|
||||||
factor = {}
|
factor = {}
|
||||||
for i, s in enumerate(symbols):
|
for i, s in enumerate(symbols):
|
||||||
|
@ -99,14 +97,12 @@ def binary_to_human(n: int) -> str:
|
||||||
|
|
||||||
|
|
||||||
def ram_available():
|
def ram_available():
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
return (psutil.virtual_memory().available, psutil.swap_memory().free)
|
return (psutil.virtual_memory().available, psutil.swap_memory().free)
|
||||||
|
|
||||||
|
|
||||||
def get_ynh_package_version(package):
|
def get_ynh_package_version(package):
|
||||||
|
|
||||||
# Returns the installed version and release version ('stable' or 'testing'
|
# Returns the installed version and release version ('stable' or 'testing'
|
||||||
# or 'unstable')
|
# or 'unstable')
|
||||||
|
|
||||||
|
@ -152,7 +148,6 @@ def dpkg_lock_available():
|
||||||
|
|
||||||
|
|
||||||
def _list_upgradable_apt_packages():
|
def _list_upgradable_apt_packages():
|
||||||
|
|
||||||
# List upgradable packages
|
# List upgradable packages
|
||||||
# LC_ALL=C is here to make sure the results are in english
|
# LC_ALL=C is here to make sure the results are in english
|
||||||
upgradable_raw = check_output("LC_ALL=C apt list --upgradable")
|
upgradable_raw = check_output("LC_ALL=C apt list --upgradable")
|
||||||
|
@ -162,7 +157,6 @@ def _list_upgradable_apt_packages():
|
||||||
line.strip() for line in upgradable_raw.split("\n") if line.strip()
|
line.strip() for line in upgradable_raw.split("\n") if line.strip()
|
||||||
]
|
]
|
||||||
for line in upgradable_raw:
|
for line in upgradable_raw:
|
||||||
|
|
||||||
# Remove stupid warning and verbose messages >.>
|
# Remove stupid warning and verbose messages >.>
|
||||||
if "apt does not have a stable CLI interface" in line or "Listing..." in line:
|
if "apt does not have a stable CLI interface" in line or "Listing..." in line:
|
||||||
continue
|
continue
|
||||||
|
@ -182,7 +176,6 @@ def _list_upgradable_apt_packages():
|
||||||
|
|
||||||
|
|
||||||
def _dump_sources_list():
|
def _dump_sources_list():
|
||||||
|
|
||||||
from glob import glob
|
from glob import glob
|
||||||
|
|
||||||
filenames = glob("/etc/apt/sources.list") + glob("/etc/apt/sources.list.d/*")
|
filenames = glob("/etc/apt/sources.list") + glob("/etc/apt/sources.list.d/*")
|
||||||
|
|
|
@ -28,7 +28,6 @@ logger = logging.getLogger("yunohost.utils.yunopaste")
|
||||||
|
|
||||||
|
|
||||||
def yunopaste(data):
|
def yunopaste(data):
|
||||||
|
|
||||||
paste_server = "https://paste.yunohost.org"
|
paste_server = "https://paste.yunohost.org"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
2
tox.ini
2
tox.ini
|
@ -8,7 +8,7 @@ deps =
|
||||||
py39-black-{run,check}: black
|
py39-black-{run,check}: black
|
||||||
py39-mypy: mypy >= 0.900
|
py39-mypy: mypy >= 0.900
|
||||||
commands =
|
commands =
|
||||||
py39-lint: flake8 src doc maintenance tests --ignore E402,E501,E203,W503 --exclude src/vendor
|
py39-lint: flake8 src doc maintenance tests --ignore E402,E501,E203,W503,E741 --exclude src/vendor
|
||||||
py39-invalidcode: flake8 src bin maintenance --exclude src/tests,src/vendor --select F,E722,W605
|
py39-invalidcode: flake8 src bin maintenance --exclude src/tests,src/vendor --select F,E722,W605
|
||||||
py39-black-check: black --check --diff bin src doc maintenance tests
|
py39-black-check: black --check --diff bin src doc maintenance tests
|
||||||
py39-black-run: black bin src doc maintenance tests
|
py39-black-run: black bin src doc maintenance tests
|
||||||
|
|
Loading…
Add table
Reference in a new issue