mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge remote-tracking branch 'origin/dev' into bookworm
This commit is contained in:
commit
bfba939927
18 changed files with 234 additions and 55 deletions
10
.github/workflows/n_updater.yml
vendored
10
.github/workflows/n_updater.yml
vendored
|
@ -20,9 +20,11 @@ jobs:
|
||||||
# Setting up Git user
|
# Setting up Git user
|
||||||
git config --global user.name 'yunohost-bot'
|
git config --global user.name 'yunohost-bot'
|
||||||
git config --global user.email 'yunohost-bot@users.noreply.github.com'
|
git config --global user.email 'yunohost-bot@users.noreply.github.com'
|
||||||
# Run the updater script
|
# Download n
|
||||||
wget https://raw.githubusercontent.com/tj/n/master/bin/n --output-document=helpers/vendor/n/n
|
wget https://raw.githubusercontent.com/tj/n/master/bin/n --output-document=helpers/vendor/n/n
|
||||||
|
# Proceed only if there is a change
|
||||||
[[ -z "$(git diff helpers/vendor/n/n)" ]] || echo "PROCEED=true" >> $GITHUB_ENV
|
[[ -z "$(git diff helpers/vendor/n/n)" ]] || echo "PROCEED=true" >> $GITHUB_ENV
|
||||||
|
echo "VERSION=$(sed -n 's/^VERSION=\"\(.*\)\"/\1/p' < n)" >> $GITHUB_ENV
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
id: commit
|
id: commit
|
||||||
if: ${{ env.PROCEED == 'true' }}
|
if: ${{ env.PROCEED == 'true' }}
|
||||||
|
@ -34,14 +36,14 @@ jobs:
|
||||||
uses: peter-evans/create-pull-request@v3
|
uses: peter-evans/create-pull-request@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
commit-message: Update n to version ${{ env.VERSION }}
|
commit-message: Update n to ${{ env.VERSION }}
|
||||||
committer: 'yunohost-bot <yunohost-bot@users.noreply.github.com>'
|
committer: 'yunohost-bot <yunohost-bot@users.noreply.github.com>'
|
||||||
author: 'yunohost-bot <yunohost-bot@users.noreply.github.com>'
|
author: 'yunohost-bot <yunohost-bot@users.noreply.github.com>'
|
||||||
signoff: false
|
signoff: false
|
||||||
base: dev
|
base: dev
|
||||||
branch: ci-auto-update-n-v${{ env.VERSION }}
|
branch: ci-auto-update-n-v${{ env.VERSION }}
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
title: 'Upgrade n to version ${{ env.VERSION }}'
|
title: 'Upgrade n to ${{ env.VERSION }}'
|
||||||
body: |
|
body: |
|
||||||
Upgrade `n` to v${{ env.VERSION }}
|
Upgrade `n` to ${{ env.VERSION }}
|
||||||
draft: false
|
draft: false
|
||||||
|
|
|
@ -118,8 +118,8 @@ plugin {
|
||||||
antispam_debug_target = syslog
|
antispam_debug_target = syslog
|
||||||
antispam_verbose_debug = 0
|
antispam_verbose_debug = 0
|
||||||
antispam_backend = pipe
|
antispam_backend = pipe
|
||||||
antispam_spam = Junk;SPAM
|
antispam_spam_pattern_ignorecase = junk;spam
|
||||||
antispam_trash = Trash
|
antispam_trash_pattern_ignorecase = trash;papierkorb;deleted messages
|
||||||
antispam_pipe_program = /usr/bin/rspamc
|
antispam_pipe_program = /usr/bin/rspamc
|
||||||
antispam_pipe_program_args = -h;localhost:11334;-P;q1
|
antispam_pipe_program_args = -h;localhost:11334;-P;q1
|
||||||
antispam_pipe_program_spam_arg = learn_spam
|
antispam_pipe_program_spam_arg = learn_spam
|
||||||
|
|
2
conf/rspamd/redis.conf
Normal file
2
conf/rspamd/redis.conf
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# set redis server
|
||||||
|
servers = "127.0.0.1";
|
18
debian/changelog
vendored
18
debian/changelog
vendored
|
@ -4,6 +4,24 @@ yunohost (12.0.0) unstable; urgency=low
|
||||||
|
|
||||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 04 May 2023 20:30:19 +0200
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 04 May 2023 20:30:19 +0200
|
||||||
|
|
||||||
|
yunohost (11.2.5) stable; urgency=low
|
||||||
|
|
||||||
|
- debian: fix conflict with openssl that is too harsh, openssl version on bullseye is now 1.1.1w, bookworm has 3.x (e8700bfe7)
|
||||||
|
- dyndns: tweak dyndns subscribe/unsubscribe for dyndns recovery password integration in webadmin ([#1715](https://github.com/yunohost/yunohost/pull/1715))
|
||||||
|
- helpers: ynh_setup_source: check and re-download a prefetched file that doesn't match the checksum (3dfab89c1)
|
||||||
|
- helpers: ynh_setup_source: fix misleading example ([#1714](https://github.com/yunohost/yunohost/pull/1714))
|
||||||
|
- helpers: php/apt: allow `phpX.Y` as sole dependency for `$phpversion=X.Y` ([#1722](https://github.com/yunohost/yunohost/pull/1722))
|
||||||
|
- apps: fix typo in log statement ([#1709](https://github.com/yunohost/yunohost/pull/1709))
|
||||||
|
- apps: allow system users to send mails from IPv6 localhost. ([#1710](https://github.com/yunohost/yunohost/pull/1710))
|
||||||
|
- apps: add "support_purge" to app info for webadmin integration ([#1719](https://github.com/yunohost/yunohost/pull/1719))
|
||||||
|
- diagnosis: be more flexible regarding accepted values for DMARC DNS records ([#1713](https://github.com/yunohost/yunohost/pull/1713))
|
||||||
|
- dns: add home.arpa as special TLD (#1718) (bb097fedc)
|
||||||
|
- i18n: Translations updated for Basque, French
|
||||||
|
|
||||||
|
Thanks to all contributors <3 ! (axolotle, Florian, Kayou, orhtej2, Pierre de La Morinerie, ppr, stanislas, tituspijean, xabirequejo)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 09 Oct 2023 23:16:13 +0200
|
||||||
|
|
||||||
yunohost (11.2.4) stable; urgency=low
|
yunohost (11.2.4) stable; urgency=low
|
||||||
|
|
||||||
- doc: Improve --help for 'yunohost app install' ([#1702](https://github.com/yunohost/yunohost/pull/1702))
|
- doc: Improve --help for 'yunohost app install' ([#1702](https://github.com/yunohost/yunohost/pull/1702))
|
||||||
|
|
|
@ -250,7 +250,7 @@ ynh_install_app_dependencies() {
|
||||||
# Check for specific php dependencies which requires sury
|
# Check for specific php dependencies which requires sury
|
||||||
# This grep will for example return "7.4" if dependencies is "foo bar php7.4-pwet php-gni"
|
# This grep will for example return "7.4" if dependencies is "foo bar php7.4-pwet php-gni"
|
||||||
# The (?<=php) syntax corresponds to lookbehind ;)
|
# The (?<=php) syntax corresponds to lookbehind ;)
|
||||||
local specific_php_version=$(echo $dependencies | grep -oP '(?<=php)[0-9.]+(?=-|\>)' | sort -u)
|
local specific_php_version=$(echo $dependencies | grep -oP '(?<=php)[0-9.]+(?=-|\>|)' | sort -u)
|
||||||
|
|
||||||
if [[ -n "$specific_php_version" ]]
|
if [[ -n "$specific_php_version" ]]
|
||||||
then
|
then
|
||||||
|
|
61
helpers/vendor/n/n
vendored
61
helpers/vendor/n/n
vendored
|
@ -61,7 +61,7 @@ function n_grep() {
|
||||||
# Setup and state
|
# Setup and state
|
||||||
#
|
#
|
||||||
|
|
||||||
VERSION="v9.1.0"
|
VERSION="v9.2.0"
|
||||||
|
|
||||||
N_PREFIX="${N_PREFIX-/usr/local}"
|
N_PREFIX="${N_PREFIX-/usr/local}"
|
||||||
N_PREFIX=${N_PREFIX%/}
|
N_PREFIX=${N_PREFIX%/}
|
||||||
|
@ -135,6 +135,7 @@ g_target_node=
|
||||||
DOWNLOAD=false # set to opt-out of activate (install), and opt-in to download (run, exec)
|
DOWNLOAD=false # set to opt-out of activate (install), and opt-in to download (run, exec)
|
||||||
ARCH=
|
ARCH=
|
||||||
SHOW_VERBOSE_LOG="true"
|
SHOW_VERBOSE_LOG="true"
|
||||||
|
OFFLINE=false
|
||||||
|
|
||||||
# ANSI escape codes
|
# ANSI escape codes
|
||||||
# https://en.wikipedia.org/wiki/ANSI_escape_code
|
# https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
@ -393,6 +394,7 @@ Options:
|
||||||
-q, --quiet Disable curl output. Disable log messages processing "auto" and "engine" labels.
|
-q, --quiet Disable curl output. Disable log messages processing "auto" and "engine" labels.
|
||||||
-d, --download Download if necessary, and don't make active
|
-d, --download Download if necessary, and don't make active
|
||||||
-a, --arch Override system architecture
|
-a, --arch Override system architecture
|
||||||
|
--offline Resolve target version against cached downloads instead of internet lookup
|
||||||
--all ls-remote displays all matches instead of last 20
|
--all ls-remote displays all matches instead of last 20
|
||||||
--insecure Turn off certificate checking for https requests (may be needed from behind a proxy server)
|
--insecure Turn off certificate checking for https requests (may be needed from behind a proxy server)
|
||||||
--use-xz/--no-use-xz Override automatic detection of xz support and enable/disable use of xz compressed node downloads.
|
--use-xz/--no-use-xz Override automatic detection of xz support and enable/disable use of xz compressed node downloads.
|
||||||
|
@ -784,6 +786,9 @@ install() {
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
if [[ "$OFFLINE" == "true" ]]; then
|
||||||
|
abort "version unavailable offline"
|
||||||
|
fi
|
||||||
|
|
||||||
log installing "${g_mirror_folder_name}-v$version"
|
log installing "${g_mirror_folder_name}-v$version"
|
||||||
|
|
||||||
|
@ -1103,6 +1108,7 @@ function get_package_engine_version() {
|
||||||
verbose_log "target" "${version}"
|
verbose_log "target" "${version}"
|
||||||
else
|
else
|
||||||
command -v npx &> /dev/null || abort "an active version of npx is required to use complex 'engine' ranges from package.json"
|
command -v npx &> /dev/null || abort "an active version of npx is required to use complex 'engine' ranges from package.json"
|
||||||
|
[[ "$OFFLINE" != "true" ]] || abort "offline: an internet connection is required for looking up complex 'engine' ranges from package.json"
|
||||||
verbose_log "resolving" "${range}"
|
verbose_log "resolving" "${range}"
|
||||||
local version_per_line="$(n lsr --all)"
|
local version_per_line="$(n lsr --all)"
|
||||||
local versions_one_line=$(echo "${version_per_line}" | tr '\n' ' ')
|
local versions_one_line=$(echo "${version_per_line}" | tr '\n' ' ')
|
||||||
|
@ -1199,6 +1205,8 @@ function get_latest_resolved_version() {
|
||||||
# Just numbers, already resolved, no need to lookup first.
|
# Just numbers, already resolved, no need to lookup first.
|
||||||
simple_version="${simple_version#v}"
|
simple_version="${simple_version#v}"
|
||||||
g_target_node="${simple_version}"
|
g_target_node="${simple_version}"
|
||||||
|
elif [[ "$OFFLINE" == "true" ]]; then
|
||||||
|
g_target_node=$(display_local_versions "${version}")
|
||||||
else
|
else
|
||||||
# Complicated recognising exact version, KISS and lookup.
|
# Complicated recognising exact version, KISS and lookup.
|
||||||
g_target_node=$(N_MAX_REMOTE_MATCHES=1 display_remote_versions "$version")
|
g_target_node=$(N_MAX_REMOTE_MATCHES=1 display_remote_versions "$version")
|
||||||
|
@ -1232,6 +1240,56 @@ function display_match_limit(){
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Synopsis: display_local_versions version
|
||||||
|
#
|
||||||
|
|
||||||
|
function display_local_versions() {
|
||||||
|
local version="$1"
|
||||||
|
local match='.'
|
||||||
|
verbose_log "offline" "matching cached versions"
|
||||||
|
|
||||||
|
# Transform some labels before processing further.
|
||||||
|
if is_node_support_version "${version}"; then
|
||||||
|
version="$(display_latest_node_support_alias "${version}")"
|
||||||
|
match_count=1
|
||||||
|
elif [[ "${version}" = "auto" ]]; then
|
||||||
|
# suppress stdout logging so lsr layout same as usual for scripting
|
||||||
|
get_auto_version || return 2
|
||||||
|
version="${g_target_node}"
|
||||||
|
elif [[ "${version}" = "engine" ]]; then
|
||||||
|
# suppress stdout logging so lsr layout same as usual for scripting
|
||||||
|
get_engine_version || return 2
|
||||||
|
version="${g_target_node}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${version}" = "latest" || "${version}" = "current" ]]; then
|
||||||
|
match='^node/.'
|
||||||
|
elif is_exact_numeric_version "${version}"; then
|
||||||
|
# Quote any dots in version so they are literal for expression
|
||||||
|
match="^node/${version//\./\.}"
|
||||||
|
elif is_numeric_version "${version}"; then
|
||||||
|
version="${version#v}"
|
||||||
|
# Quote any dots in version so they are literal for expression
|
||||||
|
match="${version//\./\.}"
|
||||||
|
# Avoid 1.2 matching 1.23
|
||||||
|
match="^node/${match}[^0-9]"
|
||||||
|
# elif is_lts_codename "${version}"; then
|
||||||
|
# see if demand
|
||||||
|
elif is_download_folder "${version}"; then
|
||||||
|
match="^${version}/"
|
||||||
|
# elif is_download_version "${version}"; then
|
||||||
|
# see if demand
|
||||||
|
else
|
||||||
|
abort "invalid version '$1' for offline matching"
|
||||||
|
fi
|
||||||
|
|
||||||
|
display_versions_paths \
|
||||||
|
| n_grep -E "${match}" \
|
||||||
|
| tail -n 1 \
|
||||||
|
| sed 's|node/||'
|
||||||
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
# Synopsis: display_remote_versions version
|
# Synopsis: display_remote_versions version
|
||||||
#
|
#
|
||||||
|
@ -1577,6 +1635,7 @@ while [[ $# -ne 0 ]]; do
|
||||||
-h|--help|help) display_help; exit ;;
|
-h|--help|help) display_help; exit ;;
|
||||||
-q|--quiet) set_quiet ;;
|
-q|--quiet) set_quiet ;;
|
||||||
-d|--download) DOWNLOAD="true" ;;
|
-d|--download) DOWNLOAD="true" ;;
|
||||||
|
--offline) OFFLINE="true" ;;
|
||||||
--insecure) set_insecure ;;
|
--insecure) set_insecure ;;
|
||||||
-p|--preserve) N_PRESERVE_NPM="true" N_PRESERVE_COREPACK="true" ;;
|
-p|--preserve) N_PRESERVE_NPM="true" N_PRESERVE_COREPACK="true" ;;
|
||||||
--no-preserve) N_PRESERVE_NPM="" N_PRESERVE_COREPACK="" ;;
|
--no-preserve) N_PRESERVE_NPM="" N_PRESERVE_COREPACK="" ;;
|
||||||
|
|
|
@ -13,6 +13,8 @@ do_pre_regen() {
|
||||||
"${pending_dir}/etc/rspamd/local.d/dkim_signing.conf"
|
"${pending_dir}/etc/rspamd/local.d/dkim_signing.conf"
|
||||||
install -D -m 644 rspamd.sieve \
|
install -D -m 644 rspamd.sieve \
|
||||||
"${pending_dir}/etc/dovecot/global_script/rspamd.sieve"
|
"${pending_dir}/etc/dovecot/global_script/rspamd.sieve"
|
||||||
|
install -D -m 644 redis.conf \
|
||||||
|
"${pending_dir}/etc/rspamd/local.d/redis.conf"
|
||||||
}
|
}
|
||||||
|
|
||||||
do_post_regen() {
|
do_post_regen() {
|
||||||
|
|
|
@ -91,6 +91,7 @@
|
||||||
"ask_new_path": "New path",
|
"ask_new_path": "New path",
|
||||||
"ask_password": "Password",
|
"ask_password": "Password",
|
||||||
"ask_dyndns_recovery_password_explain": "Please pick a recovery password for your DynDNS domain, in case you need to reset it later.",
|
"ask_dyndns_recovery_password_explain": "Please pick a recovery password for your DynDNS domain, in case you need to reset it later.",
|
||||||
|
"ask_dyndns_recovery_password_explain_unavailable": "This DynDNS domain is already registered. If you are the person who originally registered this domain, you may enter the recovery password to reclaim this domain.",
|
||||||
"ask_dyndns_recovery_password": "DynDNS recovery password",
|
"ask_dyndns_recovery_password": "DynDNS recovery password",
|
||||||
"ask_dyndns_recovery_password_explain_during_unsubscribe": "Please enter the recovery password for this DynDNS domain.",
|
"ask_dyndns_recovery_password_explain_during_unsubscribe": "Please enter the recovery password for this DynDNS domain.",
|
||||||
"ask_user_domain": "Domain to use for the user's email address and XMPP account",
|
"ask_user_domain": "Domain to use for the user's email address and XMPP account",
|
||||||
|
@ -406,6 +407,7 @@
|
||||||
"dyndns_provider_unreachable": "Unable to reach DynDNS provider {provider}: either your YunoHost is not correctly connected to the internet or the dynette server is down.",
|
"dyndns_provider_unreachable": "Unable to reach DynDNS provider {provider}: either your YunoHost is not correctly connected to the internet or the dynette server is down.",
|
||||||
"dyndns_subscribed": "DynDNS domain subscribed",
|
"dyndns_subscribed": "DynDNS domain subscribed",
|
||||||
"dyndns_subscribe_failed": "Could not subscribe DynDNS domain: {error}",
|
"dyndns_subscribe_failed": "Could not subscribe DynDNS domain: {error}",
|
||||||
|
"dyndns_too_many_requests": "YunoHost's dyndns service received too many requests from you, wait 1 hour or so before trying again.",
|
||||||
"dyndns_unsubscribe_failed": "Could not unsubscribe DynDNS domain: {error}",
|
"dyndns_unsubscribe_failed": "Could not unsubscribe DynDNS domain: {error}",
|
||||||
"dyndns_unsubscribed": "DynDNS domain unsubscribed",
|
"dyndns_unsubscribed": "DynDNS domain unsubscribed",
|
||||||
"dyndns_unsubscribe_denied": "Failed to unsubscribe domain: invalid credentials",
|
"dyndns_unsubscribe_denied": "Failed to unsubscribe domain: invalid credentials",
|
||||||
|
|
|
@ -766,5 +766,13 @@
|
||||||
"group_mailalias_add": "'{mail}' ePosta aliasa jarri zaio '{group}' taldeari",
|
"group_mailalias_add": "'{mail}' ePosta aliasa jarri zaio '{group}' taldeari",
|
||||||
"group_mailalias_remove": "'{mail}' ePosta aliasa kendu zaio '{group}' taldeari",
|
"group_mailalias_remove": "'{mail}' ePosta aliasa kendu zaio '{group}' taldeari",
|
||||||
"group_user_remove": "'{user}' erabiltzailea '{group}' taldetik kenduko da",
|
"group_user_remove": "'{user}' erabiltzailea '{group}' taldetik kenduko da",
|
||||||
"group_user_add": "'{user}' erabiltzailea '{group}' taldera gehituko da"
|
"group_user_add": "'{user}' erabiltzailea '{group}' taldera gehituko da",
|
||||||
|
"ask_dyndns_recovery_password_explain": "Aukeratu DynDNS domeinua berreskuratzeko pasahitza, etorkizunean berrezarri beharko bazenu.",
|
||||||
|
"ask_dyndns_recovery_password_explain_during_unsubscribe": "Sartu DynDNS domeinurako berreskuraketa pasahitza.",
|
||||||
|
"dyndns_no_recovery_password": "Ez da berreskurapen pasahitzik zehaztu! Domeinuaren gaineko kontrola galduz gero, YunoHost taldeko administrariarekin jarri beharko zara harremanetan!",
|
||||||
|
"ask_dyndns_recovery_password": "DynDNS berreskuratzeko pasahitza",
|
||||||
|
"dyndns_subscribed": "DynDNS domeinua harpidetu da",
|
||||||
|
"dyndns_subscribe_failed": "Ezin izan da DynDNS domeinua harpidetu: {error}",
|
||||||
|
"dyndns_unsubscribe_failed": "Ezin izan da DynDNS domeinuaren harpidetza utzi: {error}",
|
||||||
|
"dyndns_unsubscribed": "DynDNS domeinuaren harpidetza utzi da"
|
||||||
}
|
}
|
||||||
|
|
|
@ -781,6 +781,8 @@
|
||||||
"dyndns_set_recovery_password_unknown_domain": "Échec de la définition du mot de passe de récupération : le domaine n'est pas enregistré",
|
"dyndns_set_recovery_password_unknown_domain": "Échec de la définition du mot de passe de récupération : le domaine n'est pas enregistré",
|
||||||
"dyndns_set_recovery_password_invalid_password": "Échec de la définition du mot de passe de récupération : le mot de passe n'est pas assez fort/solide",
|
"dyndns_set_recovery_password_invalid_password": "Échec de la définition du mot de passe de récupération : le mot de passe n'est pas assez fort/solide",
|
||||||
"dyndns_set_recovery_password_failed": "Échec de la définition du mot de passe de récupération : {erreur}",
|
"dyndns_set_recovery_password_failed": "Échec de la définition du mot de passe de récupération : {erreur}",
|
||||||
"dyndns_set_recovery_password_success": "Mot de passe de récupération défini/configuré !",
|
"dyndns_set_recovery_password_success": "Mot de passe de récupération changé !",
|
||||||
"log_dyndns_unsubscribe": "Se désabonner d'un sous-domaine YunoHost '{}'"
|
"log_dyndns_unsubscribe": "Se désabonner d'un sous-domaine YunoHost '{}'",
|
||||||
|
"dyndns_too_many_requests": "Le service dyndns de YunoHost a reçu trop de requêtes/demandes de votre part, attendez environ 1 heure avant de réessayer.",
|
||||||
|
"ask_dyndns_recovery_password_explain_unavailable": "Ce domaine DynDNS est déjà enregistré. Si vous êtes la personne qui a enregistré ce domaine lors de sa création, vous pouvez entrer le mot de passe de récupération pour récupérer ce domaine."
|
||||||
}
|
}
|
||||||
|
|
10
src/app.py
10
src/app.py
|
@ -244,6 +244,10 @@ def app_info(app, full=False, upgradable=False):
|
||||||
ret["supports_config_panel"] = os.path.exists(
|
ret["supports_config_panel"] = os.path.exists(
|
||||||
os.path.join(setting_path, "config_panel.toml")
|
os.path.join(setting_path, "config_panel.toml")
|
||||||
)
|
)
|
||||||
|
ret["supports_purge"] = (
|
||||||
|
local_manifest["packaging_format"] >= 2
|
||||||
|
and local_manifest["resources"].get("data_dir") is not None
|
||||||
|
)
|
||||||
|
|
||||||
ret["permissions"] = permissions
|
ret["permissions"] = permissions
|
||||||
ret["label"] = permissions.get(app + ".main", {}).get("label")
|
ret["label"] = permissions.get(app + ".main", {}).get("label")
|
||||||
|
@ -2143,11 +2147,11 @@ def _parse_app_doc_and_notifications(path):
|
||||||
|
|
||||||
|
|
||||||
def _hydrate_app_template(template, data):
|
def _hydrate_app_template(template, data):
|
||||||
|
|
||||||
# Apply jinja for stuff like {% if .. %} blocks,
|
# Apply jinja for stuff like {% if .. %} blocks,
|
||||||
# but only if there's indeed an if block (to try to reduce overhead or idk)
|
# but only if there's indeed an if block (to try to reduce overhead or idk)
|
||||||
if "{%" in template:
|
if "{%" in template:
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
|
||||||
template = Template(template).render(**data)
|
template = Template(template).render(**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))
|
||||||
|
@ -3071,7 +3075,9 @@ def _filter_and_hydrate_notifications(notifications, current_version=None, data=
|
||||||
}
|
}
|
||||||
|
|
||||||
# Filter out empty notifications (notifications may be empty because of if blocks)
|
# Filter out empty notifications (notifications may be empty because of if blocks)
|
||||||
return {name:content for name, content in out.items() if content and content.strip()}
|
return {
|
||||||
|
name: content for name, content in out.items() if content and content.strip()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _display_notifications(notifications, force=False):
|
def _display_notifications(notifications, force=False):
|
||||||
|
|
|
@ -548,8 +548,16 @@ def _get_registrar_config_section(domain):
|
||||||
registrar_infos["registrar"]["default"] = "yunohost"
|
registrar_infos["registrar"]["default"] = "yunohost"
|
||||||
registrar_infos["infos"]["style"] = "success"
|
registrar_infos["infos"]["style"] = "success"
|
||||||
registrar_infos["infos"]["ask"] = m18n.n("domain_dns_registrar_yunohost")
|
registrar_infos["infos"]["ask"] = m18n.n("domain_dns_registrar_yunohost")
|
||||||
|
registrar_infos["recovery_password"] = OrderedDict(
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"ask": m18n.n("ask_dyndns_recovery_password"),
|
||||||
|
"default": "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return registrar_infos
|
return registrar_infos
|
||||||
|
|
||||||
elif is_special_use_tld(dns_zone):
|
elif is_special_use_tld(dns_zone):
|
||||||
registrar_infos["infos"]["ask"] = m18n.n("domain_dns_conf_special_use_tld")
|
registrar_infos["infos"]["ask"] = m18n.n("domain_dns_conf_special_use_tld")
|
||||||
|
|
||||||
|
|
|
@ -381,6 +381,7 @@ def domain_remove(
|
||||||
dyndns_recovery_password -- Recovery password used at the creation of the DynDNS domain
|
dyndns_recovery_password -- Recovery password used at the creation of the DynDNS domain
|
||||||
ignore_dyndns -- If we just remove the DynDNS domain, without unsubscribing
|
ignore_dyndns -- If we just remove the DynDNS domain, without unsubscribing
|
||||||
"""
|
"""
|
||||||
|
import glob
|
||||||
from yunohost.hook import hook_callback
|
from yunohost.hook import hook_callback
|
||||||
from yunohost.app import app_ssowatconf, app_info, app_remove
|
from yunohost.app import app_ssowatconf, app_info, app_remove
|
||||||
from yunohost.utils.ldap import _get_ldap_interface
|
from yunohost.utils.ldap import _get_ldap_interface
|
||||||
|
@ -466,14 +467,20 @@ def domain_remove(
|
||||||
global domain_list_cache
|
global domain_list_cache
|
||||||
domain_list_cache = []
|
domain_list_cache = []
|
||||||
|
|
||||||
stuff_to_delete = [
|
# If a password is provided, delete the DynDNS record
|
||||||
f"/etc/yunohost/certs/{domain}",
|
if dyndns:
|
||||||
f"/etc/yunohost/dyndns/K{domain}.+*",
|
try:
|
||||||
f"{DOMAIN_SETTINGS_DIR}/{domain}.yml",
|
# Actually unsubscribe
|
||||||
]
|
domain_dyndns_unsubscribe(
|
||||||
|
domain=domain, recovery_password=dyndns_recovery_password
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(str(e))
|
||||||
|
|
||||||
for stuff in stuff_to_delete:
|
rm(f"/etc/yunohost/certs/{domain}", force=True, recursive=True)
|
||||||
rm(stuff, force=True, recursive=True)
|
for key_file in glob.glob(f"/etc/yunohost/dyndns/K{domain}.+*"):
|
||||||
|
rm(key_file, force=True)
|
||||||
|
rm(f"{DOMAIN_SETTINGS_DIR}/{domain}.yml", force=True)
|
||||||
|
|
||||||
# Sometime we have weird issues with the regenconf where some files
|
# Sometime we have weird issues with the regenconf where some files
|
||||||
# appears as manually modified even though they weren't touched ...
|
# appears as manually modified even though they weren't touched ...
|
||||||
|
@ -501,13 +508,6 @@ def domain_remove(
|
||||||
|
|
||||||
hook_callback("post_domain_remove", args=[domain])
|
hook_callback("post_domain_remove", args=[domain])
|
||||||
|
|
||||||
# If a password is provided, delete the DynDNS record
|
|
||||||
if dyndns:
|
|
||||||
# Actually unsubscribe
|
|
||||||
domain_dyndns_unsubscribe(
|
|
||||||
domain=domain, recovery_password=dyndns_recovery_password
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.success(m18n.n("domain_deleted"))
|
logger.success(m18n.n("domain_deleted"))
|
||||||
|
|
||||||
|
|
||||||
|
@ -735,6 +735,19 @@ class DomainConfigPanel(ConfigPanel):
|
||||||
domain=self.entity,
|
domain=self.entity,
|
||||||
other_app=app_map(raw=True)[self.entity]["/"]["id"],
|
other_app=app_map(raw=True)[self.entity]["/"]["id"],
|
||||||
)
|
)
|
||||||
|
if (
|
||||||
|
"recovery_password" in self.new_values
|
||||||
|
and self.new_values["recovery_password"]
|
||||||
|
):
|
||||||
|
domain_dyndns_set_recovery_password(
|
||||||
|
self.entity, self.new_values["recovery_password"]
|
||||||
|
)
|
||||||
|
# Do not save password in yaml settings
|
||||||
|
if "recovery_password" in self.values:
|
||||||
|
del self.values["recovery_password"]
|
||||||
|
if "recovery_password" in self.new_values:
|
||||||
|
del self.new_values["recovery_password"]
|
||||||
|
assert "recovery_password" not in self.future_values
|
||||||
|
|
||||||
portal_options = [
|
portal_options = [
|
||||||
"default_app",
|
"default_app",
|
||||||
|
|
|
@ -27,7 +27,6 @@ from logging import getLogger
|
||||||
from moulinette import Moulinette, m18n
|
from moulinette import Moulinette, m18n
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
from moulinette.utils.filesystem import write_to_file, rm, chown, chmod
|
from moulinette.utils.filesystem import write_to_file, rm, chown, chmod
|
||||||
from moulinette.utils.network import download_json
|
|
||||||
|
|
||||||
from yunohost.utils.error import YunohostError, YunohostValidationError
|
from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||||
from yunohost.domain import _get_maindomain
|
from yunohost.domain import _get_maindomain
|
||||||
|
@ -63,19 +62,28 @@ def _dyndns_available(domain):
|
||||||
Returns:
|
Returns:
|
||||||
True if the domain is available, False otherwise.
|
True if the domain is available, False otherwise.
|
||||||
"""
|
"""
|
||||||
|
import requests # lazy loading this module for performance reasons
|
||||||
|
|
||||||
logger.debug(f"Checking if domain {domain} is available on {DYNDNS_PROVIDER} ...")
|
logger.debug(f"Checking if domain {domain} is available on {DYNDNS_PROVIDER} ...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = download_json(
|
r = requests.get(f"https://{DYNDNS_PROVIDER}/test/{domain}", timeout=30)
|
||||||
f"https://{DYNDNS_PROVIDER}/test/{domain}", expected_status_code=None
|
|
||||||
)
|
|
||||||
except MoulinetteError as e:
|
except MoulinetteError as e:
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
raise YunohostError(
|
raise YunohostError(
|
||||||
"dyndns_could_not_check_available", domain=domain, provider=DYNDNS_PROVIDER
|
"dyndns_could_not_check_available", domain=domain, provider=DYNDNS_PROVIDER
|
||||||
)
|
)
|
||||||
|
|
||||||
return r == f"Domain {domain} is available"
|
if r.status_code == 200:
|
||||||
|
return r.text.strip('"') == f"Domain {domain} is available"
|
||||||
|
elif r.status_code == 409:
|
||||||
|
return False
|
||||||
|
elif r.status_code == 429:
|
||||||
|
raise YunohostValidationError("dyndns_too_many_requests")
|
||||||
|
else:
|
||||||
|
raise YunohostError(
|
||||||
|
"dyndns_could_not_check_available", domain=domain, provider=DYNDNS_PROVIDER
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@is_unit_operation(exclude=["recovery_password"])
|
@is_unit_operation(exclude=["recovery_password"])
|
||||||
|
@ -94,14 +102,26 @@ def dyndns_subscribe(operation_logger, domain=None, recovery_password=None):
|
||||||
"dyndns_domain_not_provided", domain=domain, provider=DYNDNS_PROVIDER
|
"dyndns_domain_not_provided", domain=domain, provider=DYNDNS_PROVIDER
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify if domain is available
|
|
||||||
if not _dyndns_available(domain):
|
|
||||||
raise YunohostValidationError("dyndns_unavailable", domain=domain)
|
|
||||||
|
|
||||||
# Check adding another dyndns domain is still allowed
|
# Check adding another dyndns domain is still allowed
|
||||||
if not is_subscribing_allowed():
|
if not is_subscribing_allowed():
|
||||||
raise YunohostValidationError("domain_dyndns_already_subscribed")
|
raise YunohostValidationError("domain_dyndns_already_subscribed")
|
||||||
|
|
||||||
|
# Verify if domain is available
|
||||||
|
if not _dyndns_available(domain):
|
||||||
|
# Prompt for a password if running in CLI and no password provided
|
||||||
|
if not recovery_password and Moulinette.interface.type == "cli":
|
||||||
|
logger.warning(m18n.n("ask_dyndns_recovery_password_explain_unavailable"))
|
||||||
|
recovery_password = Moulinette.prompt(
|
||||||
|
m18n.n("ask_dyndns_recovery_password"), is_password=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if recovery_password:
|
||||||
|
# Try to unsubscribe the domain so it can be subscribed again
|
||||||
|
# If successful, it will be resubscribed with the same recovery password
|
||||||
|
dyndns_unsubscribe(domain=domain, recovery_password=recovery_password)
|
||||||
|
else:
|
||||||
|
raise YunohostValidationError("dyndns_unavailable", domain=domain)
|
||||||
|
|
||||||
# Prompt for a password if running in CLI and no password provided
|
# Prompt for a password if running in CLI and no password provided
|
||||||
if not recovery_password and Moulinette.interface.type == "cli":
|
if not recovery_password and Moulinette.interface.type == "cli":
|
||||||
logger.warning(m18n.n("ask_dyndns_recovery_password_explain"))
|
logger.warning(m18n.n("ask_dyndns_recovery_password_explain"))
|
||||||
|
@ -252,9 +272,11 @@ def dyndns_unsubscribe(operation_logger, domain, recovery_password=None):
|
||||||
# in /etc/yunohost/dyndns
|
# in /etc/yunohost/dyndns
|
||||||
regen_conf(["yunohost"])
|
regen_conf(["yunohost"])
|
||||||
elif r.status_code == 403:
|
elif r.status_code == 403:
|
||||||
raise YunohostError("dyndns_unsubscribe_denied")
|
raise YunohostValidationError("dyndns_unsubscribe_denied")
|
||||||
elif r.status_code == 409:
|
elif r.status_code == 409:
|
||||||
raise YunohostError("dyndns_unsubscribe_already_unsubscribed")
|
raise YunohostValidationError("dyndns_unsubscribe_already_unsubscribed")
|
||||||
|
elif r.status_code == 429:
|
||||||
|
raise YunohostValidationError("dyndns_too_many_requests")
|
||||||
else:
|
else:
|
||||||
raise YunohostError(
|
raise YunohostError(
|
||||||
"dyndns_unsubscribe_failed",
|
"dyndns_unsubscribe_failed",
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import pytest
|
import pytest
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
|
from moulinette import Moulinette
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
|
|
||||||
from yunohost.utils.error import YunohostError, YunohostValidationError
|
from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||||
|
@ -75,11 +77,44 @@ def test_domain_add():
|
||||||
assert TEST_DOMAINS[2] in domain_list()["domains"]
|
assert TEST_DOMAINS[2] in domain_list()["domains"]
|
||||||
|
|
||||||
|
|
||||||
def test_domain_add_subscribe():
|
def test_domain_add_and_remove_dyndns():
|
||||||
time.sleep(35) # Dynette blocks requests that happen too frequently
|
# Devs: if you get `too_many_request` errors, ask the team to add your IP to the rate limit excempt
|
||||||
assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"]
|
assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"]
|
||||||
domain_add(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD)
|
domain_add(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD)
|
||||||
assert TEST_DYNDNS_DOMAIN in domain_list()["domains"]
|
assert TEST_DYNDNS_DOMAIN in domain_list()["domains"]
|
||||||
|
domain_remove(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD)
|
||||||
|
assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_domain_dyndns_recovery():
|
||||||
|
# Devs: if you get `too_many_request` errors, ask the team to add your IP to the rate limit excempt
|
||||||
|
assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"]
|
||||||
|
# mocked as API call to avoid CLI prompts
|
||||||
|
with patch.object(Moulinette.interface, "type", "api"):
|
||||||
|
# add domain without recovery password
|
||||||
|
domain_add(TEST_DYNDNS_DOMAIN)
|
||||||
|
assert TEST_DYNDNS_DOMAIN in domain_list()["domains"]
|
||||||
|
# set the recovery password with config panel
|
||||||
|
domain_config_set(
|
||||||
|
TEST_DYNDNS_DOMAIN, "dns.registrar.recovery_password", TEST_DYNDNS_PASSWORD
|
||||||
|
)
|
||||||
|
# remove domain without unsubscribing
|
||||||
|
domain_remove(TEST_DYNDNS_DOMAIN, ignore_dyndns=True)
|
||||||
|
assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"]
|
||||||
|
# readding domain with bad password should fail
|
||||||
|
with pytest.raises(YunohostValidationError):
|
||||||
|
domain_add(
|
||||||
|
TEST_DYNDNS_DOMAIN,
|
||||||
|
dyndns_recovery_password="wrong" + TEST_DYNDNS_PASSWORD,
|
||||||
|
)
|
||||||
|
assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"]
|
||||||
|
# readding domain with password should work
|
||||||
|
domain_add(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD)
|
||||||
|
assert TEST_DYNDNS_DOMAIN in domain_list()["domains"]
|
||||||
|
# remove the dyndns domain
|
||||||
|
domain_remove(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD)
|
||||||
|
|
||||||
|
assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"]
|
||||||
|
|
||||||
|
|
||||||
def test_domain_add_existing_domain():
|
def test_domain_add_existing_domain():
|
||||||
|
@ -94,13 +129,6 @@ def test_domain_remove():
|
||||||
assert TEST_DOMAINS[1] not in domain_list()["domains"]
|
assert TEST_DOMAINS[1] not in domain_list()["domains"]
|
||||||
|
|
||||||
|
|
||||||
def test_domain_remove_unsubscribe():
|
|
||||||
time.sleep(35) # Dynette blocks requests that happen too frequently
|
|
||||||
assert TEST_DYNDNS_DOMAIN in domain_list()["domains"]
|
|
||||||
domain_remove(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD)
|
|
||||||
assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_main_domain():
|
def test_main_domain():
|
||||||
current_main_domain = _get_maindomain()
|
current_main_domain = _get_maindomain()
|
||||||
assert domain_main_domain()["current_main_domain"] == current_main_domain
|
assert domain_main_domain()["current_main_domain"] == current_main_domain
|
||||||
|
|
|
@ -156,7 +156,7 @@ def tools_postinstall(
|
||||||
force_diskspace=False,
|
force_diskspace=False,
|
||||||
overwrite_root_password=True,
|
overwrite_root_password=True,
|
||||||
):
|
):
|
||||||
from yunohost.dyndns import _dyndns_available
|
from yunohost.dyndns import _dyndns_available, dyndns_unsubscribe
|
||||||
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 (
|
||||||
assert_password_is_strong_enough,
|
assert_password_is_strong_enough,
|
||||||
|
@ -218,6 +218,13 @@ def tools_postinstall(
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if not available:
|
if not available:
|
||||||
|
if dyndns_recovery_password:
|
||||||
|
# Try to unsubscribe the domain so it can be subscribed again
|
||||||
|
# If successful, it will be resubscribed with the same recovery password
|
||||||
|
dyndns_unsubscribe(
|
||||||
|
domain=domain, recovery_password=dyndns_recovery_password
|
||||||
|
)
|
||||||
|
else:
|
||||||
raise YunohostValidationError("dyndns_unavailable", domain=domain)
|
raise YunohostValidationError("dyndns_unavailable", domain=domain)
|
||||||
|
|
||||||
if os.system("iptables -V >/dev/null 2>/dev/null") != 0:
|
if os.system("iptables -V >/dev/null 2>/dev/null") != 0:
|
||||||
|
|
|
@ -21,7 +21,7 @@ from typing import List
|
||||||
|
|
||||||
from moulinette.utils.filesystem import read_file
|
from moulinette.utils.filesystem import read_file
|
||||||
|
|
||||||
SPECIAL_USE_TLDS = ["local", "localhost", "onion", "test"]
|
SPECIAL_USE_TLDS = ["home.arpa", "local", "localhost", "onion", "test"]
|
||||||
|
|
||||||
YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"]
|
YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"]
|
||||||
|
|
||||||
|
|
|
@ -61,10 +61,10 @@ class AppResourceManager:
|
||||||
try:
|
try:
|
||||||
if todo == "deprovision":
|
if todo == "deprovision":
|
||||||
# FIXME : i18n, better info strings
|
# FIXME : i18n, better info strings
|
||||||
logger.info(f"Deprovisionning {name}...")
|
logger.info(f"Deprovisioning {name}...")
|
||||||
old.deprovision(context=context)
|
old.deprovision(context=context)
|
||||||
elif todo == "provision":
|
elif todo == "provision":
|
||||||
logger.info(f"Provisionning {name}...")
|
logger.info(f"Provisioning {name}...")
|
||||||
new.provision_or_update(context=context)
|
new.provision_or_update(context=context)
|
||||||
elif todo == "update":
|
elif todo == "update":
|
||||||
logger.info(f"Updating {name}...")
|
logger.info(f"Updating {name}...")
|
||||||
|
@ -90,10 +90,10 @@ class AppResourceManager:
|
||||||
# (NB. here we want to undo the todo)
|
# (NB. here we want to undo the todo)
|
||||||
if todo == "deprovision":
|
if todo == "deprovision":
|
||||||
# FIXME : i18n, better info strings
|
# FIXME : i18n, better info strings
|
||||||
logger.info(f"Reprovisionning {name}...")
|
logger.info(f"Reprovisioning {name}...")
|
||||||
old.provision_or_update(context=context)
|
old.provision_or_update(context=context)
|
||||||
elif todo == "provision":
|
elif todo == "provision":
|
||||||
logger.info(f"Deprovisionning {name}...")
|
logger.info(f"Deprovisioning {name}...")
|
||||||
new.deprovision(context=context)
|
new.deprovision(context=context)
|
||||||
elif todo == "update":
|
elif todo == "update":
|
||||||
logger.info(f"Reverting {name}...")
|
logger.info(f"Reverting {name}...")
|
||||||
|
|
Loading…
Add table
Reference in a new issue