From 430f9c673710d106bd3143b39c07c5484743f45d Mon Sep 17 00:00:00 2001 From: Veiko Aasa Date: Thu, 18 Apr 2024 11:06:43 +0300 Subject: [PATCH 01/36] storage: Add an option to include help text to directory selection form Signed-off-by: Veiko Aasa Reviewed-by: Sunil Mohan Adapa --- plinth/modules/storage/forms.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plinth/modules/storage/forms.py b/plinth/modules/storage/forms.py index 232cdc9bb..a8f77103b 100644 --- a/plinth/modules/storage/forms.py +++ b/plinth/modules/storage/forms.py @@ -89,11 +89,11 @@ class DirectorySelectForm(forms.Form): storage_subdir = forms.CharField(label=_('Subdirectory (optional)'), required=False) - def __init__(self, title=None, default='/', validator=DirectoryValidator, - *args, **kwargs): + def __init__(self, title=None, help_text='', default='/', + validator=DirectoryValidator, *args, **kwargs): super().__init__(*args, **kwargs) - if title: - self.fields['storage_dir'].label = title + self.fields['storage_dir'].label = title + self.fields['storage_dir'].help_text = help_text self.validator = validator self.default = default self.set_form_data() From 552fabed154f3f8084b286bd4a116071f71e0771 Mon Sep 17 00:00:00 2001 From: Veiko Aasa Date: Thu, 18 Apr 2024 12:52:12 +0300 Subject: [PATCH 02/36] minidlna: Add media directory selection form The form provides an option to select default directory, user specified directory or samba shares if enabled. The form also checks that the directory exists and is readable by the minidlna user. Tested that changing media directory to a samba share location works. Closes #2084. Signed-off-by: Veiko Aasa Reviewed-by: Sunil Mohan Adapa --- plinth/modules/minidlna/__init__.py | 2 ++ plinth/modules/minidlna/forms.py | 27 ++++++++++++++------------- plinth/modules/minidlna/views.py | 15 +++++---------- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/plinth/modules/minidlna/__init__.py b/plinth/modules/minidlna/__init__.py index edf97e245..11291ad4d 100644 --- a/plinth/modules/minidlna/__init__.py +++ b/plinth/modules/minidlna/__init__.py @@ -29,6 +29,8 @@ _description = [ 'such as PS3 and Xbox 360) or applications such as totem and Kodi.') ] +SYSTEM_USER = 'minidlna' + class MiniDLNAApp(app_module.App): """Freedombox app managing miniDlna.""" diff --git a/plinth/modules/minidlna/forms.py b/plinth/modules/minidlna/forms.py index 556c34e13..388cb19be 100644 --- a/plinth/modules/minidlna/forms.py +++ b/plinth/modules/minidlna/forms.py @@ -3,20 +3,21 @@ FreedomBox configuration form for MiniDLNA server. """ -from django import forms from django.utils.translation import gettext_lazy as _ +from plinth.modules.storage.forms import (DirectorySelectForm, + DirectoryValidator) -class MiniDLNAServerForm(forms.Form): +from . import SYSTEM_USER + + +class MiniDLNAServerForm(DirectorySelectForm): """MiniDLNA server configuration form.""" - media_dir = forms.CharField( - label=_('Media Files Directory'), - help_text=_('Directory that MiniDLNA Server will read for content. All' - ' sub-directories of this will be also scanned for media ' - 'files. ' - 'If you change the default ensure that the new directory ' - 'exists and that is readable from the "minidlna" user. ' - 'Any user media directories ("/home/username/") will ' - 'usually work.'), - required=False, - ) + + def __init__(self, *args, **kw): + validator = DirectoryValidator(username=SYSTEM_USER) + super().__init__( + title=_('Media Files Directory'), help_text=_( + 'Directory that MiniDLNA Server will read for content. All ' + 'sub-directories of this will be also scanned for media files.' + ), default='/var/lib/minidlna', validator=validator, *args, **kw) diff --git a/plinth/modules/minidlna/views.py b/plinth/modules/minidlna/views.py index 16f58fdfa..df2529fa9 100644 --- a/plinth/modules/minidlna/views.py +++ b/plinth/modules/minidlna/views.py @@ -1,8 +1,6 @@ # SPDX-License-Identifier: AGPL-3.0-or-later """Views for the minidlna module.""" -import os - from django.contrib import messages from django.utils.translation import gettext_lazy as _ @@ -21,7 +19,8 @@ class MiniDLNAAppView(AppView): def get_initial(self): """Return initial values of the form.""" initial = super().get_initial() - initial.update({'media_dir': privileged.get_media_dir()}) + initial.update({'storage_path': privileged.get_media_dir()}) + return initial def form_valid(self, form): @@ -29,12 +28,8 @@ class MiniDLNAAppView(AppView): old_config = form.initial new_config = form.cleaned_data - if old_config['media_dir'].strip() != new_config['media_dir']: - if os.path.isdir(new_config['media_dir']) is False: - messages.error(self.request, - _('Specified directory does not exist.')) - else: - privileged.set_media_dir(new_config['media_dir']) - messages.success(self.request, _('Updated media directory')) + if old_config['storage_path'] != new_config['storage_path']: + privileged.set_media_dir(new_config['storage_path']) + messages.success(self.request, _('Updated media directory')) return super().form_valid(form) From bc468401d32a41a23941fe9c157f1f009a94ff3a Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Tue, 23 Apr 2024 06:57:46 +0000 Subject: [PATCH 03/36] Translated using Weblate (Spanish) Currently translated at 100.0% (1567 of 1567 strings) --- plinth/locale/es/LC_MESSAGES/django.po | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/plinth/locale/es/LC_MESSAGES/django.po b/plinth/locale/es/LC_MESSAGES/django.po index 5a1129310..c165c57da 100644 --- a/plinth/locale/es/LC_MESSAGES/django.po +++ b/plinth/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-04-22 20:02-0400\n" -"PO-Revision-Date: 2024-04-10 04:54+0000\n" +"PO-Revision-Date: 2024-04-24 07:07+0000\n" "Last-Translator: gallegonovato \n" "Language-Team: Spanish \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.5-dev\n" +"X-Generator: Weblate 5.5.1-dev\n" #: config.py:103 #, python-brace-format @@ -5218,10 +5218,10 @@ msgstr "" "servidor Nextcloud proporciona una interfaz web bien integrada." #: modules/nextcloud/__init__.py:25 -#, fuzzy -#| msgid "All users of FreedomBox can use Nextcloud." msgid "All users of FreedomBox can use Nextcloud. To perform administrative " -msgstr "Todos los usuarios de FreedomBox pueden utilizar Nextcloud." +msgstr "" +"Todos los usuarios de FreedomBox pueden utilizar Nextcloud. Para realizar " +"tareas administrativas " #: modules/nextcloud/__init__.py:29 #, python-brace-format @@ -5231,12 +5231,15 @@ msgid "" "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" +"Ten en cuenta que Nextcloud se instala y ejecuta dentro de un contenedor " +"proporcionado por el proyecto Nextcloud. Las revisiones de seguridad, " +"calidad, privacidad y legales las realiza el proyecto original y no Debian/" +"{box_name} . Las actualizaciones se realizan siguiendo un ciclo " +"independiente." #: modules/nextcloud/__init__.py:35 -#, fuzzy -#| msgid "Uninstalling an app is an experimental feature." msgid "This app is experimental." -msgstr "Desinstalar una aplicación es una función experimental." +msgstr "Esta aplicación es experimental." #: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 @@ -5248,10 +5251,8 @@ msgid "File Storage & Collaboration" msgstr "Almacenamiento de archivos y colaboración" #: modules/nextcloud/forms.py:19 -#, fuzzy -#| msgid "Hostname set" msgid "Not set" -msgstr "Asignar nombre de anfitrión" +msgstr "No establecido" #: modules/nextcloud/forms.py:27 msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." @@ -5287,8 +5288,7 @@ msgstr "" #: modules/nextcloud/views.py:53 msgid "Password update failed. Please choose a stronger password." -msgstr "" -"Error en la actualización de la contraseña. Elija una contraseña más segura." +msgstr "Error al actualizar la contraseña. Elija una contraseña más segura." #: modules/openvpn/__init__.py:20 #, python-brace-format From 2f84b8f6b3ba789e39692126a209e62f1bcf3453 Mon Sep 17 00:00:00 2001 From: Burak Yavuz Date: Tue, 23 Apr 2024 07:03:46 +0000 Subject: [PATCH 04/36] Translated using Weblate (Turkish) Currently translated at 100.0% (1567 of 1567 strings) --- plinth/locale/tr/LC_MESSAGES/django.po | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/plinth/locale/tr/LC_MESSAGES/django.po b/plinth/locale/tr/LC_MESSAGES/django.po index c49c91348..623bdefc9 100644 --- a/plinth/locale/tr/LC_MESSAGES/django.po +++ b/plinth/locale/tr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-04-22 20:02-0400\n" -"PO-Revision-Date: 2024-04-09 20:35+0000\n" +"PO-Revision-Date: 2024-04-24 07:07+0000\n" "Last-Translator: Burak Yavuz \n" "Language-Team: Turkish \n" @@ -16,7 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 5.5-dev\n" +"X-Generator: Weblate 5.5.1-dev\n" #: config.py:103 #, python-brace-format @@ -5205,10 +5205,10 @@ msgstr "" "bütünleştirilmiş bir web arayüzü sağlar." #: modules/nextcloud/__init__.py:25 -#, fuzzy -#| msgid "All users of FreedomBox can use Nextcloud." msgid "All users of FreedomBox can use Nextcloud. To perform administrative " -msgstr "FreedomBox'ın tüm kullanıcıları Nextcloud'u kullanabilir." +msgstr "" +"FreedomBox'ın tüm kullanıcıları Nextcloud'u kullanabilir. Yönetimsel " +"işlemleri gerçekleştirmek için " #: modules/nextcloud/__init__.py:29 #, python-brace-format @@ -5218,12 +5218,15 @@ msgid "" "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" +"Lütfen Nextcloud'un Nextcloud projesi tarafından sağlanan bir kapsayıcı " +"içine yüklendiğini ve çalıştırıldığını unutmayın. Güvenlik, kalite, gizlilik " +"ve yasal incelemeler Debian/{box_name} tarafından değil, yukarı yöndeki " +"proje tarafından yapılır. Güncellemeler bağımsız bir döngünün ardından " +"gerçekleştirilir." #: modules/nextcloud/__init__.py:35 -#, fuzzy -#| msgid "Uninstalling an app is an experimental feature." msgid "This app is experimental." -msgstr "Bir uygulamayı kaldırmak, deneysel bir özelliktir." +msgstr "Bu uygulama deneyseldir." #: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 @@ -5235,10 +5238,8 @@ msgid "File Storage & Collaboration" msgstr "Dosya Depolama ve İşbirliği" #: modules/nextcloud/forms.py:19 -#, fuzzy -#| msgid "Hostname set" msgid "Not set" -msgstr "Anamakine adı ayarlandı" +msgstr "Ayarlı değil" #: modules/nextcloud/forms.py:27 msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." From 15168f43716ac6ee5fb7a9f710cb44c4ebaf1b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8E=8B=E5=8F=AB=E6=88=91=E6=9D=A5=E5=B7=A1?= =?UTF-8?q?=E5=B1=B1?= Date: Tue, 23 Apr 2024 08:30:22 +0000 Subject: [PATCH 05/36] Translated using Weblate (Chinese (Simplified)) Currently translated at 66.5% (1043 of 1567 strings) --- plinth/locale/zh_Hans/LC_MESSAGES/django.po | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plinth/locale/zh_Hans/LC_MESSAGES/django.po b/plinth/locale/zh_Hans/LC_MESSAGES/django.po index 599390bb2..812b1385c 100644 --- a/plinth/locale/zh_Hans/LC_MESSAGES/django.po +++ b/plinth/locale/zh_Hans/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: Plinth\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-04-22 20:02-0400\n" -"PO-Revision-Date: 2024-04-10 04:54+0000\n" +"PO-Revision-Date: 2024-04-24 07:07+0000\n" "Last-Translator: 大王叫我来巡山 \n" "Language-Team: Chinese (Simplified) Date: Tue, 23 Apr 2024 22:57:17 +0000 Subject: [PATCH 06/36] Translated using Weblate (Czech) Currently translated at 100.0% (1567 of 1567 strings) --- plinth/locale/cs/LC_MESSAGES/django.po | 130 ++++++++++--------------- 1 file changed, 50 insertions(+), 80 deletions(-) diff --git a/plinth/locale/cs/LC_MESSAGES/django.po b/plinth/locale/cs/LC_MESSAGES/django.po index 6cf9bfe57..61c2e5ea9 100644 --- a/plinth/locale/cs/LC_MESSAGES/django.po +++ b/plinth/locale/cs/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-04-22 20:02-0400\n" -"PO-Revision-Date: 2023-11-20 05:11+0000\n" +"PO-Revision-Date: 2024-04-24 07:07+0000\n" "Last-Translator: Jiří Podhorecký \n" "Language-Team: Czech \n" @@ -16,8 +16,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" -"X-Generator: Weblate 5.2\n" +"Plural-Forms: nplurals=3; plural=((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2);\n" +"X-Generator: Weblate 5.5.1-dev\n" #: config.py:103 #, python-brace-format @@ -102,14 +102,12 @@ msgid "Use the language preference set in the browser" msgstr "Použít upřednostňovaný jazyk nastavený ve webovém prohlížeči" #: menu.py:106 -#, fuzzy -#| msgid "Public Visibility" msgid "Visibility" -msgstr "Viditelnost na veřejnosti" +msgstr "Viditelnost" #: menu.py:108 msgid "Data" -msgstr "" +msgstr "Data" #: menu.py:110 templates/base.html:131 msgid "System" @@ -121,10 +119,8 @@ msgid "Security" msgstr "Zabezpečení" #: menu.py:114 -#, fuzzy -#| msgid "Server Administration" msgid "Administration" -msgstr "Správa serveru" +msgstr "Správa" #: middleware.py:131 msgid "System is possibly under heavy load. Please retry later." @@ -1556,28 +1552,20 @@ msgid "Go to diagnostics results" msgstr "Přejít na výsledky diagnostiky" #: modules/diagnostics/forms.py:11 -#, fuzzy -#| msgid "Enable Subdomains" msgid "Enable daily run" -msgstr "Zapnout podřízené domény" +msgstr "Zapnout denní rutiny" #: modules/diagnostics/forms.py:12 -#, fuzzy -#| msgid "When enabled, FreedomBox automatically updates once a day." msgid "When enabled, diagnostic checks will run once a day." -msgstr "Když je zapnuto, FreedomBox se jednou denně automaticky zaktualizuje." +msgstr "Když je zapnuto, diagnostické kontroly proběhnou jednou denně." #: modules/diagnostics/templates/diagnostics.html:11 -#, fuzzy -#| msgid "Diagnostics" msgid "Diagnostics Run" -msgstr "Diagnostika" +msgstr "Spuštění Diagnostiky" #: modules/diagnostics/templates/diagnostics.html:17 -#, fuzzy -#| msgid "Run Diagnostics" msgid "Run Diagnostics Now" -msgstr "Spustit diagnostiku" +msgstr "Spustit diagnostiku teď" #: modules/diagnostics/templates/diagnostics.html:22 msgid "View Results" @@ -3383,10 +3371,9 @@ msgstr "" "trvat, než se změna projeví." #: modules/letsencrypt/views.py:46 -#, fuzzy, python-brace-format -#| msgid "Failed to revoke certificate for domain {domain}: {error}" +#, python-brace-format msgid "Failed to revoke certificate for domain {domain}" -msgstr "Nepodařilo se odvolat platnost certifikátu prodoménu {domain}: {error}" +msgstr "Nepodařilo se odvolat platnost certifikátu pro doménu {domain}" #: modules/letsencrypt/views.py:59 modules/letsencrypt/views.py:77 #, python-brace-format @@ -3394,10 +3381,9 @@ msgid "Certificate successfully obtained for domain {domain}" msgstr "Úspěšně obdržen certifikát pro doménu {domain}" #: modules/letsencrypt/views.py:64 modules/letsencrypt/views.py:82 -#, fuzzy, python-brace-format -#| msgid "Failed to obtain certificate for domain {domain}: {error}" +#, python-brace-format msgid "Failed to obtain certificate for domain {domain}" -msgstr "Nepodařilo se získat certifikát pro doménu {domain}: {error}" +msgstr "Nepodařilo se získat certifikát pro doménu {domain}" #: modules/letsencrypt/views.py:95 #, python-brace-format @@ -3405,10 +3391,9 @@ msgid "Certificate successfully deleted for domain {domain}" msgstr "Certifikát pro doménu {domain} úspěšně smazán" #: modules/letsencrypt/views.py:100 -#, fuzzy, python-brace-format -#| msgid "Failed to delete certificate for domain {domain}: {error}" +#, python-brace-format msgid "Failed to delete certificate for domain {domain}" -msgstr "Nepodařilo se smazat certifikát pro doménu {domain}: {error}" +msgstr "Nepodařilo se smazat certifikát pro doménu {domain}" #: modules/matrixsynapse/__init__.py:26 msgid "" @@ -5188,10 +5173,17 @@ msgid "" "and mobile clients. The Nextcloud server provides a well integrated web " "interface." msgstr "" +"Nextcloud je samostatně hostovaná platforma pro produktivitu, která " +"poskytuje soukromé a bezpečné funkce pro sdílení souborů, spolupráci a " +"další. Nextcloud zahrnuje server Nextcloud, klientské aplikace pro stolní " +"počítače a mobilní klienty. Server Nextcloud poskytuje dobře integrované " +"webové rozhraní." #: modules/nextcloud/__init__.py:25 msgid "All users of FreedomBox can use Nextcloud. To perform administrative " msgstr "" +"Nextcloud mohou používat všichni uživatelé služby FreedomBox. Chcete-li " +"provést správu " #: modules/nextcloud/__init__.py:29 #, python-brace-format @@ -5201,74 +5193,62 @@ msgid "" "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" +"Upozorňujeme, že Nextcloud je nainstalován a spuštěn v kontejneru " +"poskytnutém projektem Nextcloud. Bezpečnost, kvalitu, ochranu soukromí a " +"právní kontrolu provádí projekt upstream, nikoli Debian/{box_name}. " +"Aktualizace jsou prováděny podle nezávislého cyklu." #: modules/nextcloud/__init__.py:35 -#, fuzzy -#| msgid "Uninstalling an app is an experimental feature." msgid "This app is experimental." -msgstr "Odinstalování aplikace je exprimentální funkce." +msgstr "Tato aplikace je experimentální." #: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 -#, fuzzy -#| msgid "Next" msgid "Nextcloud" -msgstr "Další" +msgstr "Nextcloud" #: modules/nextcloud/__init__.py:55 msgid "File Storage & Collaboration" -msgstr "" +msgstr "Ukládání souborů a spolupráce" #: modules/nextcloud/forms.py:19 -#, fuzzy -#| msgid "Hostname set" msgid "Not set" -msgstr "Nastavení názvu stroje" +msgstr "Nenastaveno" #: modules/nextcloud/forms.py:27 msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." -msgstr "" +msgstr "Příklady: \"myfreedombox.example.org\" nebo \"example.onion\"." #: modules/nextcloud/forms.py:30 -#, fuzzy -#| msgid "Administrator Password" msgid "Administrator password" msgstr "Heslo k účtu správce" #: modules/nextcloud/forms.py:31 -#, fuzzy -#| msgid "" -#| "Set a new password for MediaWiki's administrator account (admin). The " -#| "password cannot be a common one and the minimum required length is " -#| "10 characters. Leave this field blank to keep the " -#| "current password." msgid "" "Optional. Set a new password for Nextcloud's administrator account " "(nextcloud-admin). The password cannot be a common one and the minimum " "required length is 10 characters. Leave this field blank to " "keep the current password." msgstr "" -"Nastavte nové heslo pro účet správce MediaWiki (admin). Heslo nemůže být " -"běžné a minimální požadovaná délka je 10 znaků. Chcete-li " -"zachovat aktuální heslo, ponechte toto pole prázdné." +"Nastavte nové heslo pro účet správce Nextcloud (nextcloud-admin). Heslo " +"nemůže být běžné a minimální požadovaná délka je 10 znaků. " +"Chcete-li zachovat aktuální heslo, ponechte toto pole prázdné." #: modules/nextcloud/forms.py:38 -#, fuzzy -#| msgid "Default zone is external" msgid "Default phone region" -msgstr "Výchozí zóna je externí" +msgstr "Výchozí region telefonu" #: modules/nextcloud/forms.py:39 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." msgstr "" +"Výchozí telefonní předvolba je vyžadována pro ověření telefonních čísel v " +"nastavení profilu bez kódu země." #: modules/nextcloud/views.py:53 -#, fuzzy -#| msgid "Password update failed. Please choose a stronger password" msgid "Password update failed. Please choose a stronger password." -msgstr "Aktualizace hesla se nezdařila. Zvolte prosím silnější heslo" +msgstr "Aktualizace hesla se nezdařila. Zvolte prosím silnější heslo." #: modules/openvpn/__init__.py:20 #, python-brace-format @@ -6769,20 +6749,16 @@ msgid "Created snapshot." msgstr "Zachycený stav pořízen." #: modules/snapshot/views.py:160 -#, fuzzy -#| msgid "Configuration updated." msgid "Configuration update failed." -msgstr "Nastavení aktualizována." +msgstr "Aktualizace konfigurace se nezdařila." #: modules/snapshot/views.py:184 msgid "Deleted selected snapshots" msgstr "Označené zachycené stavy smazány" #: modules/snapshot/views.py:186 -#, fuzzy -#| msgid "Deleting LDAP user failed." msgid "Deleting snapshot failed." -msgstr "Smazání LDAP uživatele se nezdařilo." +msgstr "Smazání snapshotu se nezdařilo." #: modules/snapshot/views.py:189 msgid "Snapshot is currently in use. Please try again later." @@ -7012,18 +6988,16 @@ msgid "" "You cannot save configuration changes. Try rebooting the system. If the " "problem persists after a reboot, check the storage device for errors." msgstr "" +"Změny konfigurace nelze uložit. Zkuste restartovat systém. Pokud problém " +"přetrvává i po restartu, zkontrolujte, zda úložné zařízení neobsahuje chyby." #: modules/storage/__init__.py:382 -#, fuzzy -#| msgid "Root Filesystem" msgid "Read-only root filesystem" -msgstr "Kořenový souborový systém" +msgstr "Kořenový souborový systém pouze pro čtení" #: modules/storage/__init__.py:392 -#, fuzzy -#| msgid "Go to Networks" msgid "Go to Power" -msgstr "Přejít na Sítě" +msgstr "Přejít na Síť" #: modules/storage/forms.py:63 msgid "Invalid directory name." @@ -7134,10 +7108,8 @@ msgid "Device can be safely unplugged." msgstr "Zařízení je možné bezpečně odebrat." #: modules/storage/views.py:93 -#, fuzzy -#| msgid "Error ejecting device: {error_message}" msgid "Error ejecting device." -msgstr "Chyba při vysouvání zařízení: {error_message}" +msgstr "Chyba při vysouvání zařízení." #: modules/syncthing/__init__.py:23 msgid "" @@ -7739,10 +7711,8 @@ msgid "Test distribution upgrade now" msgstr "Otestujte aktualizaci distribuce" #: modules/upgrades/views.py:73 -#, fuzzy -#| msgid "Error when configuring unattended-upgrades: {error}" msgid "Error when configuring unattended-upgrades" -msgstr "Chyba při nastavování bezobslužných aktualizací: {error}" +msgstr "Chyba při nastavování bezobslužných aktualizací" #: modules/upgrades/views.py:120 msgid "Upgrade process started." @@ -7815,6 +7785,7 @@ msgstr "" msgid "" "Optional. Used to send emails to reset password and important notifications." msgstr "" +"Volitelně. Slouží k zasílání e-mailů pro reset hesla a důležitých oznámení." #: modules/users/forms.py:106 msgid "" @@ -8535,10 +8506,9 @@ msgid "Finished: {name}" msgstr "Dokončeno: {name}" #: package.py:206 -#, fuzzy, python-brace-format -#| msgid "Package {expression} is not available for install" +#, python-brace-format msgid "Package {package_expression} is not available for install" -msgstr "Balíček {expression} není k dispozici pro instalaci" +msgstr "Balíček {package_expression} není k dispozici pro instalaci" #: package.py:226 #, python-brace-format From 9fa45b42398cd549d51593f59c02d001d584a6a5 Mon Sep 17 00:00:00 2001 From: Ray Kuo Date: Tue, 23 Apr 2024 09:41:03 +0000 Subject: [PATCH 07/36] Translated using Weblate (Chinese (Traditional)) Currently translated at 17.6% (276 of 1567 strings) --- plinth/locale/zh_Hant/LC_MESSAGES/django.po | 41 +++++++++------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/plinth/locale/zh_Hant/LC_MESSAGES/django.po b/plinth/locale/zh_Hant/LC_MESSAGES/django.po index 31aa0408f..7673e5572 100644 --- a/plinth/locale/zh_Hant/LC_MESSAGES/django.po +++ b/plinth/locale/zh_Hant/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-04-22 20:02-0400\n" -"PO-Revision-Date: 2024-04-17 08:03+0000\n" +"PO-Revision-Date: 2024-04-24 07:07+0000\n" "Last-Translator: Ray Kuo \n" "Language-Team: Chinese (Traditional) \n" @@ -17,12 +17,12 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.5-dev\n" +"X-Generator: Weblate 5.5.1-dev\n" #: config.py:103 #, python-brace-format msgid "Static configuration {etc_path} is setup properly" -msgstr "" +msgstr "靜態配置 {etc_path} 已正確設置" #: context_processors.py:23 views.py:116 msgid "FreedomBox" @@ -59,7 +59,7 @@ msgstr "解除安裝前備份應用程式" #: forms.py:37 msgid "Restoring from the backup will restore app data." -msgstr "" +msgstr "從備份中恢復將還原應用程式資料" #: forms.py:39 #, fuzzy @@ -101,7 +101,7 @@ msgstr "使用瀏覽器語言設定" #: menu.py:106 msgid "Visibility" -msgstr "" +msgstr "可見性" #: menu.py:108 msgid "Data" @@ -117,14 +117,12 @@ msgid "Security" msgstr "安全性" #: menu.py:114 -#, fuzzy -#| msgid "Server Administration" msgid "Administration" -msgstr "伺服器管理" +msgstr "管理" #: middleware.py:131 msgid "System is possibly under heavy load. Please retry later." -msgstr "" +msgstr "系統可能負載過重. 請稍後重試." #: modules/apache/__init__.py:32 msgid "Apache HTTP Server" @@ -491,8 +489,8 @@ msgid "" "To restore a backup on a new %(box_name)s you need the SSH credentials and, " "if chosen, the encryption passphrase." msgstr "" -"此儲存庫的認證存在 %(box_name)s。
如果要備份還原到新的 %(box_name)s 您" -"需要 SSH 認證和(如果您有設定)加密密碼。" +"此儲存庫的認證存在 %(box_name)s。
如果要備份還原到新的 %(box_name)s " +"您需要 SSH 認證和(如果您有設定)加密密碼。" #: modules/backups/templates/backups_add_remote_repository.html:28 msgid "Create Location" @@ -627,18 +625,13 @@ msgid "How to verify?" msgstr "如何校驗?" #: modules/backups/templates/verify_ssh_hostkey.html:45 -#, fuzzy -#| msgid "" -#| "Run the following command on the SSH host machine. The output should " -#| "match one of the provided options. You can also use dsa, ecdsa, ed25519 " -#| "etc. instead of rsa, by choosing the corresponding file." msgid "" "Run the following command on the SSH host machine. The output should match " "one of the provided options. You can also use DSA, ECDSA, Ed25519 etc. " "instead of RSA, by choosing the corresponding file." msgstr "" -"在 SSH 主機執行下列指令。輸出結果應該符合提供選項之一。您也可以使用 dsa、" -"ecdsa、ed25519 等等。要替換 rsa,可以選擇對應的檔案。" +"在 SSH 主機執行下列指令。輸出結果應該符合提供選項之一。您也可以使用 " +"DSA、ECDSA、Ed25519 等等。要替換 RSA,可以選擇對應的檔案。" #: modules/backups/templates/verify_ssh_hostkey.html:60 msgid "Verify Host" @@ -1045,7 +1038,7 @@ msgstr "新圖書館名稱" msgid "" "Only letters of the English alphabet, numbers and the characters _ . and - " "without spaces or special characters. Example: My_Library_2000" -msgstr "" +msgstr "只有英文字母、數字和字符 _ . 和 - 不包含空格或特殊字符. 例如:My_Library_2000" #: modules/calibre/forms.py:28 msgid "A library with this name already exists." @@ -1257,25 +1250,25 @@ msgstr "顯示需要更多技術知識的 app 與功能。" #: modules/config/forms.py:104 msgid "System-wide logging" -msgstr "" +msgstr "系統-全域 記錄" #: modules/config/forms.py:105 msgid "Disable logging, for privacy" -msgstr "" +msgstr "為了保護隱私,禁用日誌記錄" #: modules/config/forms.py:107 msgid "Keep some in memory until a restart, for performance" -msgstr "" +msgstr "為了提高性能,保留在記憶體中,直到重新啟動" #: modules/config/forms.py:110 msgid "Write to disk, useful for debugging" -msgstr "" +msgstr "寫入磁碟,用於調試" #: modules/config/forms.py:112 msgid "" "Logs contain information about who accessed the system and debug information " "from various services" -msgstr "" +msgstr "日誌記錄了存取系統的人員以及各種服務的調試資訊" #: modules/config/views.py:49 #, python-brace-format From ddc9b434a72ce4d7f28891f237de48ebbde6af91 Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Sat, 20 Apr 2024 18:43:35 -0400 Subject: [PATCH 08/36] diagnostics: Add optional component_id to DiagnosticCheck Signed-off-by: James Valleroy Reviewed-by: Sunil Mohan Adapa --- plinth/app.py | 12 ++--- plinth/config.py | 2 +- plinth/daemon.py | 18 +++++--- plinth/diagnostic_check.py | 4 +- plinth/modules/apache/components.py | 21 ++++++--- .../modules/apache/tests/test_components.py | 45 ++++++++++++------- plinth/modules/firewall/components.py | 6 ++- .../modules/firewall/tests/test_components.py | 16 +++---- plinth/package.py | 5 ++- plinth/tests/test_config.py | 6 ++- plinth/tests/test_daemon.py | 20 +++++---- plinth/tests/test_diagnostic_check.py | 9 +++- plinth/tests/test_package.py | 12 ++--- 13 files changed, 110 insertions(+), 66 deletions(-) diff --git a/plinth/app.py b/plinth/app.py index c093ceb32..5df311ba7 100644 --- a/plinth/app.py +++ b/plinth/app.py @@ -222,9 +222,9 @@ class App: Return value must be a list of results. Each result is a :class:`~plinth.diagnostic_check.DiagnosticCheck` with a - unique check_id, a user visible description of the test, and the - result. The test result is a string enumeration from 'failed', - 'passed', 'error', 'warning' and 'not_done'. + unique check_id, a user visible description of the test, the result, + test parameters, and the component ID. The test result is a string + enumeration from 'failed', 'passed', 'error', 'warning' and 'not_done'. Results are typically collected by diagnosing each component of the app and then supplementing the results with any app level diagnostic tests. @@ -314,9 +314,9 @@ class Component: Return value must be a list of results. Each result is a :class:`~plinth.diagnostic_check.DiagnosticCheck` with a - unique check_id, a user visible description of the test, and the - result. The test result is a string enumeration from 'failed', - 'passed', 'error', 'warning' and 'not_done'. + unique check_id, a user visible description of the test, the result, + test parameters, and the component ID. The test result is a string + enumeration from 'failed', 'passed', 'error', 'warning' and 'not_done'. Also see :meth:`.has_diagnostics`. diff --git a/plinth/config.py b/plinth/config.py index cf15821e1..1b7f6c8f0 100644 --- a/plinth/config.py +++ b/plinth/config.py @@ -104,7 +104,7 @@ class DropinConfigs(app_module.FollowerComponent): parameters: DiagnosticCheckParameters = {'etc_path': str(etc_path)} results.append( DiagnosticCheck(check_id, description, result_string, - parameters)) + parameters, self.component_id)) return results diff --git a/plinth/daemon.py b/plinth/daemon.py index ae946800d..b70d2e099 100644 --- a/plinth/daemon.py +++ b/plinth/daemon.py @@ -110,7 +110,9 @@ class Daemon(app.LeaderComponent): results = [] results.append(self._diagnose_unit_is_running()) for port in self.listen_ports: - results.append(diagnose_port_listening(port[0], port[1])) + results.append( + diagnose_port_listening(port[0], port[1], None, + self.component_id)) return results @@ -124,7 +126,8 @@ class Daemon(app.LeaderComponent): 'service_name': str(self.unit) } - return DiagnosticCheck(check_id, description, result, parameters) + return DiagnosticCheck(check_id, description, result, parameters, + self.component_id) class RelatedDaemon(app.FollowerComponent): @@ -200,7 +203,8 @@ def app_is_running(app_): def diagnose_port_listening( port: int, kind: str = 'tcp', - listen_address: str | None = None) -> DiagnosticCheck: + listen_address: str | None = None, + component_id: str | None = None) -> DiagnosticCheck: """Run a diagnostic on whether a port is being listened on. Kind must be one of inet, inet4, inet6, tcp, tcp4, tcp6, udp, @@ -222,7 +226,7 @@ def diagnose_port_listening( return DiagnosticCheck(check_id, description, Result.PASSED if result else Result.FAILED, - parameters) + parameters, component_id) def _check_port(port: int, kind: str = 'tcp', @@ -272,7 +276,8 @@ def _check_port(port: int, kind: str = 'tcp', def diagnose_netcat(host: str, port: int, remote_input: str = '', - negate: bool = False) -> DiagnosticCheck: + negate: bool = False, + component_id: str | None = None) -> DiagnosticCheck: """Run a diagnostic using netcat.""" try: process = subprocess.Popen(['nc', host, str(port)], @@ -298,4 +303,5 @@ def diagnose_netcat(host: str, port: int, remote_input: str = '', check_id = f'daemon-netcat-negate-{host}-{port}' description = gettext_noop('Cannot connect to {host}:{port}') - return DiagnosticCheck(check_id, description, result, parameters) + return DiagnosticCheck(check_id, description, result, parameters, + component_id) diff --git a/plinth/diagnostic_check.py b/plinth/diagnostic_check.py index f67661421..d3d6164d2 100644 --- a/plinth/diagnostic_check.py +++ b/plinth/diagnostic_check.py @@ -30,6 +30,7 @@ class DiagnosticCheck: description: str result: Result = Result.NOT_DONE parameters: DiagnosticCheckParameters = field(default_factory=dict) + component_id: str | None = None @property def translated_description(self): @@ -65,6 +66,7 @@ class CheckJSONDecoder(json.JSONDecoder): """Convert tagged data to DiagnosticCheck.""" if data.get('__class__') == 'DiagnosticCheck': return DiagnosticCheck(data['check_id'], data['description'], - data['result'], data['parameters']) + data['result'], data['parameters'], + data.get('component_id')) return data diff --git a/plinth/modules/apache/components.py b/plinth/modules/apache/components.py index 2a9ed9e1d..81f770e6b 100644 --- a/plinth/modules/apache/components.py +++ b/plinth/modules/apache/components.py @@ -69,11 +69,13 @@ class Webserver(app.LeaderComponent): for url in self.urls: if '{host}' in url: results.extend( - diagnose_url_on_all( - url, check_certificate=False, - expect_redirects=self.expect_redirects)) + diagnose_url_on_all(url, check_certificate=False, + expect_redirects=self.expect_redirects, + component_id=self.component_id)) else: - results.append(diagnose_url(url, check_certificate=False)) + results.append( + diagnose_url(url, check_certificate=False, + component_id=self.component_id)) return results @@ -141,7 +143,8 @@ def diagnose_url(url: str, kind: str | None = None, check_certificate: bool = True, extra_options: list[str] | None = None, wrapper: str | None = None, - expected_output: str | None = None) -> DiagnosticCheck: + expected_output: str | None = None, + component_id: str | None = None) -> DiagnosticCheck: """Run a diagnostic on whether a URL is accessible. Kind can be '4' for IPv4 or '6' for IPv6. @@ -161,10 +164,12 @@ def diagnose_url(url: str, kind: str | None = None, check_id = f'apache-url-{url}' description = gettext_noop('Access URL {url}') - return DiagnosticCheck(check_id, description, result, parameters) + return DiagnosticCheck(check_id, description, result, parameters, + component_id) def diagnose_url_on_all(url: str, expect_redirects: bool = False, + component_id: str | None = None, **kwargs) -> list[DiagnosticCheck]: """Run a diagnostic on whether a URL is accessible.""" results = [] @@ -174,7 +179,9 @@ def diagnose_url_on_all(url: str, expect_redirects: bool = False, if not expect_redirects: diagnose_kwargs.setdefault('kind', address['kind']) - results.append(diagnose_url(current_url, **diagnose_kwargs)) + results.append( + diagnose_url(current_url, component_id=component_id, + **diagnose_kwargs)) return results diff --git a/plinth/modules/apache/tests/test_components.py b/plinth/modules/apache/tests/test_components.py index 39d6e1229..9a7a2cd83 100644 --- a/plinth/modules/apache/tests/test_components.py +++ b/plinth/modules/apache/tests/test_components.py @@ -72,13 +72,16 @@ def test_webserver_disable(disable): def test_webserver_diagnose(diagnose_url_on_all, diagnose_url): """Test running diagnostics.""" - def on_all_side_effect(url, check_certificate, expect_redirects): + def on_all_side_effect(url, check_certificate, expect_redirects, + component_id): return [ - DiagnosticCheck('test-all-id', 'test-result-' + url, 'success') + DiagnosticCheck('test-all-id', 'test-result-' + url, 'success', {}, + component_id) ] - def side_effect(url, check_certificate): - return DiagnosticCheck('test-id', 'test-result-' + url, 'success') + def side_effect(url, check_certificate, component_id): + return DiagnosticCheck('test-id', 'test-result-' + url, 'success', {}, + component_id) diagnose_url_on_all.side_effect = on_all_side_effect diagnose_url.side_effect = side_effect @@ -86,19 +89,26 @@ def test_webserver_diagnose(diagnose_url_on_all, diagnose_url): urls=['{host}url1', 'url2'], expect_redirects=True) results = webserver1.diagnose() assert results == [ - DiagnosticCheck('test-all-id', 'test-result-{host}url1', 'success'), - DiagnosticCheck('test-id', 'test-result-url2', 'success') + DiagnosticCheck('test-all-id', 'test-result-{host}url1', 'success', {}, + 'test-webserver'), + DiagnosticCheck('test-id', 'test-result-url2', 'success', {}, + 'test-webserver') ] - diagnose_url_on_all.assert_has_calls( - [call('{host}url1', check_certificate=False, expect_redirects=True)]) - diagnose_url.assert_has_calls([call('url2', check_certificate=False)]) + diagnose_url_on_all.assert_has_calls([ + call('{host}url1', check_certificate=False, expect_redirects=True, + component_id='test-webserver') + ]) + diagnose_url.assert_has_calls( + [call('url2', check_certificate=False, component_id='test-webserver')]) diagnose_url_on_all.reset_mock() webserver2 = Webserver('test-webserver', 'test-config', urls=['{host}url1', 'url2'], expect_redirects=False) results = webserver2.diagnose() - diagnose_url_on_all.assert_has_calls( - [call('{host}url1', check_certificate=False, expect_redirects=False)]) + diagnose_url_on_all.assert_has_calls([ + call('{host}url1', check_certificate=False, expect_redirects=False, + component_id='test-webserver') + ]) @patch('plinth.privileged.service.restart') @@ -244,20 +254,23 @@ def test_diagnose_url(get_addresses, check): 'test-1': 'value-1' }, 'wrapper': 'test-wrapper', - 'expected_output': 'test-expected' + 'expected_output': 'test-expected', + 'component_id': 'test-component', } parameters = {key: args[key] for key in ['url', 'kind']} check.return_value = True result = diagnose_url(**args) assert result == DiagnosticCheck( 'apache-url-kind-https://localhost/test-4', - 'Access URL {url} on tcp{kind}', Result.PASSED, parameters) + 'Access URL {url} on tcp{kind}', Result.PASSED, parameters, + 'test-component') check.return_value = False result = diagnose_url(**args) assert result == DiagnosticCheck( 'apache-url-kind-https://localhost/test-4', - 'Access URL {url} on tcp{kind}', Result.FAILED, parameters) + 'Access URL {url} on tcp{kind}', Result.FAILED, parameters, + 'test-component') del args['kind'] args['url'] = 'https://{host}/test' @@ -287,10 +300,10 @@ def test_diagnose_url(get_addresses, check): assert results == [ DiagnosticCheck('apache-url-kind-https://test-host-1/test-4', 'Access URL {url} on tcp{kind}', Result.PASSED, - parameters[0]), + parameters[0], 'test-component'), DiagnosticCheck('apache-url-kind-https://test-host-2/test-6', 'Access URL {url} on tcp{kind}', Result.PASSED, - parameters[1]), + parameters[1], 'test-component'), ] diff --git a/plinth/modules/firewall/components.py b/plinth/modules/firewall/components.py index e3c381b26..b0f7239b7 100644 --- a/plinth/modules/firewall/components.py +++ b/plinth/modules/firewall/components.py @@ -142,7 +142,8 @@ class Firewall(app.FollowerComponent): 'details': details } results.append( - DiagnosticCheck(check_id, description, result, parameters)) + DiagnosticCheck(check_id, description, result, parameters, + self.component_id)) # External zone if self.is_external: @@ -161,7 +162,8 @@ class Firewall(app.FollowerComponent): parameters = {'name': port, 'details': details} results.append( - DiagnosticCheck(check_id, description, result, parameters)) + DiagnosticCheck(check_id, description, result, parameters, + self.component_id)) return results diff --git a/plinth/modules/firewall/tests/test_components.py b/plinth/modules/firewall/tests/test_components.py index a6d943db9..4d0668f21 100644 --- a/plinth/modules/firewall/tests/test_components.py +++ b/plinth/modules/firewall/tests/test_components.py @@ -161,28 +161,28 @@ def test_diagnose(get_enabled_services, get_port_details): 'networks', Result.PASSED, { 'name': 'test-port1', 'details': '1234/tcp, 1234/udp' - }), + }, 'test-firewall-1'), DiagnosticCheck( 'firewall-port-external-unavailable-test-port1', 'Port {name} ({details}) unavailable for external ' 'networks', Result.PASSED, { 'name': 'test-port1', 'details': '1234/tcp, 1234/udp' - }), + }, 'test-firewall-1'), DiagnosticCheck( 'firewall-port-internal-test-port2', 'Port {name} ({details}) available for internal networks', Result.FAILED, { 'name': 'test-port2', 'details': '2345/udp' - }), + }, 'test-firewall-1'), DiagnosticCheck( 'firewall-port-external-unavailable-test-port2', 'Port {name} ({details}) unavailable for external networks', Result.FAILED, { 'name': 'test-port2', 'details': '2345/udp' - }), + }, 'test-firewall-1'), ] firewall = Firewall('test-firewall-1', ports=['test-port3', 'test-port4'], @@ -195,28 +195,28 @@ def test_diagnose(get_enabled_services, get_port_details): Result.PASSED, { 'name': 'test-port3', 'details': '3456/tcp' - }), + }, 'test-firewall-1'), DiagnosticCheck( 'firewall-port-external-available-test-port3', 'Port {name} ({details}) available for external networks', Result.PASSED, { 'name': 'test-port3', 'details': '3456/tcp' - }), + }, 'test-firewall-1'), DiagnosticCheck( 'firewall-port-internal-test-port4', 'Port {name} ({details}) available for internal networks', Result.FAILED, { 'name': 'test-port4', 'details': '4567/udp' - }), + }, 'test-firewall-1'), DiagnosticCheck( 'firewall-port-external-available-test-port4', 'Port {name} ({details}) available for external networks', Result.FAILED, { 'name': 'test-port4', 'details': '4567/udp' - }), + }, 'test-firewall-1'), ] diff --git a/plinth/package.py b/plinth/package.py index 6bb0a71ae..22a158f41 100644 --- a/plinth/package.py +++ b/plinth/package.py @@ -210,7 +210,7 @@ class Packages(app_module.FollowerComponent): } results.append( DiagnosticCheck(check_id, description, Result.FAILED, - parameters)) + parameters, self.component_id)) continue result = Result.WARNING @@ -230,7 +230,8 @@ class Packages(app_module.FollowerComponent): 'latest_version': str(latest_version) } results.append( - DiagnosticCheck(check_id, description, result, parameters)) + DiagnosticCheck(check_id, description, result, parameters, + self.component_id)) return results diff --git a/plinth/tests/test_config.py b/plinth/tests/test_config.py index bbab5a343..f0dc12d82 100644 --- a/plinth/tests/test_config.py +++ b/plinth/tests/test_config.py @@ -165,11 +165,13 @@ def test_dropin_config_diagnose_symlinks(dropin_configs, tmp_path): DiagnosticCheck( f'dropin-config-{tmp_path}/etc/test/path1', 'Static configuration {etc_path} is setup properly', - Result.FAILED, {'etc_path': f'{tmp_path}/etc/test/path1'}), + Result.FAILED, {'etc_path': f'{tmp_path}/etc/test/path1'}, + 'test-component'), DiagnosticCheck( f'dropin-config-{tmp_path}/etc/path2', 'Static configuration {etc_path} is setup properly', - Result.FAILED, {'etc_path': f'{tmp_path}/etc/path2'}), + Result.FAILED, {'etc_path': f'{tmp_path}/etc/path2'}, + 'test-component'), ] # Proper symlinks exist diff --git a/plinth/tests/test_daemon.py b/plinth/tests/test_daemon.py index bdf74d3d6..a7019ad2c 100644 --- a/plinth/tests/test_daemon.py +++ b/plinth/tests/test_daemon.py @@ -181,9 +181,9 @@ def test_ensure_running(subprocess_call, subprocess_run, service_is_running, def test_diagnose(port_listening, service_is_running, daemon): """Test running diagnostics.""" - def side_effect(port, kind): + def side_effect(port, kind, _listen_address, component_id): name = f'test-result-{port}-{kind}' - return DiagnosticCheck(name, name, Result.PASSED) + return DiagnosticCheck(name, name, Result.PASSED, {}, component_id) daemon = Daemon('test-daemon', 'test-unit', listen_ports=[(8273, 'tcp4'), (345, 'udp')]) @@ -193,13 +193,16 @@ def test_diagnose(port_listening, service_is_running, daemon): assert results == [ DiagnosticCheck('daemon-running-test-unit', 'Service {service_name} is running', Result.PASSED, - {'service_name': 'test-unit'}), + {'service_name': 'test-unit'}, 'test-daemon'), DiagnosticCheck('test-result-8273-tcp4', 'test-result-8273-tcp4', - Result.PASSED), + Result.PASSED, {}, 'test-daemon'), DiagnosticCheck('test-result-345-udp', 'test-result-345-udp', - Result.PASSED) + Result.PASSED, {}, 'test-daemon') ] - port_listening.assert_has_calls([call(8273, 'tcp4'), call(345, 'udp')]) + port_listening.assert_has_calls([ + call(8273, 'tcp4', None, 'test-daemon'), + call(345, 'udp', None, 'test-daemon') + ]) service_is_running.assert_has_calls([call('test-unit')]) service_is_running.return_value = False @@ -342,12 +345,13 @@ def test_diagnose_netcat(popen): assert popen.mock_calls[2] == call().communicate(input=b'test-input') result = diagnose_netcat('test-host', 3300, remote_input='test-input', - negate=True) + negate=True, component_id='test-component') parameters2 = parameters.copy() parameters2['negate'] = True assert result == DiagnosticCheck('daemon-netcat-negate-test-host-3300', 'Cannot connect to {host}:{port}', - Result.FAILED, parameters2) + Result.FAILED, parameters2, + 'test-component') popen().returncode = 1 result = diagnose_netcat('test-host', 3300, remote_input='test-input') diff --git a/plinth/tests/test_diagnostic_check.py b/plinth/tests/test_diagnostic_check.py index caf6f1199..66bbf3261 100644 --- a/plinth/tests/test_diagnostic_check.py +++ b/plinth/tests/test_diagnostic_check.py @@ -23,13 +23,14 @@ def test_result(): def test_diagnostic_check(): """Test the diagnostic check data class.""" with pytest.raises(TypeError): - DiagnosticCheck() + DiagnosticCheck() # pylint: disable=E1120 check = DiagnosticCheck('some-check-id', 'sample check') assert check.check_id == 'some-check-id' assert check.description == 'sample check' assert check.translated_description == 'sample check' assert check.result == Result.NOT_DONE + assert check.component_id is None assert not check.parameters check = DiagnosticCheck('some-check-id', 'sample check', Result.PASSED) @@ -40,6 +41,10 @@ def test_diagnostic_check(): {'key': 'value'}) assert check.parameters['key'] == 'value' + check = DiagnosticCheck('some-check-id', 'sample check', Result.FAILED, {}, + 'some-component') + assert check.component_id == 'some-component' + def test_translate(): """Test formatting the translated description.""" @@ -58,7 +63,7 @@ def test_json_encoder_decoder(): check_json = json.dumps(check, cls=CheckJSONEncoder) for string in [ '"check_id": "some-check-id"', '"description": "sample check"', - '"result": "passed"', '"parameters": {}', + '"result": "passed"', '"parameters": {}', '"component_id": null', '"__class__": "DiagnosticCheck"' ]: assert string in check_json diff --git a/plinth/tests/test_package.py b/plinth/tests/test_package.py index d5308d88b..3a811e4e4 100644 --- a/plinth/tests/test_package.py +++ b/plinth/tests/test_package.py @@ -284,32 +284,34 @@ def test_diagnose(cache): DiagnosticCheck( 'package-available-package1', 'Package {package_expression} is not available for install', - Result.FAILED, {'package_expression': 'package1'}), + Result.FAILED, {'package_expression': 'package1'}, + 'test-component'), DiagnosticCheck( 'package-latest-package2', 'Package {package_name} is the latest version ({latest_version})', Result.PASSED, { 'package_name': 'package2', 'latest_version': '2.0' - }), + }, 'test-component'), DiagnosticCheck( 'package-latest-package3', 'Package {package_name} is the latest version ({latest_version})', Result.WARNING, { 'package_name': 'package3', 'latest_version': '3.0' - }), + }, 'test-component'), DiagnosticCheck( 'package-available-package4 | package5', 'Package {package_expression} is not available for install', - Result.FAILED, {'package_expression': 'package4 | package5'}), + Result.FAILED, {'package_expression': 'package4 | package5'}, + 'test-component'), DiagnosticCheck( 'package-latest-package7', 'Package {package_name} is the latest version ({latest_version})', Result.PASSED, { 'package_name': 'package7', 'latest_version': '4.0' - }), + }, 'test-component'), ] From f487565b2cdff07d91aec7ecdd8af9d2bd549e41 Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Sat, 20 Apr 2024 18:44:57 -0400 Subject: [PATCH 09/36] app, component: Add repair method - Allows apps and component to implement custom repair methods. - Default implementation asks relevant components to repair, and then if needed, requests re-run setup for the app. - Component.repair will return True by default, indicating that setup should be re-run. Signed-off-by: James Valleroy [sunil: Minor docstring styling fixes] [sunil: Improve tests for repair] Signed-off-by: Sunil Mohan Adapa Reviewed-by: Sunil Mohan Adapa --- plinth/app.py | 55 ++++++++++++++++++++++++++++++++++++---- plinth/tests/test_app.py | 39 ++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/plinth/app.py b/plinth/app.py index 5df311ba7..be936b674 100644 --- a/plinth/app.py +++ b/plinth/app.py @@ -211,7 +211,6 @@ class App: """Update the status of all follower components. Do not query or update the status of the leader components. - """ for component in self.components.values(): if not component.is_leader: @@ -230,7 +229,6 @@ class App: and then supplementing the results with any app level diagnostic tests. Also see :meth:`.has_diagnostics`. - """ results = [] for component in self.components.values(): @@ -255,7 +253,6 @@ class App: it is assumed that it is for implementing diagnostic tests and this method returns True for such an app. Override this method if this default behavior does not fit the needs. - """ # App implements some diagnostics if self.__class__.diagnose is not App.diagnose: @@ -268,6 +265,40 @@ class App: return False + def repair(self, failed_checks: _list_type) -> bool: + """Try to fix failed diagnostics. + + The default implementation asks relevant components to repair, and then + requests re-run setup for the app. + + failed_checks is a list of DiagnosticChecks that had failed or resulted + in a warning. The list will be split up by component_id, and passed to + the appropriate components. Remaining failed diagnostics do not have a + related component, and should be handled by the app. + + Returns whether the app setup should be re-run. + """ + should_rerun_setup = False + + # Group the failed_checks by component + components_failed_checks = collections.defaultdict(list) + for failed_check in failed_checks: + if failed_check.component_id: + components_failed_checks[failed_check.component_id].append( + failed_check) + else: + # There is a failed check with no related component. + should_rerun_setup = True + + # Repair each component that has failed checks + for component_id, component in self.components.items(): + if components_failed_checks[component_id]: + result = component.repair( + components_failed_checks[component_id]) + should_rerun_setup = should_rerun_setup or result + + return should_rerun_setup + class Component: """Interface for an app component. @@ -319,7 +350,6 @@ class Component: enumeration from 'failed', 'passed', 'error', 'warning' and 'not_done'. Also see :meth:`.has_diagnostics`. - """ return [] @@ -333,10 +363,25 @@ class Component: is assumed that it is for implementing diagnostic tests and this method returns True for such a component. Override this method if this default behavior does not fit the needs. - """ return self.__class__.diagnose is not Component.diagnose + def repair(self, failed_checks: list) -> bool: + """Try to fix failed diagnostics. + + The default implementation only requests re-run setup for the app. + + Returns whether the app setup should be re-run by the caller. + + This method should be overridden by components that implement + diagnose(), if there is a known way to fix failed checks. The return + value can be changed to False to avoid causing a re-run setup. + + failed_checks is a list of DiagnosticChecks related to this component + that had failed or warning result. + """ + return True + class FollowerComponent(Component): """Interface for an app component that follows other components. diff --git a/plinth/tests/test_app.py b/plinth/tests/test_app.py index 1ea6fc523..24c46033b 100644 --- a/plinth/tests/test_app.py +++ b/plinth/tests/test_app.py @@ -279,6 +279,38 @@ def test_app_has_diagnostics(app_with_components): assert app.has_diagnostics() +@patch('plinth.setup.run_setup_on_app') +def test_app_repair(_run_setup_on_app, app_with_components): + """Test running repair on an app.""" + component = app_with_components.get_component('test-follower-1') + component.repair = Mock(return_value=True) + + check1 = DiagnosticCheck('check1', 'check1', Result.FAILED, {}) + check2 = DiagnosticCheck('check2', 'check2', Result.WARNING, {}) + check3 = DiagnosticCheck('check3', 'check3', Result.FAILED, {}, + 'test-follower-1') + should_rerun_setup = app_with_components.repair([]) + assert not should_rerun_setup + + should_rerun_setup = app_with_components.repair([check1]) + assert should_rerun_setup + + should_rerun_setup = app_with_components.repair([check2]) + assert should_rerun_setup + + should_rerun_setup = app_with_components.repair([check1, check2]) + assert should_rerun_setup + component.repair.assert_not_called() + + should_rerun_setup = app_with_components.repair([check3]) + assert should_rerun_setup + assert component.repair.mock_calls == [call([check3])] + + component.repair = Mock(return_value=False) + should_rerun_setup = app_with_components.repair([check3]) + assert not should_rerun_setup + + def test_component_initialization(): """Test that component is initialized properly.""" with pytest.raises(ValueError): @@ -340,6 +372,13 @@ def test_component_has_diagnostics(): assert not component.has_diagnostics() +@patch('plinth.setup.run_setup_on_app') +def test_component_repair(_run_setup_on_app): + """Test running repair on component.""" + component = Component('test-component') + assert component.repair(['test-check']) + + def test_follower_component_initialization(): """Test that follower component is initialized properly.""" component = FollowerComponent('test-follower-1') From 35c2326261088c7fdecd3728cef240dd3c414938 Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Sat, 20 Apr 2024 18:49:26 -0400 Subject: [PATCH 10/36] setup: Add method to run app repair - Repair is run within an operation. - Diagnostics are run for the app first. - Call app.repair, then re-run setup if needed. - Add helper functions for apps or components to store error messages in thread local storage. These error messages are shown at the end. Signed-off-by: James Valleroy [sunil: Undo minor reformatting, due to automatic tool] [sunil: Fix passing incorrect Exception argument to operation.on_update] [sunil: Add full stop at the end of the success message to match install message] Signed-off-by: Sunil Mohan Adapa Reviewed-by: Sunil Mohan Adapa --- plinth/setup.py | 113 ++++++++++++++++++++++++++++++++++++- plinth/tests/test_setup.py | 19 +++++++ 2 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 plinth/tests/test_setup.py diff --git a/plinth/setup.py b/plinth/setup.py index 12729e519..6c8bbf4c8 100644 --- a/plinth/setup.py +++ b/plinth/setup.py @@ -13,6 +13,7 @@ from django.utils.translation import gettext_noop import plinth from plinth import app as app_module +from plinth.diagnostic_check import Result from plinth.package import Packages from plinth.signals import post_setup @@ -26,6 +27,8 @@ _is_first_setup = False is_first_setup_running = False _is_shutting_down = False +thread_local_storage = threading.local() + def run_setup_on_app(app_id, allow_install=True, rerun=False): """Execute the setup process in a thread.""" @@ -50,10 +53,10 @@ def run_setup_on_app(app_id, allow_install=True, rerun=False): thread_data={'allow_install': allow_install}) -def _run_setup_on_app(app, current_version): +def _run_setup_on_app(app, current_version, repair: bool = False): """Execute the setup process.""" logger.info('Setup run: %s', app.app_id) - exception_to_update = None + exception_to_update: Exception | None = None message = None try: current_version = app.get_setup_version() @@ -74,12 +77,17 @@ def _run_setup_on_app(app, current_version): if not current_version: message = gettext_noop('Error installing app: {error}').format( error=exception) + elif repair: + message = gettext_noop('Error repairing app: {error}').format( + error=exception) else: message = gettext_noop('Error updating app: {error}').format( error=exception) else: if not current_version: message = gettext_noop('App installed.') + elif repair: + return else: message = gettext_noop('App updated') @@ -89,6 +97,86 @@ def _run_setup_on_app(app, current_version): operation.on_update(message, exception_to_update) +def run_repair_on_app(app_id): + """Execute the repair process in a thread.""" + app = app_module.App.get(app_id) + current_version = app.get_setup_version() + if not current_version: + logger.warning('App %s is not installed, cannot repair', app_id) + return + + logger.debug('Creating operation to repair app: %s', app_id) + return operation_module.manager.new(f'{app_id}-repair', app_id, + gettext_noop('Repairing app'), + _run_repair_on_app, [app], + show_message=True, + show_notification=True) + + +def _run_repair_on_app(app: app_module.App): + """Execute the repair process.""" + logger.info('Repair run: %s', app.app_id) + message = None + operation = operation_module.Operation.get_operation() + + # Always re-run diagnostics first for this app, to ensure results are + # current. + checks = [] + try: + checks = app.diagnose() + except Exception as exception: + logger.error('Error running %s diagnostics - %s', app.app_id, + exception) + message = gettext_noop('Error running diagnostics: {error}').format( + error=exception) + operation.on_update(message, exception) + return + + # Filter for checks that have failed. + failed_checks = [] + for check in checks: + if check.result in [Result.FAILED, Result.WARNING]: + failed_checks.append(check) + + if not failed_checks: + logger.warning('Skipping repair for %s: no failed checks', app.app_id) + message = gettext_noop('Skipping repair, no failed checks') + operation.on_update(message, None) + return + + try: + should_rerun_setup = app.repair(failed_checks) + except Exception as exception: + logger.error('Repair error: %s: %s %s', app.app_id, message, exception) + message = gettext_noop('Error repairing app: {error}').format( + error=exception) + operation.on_update(message, exception) + return + + if should_rerun_setup: + message = gettext_noop('Re-running setup to complete repairs') + operation.on_update(message, None) + current_version = app.get_setup_version() + _run_setup_on_app(app, current_version, True) + + logger.info('Repair completed: %s', app.app_id) + + # Check for errors in thread local storage + message = gettext_noop('App repaired.') + errors = retrieve_error_messages() + exceptions = None + if errors: + message = gettext_noop('App repair completed with errors:\n') + error_message = '' + for error in errors: + message += str(error) + '\n' + error_message += str(error) + '\n' + + exceptions = Exception(error_message) + + operation.on_update(message, exceptions) + + def run_uninstall_on_app(app_id): """Execute the uninstall process in a thread.""" # App is already uninstalled @@ -565,3 +653,24 @@ def on_package_cache_updated(): """Called by D-Bus service when apt package cache is updated.""" force_upgrader = ForceUpgrader.get_instance() force_upgrader.on_package_cache_updated() + + +def store_error_message(error_message: str): + """Add an error message to thread local storage.""" + try: + thread_local_storage.errors.append(error_message) + except AttributeError: + thread_local_storage.errors = [error_message] + + +def retrieve_error_messages() -> list[str]: + """Retrieve the error messages from thread local storage. + + Errors are cleared after retrieval.""" + try: + errors = thread_local_storage.errors + thread_local_storage.errors = [] + except AttributeError: + errors = [] + + return errors diff --git a/plinth/tests/test_setup.py b/plinth/tests/test_setup.py new file mode 100644 index 000000000..c8ca312b1 --- /dev/null +++ b/plinth/tests/test_setup.py @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +Test module for setup module. +""" + +from plinth.setup import store_error_message, retrieve_error_messages + + +def test_store_retrieve_error_message(): + """Test storing and retrieving error messages.""" + store_error_message('error 1') + assert retrieve_error_messages() == ['error 1'] + + store_error_message('error 1') + store_error_message('error 2') + assert retrieve_error_messages() == ['error 1', 'error 2'] + + # errors are cleared after retrieving + assert retrieve_error_messages() == [] From f5f687c8fd782fa09a1b97d5f6ea3dbe9a9fe8fe Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Sat, 20 Apr 2024 18:56:30 -0400 Subject: [PATCH 11/36] diagnostics: Change "Re-run setup" to "Try to repair" Signed-off-by: James Valleroy [sunil: Fix issue with formatting i18n message] Signed-off-by: Sunil Mohan Adapa Reviewed-by: Sunil Mohan Adapa --- plinth/modules/diagnostics/__init__.py | 4 +- .../templates/diagnostics_app.html | 8 ++-- .../templates/diagnostics_full.html | 8 ++-- plinth/modules/diagnostics/urls.py | 2 + plinth/modules/diagnostics/views.py | 38 +++++++++++++++++-- 5 files changed, 47 insertions(+), 13 deletions(-) diff --git a/plinth/modules/diagnostics/__init__.py b/plinth/modules/diagnostics/__init__.py index 13fed1628..f763b239c 100644 --- a/plinth/modules/diagnostics/__init__.py +++ b/plinth/modules/diagnostics/__init__.py @@ -128,7 +128,7 @@ def _run_on_all_enabled_modules(): app_results = { 'diagnosis': [], 'exception': None, - 'show_rerun_setup': False, + 'show_repair': False, } try: @@ -140,7 +140,7 @@ def _run_on_all_enabled_modules(): for check in app_results['diagnosis']: if check.result in [Result.FAILED, Result.WARNING]: - app_results['show_rerun_setup'] = True + app_results['show_repair'] = True break with results_lock: diff --git a/plinth/modules/diagnostics/templates/diagnostics_app.html b/plinth/modules/diagnostics/templates/diagnostics_app.html index ba2eb77e7..70d06526c 100644 --- a/plinth/modules/diagnostics/templates/diagnostics_app.html +++ b/plinth/modules/diagnostics/templates/diagnostics_app.html @@ -12,12 +12,12 @@

{% blocktrans %}App: {{ app_name }}{% endblocktrans %}

- {% if show_rerun_setup %} -
+ {% if show_repair %} + {% csrf_token %} + name="repair" value="{% trans "Try to repair" %}"/>
{% endif %}
diff --git a/plinth/modules/diagnostics/templates/diagnostics_full.html b/plinth/modules/diagnostics/templates/diagnostics_full.html index 836abd505..a71a6ca57 100644 --- a/plinth/modules/diagnostics/templates/diagnostics_full.html +++ b/plinth/modules/diagnostics/templates/diagnostics_full.html @@ -40,12 +40,12 @@ {% endblocktrans %} - {% if app_data.show_rerun_setup %} -
+ {% if app_data.show_repair %} + {% csrf_token %} + name="repair" value="{% trans "Try to repair" %}"/>
{% endif %} diff --git a/plinth/modules/diagnostics/urls.py b/plinth/modules/diagnostics/urls.py index 215e5d070..b89e831a0 100644 --- a/plinth/modules/diagnostics/urls.py +++ b/plinth/modules/diagnostics/urls.py @@ -14,4 +14,6 @@ urlpatterns = [ name='full'), re_path(r'^sys/diagnostics/(?P[1-9a-z\-_]+)/$', views.diagnose_app, name='app'), + re_path(r'^sys/diagnostics/repair/(?P[1-9a-z\-_]+)/$', + views.repair, name='repair'), ] diff --git a/plinth/modules/diagnostics/views.py b/plinth/modules/diagnostics/views.py index 6102db56e..8c8648ac2 100644 --- a/plinth/modules/diagnostics/views.py +++ b/plinth/modules/diagnostics/views.py @@ -7,7 +7,9 @@ import logging from django.contrib import messages from django.http import Http404 +from django.shortcuts import redirect from django.template.response import TemplateResponse +from django.urls import NoReverseMatch, reverse from django.utils.translation import gettext_lazy as _ from django.views.decorators.http import require_POST from django.views.generic import TemplateView @@ -16,6 +18,7 @@ from plinth import operation from plinth.app import App from plinth.diagnostic_check import Result from plinth.modules import diagnostics +from plinth.setup import run_repair_on_app from plinth.views import AppView from .forms import ConfigureForm @@ -100,10 +103,10 @@ def diagnose_app(request, app_id): exception) diagnosis_exception = str(exception) - show_rerun_setup = False + show_repair = False for check in diagnosis: if check.result in [Result.FAILED, Result.WARNING]: - show_rerun_setup = True + show_repair = True break return TemplateResponse( @@ -113,5 +116,34 @@ def diagnose_app(request, app_id): 'app_name': app_name, 'results': diagnosis, 'exception': diagnosis_exception, - 'show_rerun_setup': show_rerun_setup, + 'show_repair': show_repair, }) + + +@require_POST +def repair(request, app_id): + """Try to repair failed diagnostics on an app. + + Allows apps and components to customize the repair method. Re-run setup is + the default if not specified. + """ + try: + app = App.get(app_id) + except KeyError: + raise Http404('App does not exist') + + try: + finish_url = reverse(f'{app_id}:index') + except NoReverseMatch: + # for apps like apache that don't have an index route + finish_url = reverse('diagnostics:index') + + current_version = app.get_setup_version() + if not current_version: + logger.warning('App %s is not installed, cannot repair', app_id) + message = _('App {app_id} is not installed, cannot repair') + messages.error(request, str(message).format(app_id=app_id)) + return redirect(finish_url) + + run_repair_on_app(app_id) + return redirect(finish_url) From 8ff1a14e84e8ccd66b2898d661d9d566e6c8d1b1 Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Sat, 20 Apr 2024 18:59:14 -0400 Subject: [PATCH 12/36] letsencrypt: Re-obtain certificates during repair - Set check_id and domain for domain diagnostic check - If there is an error, store it in thread local storage. - Drop checking for case when no domain is configured. This is better done after initial setup via notification rather than in diagnostics. Proposed change also will not show the warning if a .local domain is configured (almost always). Keep checking for Check id to deal with older stored diagnostic results. - Call obtain when certificate is not available. Call re-obtain otherwise. This is important for properly calling the post-obtain operations. Signed-off-by: James Valleroy [sunil: Drop check for case when no domain is configured] [sunil: Call either obtain or re-obtain based on current state of certificate] Signed-off-by: Sunil Mohan Adapa Reviewed-by: Sunil Mohan Adapa --- plinth/modules/letsencrypt/__init__.py | 40 ++++++++++++++++++++------ 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/plinth/modules/letsencrypt/__init__.py b/plinth/modules/letsencrypt/__init__.py index 5464126f0..4f652b11d 100644 --- a/plinth/modules/letsencrypt/__init__.py +++ b/plinth/modules/letsencrypt/__init__.py @@ -17,6 +17,7 @@ from plinth.modules.apache.components import diagnose_url from plinth.modules.backups.components import BackupRestore from plinth.modules.names.components import DomainType from plinth.package import Packages +from plinth.setup import store_error_message from plinth.signals import domain_added, domain_removed, post_app_loading from plinth.utils import format_lazy @@ -96,17 +97,40 @@ class LetsEncryptApp(app_module.App): for domain in names.components.DomainName.list(): if domain.domain_type.can_have_certificate: - results.append(diagnose_url('https://' + domain.name)) - - if not results: - results.append( - DiagnosticCheck( - 'letsencrypt-cannot-test', - gettext_noop('Cannot test: No domains are configured.'), - Result.WARNING)) + result = diagnose_url('https://' + domain.name) + result.check_id = f'letsencrypt-domain-{domain.name}' + result.parameters['domain'] = domain.name + results.append(result) return results + def repair(self, failed_checks: list) -> bool: + """Try to repair failed diagnostics. + + Returns whether the app setup should be re-run. + """ + status = get_status() + + # Obtain/re-obtain certificates for failing domains + for failed_check in failed_checks: + if not failed_check.check_id.startswith('letsencrypt-domain'): + continue + + domain = failed_check.parameters['domain'] + try: + domain_status = status['domains'][domain] + if domain_status.get('certificate_available', False): + certificate_obtain(domain) + else: + certificate_reobtain(domain) + except Exception as error: + # This happens if a non-functional domain is configured. + logger.error('Could not re-obtain certificate: %s', error) + # Add the error message to thread local storage + store_error_message(str(error)) + + return False + def setup(self, old_version): """Install and configure the app.""" super().setup(old_version) From 75e99f28cd11af63bca99026c372046081891d9d Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 3 May 2024 08:20:38 -0700 Subject: [PATCH 13/36] letsencrypt: Remove unused imports Signed-off-by: Sunil Mohan Adapa --- plinth/modules/letsencrypt/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plinth/modules/letsencrypt/__init__.py b/plinth/modules/letsencrypt/__init__.py index 4f652b11d..39c7014af 100644 --- a/plinth/modules/letsencrypt/__init__.py +++ b/plinth/modules/letsencrypt/__init__.py @@ -6,12 +6,11 @@ import logging import pathlib from django.utils.translation import gettext_lazy as _ -from django.utils.translation import gettext_noop from plinth import app as app_module from plinth import cfg, menu from plinth.config import DropinConfigs -from plinth.diagnostic_check import DiagnosticCheck, Result +from plinth.diagnostic_check import DiagnosticCheck from plinth.modules import names from plinth.modules.apache.components import diagnose_url from plinth.modules.backups.components import BackupRestore From 0d0eb4472da186d105198daa74ec9335b0cd0a19 Mon Sep 17 00:00:00 2001 From: Veiko Aasa Date: Thu, 18 Apr 2024 12:39:18 +0300 Subject: [PATCH 14/36] minidlna: Explicitly include ssdp service to firewall configuration It helps showing ssdp protocol ports in minidlna diagnostics. Also avoid overwrite imported name 'firewall'. Tested that ssdp port 1900 is shown in the minidlna diagnostics page. Signed-off-by: Veiko Aasa Reviewed-by: Sunil Mohan Adapa --- plinth/modules/minidlna/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plinth/modules/minidlna/__init__.py b/plinth/modules/minidlna/__init__.py index 11291ad4d..9f6dfcd73 100644 --- a/plinth/modules/minidlna/__init__.py +++ b/plinth/modules/minidlna/__init__.py @@ -79,9 +79,10 @@ class MiniDLNAApp(app_module.App): ['/etc/apache2/conf-available/minidlna-freedombox.conf']) self.add(dropin_configs) - firewall = Firewall('firewall-minidlna', info.name, ports=['minidlna'], - is_external=False) - self.add(firewall) + firewall_minidlna = Firewall('firewall-minidlna', info.name, + ports=['minidlna', + 'ssdp'], is_external=False) + self.add(firewall_minidlna) webserver = Webserver('webserver-minidlna', 'minidlna-freedombox', urls=['https://{host}/_minidlna/']) From 681f2ef994723f45779e6e83589cedaa4ad50e91 Mon Sep 17 00:00:00 2001 From: Veiko Aasa Date: Thu, 18 Apr 2024 13:00:15 +0300 Subject: [PATCH 15/36] minidlna: Do not proxy minidlna web interface over Apache Minidlna interface is still available to everybody in internal networks at http://:8200. (Note that using mDNS name like freedombox.local doesn't work here). Remove 'minidlna' group and apache minidlna site configuration as those are not useful any more. Reconfigure minidlna front page shortcut to link to the app description page. Tests performed with stable and testing containers: Create a user that belongs to minidlna group. Apply changes, after minidlna app upgrade: - the user is not in minidlna group any more. - the users configuration page doesn't show minidlna group. - Apache site /_minidlna is disabled. Closes #2012, #2013, #2416. Signed-off-by: Veiko Aasa [sunil: Minor formatting, use single quotes for strings for consistency] Signed-off-by: Sunil Mohan Adapa Reviewed-by: Sunil Mohan Adapa --- plinth/modules/minidlna/__init__.py | 40 +++++++------------ .../conf-available/minidlna-freedombox.conf | 9 ----- 2 files changed, 15 insertions(+), 34 deletions(-) delete mode 100644 plinth/modules/minidlna/data/usr/share/freedombox/etc/apache2/conf-available/minidlna-freedombox.conf diff --git a/plinth/modules/minidlna/__init__.py b/plinth/modules/minidlna/__init__.py index 9f6dfcd73..e1cd1089f 100644 --- a/plinth/modules/minidlna/__init__.py +++ b/plinth/modules/minidlna/__init__.py @@ -2,17 +2,15 @@ """ FreedomBox app to configure minidlna. """ +from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from plinth import app as app_module from plinth import frontpage, menu -from plinth.config import DropinConfigs from plinth.daemon import Daemon from plinth.modules import firewall -from plinth.modules.apache.components import Webserver from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall -from plinth.modules.users.components import UsersAndGroups from plinth.package import Packages, install from plinth.utils import Version @@ -37,14 +35,12 @@ class MiniDLNAApp(app_module.App): app_id = 'minidlna' - _version = 5 + _version = 6 def __init__(self) -> None: """Initialize the app components.""" super().__init__() - groups = {'minidlna': _('Media streaming server')} - info = app_module.Info(app_id=self.app_id, version=self._version, name=_('MiniDLNA'), icon_filename='minidlna', short_description=_('Simple Media Server'), @@ -63,31 +59,21 @@ class MiniDLNAApp(app_module.App): ) self.add(menu_item) - shortcut = frontpage.Shortcut('shortcut-minidlna', info.name, - short_description=info.short_description, - description=info.description, - icon=info.icon_filename, - url='/_minidlna/', login_required=True, - allowed_groups=list(groups)) + shortcut = frontpage.Shortcut( + 'shortcut-minidlna', info.name, + short_description=info.short_description, + description=info.description, icon=info.icon_filename, + configure_url=reverse_lazy('minidlna:index'), login_required=True) self.add(shortcut) packages = Packages('packages-minidlna', ['minidlna']) self.add(packages) - dropin_configs = DropinConfigs( - 'dropin-configs-minidlna', - ['/etc/apache2/conf-available/minidlna-freedombox.conf']) - self.add(dropin_configs) - firewall_minidlna = Firewall('firewall-minidlna', info.name, ports=['minidlna', 'ssdp'], is_external=False) self.add(firewall_minidlna) - webserver = Webserver('webserver-minidlna', 'minidlna-freedombox', - urls=['https://{host}/_minidlna/']) - self.add(webserver) - daemon = Daemon('daemon-minidlna', 'minidlna') self.add(daemon) @@ -95,10 +81,6 @@ class MiniDLNAApp(app_module.App): **manifest.backup) self.add(backup_restore) - users_and_groups = UsersAndGroups('users-and-groups-minidlna', - groups=groups) - self.add(users_and_groups) - def setup(self, old_version): """Install and configure the app.""" super().setup(old_version) @@ -111,6 +93,14 @@ class MiniDLNAApp(app_module.App): firewall.remove_passthrough('ipv4', '-A', 'INPUT', '-p', 'tcp', '--dport', '8200', '-j', 'REJECT') + if old_version and old_version <= 5: + # Remove minidlna LDAP group and disable minidlna apache config + from plinth.modules.apache import privileged as apache_privileged + from plinth.modules.users import privileged as users_privileged + + users_privileged.remove_group('minidlna') + apache_privileged.disable('minidlna-freedombox', 'config') + if not old_version: self.enable() diff --git a/plinth/modules/minidlna/data/usr/share/freedombox/etc/apache2/conf-available/minidlna-freedombox.conf b/plinth/modules/minidlna/data/usr/share/freedombox/etc/apache2/conf-available/minidlna-freedombox.conf deleted file mode 100644 index aaaf5f876..000000000 --- a/plinth/modules/minidlna/data/usr/share/freedombox/etc/apache2/conf-available/minidlna-freedombox.conf +++ /dev/null @@ -1,9 +0,0 @@ - - Include includes/freedombox-single-sign-on.conf - - - TKTAuthToken "admin" - - - ProxyPass http://localhost:8200/ - From 8670a56610517b059567c33d76642ae3318a78e6 Mon Sep 17 00:00:00 2001 From: Veiko Aasa Date: Wed, 24 Apr 2024 12:51:15 +0300 Subject: [PATCH 16/36] minidlna: Restart app when upgrading to reconfigure firewall Tested when minidlna app is running - after upgrade the ssdp port is open. Signed-off-by: Veiko Aasa Reviewed-by: Sunil Mohan Adapa --- plinth/modules/minidlna/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plinth/modules/minidlna/__init__.py b/plinth/modules/minidlna/__init__.py index e1cd1089f..a301b9764 100644 --- a/plinth/modules/minidlna/__init__.py +++ b/plinth/modules/minidlna/__init__.py @@ -101,6 +101,11 @@ class MiniDLNAApp(app_module.App): users_privileged.remove_group('minidlna') apache_privileged.disable('minidlna-freedombox', 'config') + # Restart app to reload firewall + if self.is_enabled(): + self.disable() + self.enable() + if not old_version: self.enable() From df16f74b74b716a530f015732ab57dd1c912c765 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 9 Apr 2024 18:53:10 -0700 Subject: [PATCH 17/36] nextcloud: Use systemd generator for creating container service - See quadlet(5). - Using 'podman generate systemd' is deprecated. Quadlets are recommended. - When using the systemd generator, enable/disable is not possible. The container is automatically started when system is booted or systemd is reloaded after .container file changes. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/action_utils.py | 67 ++++++++++++++------------ plinth/modules/nextcloud/privileged.py | 27 ++++++----- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/plinth/action_utils.py b/plinth/action_utils.py index ac6e617bc..baa662dae 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -489,50 +489,55 @@ def is_package_manager_busy(): return False -def podman_run(network_name: str, subnet: str, bridge_ip: str, host_port: str, - container_port: str, container_ip: str, container_name: str, - image_name: str, extra_run_options: list[str] | None = None, - extra_network_options: list[str] | None = None): - """Remove, recreate and run a podman container.""" +def podman_create(network_name: str, subnet: str, bridge_ip: str, + host_port: str, container_port: str, container_ip: str, + container_name: str, image_name: str, + volumes: dict[str, str] | None = None, + env: dict[str, str] | None = None, + extra_network_options: list[str] | None = None): + """Remove and recreate a podman container.""" + service_stop(container_name) + try: - service_stop(container_name) subprocess.run(['podman', 'network', 'rm', '--force', network_name], check=False) except subprocess.CalledProcessError: pass + # Create bridge network network_create_command = [ 'podman', 'network', 'create', '--driver', 'bridge', '--subnet', subnet, '--gateway', bridge_ip, '--dns', bridge_ip, '--interface-name', network_name, network_name ] + (extra_network_options or []) - - # Create bridge network subprocess.run(network_create_command, check=True) - args = [ - 'podman', 'run', '--detach', '--network', network_name, '--ip', - container_ip, '--name', container_name, '--restart', 'unless-stopped', - '--quiet' - ] - # Only listen on localhost. This is to prevent exposing the host port to - # the internet. - args += ['--publish', f'127.0.0.1:{host_port}:{container_port}'] - # Enable automatic updates. - args += ['--label', 'io.containers.autoupdate=registry'] - # If another container with the same name already exists, replace and - # remove it. - args += ['--replace'] - args += (extra_run_options or []) + [image_name] - subprocess.run(args, check=True) + directory = pathlib.Path('/etc/containers/systemd') + directory.mkdir(parents=True, exist_ok=True) + service_file = directory / f'{container_name}.container' + volume_lines = '\n'.join([ + f'Volume={source}:{dest}' for source, dest in (volumes or {}).items() + ]) + env_lines = '\n'.join( + [f'Environment={key}={value}' for key, value in (env or {}).items()]) + contents = f'''[Container] +AutoUpdate=registry +ContainerName=%N +{env_lines} +Image={image_name} +IP={container_ip} +Network={network_name} +PublishPort=127.0.0.1:{host_port}:{container_port} +{volume_lines} - # Create service file for starting/stopping container using systemd - service_file = pathlib.Path( - '/etc/systemd/system') / f'{container_name}.service' - with service_file.open('wb') as file_handle: - subprocess.run( - ['podman', 'generate', 'systemd', '--new', container_name], - stdout=file_handle, check=True) +[Service] +Restart=always + +[Install] +WantedBy=default.target +''' + service_file.write_text(contents) + service_daemon_reload() def podman_uninstall(container_name: str, network_name: str, volume_name: str, @@ -542,6 +547,6 @@ def podman_uninstall(container_name: str, network_name: str, volume_name: str, subprocess.run(['podman', 'volume', 'rm', volume_name], check=True) subprocess.run(['podman', 'image', 'rm', image_name], check=True) service_file = pathlib.Path( - '/etc/systemd/system/') / f'{container_name}.service' + '/etc/containers/systemd/') / f'{container_name}.container' service_file.unlink(missing_ok=True) service_daemon_reload() diff --git a/plinth/modules/nextcloud/privileged.py b/plinth/modules/nextcloud/privileged.py index 2021374bc..12479b7c9 100644 --- a/plinth/modules/nextcloud/privileged.py +++ b/plinth/modules/nextcloud/privileged.py @@ -52,19 +52,20 @@ def setup(): # Setup redis for caching _redis_listen_socket() - action_utils.podman_run( - network_name=NETWORK_NAME, subnet='172.16.16.0/24', - bridge_ip=BRIDGE_IP, host_port='8181', container_port='80', - container_ip=CONTAINER_IP, container_name=CONTAINER_NAME, - image_name=IMAGE_NAME, extra_run_options=[ - '--volume=/run/mysqld/mysqld.sock:/run/mysqld/mysqld.sock', - '--volume=/run/redis/redis-server.sock:' - '/run/redis/redis-server.sock', - '--volume=/run/slapd/ldapi:/run/slapd/ldapi', - f'--volume={VOLUME_NAME}:/var/www/html', - f'--env=TRUSTED_PROXIES={BRIDGE_IP}', - '--env=OVERWRITEWEBROOT=/nextcloud' - ]) + volumes = { + '/run/mysqld/mysqld.sock': '/run/mysqld/mysqld.sock', + '/run/redis/redis-server.sock': '/run/redis/redis-server.sock', + '/run/slapd/ldapi': '/run/slapd/ldapi', + VOLUME_NAME: '/var/www/html' + } + env = {'TRUSTED_PROXIES': BRIDGE_IP, 'OVERWRITEWEBROOT': '/nextcloud'} + action_utils.podman_create(network_name=NETWORK_NAME, + subnet='172.16.16.0/24', bridge_ip=BRIDGE_IP, + host_port='8181', container_port='80', + container_ip=CONTAINER_IP, + container_name=CONTAINER_NAME, + image_name=IMAGE_NAME, volumes=volumes, env=env) + action_utils.service_start(CONTAINER_NAME) _configure_firewall(action='add', interface_name=NETWORK_NAME) # OCC isn't immediately available after the container is spun up. From 3f8874f461548a0941043f43ecd3f0d8ed1c2e1d Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Wed, 10 Apr 2024 11:20:47 -0700 Subject: [PATCH 18/36] nextcloud: Create network using systemd generator Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/action_utils.py | 44 ++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/plinth/action_utils.py b/plinth/action_utils.py index baa662dae..8df737493 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -493,34 +493,40 @@ def podman_create(network_name: str, subnet: str, bridge_ip: str, host_port: str, container_port: str, container_ip: str, container_name: str, image_name: str, volumes: dict[str, str] | None = None, - env: dict[str, str] | None = None, - extra_network_options: list[str] | None = None): + env: dict[str, str] | None = None): """Remove and recreate a podman container.""" + service_stop(f'{network_name}-network.service') service_stop(container_name) - try: - subprocess.run(['podman', 'network', 'rm', '--force', network_name], - check=False) - except subprocess.CalledProcessError: - pass - - # Create bridge network - network_create_command = [ - 'podman', 'network', 'create', '--driver', 'bridge', '--subnet', - subnet, '--gateway', bridge_ip, '--dns', bridge_ip, '--interface-name', - network_name, network_name - ] + (extra_network_options or []) - subprocess.run(network_create_command, check=True) + subprocess.run(['podman', 'network', 'rm', '--force', network_name], + check=False) directory = pathlib.Path('/etc/containers/systemd') directory.mkdir(parents=True, exist_ok=True) + + # Create bridge network + network_file = directory / f'{network_name}.network' + contents = f'''[Network] +DNS={bridge_ip} +Driver=bridge +Gateway={bridge_ip} +NetworkName={network_name} +Subnet={subnet} +PodmanArgs=--interface-name={network_name} +''' + network_file.write_text(contents) + service_file = directory / f'{container_name}.container' volume_lines = '\n'.join([ f'Volume={source}:{dest}' for source, dest in (volumes or {}).items() ]) env_lines = '\n'.join( [f'Environment={key}={value}' for key, value in (env or {}).items()]) - contents = f'''[Container] + contents = f'''[Unit] +Requires=nextcloud-fbx-network.service +After=nextcloud-fbx-network.service + +[Container] AutoUpdate=registry ContainerName=%N {env_lines} @@ -543,9 +549,13 @@ WantedBy=default.target def podman_uninstall(container_name: str, network_name: str, volume_name: str, image_name: str): """Remove a podman container's components and systemd unit.""" - subprocess.run(['podman', 'network', 'rm', network_name], check=True) subprocess.run(['podman', 'volume', 'rm', volume_name], check=True) subprocess.run(['podman', 'image', 'rm', image_name], check=True) + subprocess.run(['podman', 'network', 'rm', '--force', network_name], + check=True) + network_file = pathlib.Path( + '/etc/containers/systemd/') / f'{network_name}.network' + network_file.unlink(missing_ok=True) service_file = pathlib.Path( '/etc/containers/systemd/') / f'{container_name}.container' service_file.unlink(missing_ok=True) From e7e1a6b41d1e643df38c3af107d97c665b546d27 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Apr 2024 13:38:11 -0700 Subject: [PATCH 19/36] nextcloud: Drop network namespacing in container, use host network - This is not ideal and reduces security. However it simplifies quite a bit of setup. - Services on the host network are already exposed to the container (however, they could easily be protected with firewall rules). - Container has full access to external networks already. So this part does not change. - This setup would be at par with how other services run on FreedomBox right now. We can think of generalized solution for all the apps later. - FirewallLocalProtection for the single service the runs in the container works as usual without change. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/action_utils.py | 38 +++----------------------- plinth/modules/nextcloud/privileged.py | 22 ++------------- 2 files changed, 6 insertions(+), 54 deletions(-) diff --git a/plinth/action_utils.py b/plinth/action_utils.py index 8df737493..97201bb32 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -489,51 +489,27 @@ def is_package_manager_busy(): return False -def podman_create(network_name: str, subnet: str, bridge_ip: str, - host_port: str, container_port: str, container_ip: str, - container_name: str, image_name: str, +def podman_create(container_name: str, image_name: str, volumes: dict[str, str] | None = None, env: dict[str, str] | None = None): """Remove and recreate a podman container.""" - service_stop(f'{network_name}-network.service') service_stop(container_name) - subprocess.run(['podman', 'network', 'rm', '--force', network_name], - check=False) - directory = pathlib.Path('/etc/containers/systemd') directory.mkdir(parents=True, exist_ok=True) - # Create bridge network - network_file = directory / f'{network_name}.network' - contents = f'''[Network] -DNS={bridge_ip} -Driver=bridge -Gateway={bridge_ip} -NetworkName={network_name} -Subnet={subnet} -PodmanArgs=--interface-name={network_name} -''' - network_file.write_text(contents) - service_file = directory / f'{container_name}.container' volume_lines = '\n'.join([ f'Volume={source}:{dest}' for source, dest in (volumes or {}).items() ]) env_lines = '\n'.join( [f'Environment={key}={value}' for key, value in (env or {}).items()]) - contents = f'''[Unit] -Requires=nextcloud-fbx-network.service -After=nextcloud-fbx-network.service - -[Container] + contents = f'''[Container] AutoUpdate=registry ContainerName=%N {env_lines} Image={image_name} -IP={container_ip} -Network={network_name} -PublishPort=127.0.0.1:{host_port}:{container_port} +Network=host {volume_lines} [Service] @@ -546,16 +522,10 @@ WantedBy=default.target service_daemon_reload() -def podman_uninstall(container_name: str, network_name: str, volume_name: str, - image_name: str): +def podman_uninstall(container_name: str, volume_name: str, image_name: str): """Remove a podman container's components and systemd unit.""" subprocess.run(['podman', 'volume', 'rm', volume_name], check=True) subprocess.run(['podman', 'image', 'rm', image_name], check=True) - subprocess.run(['podman', 'network', 'rm', '--force', network_name], - check=True) - network_file = pathlib.Path( - '/etc/containers/systemd/') / f'{network_name}.network' - network_file.unlink(missing_ok=True) service_file = pathlib.Path( '/etc/containers/systemd/') / f'{container_name}.container' service_file.unlink(missing_ok=True) diff --git a/plinth/modules/nextcloud/privileged.py b/plinth/modules/nextcloud/privileged.py index 12479b7c9..dd19ef49a 100644 --- a/plinth/modules/nextcloud/privileged.py +++ b/plinth/modules/nextcloud/privileged.py @@ -15,9 +15,6 @@ import augeas from plinth import action_utils from plinth.actions import privileged -NETWORK_NAME = 'nextcloud-fbx' -BRIDGE_IP = '172.16.16.1' -CONTAINER_IP = '172.16.16.2' CONTAINER_NAME = 'nextcloud-freedombox' SERVICE_NAME = 'nextcloud-freedombox' VOLUME_NAME = 'nextcloud-volume-freedombox' @@ -58,15 +55,10 @@ def setup(): '/run/slapd/ldapi': '/run/slapd/ldapi', VOLUME_NAME: '/var/www/html' } - env = {'TRUSTED_PROXIES': BRIDGE_IP, 'OVERWRITEWEBROOT': '/nextcloud'} - action_utils.podman_create(network_name=NETWORK_NAME, - subnet='172.16.16.0/24', bridge_ip=BRIDGE_IP, - host_port='8181', container_port='80', - container_ip=CONTAINER_IP, - container_name=CONTAINER_NAME, + env = {'TRUSTED_PROXIES': '127.0.0.1', 'OVERWRITEWEBROOT': '/nextcloud'} + action_utils.podman_create(container_name=CONTAINER_NAME, image_name=IMAGE_NAME, volumes=volumes, env=env) action_utils.service_start(CONTAINER_NAME) - _configure_firewall(action='add', interface_name=NETWORK_NAME) # OCC isn't immediately available after the container is spun up. # Wait until CAN_INSTALL file is available. @@ -156,14 +148,6 @@ def set_default_phone_region(region: str): _run_occ('config:system:set', 'default_phone_region', '--value', region) -def _configure_firewall(action, interface_name): - subprocess.run([ - 'firewall-cmd', '--permanent', '--zone=trusted', - f'--{action}-interface={interface_name}' - ], check=True) - action_utils.service_restart('firewalld') - - def _database_query(query: str): """Run a database query.""" subprocess.run(['mysql'], input=query.encode(), check=True) @@ -292,9 +276,7 @@ WantedBy=timers.target def uninstall(): """Uninstall Nextcloud""" _drop_database() - _configure_firewall(action='remove', interface_name=NETWORK_NAME) action_utils.podman_uninstall(container_name=CONTAINER_NAME, - network_name=NETWORK_NAME, volume_name=VOLUME_NAME, image_name=IMAGE_NAME) for path in [_cron_service_file, _cron_timer_file]: From 85cc9f08facb7bd8d428688827ea6872d54c42c8 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Thu, 11 Apr 2024 13:33:42 -0700 Subject: [PATCH 20/36] nextcloud: Use php-fpm container instead of apache container - Configuring just php-fpm is easier compared to configuring Apache + mod_php. There is no need to configure trusted proxies as the requests are made using the FastCGI protocol. - There is no need for a full web server as we already run Apache. - Place nextcloud data in /var/lib/container so that non-PHP files can be served directly without php-fpm involved. This location is more suitable for switching to nextcloud based on a .deb file (if ever). This is done by configuring the volume to serve a bind mounted directory of our choice. - Update Apache configuration to proxy to php-fpm instead of another web server. Include the changes needed for Apache configuration to serve non-php files directly. - Managed the volume using quadlet podman systemd generator. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/action_utils.py | 41 ++++++++++++++++--- plinth/modules/nextcloud/__init__.py | 2 +- .../conf-available/nextcloud-freedombox.conf | 38 ++++++++++++++--- plinth/modules/nextcloud/manifest.py | 4 +- plinth/modules/nextcloud/privileged.py | 20 +++++---- 5 files changed, 80 insertions(+), 25 deletions(-) diff --git a/plinth/action_utils.py b/plinth/action_utils.py index 97201bb32..61783f21a 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -489,22 +489,44 @@ def is_package_manager_busy(): return False -def podman_create(container_name: str, image_name: str, - volumes: dict[str, str] | None = None, +def podman_create(container_name: str, image_name: str, volume_name: str, + volume_path: str, volumes: dict[str, str] | None = None, env: dict[str, str] | None = None): """Remove and recreate a podman container.""" + service_stop(f'{volume_name}-volume.service') service_stop(container_name) directory = pathlib.Path('/etc/containers/systemd') directory.mkdir(parents=True, exist_ok=True) + subprocess.run(['podman', 'volume', 'rm', '--force', volume_name], + check=False) + + directory = pathlib.Path('/etc/containers/systemd') + directory.mkdir(parents=True, exist_ok=True) + + pathlib.Path(volume_path).mkdir(parents=True, exist_ok=True) + # Create storage volume + volume_file = directory / f'{volume_name}.volume' + contents = f'''[Volume] +Device={volume_path} +Driver=local +VolumeName={volume_name} +Options=bind +''' + volume_file.write_text(contents) + service_file = directory / f'{container_name}.container' volume_lines = '\n'.join([ f'Volume={source}:{dest}' for source, dest in (volumes or {}).items() ]) env_lines = '\n'.join( [f'Environment={key}={value}' for key, value in (env or {}).items()]) - contents = f'''[Container] + contents = f'''[Unit] +Requires=nextcloud-freedombox-volume.service +After=nextcloud-freedombox-volume.service + +[Container] AutoUpdate=registry ContainerName=%N {env_lines} @@ -522,11 +544,18 @@ WantedBy=default.target service_daemon_reload() -def podman_uninstall(container_name: str, volume_name: str, image_name: str): +def podman_uninstall(container_name: str, volume_name: str, image_name: str, + volume_path: str): """Remove a podman container's components and systemd unit.""" - subprocess.run(['podman', 'volume', 'rm', volume_name], check=True) - subprocess.run(['podman', 'image', 'rm', image_name], check=True) + subprocess.run(['podman', 'volume', 'rm', '--force', volume_name], + check=True) + subprocess.run(['podman', 'image', 'rm', '--ignore', image_name], + check=True) + volume_file = pathlib.Path( + '/etc/containers/systemd/') / f'{volume_name}.volume' + volume_file.unlink(missing_ok=True) service_file = pathlib.Path( '/etc/containers/systemd/') / f'{container_name}.container' service_file.unlink(missing_ok=True) + shutil.rmtree(volume_path, ignore_errors=True) service_daemon_reload() diff --git a/plinth/modules/nextcloud/__init__.py b/plinth/modules/nextcloud/__init__.py index f21e3f1aa..9ae5dc3f7 100644 --- a/plinth/modules/nextcloud/__init__.py +++ b/plinth/modules/nextcloud/__init__.py @@ -87,7 +87,7 @@ class NextcloudApp(app_module.App): self.add(firewall) firewall_local_protection = FirewallLocalProtection( - 'firewall-local-protection-nextcloud', ['8181']) + 'firewall-local-protection-nextcloud', ['9000']) self.add(firewall_local_protection) webserver = Webserver('webserver-nextcloud', 'nextcloud-freedombox', diff --git a/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf b/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf index 8d3758f54..b201d0e2f 100644 --- a/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf +++ b/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf @@ -16,10 +16,36 @@ Redirect 301 /.well-known/caldav /nextcloud/remote.php/dav Redirect 301 /.well-known/webfinger /nextcloud/index.php/.well-known/webfinger Redirect 301 /.well-known/nodeinfo /nextcloud/index.php/.well-known/nodeinfo - - ProxyPass http://127.0.0.1:8181 +Alias /nextcloud/ /var/lib/nextcloud/ - ## Send the scheme from user's request to enable Nextcloud to redirect URLs, - ## set cookies, set absolute URLs (if any) properly. - RequestHeader set X-Forwarded-Proto 'https' env=HTTPS - + + ProxyPassMatch "^/nextcloud/(.*\.php(/.*)?)$" "fcgi://localhost:9000/var/www/html/$1" + + + + + + # Enable http authorization headers + + SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1 + + + + # Deny access to raw php sources by default + # To re-enable it's recommended to enable access to the files + # only in specific virtual host or directory + Require all denied + + # Deny access to files without filename (e.g. '.php') + + Require all denied + + + + + Require all granted + + # Allow a limited set of directives in .htaccess files found in /, /config, + # and /data directories of nextcloud. + AllowOverride AuthConfig FileInfo Indexes Limit Options + diff --git a/plinth/modules/nextcloud/manifest.py b/plinth/modules/nextcloud/manifest.py index 31a1fce10..fa875a67a 100644 --- a/plinth/modules/nextcloud/manifest.py +++ b/plinth/modules/nextcloud/manifest.py @@ -48,9 +48,7 @@ clients = [{ backup = { 'data': { - 'directories': [ - '/var/lib/containers/storage/volumes/nextcloud-volume-freedombox/' - ], + 'directories': ['/var/lib/nextcloud/'], 'files': ['/var/lib/plinth/backups-data/nextcloud-database.sql'] } } diff --git a/plinth/modules/nextcloud/privileged.py b/plinth/modules/nextcloud/privileged.py index dd19ef49a..ac9358a61 100644 --- a/plinth/modules/nextcloud/privileged.py +++ b/plinth/modules/nextcloud/privileged.py @@ -17,8 +17,8 @@ from plinth.actions import privileged CONTAINER_NAME = 'nextcloud-freedombox' SERVICE_NAME = 'nextcloud-freedombox' -VOLUME_NAME = 'nextcloud-volume-freedombox' -IMAGE_NAME = 'docker.io/library/nextcloud:stable-apache' +VOLUME_NAME = 'nextcloud-freedombox' +IMAGE_NAME = 'docker.io/library/nextcloud:stable-fpm' DB_HOST = 'localhost' DB_NAME = 'nextcloud_fbx' @@ -26,8 +26,7 @@ DB_USER = 'nextcloud_fbx' GUI_ADMIN = 'nextcloud-admin' REDIS_DB = 8 # Don't clash with other redis apps -_volume_path = pathlib.Path( - '/var/lib/containers/storage/volumes/') / VOLUME_NAME +_data_path = pathlib.Path('/var/lib/nextcloud/') _systemd_location = pathlib.Path('/etc/systemd/system/') _cron_service_file = _systemd_location / 'nextcloud-cron-freedombox.service' _cron_timer_file = _systemd_location / 'nextcloud-cron-freedombox.timer' @@ -55,16 +54,18 @@ def setup(): '/run/slapd/ldapi': '/run/slapd/ldapi', VOLUME_NAME: '/var/www/html' } - env = {'TRUSTED_PROXIES': '127.0.0.1', 'OVERWRITEWEBROOT': '/nextcloud'} + env = {'OVERWRITEWEBROOT': '/nextcloud'} action_utils.podman_create(container_name=CONTAINER_NAME, - image_name=IMAGE_NAME, volumes=volumes, env=env) + image_name=IMAGE_NAME, volume_name=VOLUME_NAME, + volume_path=str(_data_path), volumes=volumes, + env=env) action_utils.service_start(CONTAINER_NAME) # OCC isn't immediately available after the container is spun up. # Wait until CAN_INSTALL file is available. timeout = 300 while timeout > 0: - if (_volume_path / '_data/config/CAN_INSTALL').exists(): + if (_data_path / 'config/CAN_INSTALL').exists(): break timeout = timeout - 1 @@ -278,7 +279,8 @@ def uninstall(): _drop_database() action_utils.podman_uninstall(container_name=CONTAINER_NAME, volume_name=VOLUME_NAME, - image_name=IMAGE_NAME) + image_name=IMAGE_NAME, + volume_path=str(_data_path)) for path in [_cron_service_file, _cron_timer_file]: path.unlink(missing_ok=True) @@ -366,7 +368,7 @@ def _get_dbpassword(): def _create_redis_config(): """Create a php file for Redis configuration.""" - config_file = _volume_path / '_data/config/freedombox.config.php' + config_file = _data_path / 'config/freedombox.config.php' file_content = fr''' '\OC\Memcache\Redis', From 6e2db19a26e5f543105f19cf50b654b8904b2dd5 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sun, 14 Apr 2024 17:38:42 -0700 Subject: [PATCH 21/36] nextcloud: Wait on init sync lock - First wait until the files are copied into /var/www/html from /usr/src/nextcloud. - Then wait until init-sync lock is released. - This allows for re-running setup as CAN_INSTALL file is removed after install process in completed. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/nextcloud/privileged.py | 34 ++++++++++++++++++-------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/plinth/modules/nextcloud/privileged.py b/plinth/modules/nextcloud/privileged.py index ac9358a61..7bfc24e7c 100644 --- a/plinth/modules/nextcloud/privileged.py +++ b/plinth/modules/nextcloud/privileged.py @@ -61,16 +61,7 @@ def setup(): env=env) action_utils.service_start(CONTAINER_NAME) - # OCC isn't immediately available after the container is spun up. - # Wait until CAN_INSTALL file is available. - timeout = 300 - while timeout > 0: - if (_data_path / 'config/CAN_INSTALL').exists(): - break - - timeout = timeout - 1 - time.sleep(1) - + _nextcloud_wait_until_ready() _nextcloud_setup_wizard(database_password, administrator_password) _create_redis_config() @@ -178,6 +169,29 @@ def _set_database_privileges(db_password: str): _database_query(query) +def _nextcloud_wait_until_ready(): + """Wait for Nextcloud container to get ready.""" + # Nextcloud copies sources from /usr/src/nextcloud to /var/www/html inside + # the container. Nextcloud is served from the latter location. This happens + # on first run of the container and when upgrade happen. + start_time = time.time() + while time.time() < start_time + 300: + if (_data_path / 'version.php').exists(): + break + + time.sleep(1) + + # Wait while Nextcloud is syncing files, running install or performing an + # upgrade by trying to obtain an exclusive on its init-sync.lock. Wrap the + # echo command with the lock so that the lock is immediately released after + # obtaining. We are unable to obtain the lock for 5 minutes, fail and stop + # the setup process. + lock_file = _data_path / 'nextcloud-init-sync.lock' + subprocess.run( + ['flock', '--exclusive', '--wait', '300', lock_file, 'echo'], + check=True) + + def _nextcloud_get_status(): """Return Nextcloud status such installed, in maintenance, etc.""" output = _run_occ('status', '--output=json', capture_output=True) From 035d3b49bf4c227669d14f30165d322cdf8df92f Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sun, 14 Apr 2024 17:42:47 -0700 Subject: [PATCH 22/36] nextcloud: Pull the image separately before starting systemd unit This prevents timeout of the service if the image pull is slow. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/action_utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plinth/action_utils.py b/plinth/action_utils.py index 61783f21a..c4b338e56 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -505,6 +505,10 @@ def podman_create(container_name: str, image_name: str, volume_name: str, directory = pathlib.Path('/etc/containers/systemd') directory.mkdir(parents=True, exist_ok=True) + # Fetch the image before creating the container. The systemd service for + # the container won't timeout due to slow internet connectivity. + subprocess.run(['podman', 'image', 'pull', image_name], check=True) + pathlib.Path(volume_path).mkdir(parents=True, exist_ok=True) # Create storage volume volume_file = directory / f'{volume_name}.volume' From 6b046ec27d4e7b6a3a9a31f6be2844fde1170684 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sun, 14 Apr 2024 17:55:21 -0700 Subject: [PATCH 23/36] nextcloud: Ship instead of create cron timer related units Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- .../system/nextcloud-cron-freedombox.service | 8 ++++ .../system/nextcloud-cron-freedombox.timer | 11 +++++ plinth/modules/nextcloud/privileged.py | 40 ------------------- 3 files changed, 19 insertions(+), 40 deletions(-) create mode 100644 plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.service create mode 100644 plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.timer diff --git a/plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.service b/plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.service new file mode 100644 index 000000000..5f24bb040 --- /dev/null +++ b/plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.service @@ -0,0 +1,8 @@ +[Unit] +Description=Nextcloud cron.php job +Documentation=https://docs.nextcloud.com/server/stable/admin_manual/configuration_server/background_jobs_configuration.html#systemd + +[Service] +ExecCondition=/usr/bin/podman exec --user www-data nextcloud-freedombox /var/www/html/occ status -e +ExecStart=/usr/bin/podman exec --user www-data nextcloud-freedombox php -f /var/www/html/cron.php +KillMode=process diff --git a/plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.timer b/plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.timer new file mode 100644 index 000000000..3a5e8bd18 --- /dev/null +++ b/plinth/modules/nextcloud/data/usr/lib/systemd/system/nextcloud-cron-freedombox.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Run Nextcloud cron.php every 5 minutes +Documentation=https://docs.nextcloud.com/server/stable/admin_manual/configuration_server/background_jobs_configuration.html#systemd + +[Timer] +OnBootSec=5min +OnUnitActiveSec=5min +Unit=nextcloud-cron-freedombox.service + +[Install] +WantedBy=timers.target diff --git a/plinth/modules/nextcloud/privileged.py b/plinth/modules/nextcloud/privileged.py index 7bfc24e7c..74efb2e56 100644 --- a/plinth/modules/nextcloud/privileged.py +++ b/plinth/modules/nextcloud/privileged.py @@ -27,9 +27,6 @@ GUI_ADMIN = 'nextcloud-admin' REDIS_DB = 8 # Don't clash with other redis apps _data_path = pathlib.Path('/var/lib/nextcloud/') -_systemd_location = pathlib.Path('/etc/systemd/system/') -_cron_service_file = _systemd_location / 'nextcloud-cron-freedombox.service' -_cron_timer_file = _systemd_location / 'nextcloud-cron-freedombox.timer' DB_BACKUP_FILE = pathlib.Path( '/var/lib/plinth/backups-data/nextcloud-database.sql') @@ -67,8 +64,6 @@ def setup(): _configure_ldap() - _configure_systemd() - def _run_in_container( *args, capture_output: bool = False, check: bool = True, @@ -254,39 +249,6 @@ def _configure_ldap(): _run_occ('ldap:set-config', 's01', key, value) -def _configure_systemd(): - """Create systemd units files for container and cron jobs.""" - # Create service and timer for running periodic php jobs. - doc = 'https://docs.nextcloud.com/server/stable/admin_manual/' \ - 'configuration_server/background_jobs_configuration.html#systemd' - nextcloud_cron_service_content = f''' -[Unit] -Description=Nextcloud cron.php job -Documentation={doc} - -[Service] -ExecCondition=/usr/bin/podman exec --user www-data {CONTAINER_NAME} /var/www/html/occ status -e -ExecStart=/usr/bin/podman exec --user www-data {CONTAINER_NAME} php -f /var/www/html/cron.php -KillMode=process -''' # noqa: E501 - nextcloud_cron_timer_content = '''[Unit] -Description=Run Nextcloud cron.php every 5 minutes -Documentation={doc} - -[Timer] -OnBootSec=5min -OnUnitActiveSec=5min -Unit=nextcloud-cron-freedombox.service - -[Install] -WantedBy=timers.target -''' - _cron_service_file.write_text(nextcloud_cron_service_content) - _cron_timer_file.write_text(nextcloud_cron_timer_content) - - action_utils.service_daemon_reload() - - @privileged def uninstall(): """Uninstall Nextcloud""" @@ -295,8 +257,6 @@ def uninstall(): volume_name=VOLUME_NAME, image_name=IMAGE_NAME, volume_path=str(_data_path)) - for path in [_cron_service_file, _cron_timer_file]: - path.unlink(missing_ok=True) def _drop_database(): From 614bea45113ace35948f95eb9c9ea46f9cbebda6 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Mon, 15 Apr 2024 10:50:46 -0700 Subject: [PATCH 24/36] nextcloud: Restart container when dependent services are restarted This is required because when services are restarted, their Unix domain sockets are removed and new ones are created. The container will still be using the old sockets and will fail to connect to the service. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/action_utils.py | 6 +++++- plinth/modules/nextcloud/privileged.py | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plinth/action_utils.py b/plinth/action_utils.py index c4b338e56..881a7f5b4 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -491,7 +491,8 @@ def is_package_manager_busy(): def podman_create(container_name: str, image_name: str, volume_name: str, volume_path: str, volumes: dict[str, str] | None = None, - env: dict[str, str] | None = None): + env: dict[str, str] | None = None, + binds_to: list[str] | None = None): """Remove and recreate a podman container.""" service_stop(f'{volume_name}-volume.service') service_stop(container_name) @@ -526,9 +527,12 @@ Options=bind ]) env_lines = '\n'.join( [f'Environment={key}={value}' for key, value in (env or {}).items()]) + bind_lines = '\n'.join(f'BindsTo={service}\nAfter={service}' + for service in (binds_to or [])) contents = f'''[Unit] Requires=nextcloud-freedombox-volume.service After=nextcloud-freedombox-volume.service +{bind_lines} [Container] AutoUpdate=registry diff --git a/plinth/modules/nextcloud/privileged.py b/plinth/modules/nextcloud/privileged.py index 74efb2e56..6b080ddf6 100644 --- a/plinth/modules/nextcloud/privileged.py +++ b/plinth/modules/nextcloud/privileged.py @@ -52,10 +52,11 @@ def setup(): VOLUME_NAME: '/var/www/html' } env = {'OVERWRITEWEBROOT': '/nextcloud'} + binds_to = ['mariadb.service', 'redis-server.service', 'slapd.service'] action_utils.podman_create(container_name=CONTAINER_NAME, image_name=IMAGE_NAME, volume_name=VOLUME_NAME, volume_path=str(_data_path), volumes=volumes, - env=env) + env=env, binds_to=binds_to) action_utils.service_start(CONTAINER_NAME) _nextcloud_wait_until_ready() From f8ddc774b0b2deeef84bc4ff312a47b6b3e63f62 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Mon, 15 Apr 2024 12:19:11 -0700 Subject: [PATCH 25/36] nextcloud: Allow re-running setup - Retrieve a database password from configuration if one is set instead of generating new one. - Create database after starting the container. This is okay as database configuration is not set until maintenance:install operation is run. - Minor change to setting administrator password during install. Tests: - Update profile in Nextcloud and re-run setup. The updated settings are still available. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/action_utils.py | 1 + plinth/modules/nextcloud/privileged.py | 40 +++++++++++++++----------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/plinth/action_utils.py b/plinth/action_utils.py index 881a7f5b4..b073a7591 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -500,6 +500,7 @@ def podman_create(container_name: str, image_name: str, volume_name: str, directory = pathlib.Path('/etc/containers/systemd') directory.mkdir(parents=True, exist_ok=True) + # Data is kept subprocess.run(['podman', 'volume', 'rm', '--force', volume_name], check=False) diff --git a/plinth/modules/nextcloud/privileged.py b/plinth/modules/nextcloud/privileged.py index 6b080ddf6..d9c1a0b47 100644 --- a/plinth/modules/nextcloud/privileged.py +++ b/plinth/modules/nextcloud/privileged.py @@ -35,13 +35,6 @@ DB_BACKUP_FILE = pathlib.Path( @privileged def setup(): """Setup Nextcloud configuration.""" - database_password = _generate_secret_key(16) - administrator_password = _generate_secret_key(16) - - # Setup database - _create_database() - _set_database_privileges(database_password) - # Setup redis for caching _redis_listen_socket() @@ -60,9 +53,21 @@ def setup(): action_utils.service_start(CONTAINER_NAME) _nextcloud_wait_until_ready() - _nextcloud_setup_wizard(database_password, administrator_password) + + # Setup database + _create_database() + database_password = _get_database_password() + if not database_password: + database_password = _generate_secret_key(16) + _set_database_privileges(database_password) + + # Setup redis configuration _create_redis_config() + # Run setup wizard + _nextcloud_setup_wizard(database_password) + + # Setup LDAP configuraiton _configure_ldap() @@ -148,9 +153,8 @@ def _create_database(): if _db_file_path.exists(): return - query = f'''CREATE DATABASE {DB_NAME} CHARACTER SET utf8mb4 - COLLATE utf8mb4_general_ci; -''' + query = f'CREATE DATABASE IF NOT EXISTS {DB_NAME} ' \ + 'CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;' _database_query(query) @@ -194,9 +198,10 @@ def _nextcloud_get_status(): return json.loads(output.stdout) -def _nextcloud_setup_wizard(db_password, admin_password): +def _nextcloud_setup_wizard(db_password: str): """Run the Nextcloud installation wizard and enable cron jobs.""" if not _nextcloud_get_status()['installed']: + admin_password = _generate_secret_key(16) _run_occ('maintenance:install', '--database=mysql', '--database-host=localhost:/run/mysqld/mysqld.sock', f'--database-name={DB_NAME}', f'--database-user={DB_USER}', @@ -313,7 +318,7 @@ def restore_database(): subprocess.run(['redis-cli', '-n', str(REDIS_DB), 'FLUSHDB', 'SYNC'], check=False) - _set_database_privileges(_get_dbpassword()) + _set_database_privileges(_get_database_password()) # After updating the configuration, a restart seems to be required for the # new DB password be used. @@ -330,13 +335,14 @@ def restore_database(): _run_occ('maintenance:data-fingerprint') -def _get_dbpassword(): - """Return the database password from config.php. +def _get_database_password(): + """Return the database password from config.php or '' if not set. OCC cannot run unless Nextcloud can already connect to the database. """ - code = 'include_once("/var/www/html/config/config.php");' \ - 'print($CONFIG["dbpassword"]);' + code = 'if (file_exists("/var/www/html/config/config.php")) {' \ + 'include_once("/var/www/html/config/config.php");' \ + 'print($CONFIG["dbpassword"] ?? ""); }' return _run_in_container('php', '-r', code, capture_output=True).stdout.decode().strip() From f1276d736ab9539108ddf5d95edd57b0ed525057 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 16 Apr 2024 16:00:16 -0700 Subject: [PATCH 26/36] nextcloud: Implement enable/disable container Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/action_utils.py | 42 ++++++++++++++++++++++++++ plinth/modules/nextcloud/__init__.py | 20 +++++++++++- plinth/modules/nextcloud/privileged.py | 18 +++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/plinth/action_utils.py b/plinth/action_utils.py index b073a7591..9afbd6cd3 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -11,6 +11,8 @@ import subprocess import tempfile from contextlib import contextmanager +import augeas + logger = logging.getLogger(__name__) UWSGI_ENABLED_PATH = '/etc/uwsgi/apps-enabled/{config_name}.ini' @@ -553,6 +555,46 @@ WantedBy=default.target service_daemon_reload() +def _podman_augeus(container_name: str): + """Return an augues instance to edit container configuration file.""" + aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + + augeas.Augeas.NO_MODL_AUTOLOAD) + container = f'/etc/containers/systemd/{container_name}.container' + aug.transform('Systemd', container) + aug.set('/augeas/context', '/files' + container) + aug.load() + return aug + + +def podman_is_enabled(container_name: str) -> bool: + """Return whether the container to start on boot.""" + aug = _podman_augeus(container_name) + aug = _podman_augeus(container_name) + value = 'default.target' + key = 'Install/WantedBy' + return any( + (aug.get(match_ + '/value') == value for match_ in aug.match(key))) + + +def podman_enable(container_name: str): + """Enable container to start on boot.""" + aug = _podman_augeus(container_name) + value = 'default.target' + key = 'Install/WantedBy' + found = any( + (aug.get(match_ + '/value') == value for match_ in aug.match(key))) + if not found: + aug.set(f'{key}[last() +1]/value', value) + aug.save() + + +def podman_disable(container_name: str): + """Disable container to start on boot.""" + aug = _podman_augeus(container_name) + aug.remove('Install/WantedBy') + aug.save() + + def podman_uninstall(container_name: str, volume_name: str, image_name: str, volume_path: str): """Remove a podman container's components and systemd unit.""" diff --git a/plinth/modules/nextcloud/__init__.py b/plinth/modules/nextcloud/__init__.py index 9ae5dc3f7..6ccb63f0e 100644 --- a/plinth/modules/nextcloud/__init__.py +++ b/plinth/modules/nextcloud/__init__.py @@ -105,7 +105,7 @@ class NextcloudApp(app_module.App): daemon = SharedDaemon('shared-daemon-nextcloud-mysql', 'mysql') self.add(daemon) - daemon = Daemon('daemon-nextcloud', 'nextcloud-freedombox') + daemon = NextcloudDaemon('daemon-nextcloud', 'nextcloud-freedombox') self.add(daemon) daemon = Daemon('daemon-nextcloud-timer', @@ -145,6 +145,24 @@ class NextcloudApp(app_module.App): return results +class NextcloudDaemon(Daemon): + """Component to manage Nextcloud container service.""" + + def is_enabled(self): + """Return if the daemon/unit is enabled.""" + return privileged.is_enabled() + + def enable(self): + """Run operations to enable the daemon/unit.""" + super().enable() + privileged.enable() + + def disable(self): + """Run operations to disable the daemon/unit.""" + super().disable() + privileged.disable() + + class NextcloudBackupRestore(BackupRestore): """Component to backup/restore Nextcloud.""" diff --git a/plinth/modules/nextcloud/privileged.py b/plinth/modules/nextcloud/privileged.py index d9c1a0b47..679ab212f 100644 --- a/plinth/modules/nextcloud/privileged.py +++ b/plinth/modules/nextcloud/privileged.py @@ -86,6 +86,24 @@ def _run_occ(*args, **kwargs) -> subprocess.CompletedProcess: return _run_in_container('/var/www/html/occ', *args, **kwargs) +@privileged +def is_enabled() -> bool: + """Return if the systemd container service is enabled.""" + return action_utils.podman_is_enabled(CONTAINER_NAME) + + +@privileged +def enable(): + """Enable the systemd container service.""" + action_utils.podman_enable(CONTAINER_NAME) + + +@privileged +def disable(): + """Disable the systemd container service.""" + action_utils.podman_disable(CONTAINER_NAME) + + @privileged def get_domain(): """Return domain name set in Nextcloud.""" From 5c101a1447081195ceba664eaeb08ed806a12609 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Wed, 17 Apr 2024 15:38:07 -0700 Subject: [PATCH 27/36] nextcloud: Enable pretty URLs without /index.php in them Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- .../apache2/conf-available/nextcloud-freedombox.conf | 2 +- plinth/modules/nextcloud/privileged.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf b/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf index b201d0e2f..6aa23576c 100644 --- a/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf +++ b/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf @@ -47,5 +47,5 @@ Alias /nextcloud/ /var/lib/nextcloud/ # Allow a limited set of directives in .htaccess files found in /, /config, # and /data directories of nextcloud. - AllowOverride AuthConfig FileInfo Indexes Limit Options + AllowOverride AuthConfig FileInfo Indexes Limit Options=Indexes,MultiViews diff --git a/plinth/modules/nextcloud/privileged.py b/plinth/modules/nextcloud/privileged.py index 679ab212f..d45cfe8f1 100644 --- a/plinth/modules/nextcloud/privileged.py +++ b/plinth/modules/nextcloud/privileged.py @@ -230,6 +230,16 @@ def _nextcloud_setup_wizard(db_password: str): # jobs correctly. Cron is the recommended setting. _run_occ('background:cron') + # Enable pretty URLs without /index.php in them. + _run_occ('config:system:set', 'htaccess.RewriteBase', '--value', + '/nextcloud') + _run_occ('config:system:set', 'htaccess.IgnoreFrontController', + '--type=boolean', '--value=true') + # Update the .htaccess file to contain mod_rewrite rules needed for pretty + # URLs. This is automatically re-run by scripts when upgrading to next + # version. + _run_occ('maintenance:update:htaccess') + def _configure_ldap(): _run_occ('app:enable', 'user_ldap') From 685e636a93d92421b415bdab727eaa0d24cfe1a7 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Wed, 17 Apr 2024 18:33:11 -0700 Subject: [PATCH 28/36] notification: Handle more formatting errors When there message strings containing substrings of the form "{object.property}", an AttributeError is raised instead of KeyError during string formatting. Catch these errors. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/notification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plinth/notification.py b/plinth/notification.py index 07aee714c..c1be960e0 100644 --- a/plinth/notification.py +++ b/plinth/notification.py @@ -292,7 +292,7 @@ class Notification(models.StoredNotification): string_ = str(string_) if data: string_ = SafeFormatter().vformat(string_, [], data) - except KeyError as error: + except (KeyError, AttributeError) as error: logger.warning( 'Notification missing required key during translation: %s', error) From 1272be0ad671f4b0cd355ec1f607feb619a68ce5 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Wed, 17 Apr 2024 19:09:40 -0700 Subject: [PATCH 29/36] nextcloud: Allow re-running setup when app is disabled - Enable the redis drop-in configurations before redis-server is started so that the configuration is effective. - When app is disabled and re-running setup, disable it after running setup because setup() enables it. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/nextcloud/__init__.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/plinth/modules/nextcloud/__init__.py b/plinth/modules/nextcloud/__init__.py index 6ccb63f0e..38a45e4ed 100644 --- a/plinth/modules/nextcloud/__init__.py +++ b/plinth/modules/nextcloud/__init__.py @@ -119,17 +119,24 @@ class NextcloudApp(app_module.App): def setup(self, old_version): """Install and configure the app.""" super().setup(old_version) - with self.get_component( - 'shared-daemon-nextcloud-redis').ensure_running(): - with self.get_component( - 'shared-daemon-nextcloud-mysql').ensure_running(): + # Drop-in configs need to be enabled for setup to succeed + self.get_component('dropin-configs-nextcloud').enable() + redis = self.get_component('shared-daemon-nextcloud-redis') + mysql = self.get_component('shared-daemon-nextcloud-mysql') + nextcloud = self.get_component('daemon-nextcloud') + + # Determine whether app should be disabled after setup + should_disable = old_version and not nextcloud.is_enabled() + + with redis.ensure_running(): + with mysql.ensure_running(): # Database needs to be running for successful initialization or # upgrade of Nextcloud database. - - # Drop-in configs need to be enabled for setup to succeed - self.get_component('dropin-configs-nextcloud').enable() privileged.setup() + if should_disable: + self.disable() + if not old_version: self.enable() From fb0dd323ff953e7acea36e862f4c016c1352e4b5 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 19 Apr 2024 11:00:20 -0700 Subject: [PATCH 30/36] nextcloud: Populated and maintain a list of trusted domains - Rename 'domain' to 'override domain'. See below. - If override domain is not set and trusted domains list is properly maintained, then Nextcloud can be accessed using a domain from list of trusted domains. This is ideal as accessing from .onion domain and a regular domain will simultaneously without forcing a single domain. However, non-localhost IP addresses will not work with this approach and 'override domain' will be needed. - When override domain is set to an IP address or a domain, then that domain will forced. Also hostname are accepted on a request but after the first page load, access will be forcefully redirected to the configured override domain. Multiple domains, even trusted domains, will thus not work. This option should be used as a last resort. - All un-setting the override domain to an empty value so that trusted domains can be used again. - Update diagnostic checks to ensure that above logic is used with checking domains. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/nextcloud/__init__.py | 103 ++++++++++++++++++++----- plinth/modules/nextcloud/forms.py | 9 ++- plinth/modules/nextcloud/privileged.py | 27 +++++-- plinth/modules/nextcloud/views.py | 6 +- 4 files changed, 111 insertions(+), 34 deletions(-) diff --git a/plinth/modules/nextcloud/__init__.py b/plinth/modules/nextcloud/__init__.py index 38a45e4ed..e1e42c13f 100644 --- a/plinth/modules/nextcloud/__init__.py +++ b/plinth/modules/nextcloud/__init__.py @@ -1,17 +1,22 @@ # SPDX-License-Identifier: AGPL-3.0-or-later """FreedomBox app to configure Nextcloud.""" +import contextlib + from django.utils.translation import gettext_lazy as _ from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.config import DropinConfigs from plinth.daemon import Daemon, SharedDaemon -from plinth.modules.apache.components import Webserver, diagnose_url +from plinth.modules.apache.components import (Webserver, diagnose_url, + diagnose_url_on_all) from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import (Firewall, FirewallLocalProtection) +from plinth.modules.names.components import DomainName from plinth.package import Packages +from plinth.signals import domain_added, domain_removed from plinth.utils import format_lazy from . import manifest, privileged @@ -90,8 +95,7 @@ class NextcloudApp(app_module.App): 'firewall-local-protection-nextcloud', ['9000']) self.add(firewall_local_protection) - webserver = Webserver('webserver-nextcloud', 'nextcloud-freedombox', - urls=['https://{host}/nextcloud/login']) + webserver = Webserver('webserver-nextcloud', 'nextcloud-freedombox') self.add(webserver) daemon = SharedDaemon('shared-daemon-podman-auto-update', @@ -116,6 +120,12 @@ class NextcloudApp(app_module.App): **manifest.backup) self.add(backup_restore) + @staticmethod + def post_init(): + """Perform post initialization operations.""" + domain_added.connect(_on_domain_added) + domain_removed.connect(_on_domain_removed) + def setup(self, old_version): """Install and configure the app.""" super().setup(old_version) @@ -133,6 +143,7 @@ class NextcloudApp(app_module.App): # Database needs to be running for successful initialization or # upgrade of Nextcloud database. privileged.setup() + _set_trusted_domains() if should_disable: self.disable() @@ -146,9 +157,33 @@ class NextcloudApp(app_module.App): super().uninstall() def diagnose(self): - """Run diagnostics and return the results.""" + """Run diagnostics and return the results. + + When an override domain is set, that domain and all other addresses are + expected to work. This is because Nextcloud will accept any Host: HTTP + header and then override it with the provided domain name. When + override domain is not set, only the configured trusted domains along + with local IP addresses are allowed. Others are rejected with an error. + """ results = super().diagnose() + + kwargs = {'check_certificate': False} + url = 'https://{domain}/nextcloud/login' + domain = privileged.get_override_domain() + if domain: + results.append(diagnose_url(url.format(domain=domain), **kwargs)) + results += diagnose_url_on_all(url.format(domain='{host}'), + **kwargs) + else: + local_addresses = [('localhost', '4'), ('localhost', '6'), + ('127.0.0.1', '4'), ('[::1]', '6')] + for address, kind in local_addresses: + results.append( + diagnose_url(url.format(domain=address), kind=kind, + **kwargs)) + results.append(diagnose_url('docker.com')) + return results @@ -176,23 +211,51 @@ class NextcloudBackupRestore(BackupRestore): def backup_pre(self, packet): """Save database contents.""" super().backup_pre(packet) - self.app.get_component('dropin-configs-nextcloud').enable() - mysql = self.app.get_component('shared-daemon-nextcloud-mysql') - redis = self.app.get_component('shared-daemon-nextcloud-redis') - container = self.app.get_component('daemon-nextcloud') - with mysql.ensure_running(): - with redis.ensure_running(): - with container.ensure_running(): - privileged.dump_database() + with _ensure_nextcloud_running(): + privileged.dump_database() def restore_post(self, packet): """Restore database contents.""" super().restore_post(packet) - self.app.get_component('dropin-configs-nextcloud').enable() - mysql = self.app.get_component('shared-daemon-nextcloud-mysql') - redis = self.app.get_component('shared-daemon-nextcloud-redis') - container = self.app.get_component('daemon-nextcloud') - with mysql.ensure_running(): - with redis.ensure_running(): - with container.ensure_running(): - privileged.restore_database() + with _ensure_nextcloud_running(): + privileged.restore_database() + + +def _on_domain_added(sender, domain_type, name='', description='', + services=None, **kwargs): + """Add domain to list of trusted domains.""" + app = app_module.App.get('nextcloud') + if app.needs_setup(): + return + + _set_trusted_domains() + + +def _on_domain_removed(sender, domain_type, name='', **kwargs): + """Update the list of trusted domains.""" + app = app_module.App.get('nextcloud') + if app.needs_setup(): + return + + _set_trusted_domains() + + +def _set_trusted_domains(): + """Set the list of trusted domains.""" + all_domains = DomainName.list_names() + with _ensure_nextcloud_running(): + privileged.set_trusted_domains(list(all_domains)) + + +@contextlib.contextmanager +def _ensure_nextcloud_running(): + """Ensure the nextcloud is running and returns to original state.""" + app = app_module.App.get('nextcloud') + app.get_component('dropin-configs-nextcloud').enable() + mysql = app.get_component('shared-daemon-nextcloud-mysql') + redis = app.get_component('shared-daemon-nextcloud-redis') + container = app.get_component('daemon-nextcloud') + with mysql.ensure_running(): + with redis.ensure_running(): + with container.ensure_running(): + yield diff --git a/plinth/modules/nextcloud/forms.py b/plinth/modules/nextcloud/forms.py index 731ac25ec..b5f6d1cb5 100644 --- a/plinth/modules/nextcloud/forms.py +++ b/plinth/modules/nextcloud/forms.py @@ -22,9 +22,12 @@ def _get_phone_regions(): class NextcloudForm(forms.Form): """Nextcloud configuration form.""" - domain = forms.CharField( - label=_('Domain'), required=False, help_text=_( - 'Examples: "myfreedombox.example.org" or "example.onion".')) + override_domain = forms.CharField( + label=_('Override domain'), required=False, help_text=_( + 'Set to the domain or IP address that Nextcloud should be forced ' + 'to generate URLs with. Should not be needed if a valid domain is ' + 'used to access Nextcloud. Examples: "myfreedombox.example.org" ' + 'or "example.onion".')) admin_password = forms.CharField( label=_('Administrator password'), help_text=_( diff --git a/plinth/modules/nextcloud/privileged.py b/plinth/modules/nextcloud/privileged.py index d45cfe8f1..5770b0c56 100644 --- a/plinth/modules/nextcloud/privileged.py +++ b/plinth/modules/nextcloud/privileged.py @@ -105,8 +105,8 @@ def disable(): @privileged -def get_domain(): - """Return domain name set in Nextcloud.""" +def get_override_domain(): + """Return the domain name that Nextcloud is configured to override with.""" try: domain = _run_occ('config:system:get', 'overwritehost', capture_output=True) @@ -116,22 +116,33 @@ def get_domain(): @privileged -def set_domain(domain_name: str): - """Set Nextcloud domain name.""" +def set_override_domain(domain_name: str): + """Set the domain name that Nextcloud will use to override all domains.""" protocol = 'https' if domain_name.endswith('.onion'): protocol = 'http' if domain_name: _run_occ('config:system:set', 'overwritehost', '--value', domain_name) - + _run_occ('config:system:set', 'overwriteprotocol', '--value', protocol) _run_occ('config:system:set', 'overwrite.cli.url', '--value', f'{protocol}://{domain_name}/nextcloud') + else: + _run_occ('config:system:delete', 'overwritehost') + _run_occ('config:system:delete', 'overwriteprotocol') + _run_occ('config:system:delete', 'overwrite.cli.url') - _run_occ('config:system:set', 'overwriteprotocol', '--value', protocol) + # Restart to apply changes immediately + action_utils.service_restart('nextcloud-freedombox') - # Restart to apply changes immediately - action_utils.service_restart('nextcloud-freedombox') + +@privileged +def set_trusted_domains(domains: list[str]): + """Set the list of trusted domains.""" + _run_occ('config:system:delete', 'trusted_domains') + for index, domain in enumerate(domains): + _run_occ('config:system:set', 'trusted_domains', str(index), '--value', + domain) @privileged diff --git a/plinth/modules/nextcloud/views.py b/plinth/modules/nextcloud/views.py index 92a1e21f6..eac1fc9cb 100644 --- a/plinth/modules/nextcloud/views.py +++ b/plinth/modules/nextcloud/views.py @@ -24,7 +24,7 @@ class NextcloudAppView(AppView): """Return the values to fill in the form.""" initial = super().get_initial() initial.update({ - 'domain': privileged.get_domain(), + 'override_domain': privileged.get_override_domain(), 'default_phone_region': privileged.get_default_phone_region() or '' }) return initial @@ -39,8 +39,8 @@ class NextcloudAppView(AppView): def _value_changed(key): return old_config.get(key) != new_config.get(key) - if _value_changed('domain'): - privileged.set_domain(new_config['domain']) + if _value_changed('override_domain'): + privileged.set_override_domain(new_config['override_domain']) is_changed = True if new_config['admin_password']: From 31785857b323f4c4a88f2f0d51245e2acb777afa Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 2 Apr 2024 11:43:13 -0700 Subject: [PATCH 31/36] nextlcoud: Enable app with experimental warning Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- .../data/usr/share/freedombox/modules-enabled/nextcloud | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plinth/modules/nextcloud/data/usr/share/freedombox/modules-enabled/nextcloud b/plinth/modules/nextcloud/data/usr/share/freedombox/modules-enabled/nextcloud index 50ee07ded..4a1247e87 100644 --- a/plinth/modules/nextcloud/data/usr/share/freedombox/modules-enabled/nextcloud +++ b/plinth/modules/nextcloud/data/usr/share/freedombox/modules-enabled/nextcloud @@ -1 +1 @@ -#plinth.modules.nextcloud +plinth.modules.nextcloud From 653b642dae3c27064548018f26c70206af955cc4 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sat, 4 May 2024 12:19:52 -0700 Subject: [PATCH 32/36] nextcloud: Warn that community provides the container not team Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/modules/nextcloud/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plinth/modules/nextcloud/__init__.py b/plinth/modules/nextcloud/__init__.py index e1e42c13f..67f501770 100644 --- a/plinth/modules/nextcloud/__init__.py +++ b/plinth/modules/nextcloud/__init__.py @@ -32,8 +32,8 @@ _description = [ 'setting a password here.'), format_lazy( _('Please note that Nextcloud is installed and run inside a container ' - 'provided by the Nextcloud project. Security, quality, privacy and ' - 'legal reviews are done by the upstream project and not by ' + 'provided by the Nextcloud community. Security, quality, privacy ' + 'and legal reviews are done by the upstream project and not by ' 'Debian/{box_name}. Updates are performed following an independent ' 'cycle.'), box_name=_(cfg.box_name)), format_lazy('', From f494760f2aa516f71393b18fd79edef1fc305d28 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sat, 4 May 2024 17:27:38 -0700 Subject: [PATCH 33/36] nextcloud: Add fallback for when quadlet is not available - Let the .container file be created and modified even when quadlets are not available. This is harmless. - When upgrading from bookworm to trixie, the fallback service file is removed if setup is re-run. Signed-off-by: Sunil Mohan Adapa [jvalleroy: Add daemon-reload after creating service file] Signed-off-by: James Valleroy Reviewed-by: James Valleroy --- plinth/action_utils.py | 75 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/plinth/action_utils.py b/plinth/action_utils.py index 9afbd6cd3..552194756 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -499,9 +499,6 @@ def podman_create(container_name: str, image_name: str, volume_name: str, service_stop(f'{volume_name}-volume.service') service_stop(container_name) - directory = pathlib.Path('/etc/containers/systemd') - directory.mkdir(parents=True, exist_ok=True) - # Data is kept subprocess.run(['podman', 'volume', 'rm', '--force', volume_name], check=False) @@ -533,8 +530,8 @@ Options=bind bind_lines = '\n'.join(f'BindsTo={service}\nAfter={service}' for service in (binds_to or [])) contents = f'''[Unit] -Requires=nextcloud-freedombox-volume.service -After=nextcloud-freedombox-volume.service +Requires={volume_name}-volume.service +After={volume_name}-volume.service {bind_lines} [Container] @@ -552,6 +549,70 @@ Restart=always WantedBy=default.target ''' service_file.write_text(contents) + + # Remove the fallback service file when upgrading from bookworm to trixie. + # Re-running setup should be sufficient. + _podman_create_fallback_service_file(container_name, image_name, + volume_name, volume_path, volumes, + env, binds_to) + + service_daemon_reload() + + +def _podman_create_fallback_service_file(container_name: str, image_name: str, + volume_name: str, volume_path: str, + volumes: dict[str, str] | None = None, + env: dict[str, str] | None = None, + binds_to: list[str] | None = None): + """Create a systemd unit file if systemd generator is not available.""" + service_file = pathlib.Path( + f'/etc/systemd/system/{container_name}.service') + + generator = '/usr/lib/systemd/system-generators/podman-system-generator' + if pathlib.Path(generator).exists(): + # If systemd generator is present, during an upgrade, remove the + # .service file (perhaps created when generator is not present). + service_file.unlink(missing_ok=True) + return + + service_file.parent.mkdir(parents=True, exist_ok=True) + bind_lines = '\n'.join(f'BindsTo={service}\nAfter={service}' + for service in (binds_to or [])) + require_mounts_for = '\n'.join((f'RequiresMountsFor={host_path}' + for host_path in (volumes or {}) + if host_path.startswith('/'))) + env_args = ' '.join( + (f'--env {key}={value}' for key, value in (env or {}).items())) + volume_args = ' '.join( + (f'-v {host_path}:{container_path}' + for host_path, container_path in (volumes or {}).items())) + + # Similar to the file quadlet systemd generator produces but with volume + # related commands merged. + contents = f'''[Unit] +{bind_lines} +RequiresMountsFor=%t/containers +{require_mounts_for} + +[Service] +Restart=always +Environment=PODMAN_SYSTEMD_UNIT=%n +KillMode=mixed +ExecStop=/usr/bin/podman rm -v -f -i --cidfile=%t/%N.cid +ExecStopPost=-/usr/bin/podman rm -v -f -i --cidfile=%t/%N.cid +Delegate=yes +Type=notify +NotifyAccess=all +SyslogIdentifier=%N +ExecStartPre=/usr/bin/rm -f %t/%N.cid +ExecStartPre=/usr/bin/podman volume rm --force {volume_name} +ExecStartPre=/usr/bin/podman volume create --driver=local --opt device={volume_path} --opt o=bind {volume_name} +ExecStart=/usr/bin/podman run --name=%N --cidfile=%t/%N.cid --replace --rm --cgroups=split --network=host --sdnotify=conmon --detach --label io.containers.autoupdate=registry {volume_args} {env_args} {image_name} + +[Install] +WantedBy=default.target +''' # noqa: E501 + service_file.write_text(contents, encoding='utf-8') service_daemon_reload() @@ -608,5 +669,9 @@ def podman_uninstall(container_name: str, volume_name: str, image_name: str, service_file = pathlib.Path( '/etc/containers/systemd/') / f'{container_name}.container' service_file.unlink(missing_ok=True) + # Remove fallback service file + service_file = pathlib.Path( + '/etc/systemd/system/') / f'{container_name}.service' + service_file.unlink(missing_ok=True) shutil.rmtree(volume_path, ignore_errors=True) service_daemon_reload() From 23592667f65830ab9d3941b5f77cf69b9296116b Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Mon, 6 May 2024 20:18:39 -0400 Subject: [PATCH 34/36] locale: Update translation strings Signed-off-by: James Valleroy --- plinth/locale/ar/LC_MESSAGES/django.po | 161 +++++++++------ plinth/locale/ar_SA/LC_MESSAGES/django.po | 161 +++++++++------ plinth/locale/be/LC_MESSAGES/django.po | 167 +++++++++------ plinth/locale/bg/LC_MESSAGES/django.po | 168 +++++++++------ plinth/locale/bn/LC_MESSAGES/django.po | 168 +++++++++------ plinth/locale/cs/LC_MESSAGES/django.po | 216 +++++++++++++------- plinth/locale/da/LC_MESSAGES/django.po | 171 ++++++++++------ plinth/locale/de/LC_MESSAGES/django.po | 208 ++++++++++++------- plinth/locale/django.pot | 167 +++++++++------ plinth/locale/el/LC_MESSAGES/django.po | 195 +++++++++++------- plinth/locale/es/LC_MESSAGES/django.po | 214 ++++++++++++------- plinth/locale/fa/LC_MESSAGES/django.po | 170 +++++++++------ plinth/locale/fake/LC_MESSAGES/django.po | 171 ++++++++++------ plinth/locale/fr/LC_MESSAGES/django.po | 208 ++++++++++++------- plinth/locale/gl/LC_MESSAGES/django.po | 161 +++++++++------ plinth/locale/gu/LC_MESSAGES/django.po | 168 +++++++++------ plinth/locale/hi/LC_MESSAGES/django.po | 171 ++++++++++------ plinth/locale/hu/LC_MESSAGES/django.po | 210 ++++++++++++------- plinth/locale/id/LC_MESSAGES/django.po | 174 ++++++++++------ plinth/locale/it/LC_MESSAGES/django.po | 169 +++++++++------ plinth/locale/ja/LC_MESSAGES/django.po | 167 +++++++++------ plinth/locale/kn/LC_MESSAGES/django.po | 167 +++++++++------ plinth/locale/lt/LC_MESSAGES/django.po | 167 +++++++++------ plinth/locale/lv/LC_MESSAGES/django.po | 167 +++++++++------ plinth/locale/nb/LC_MESSAGES/django.po | 193 ++++++++++------- plinth/locale/nl/LC_MESSAGES/django.po | 206 ++++++++++++------- plinth/locale/pl/LC_MESSAGES/django.po | 168 +++++++++------ plinth/locale/pt/LC_MESSAGES/django.po | 163 +++++++++------ plinth/locale/ru/LC_MESSAGES/django.po | 213 ++++++++++++------- plinth/locale/si/LC_MESSAGES/django.po | 167 +++++++++------ plinth/locale/sl/LC_MESSAGES/django.po | 168 +++++++++------ plinth/locale/sq/LC_MESSAGES/django.po | 209 ++++++++++++------- plinth/locale/sr/LC_MESSAGES/django.po | 161 +++++++++------ plinth/locale/sv/LC_MESSAGES/django.po | 206 ++++++++++++------- plinth/locale/ta/LC_MESSAGES/django.po | 167 +++++++++------ plinth/locale/te/LC_MESSAGES/django.po | 203 +++++++++++------- plinth/locale/tr/LC_MESSAGES/django.po | 216 +++++++++++++------- plinth/locale/uk/LC_MESSAGES/django.po | 208 ++++++++++++------- plinth/locale/vi/LC_MESSAGES/django.po | 166 +++++++++------ plinth/locale/zh_Hans/LC_MESSAGES/django.po | 167 +++++++++------ plinth/locale/zh_Hant/LC_MESSAGES/django.po | 177 +++++++++------- 41 files changed, 4623 insertions(+), 2801 deletions(-) diff --git a/plinth/locale/ar/LC_MESSAGES/django.po b/plinth/locale/ar/LC_MESSAGES/django.po index a755190b6..7b2d13c80 100644 --- a/plinth/locale/ar/LC_MESSAGES/django.po +++ b/plinth/locale/ar/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2023-10-19 06:18+0000\n" "Last-Translator: Shaik \n" "Language-Team: Arabic \n" "Language-Team: Arabic (Saudi Arabia) \n" "Language-Team: Bulgarian 10 знака . " "Оставете полето празно, за да запазите текущата парола." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -7875,67 +7872,103 @@ msgstr "Пакетът „{expression}“ е недостъпен за инст msgid "Package {package_name} is the latest version ({latest_version})" msgstr "" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Времето за изчакване на диспечера на пакети е изтекло" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Инсталиране на приложение" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Обновяване на приложение" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Грешка при инсталиране на приложението: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Грешка при обновяване на приложението: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Грешка при обновяване на приложението: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "Приложението е инсталирано." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Приложението е обновено" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Обновяване на приложение" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Грешка при премахване на приложението: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Приложението е обновено" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Премахване на приложение" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Грешка при премахване на приложението: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Приложението е премахнато." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Обновяване на пакетите на приложението" @@ -8278,6 +8311,10 @@ msgstr "Обновяване" msgid "Backup" msgstr "Резервно копие" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -8309,6 +8346,9 @@ msgstr "преди премахване на {app_id}" msgid "Gujarati" msgstr "Гуджарати" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Не може да бъде извършена проба: Не са настроени домейни." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Настройките на моментните снимки на хранилището са променени" diff --git a/plinth/locale/bn/LC_MESSAGES/django.po b/plinth/locale/bn/LC_MESSAGES/django.po index f568bc539..b685e9f14 100644 --- a/plinth/locale/bn/LC_MESSAGES/django.po +++ b/plinth/locale/bn/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2021-06-16 07:33+0000\n" "Last-Translator: Oymate \n" "Language-Team: Bengali \n" "Language-Team: Czech 10 znaků. " "Chcete-li zachovat aktuální heslo, ponechte toto pole prázdné." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "Výchozí region telefonu" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8515,67 +8522,103 @@ msgstr "Balíček {package_expression} není k dispozici pro instalaci" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Balíček {package_name} je nejnovější verze ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "Instalace" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "stahování" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "změna média" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "soubor s nastaveními: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Časový limit čekání na správce balíčků" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Instalace aplikací" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Aktualizace aplikací" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Chyba při instalaci aplikace: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Chyba při aktualizaci aplikace: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Chyba při aktualizaci aplikace: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "Aplikace nainstalována." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Aplikace aktualizována" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Aktualizace aplikací" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Chyba při odinstalaci aplikace: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Aplikace aktualizována" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Odinstalování aplikace" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Chyba při odinstalaci aplikace: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Aplikace odinstalována." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Aktualizace balíčků aplikací" @@ -8946,6 +8989,10 @@ msgstr "Aktualizovat" msgid "Backup" msgstr "Záloha" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Znovu spustit nastavení" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -8977,6 +9024,31 @@ msgstr "před odinstalací {app_id}" msgid "Gujarati" msgstr "gudžarátština" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Nelze testovat: Nejsou nakonfigurovány žádné domény." + +#~ msgid "Media streaming server" +#~ msgstr "Server pro streamování médií" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Adresář, do kterého bude server MiniDLNA načítat obsah. Všechny jeho " +#~ "podadresáře budou rovněž prohledávány kvůli mediálním souborům. Pokud " +#~ "změníte výchozí nastavení, ujistěte se, že nový adresář existuje a že je " +#~ "čitelný pro uživatele \"minidlna\". Obvykle budou fungovat jakékoli " +#~ "adresáře médií uživatele (\"/home/username/\")." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Zadaný adresář neexistuje." + +#~ msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#~ msgstr "Příklady: \"myfreedombox.example.org\" nebo \"example.onion\"." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Nastavení zachycování stavů úložiště aktualizováno" diff --git a/plinth/locale/da/LC_MESSAGES/django.po b/plinth/locale/da/LC_MESSAGES/django.po index eea4b4eea..db4c74bb7 100644 --- a/plinth/locale/da/LC_MESSAGES/django.po +++ b/plinth/locale/da/LC_MESSAGES/django.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: FreedomBox UI\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-09-14 17:19+0000\n" "Last-Translator: ikmaak \n" "Language-Team: Danish \n" "Language-Team: German 10 Zeichen lang sein. Lassen Sie das Feld leer, bleibt das " "derzeitige Passwort bestehen." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default zone is external" msgid "Default phone region" msgstr "Standard-Zone ist extern" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8717,67 +8718,103 @@ msgstr "Paket {package_expression} ist nicht zur Installation verfügbar" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Paket {package_name} ist die aktuellste Version ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "Installation läuft" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "herunterladen" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "Medienwechsel" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "Konfigurationsdatei: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Zeitüberschreitung beim Warten auf den Paket-Manager" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Installation der App" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Aktualisieren der App" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Fehler bei der Installation der App: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Fehler beim Aktualisieren der App: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Fehler beim Aktualisieren der App: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "App installiert." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "App aktualisiert" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Aktualisieren der App" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Fehler bei der Deinstallation der App: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "App aktualisiert" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Deinstallation der App" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Fehler bei der Deinstallation der App: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "App deinstalliert." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Aktualisieren von App-Paketen" @@ -9152,6 +9189,10 @@ msgstr "Aktualisieren" msgid "Backup" msgstr "Sicherungskopie" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Einrichtung erneut ausführen" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9183,6 +9224,29 @@ msgstr "vor der Deinstallation von {app_id}" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Kann nicht testen: Es sind keine Domains konfiguriert." + +#~ msgid "Media streaming server" +#~ msgstr "Medien-Streaming-Server" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Verzeichnis, in dem MiniDLNA Server nach Inhalten sucht. Alle " +#~ "Unterverzeichnisse davon werden ebenfalls nach Mediendateien durchsucht. " +#~ "Wenn Sie die Standardeinstellung ändern, stellen Sie sicher, dass das " +#~ "neue Verzeichnis vorhanden und für den Benutzer \"minidlna\" lesbar ist. " +#~ "Alle Benutzermedienverzeichnisse (\"/ home / username /\") funktionieren " +#~ "normalerweise." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Das angegebene Verzeichnis ist nicht vorhanden." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Konfiguration der Speicherauszüge aktualisiert" diff --git a/plinth/locale/django.pot b/plinth/locale/django.pot index 30bfdefa9..efd31b7d5 100644 --- a/plinth/locale/django.pot +++ b/plinth/locale/django.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -26,27 +26,27 @@ msgstr "" msgid "FreedomBox" msgstr "" -#: daemon.py:122 +#: daemon.py:124 #, python-brace-format msgid "Service {service_name} is running" msgstr "" -#: daemon.py:218 +#: daemon.py:222 #, python-brace-format msgid "Listening on {kind} port {listen_address}:{port}" msgstr "" -#: daemon.py:221 +#: daemon.py:225 #, python-brace-format msgid "Listening on {kind} port {port}" msgstr "" -#: daemon.py:291 +#: daemon.py:296 #, python-brace-format msgid "Connect to {host}:{port}" msgstr "" -#: daemon.py:299 +#: daemon.py:304 #, python-brace-format msgid "Cannot connect to {host}:{port}" msgstr "" @@ -133,12 +133,12 @@ msgstr "" msgid "{box_name} Web Interface (Plinth)" msgstr "" -#: modules/apache/components.py:159 +#: modules/apache/components.py:162 #, python-brace-format msgid "Access URL {url} on tcp{kind}" msgstr "" -#: modules/apache/components.py:162 +#: modules/apache/components.py:165 #, python-brace-format msgid "Access URL {url}" msgstr "" @@ -843,7 +843,7 @@ msgstr "" msgid "Admin" msgstr "" -#: modules/bepasty/views.py:88 modules/diagnostics/views.py:52 +#: modules/bepasty/views.py:88 modules/diagnostics/views.py:55 #: modules/nextcloud/views.py:62 modules/searx/views.py:35 #: modules/searx/views.py:46 modules/security/views.py:56 #: modules/snapshot/views.py:158 modules/tor/views.py:73 @@ -1447,8 +1447,7 @@ msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:20 #: modules/diagnostics/templates/diagnostics_full.html:48 -#: templates/toolbar.html:53 -msgid "Re-run setup" +msgid "Try to repair" msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:32 @@ -1488,10 +1487,15 @@ msgstr "" msgid "Result" msgstr "" -#: modules/diagnostics/views.py:111 +#: modules/diagnostics/views.py:114 msgid "Diagnostic Test" msgstr "" +#: modules/diagnostics/views.py:144 +#, python-brace-format +msgid "App {app_id} is not installed, cannot repair" +msgstr "" + #: modules/dynamicdns/__init__.py:28 #, python-brace-format msgid "" @@ -1649,7 +1653,7 @@ msgstr "" #: modules/dynamicdns/templates/dynamicdns.html:18 #: modules/email/templates/email.html:35 #: modules/letsencrypt/templates/letsencrypt.html:24 -#: modules/mediawiki/forms.py:64 modules/nextcloud/forms.py:26 +#: modules/mediawiki/forms.py:64 msgid "Domain" msgstr "" @@ -1997,12 +2001,12 @@ msgstr "" msgid "Port {name} ({details}) available for internal networks" msgstr "" -#: modules/firewall/components.py:153 +#: modules/firewall/components.py:154 #, python-brace-format msgid "Port {name} ({details}) available for external networks" msgstr "" -#: modules/firewall/components.py:159 +#: modules/firewall/components.py:160 #, python-brace-format msgid "Port {name} ({details}) unavailable for external networks" msgstr "" @@ -2920,10 +2924,6 @@ msgstr "" msgid "Certificates" msgstr "" -#: modules/letsencrypt/__init__.py:105 -msgid "Cannot test: No domains are configured." -msgstr "" - #: modules/letsencrypt/templates/letsencrypt.html:25 msgid "Certificate Status" msgstr "" @@ -3361,7 +3361,7 @@ msgstr "" msgid "Address" msgstr "" -#: modules/minidlna/__init__.py:22 +#: modules/minidlna/__init__.py:20 msgid "" "MiniDLNA is a simple media server software, with the aim of being fully " "compliant with DLNA/UPnP-AV clients. The MiniDLNA daemon serves media files " @@ -3372,28 +3372,22 @@ msgid "" "Kodi." msgstr "" -#: modules/minidlna/__init__.py:44 -msgid "Media streaming server" -msgstr "" - -#: modules/minidlna/__init__.py:47 +#: modules/minidlna/__init__.py:45 msgid "MiniDLNA" msgstr "" -#: modules/minidlna/__init__.py:48 +#: modules/minidlna/__init__.py:46 msgid "Simple Media Server" msgstr "" -#: modules/minidlna/forms.py:13 +#: modules/minidlna/forms.py:20 msgid "Media Files Directory" msgstr "" -#: modules/minidlna/forms.py:14 +#: modules/minidlna/forms.py:21 msgid "" "Directory that MiniDLNA Server will read for content. All sub-directories of " -"this will be also scanned for media files. If you change the default ensure " -"that the new directory exists and that is readable from the \"minidlna\" " -"user. Any user media directories (\"/home/username/\") will usually work." +"this will be also scanned for media files." msgstr "" #: modules/minidlna/manifest.py:10 @@ -3412,11 +3406,7 @@ msgstr "" msgid "totem" msgstr "" -#: modules/minidlna/views.py:35 -msgid "Specified directory does not exist." -msgstr "" - -#: modules/minidlna/views.py:38 +#: modules/minidlna/views.py:33 msgid "Updated media directory" msgstr "" @@ -4540,7 +4530,7 @@ msgstr "" msgid "Failed to delete connection: Connection not found." msgstr "" -#: modules/nextcloud/__init__.py:20 +#: modules/nextcloud/__init__.py:25 msgid "" "Nextcloud is a self-hosted productivity platform which provides private and " "secure functions for file sharing, collaborative work, and more. Nextcloud " @@ -4549,29 +4539,29 @@ msgid "" "interface." msgstr "" -#: modules/nextcloud/__init__.py:25 +#: modules/nextcloud/__init__.py:30 msgid "All users of FreedomBox can use Nextcloud. To perform administrative " msgstr "" -#: modules/nextcloud/__init__.py:29 +#: modules/nextcloud/__init__.py:34 #, python-brace-format msgid "" "Please note that Nextcloud is installed and run inside a container provided " -"by the Nextcloud project. Security, quality, privacy and legal reviews are " +"by the Nextcloud community. Security, quality, privacy and legal reviews are " "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" -#: modules/nextcloud/__init__.py:35 +#: modules/nextcloud/__init__.py:40 msgid "This app is experimental." msgstr "" -#: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 +#: modules/nextcloud/__init__.py:58 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 msgid "Nextcloud" msgstr "" -#: modules/nextcloud/__init__.py:55 +#: modules/nextcloud/__init__.py:60 msgid "File Storage & Collaboration" msgstr "" @@ -4579,15 +4569,22 @@ msgstr "" msgid "Not set" msgstr "" -#: modules/nextcloud/forms.py:27 -msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#: modules/nextcloud/forms.py:26 +msgid "Override domain" msgstr "" -#: modules/nextcloud/forms.py:30 +#: modules/nextcloud/forms.py:27 +msgid "" +"Set to the domain or IP address that Nextcloud should be forced to generate " +"URLs with. Should not be needed if a valid domain is used to access " +"Nextcloud. Examples: \"myfreedombox.example.org\" or \"example.onion\"." +msgstr "" + +#: modules/nextcloud/forms.py:33 msgid "Administrator password" msgstr "" -#: modules/nextcloud/forms.py:31 +#: modules/nextcloud/forms.py:34 msgid "" "Optional. Set a new password for Nextcloud's administrator account " "(nextcloud-admin). The password cannot be a common one and the minimum " @@ -4595,11 +4592,11 @@ msgid "" "keep the current password." msgstr "" -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -7427,67 +7424,97 @@ msgstr "" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "" -#: setup.py:75 -#, python-brace-format -msgid "Error installing app: {error}" -msgstr "" - #: setup.py:78 #, python-brace-format -msgid "Error updating app: {error}" +msgid "Error installing app: {error}" msgstr "" -#: setup.py:82 -msgid "App installed." +#: setup.py:81 setup.py:151 +#, python-brace-format +msgid "Error repairing app: {error}" msgstr "" #: setup.py:84 +#, python-brace-format +msgid "Error updating app: {error}" +msgstr "" + +#: setup.py:88 +msgid "App installed." +msgstr "" + +#: setup.py:92 msgid "App updated" msgstr "" -#: setup.py:101 +#: setup.py:110 +msgid "Repairing app" +msgstr "" + +#: setup.py:130 +#, python-brace-format +msgid "Error running diagnostics: {error}" +msgstr "" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +msgid "App repaired." +msgstr "" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "" -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "" @@ -7820,6 +7847,10 @@ msgstr "" msgid "Backup" msgstr "" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" diff --git a/plinth/locale/el/LC_MESSAGES/django.po b/plinth/locale/el/LC_MESSAGES/django.po index 848b1d2c0..35cc2f822 100644 --- a/plinth/locale/el/LC_MESSAGES/django.po +++ b/plinth/locale/el/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-09-14 17:20+0000\n" "Last-Translator: ikmaak \n" "Language-Team: Greek \n" "Language-Team: Spanish 10 caracteres. Deje este campo en " "blanco para conservar la contraseña actual." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "Región telefónica por defecto" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8584,67 +8592,103 @@ msgstr "" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "El paquete {package_name} es la última versión ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "instalando" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "descargando" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "cambio de medio" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "archivo de configuración: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Tiempo máximo esperando al administrador de paquetes" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Instalando app" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Actualizando app" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Error al instalar la app: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Error al actualizar la app: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Error al actualizar la app: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "App instalada." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "App actualizada" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Actualizando app" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Error desinstalando la aplicación: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "App actualizada" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Instalando app" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Error desinstalando la aplicación: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Aplicación desinstalada." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Actualizando los paquetes de la app" @@ -9015,6 +9059,10 @@ msgstr "Actualización" msgid "Backup" msgstr "Copia de seguridad" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Volver a ejecutar la configuración" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9046,6 +9094,30 @@ msgstr "antes de desinstalar {app_id}" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "No puedo probar: No hay dominios configurados." + +#~ msgid "Media streaming server" +#~ msgstr "Servidor de emisión multimedia" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Es la carpeta en el que MiniDLNA buscará los contenidos, incluyendo las " +#~ "subcarpetas. Si modifica su valor asegúrese de que la nueva carpeta " +#~ "existe y es accesible el usuario \"minidlna\". Se pueden usar carpetas en " +#~ "el directorio del usuario (\"/home/username\")." + +#~ msgid "Specified directory does not exist." +#~ msgstr "La carpeta especificada no existe." + +#~ msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#~ msgstr "Ejemplos: \"myfreedombox.example.org\" o \"example.onion\"." + #~ msgid "To perform administrative actions, use the " #~ msgstr "Para realizar acciones administrativas, utilice " diff --git a/plinth/locale/fa/LC_MESSAGES/django.po b/plinth/locale/fa/LC_MESSAGES/django.po index f3f086bd2..abc024d27 100644 --- a/plinth/locale/fa/LC_MESSAGES/django.po +++ b/plinth/locale/fa/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-09-14 17:19+0000\n" "Last-Translator: ikmaak \n" "Language-Team: Persian \n" "Language-Team: Plinth Developers \n" "Language-Team: French 10 caractères. Laissez ce champ vide pour " "conserver le mot de passe actuel." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default zone is external" msgid "Default phone region" msgstr "La zone par défaut est externe" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8763,67 +8764,103 @@ msgstr "Le paquet {package_expression} n’est pas disponible à l’installatio msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Le paquet {package_name} est à la dernière version ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "installation en cours" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "téléchargement en cours" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "changement de support" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "fichier de configuration : {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Aucune réponse du gestionnaire de paquets" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Installation de l’application" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Mise à jour de l’application" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Erreur lors de l’installation de l’appli : {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Erreur lors de la mise à jour de l’appli : {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Erreur lors de la mise à jour de l’appli : {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "Application installée." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Application mise à jour" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Mise à jour de l’application" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Erreur lors de la désinstallation de l’appli : {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Application mise à jour" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Désinstallation de l’application" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Erreur lors de la désinstallation de l’appli : {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Application désinstallée." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Mise à jour des paquets de l’application" @@ -9200,6 +9237,10 @@ msgstr "Mettre à jour" msgid "Backup" msgstr "Sauvegarder" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Exécuter à nouveau la configuration" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9231,6 +9272,29 @@ msgstr "avant la désinstallation de {app_id}" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Test impossible : aucun domaine n’est configuré." + +#~ msgid "Media streaming server" +#~ msgstr "Serveur de streaming de médias" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Répertoire utilisé par MiniDLNA pour trouver les contenus. Tous les sous-" +#~ "répertoires sont également analysés à la recherche de fichiers " +#~ "multimédia. Si vous choisissez un répertoire autre que celui par défaut, " +#~ "assurez-vous qu’il existe et que l’utilisateur « minidlna » puisse le " +#~ "lire. En général cela fonctionne si vous prenez un répertoire utilisateur " +#~ "(\"/home/utilisateur\") comme répertoire de médias." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Le répertoire indiqué n’existe pas." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Configuration des instantanés de disque mise à jour" diff --git a/plinth/locale/gl/LC_MESSAGES/django.po b/plinth/locale/gl/LC_MESSAGES/django.po index 9500eac5b..b62d5766a 100644 --- a/plinth/locale/gl/LC_MESSAGES/django.po +++ b/plinth/locale/gl/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-12-30 10:51+0000\n" "Last-Translator: gallegonovato \n" "Language-Team: Galician \n" "Language-Team: Gujarati \n" "Language-Team: Hindi %(service_name)s is running." msgid "Service {service_name} is running" msgstr "सर्विस %(service_name)s चल रहा है." -#: daemon.py:218 +#: daemon.py:222 #, python-brace-format msgid "Listening on {kind} port {listen_address}:{port}" msgstr "{kind} में सुन कर पोर्ट {listen_address} :{port}" -#: daemon.py:221 +#: daemon.py:225 #, python-brace-format msgid "Listening on {kind} port {port}" msgstr "{kind} में सुन कर पोर्ट{port}" -#: daemon.py:291 +#: daemon.py:296 #, python-brace-format msgid "Connect to {host}:{port}" msgstr "{host}:{port} से जुड़े" -#: daemon.py:299 +#: daemon.py:304 #, python-brace-format msgid "Cannot connect to {host}:{port}" msgstr "{host}:{port} से नहीं जोड़ सखता" @@ -147,12 +147,12 @@ msgstr "वेब सर्वर" msgid "{box_name} Web Interface (Plinth)" msgstr "{box_name} वेब इंटरफेस (प्लिंथ)" -#: modules/apache/components.py:159 +#: modules/apache/components.py:162 #, python-brace-format msgid "Access URL {url} on tcp{kind}" msgstr "इस यूआरएल का अपयोग करें {url} टीसीपी पर {kind}" -#: modules/apache/components.py:162 +#: modules/apache/components.py:165 #, python-brace-format msgid "Access URL {url}" msgstr "इस यूआरएल का अपयोग करें {url}" @@ -948,7 +948,7 @@ msgstr "हटाईये" msgid "Admin" msgstr "" -#: modules/bepasty/views.py:88 modules/diagnostics/views.py:52 +#: modules/bepasty/views.py:88 modules/diagnostics/views.py:55 #: modules/nextcloud/views.py:62 modules/searx/views.py:35 #: modules/searx/views.py:46 modules/security/views.py:56 #: modules/snapshot/views.py:158 modules/tor/views.py:73 @@ -1657,11 +1657,8 @@ msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:20 #: modules/diagnostics/templates/diagnostics_full.html:48 -#: templates/toolbar.html:53 -#, fuzzy -#| msgid "Start setup" -msgid "Re-run setup" -msgstr "सटअप शुरु करें" +msgid "Try to repair" +msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:32 #, fuzzy @@ -1704,10 +1701,15 @@ msgstr "परीक्षा" msgid "Result" msgstr "परिणाम" -#: modules/diagnostics/views.py:111 +#: modules/diagnostics/views.py:114 msgid "Diagnostic Test" msgstr "निदान परिक्षा" +#: modules/diagnostics/views.py:144 +#, python-brace-format +msgid "App {app_id} is not installed, cannot repair" +msgstr "" + #: modules/dynamicdns/__init__.py:28 #, python-brace-format msgid "" @@ -1907,7 +1909,7 @@ msgstr "स्थिति" #: modules/dynamicdns/templates/dynamicdns.html:18 #: modules/email/templates/email.html:35 #: modules/letsencrypt/templates/letsencrypt.html:24 -#: modules/mediawiki/forms.py:64 modules/nextcloud/forms.py:26 +#: modules/mediawiki/forms.py:64 msgid "Domain" msgstr "डोमेन" @@ -2316,13 +2318,13 @@ msgstr "" msgid "Port {name} ({details}) available for internal networks" msgstr "%(service_name)s सिर्फ आंतरिक नेटवर्क्स पर उपलब्ध है." -#: modules/firewall/components.py:153 +#: modules/firewall/components.py:154 #, fuzzy, python-brace-format #| msgid "%(service_name)s is available only on internal networks." msgid "Port {name} ({details}) available for external networks" msgstr "%(service_name)s सिर्फ आंतरिक नेटवर्क्स पर उपलब्ध है." -#: modules/firewall/components.py:159 +#: modules/firewall/components.py:160 #, python-brace-format msgid "Port {name} ({details}) unavailable for external networks" msgstr "" @@ -3378,10 +3380,6 @@ msgstr "लेटस एंक्रिप्ट" msgid "Certificates" msgstr "प्रमाण पत्र" -#: modules/letsencrypt/__init__.py:105 -msgid "Cannot test: No domains are configured." -msgstr "" - #: modules/letsencrypt/templates/letsencrypt.html:25 msgid "Certificate Status" msgstr "प्रमाणपत्र स्थिति" @@ -3903,7 +3901,7 @@ msgstr "अक्षम होने पर खिलाड़ियों न msgid "Address" msgstr "ऍड्रेस" -#: modules/minidlna/__init__.py:22 +#: modules/minidlna/__init__.py:20 msgid "" "MiniDLNA is a simple media server software, with the aim of being fully " "compliant with DLNA/UPnP-AV clients. The MiniDLNA daemon serves media files " @@ -3914,28 +3912,22 @@ msgid "" "Kodi." msgstr "" -#: modules/minidlna/__init__.py:44 -msgid "Media streaming server" -msgstr "" - -#: modules/minidlna/__init__.py:47 +#: modules/minidlna/__init__.py:45 msgid "MiniDLNA" msgstr "" -#: modules/minidlna/__init__.py:48 +#: modules/minidlna/__init__.py:46 msgid "Simple Media Server" msgstr "" -#: modules/minidlna/forms.py:13 +#: modules/minidlna/forms.py:20 msgid "Media Files Directory" msgstr "" -#: modules/minidlna/forms.py:14 +#: modules/minidlna/forms.py:21 msgid "" "Directory that MiniDLNA Server will read for content. All sub-directories of " -"this will be also scanned for media files. If you change the default ensure " -"that the new directory exists and that is readable from the \"minidlna\" " -"user. Any user media directories (\"/home/username/\") will usually work." +"this will be also scanned for media files." msgstr "" #: modules/minidlna/manifest.py:10 @@ -3954,11 +3946,7 @@ msgstr "" msgid "totem" msgstr "" -#: modules/minidlna/views.py:35 -msgid "Specified directory does not exist." -msgstr "" - -#: modules/minidlna/views.py:38 +#: modules/minidlna/views.py:33 msgid "Updated media directory" msgstr "" @@ -5195,7 +5183,7 @@ msgstr "कनेक्शन {name} हटाया गया." msgid "Failed to delete connection: Connection not found." msgstr "कनेक्शन हटाने में विफल: कनेक्शन नहीं मिला." -#: modules/nextcloud/__init__.py:20 +#: modules/nextcloud/__init__.py:25 msgid "" "Nextcloud is a self-hosted productivity platform which provides private and " "secure functions for file sharing, collaborative work, and more. Nextcloud " @@ -5204,29 +5192,29 @@ msgid "" "interface." msgstr "" -#: modules/nextcloud/__init__.py:25 +#: modules/nextcloud/__init__.py:30 msgid "All users of FreedomBox can use Nextcloud. To perform administrative " msgstr "" -#: modules/nextcloud/__init__.py:29 +#: modules/nextcloud/__init__.py:34 #, python-brace-format msgid "" "Please note that Nextcloud is installed and run inside a container provided " -"by the Nextcloud project. Security, quality, privacy and legal reviews are " +"by the Nextcloud community. Security, quality, privacy and legal reviews are " "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" -#: modules/nextcloud/__init__.py:35 +#: modules/nextcloud/__init__.py:40 msgid "This app is experimental." msgstr "" -#: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 +#: modules/nextcloud/__init__.py:58 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 msgid "Nextcloud" msgstr "" -#: modules/nextcloud/__init__.py:55 +#: modules/nextcloud/__init__.py:60 msgid "File Storage & Collaboration" msgstr "" @@ -5236,17 +5224,26 @@ msgstr "" msgid "Not set" msgstr "होस्ट नाम सेट हो गया" +#: modules/nextcloud/forms.py:26 +#, fuzzy +#| msgid "Server domain" +msgid "Override domain" +msgstr "सर्वर डोमेन" + #: modules/nextcloud/forms.py:27 -msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +msgid "" +"Set to the domain or IP address that Nextcloud should be forced to generate " +"URLs with. Should not be needed if a valid domain is used to access " +"Nextcloud. Examples: \"myfreedombox.example.org\" or \"example.onion\"." msgstr "" -#: modules/nextcloud/forms.py:30 +#: modules/nextcloud/forms.py:33 #, fuzzy #| msgid "Administrator Password" msgid "Administrator password" msgstr "व्यवस्थापक पासवर्ड" -#: modules/nextcloud/forms.py:31 +#: modules/nextcloud/forms.py:34 #, fuzzy #| msgid "" #| "Set a new password for MediaWiki's administrator account (admin). Leave " @@ -5260,13 +5257,13 @@ msgstr "" "मीडियाविकी एेडमिन अकाउंट के लिये नया पासवर्ड सेट करें (एेडमिन). वर्तमान पासवर्ड रखने के " "लिए इस फ़ील्ड को रिक्त छोड़ें." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default app set" msgid "Default phone region" msgstr "डिफ़ॉल्ट एेप सेट" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8650,80 +8647,116 @@ msgstr "" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "इंस्टॉलिंग" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "डाउनलोडिंग" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "मीडिया बदलाव" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "कॉंफ़िगरेशन फ़ाइल: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "" -#: setup.py:39 +#: setup.py:42 #, fuzzy #| msgid "Install Apps" msgid "Installing app" msgstr "ऐप्लिकेशन इंस्टॉल करें" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "" -#: setup.py:75 +#: setup.py:78 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error installing app: {error}" msgstr "एप्लिकेशन नहीं इंस्टॉल जा सकता: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error repairing app: {error}" +msgstr "एप्लिकेशन नहीं इंस्टॉल जा सकता: {error}" + +#: setup.py:84 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error updating app: {error}" msgstr "एप्लिकेशन नहीं इंस्टॉल जा सकता: {error}" -#: setup.py:82 +#: setup.py:88 #, fuzzy #| msgid "Application installed." msgid "App installed." msgstr "एप्लिकेशन इंस्टॉल हो गया." -#: setup.py:84 +#: setup.py:92 #, fuzzy #| msgid "Last update" msgid "App updated" msgstr "अंतिम अपडेट" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Sharing" +msgid "Repairing app" +msgstr "शेयरिंग" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error running diagnostics: {error}" +msgstr "एप्लिकेशन नहीं इंस्टॉल जा सकता: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "Last update" +msgid "App repaired." +msgstr "अंतिम अपडेट" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 #, fuzzy #| msgid "Install Apps" msgid "Uninstalling app" msgstr "ऐप्लिकेशन इंस्टॉल करें" -#: setup.py:117 +#: setup.py:205 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error uninstalling app: {error}" msgstr "एप्लिकेशन नहीं इंस्टॉल जा सकता: {error}" -#: setup.py:120 +#: setup.py:208 #, fuzzy #| msgid "Application installed." msgid "App uninstalled." msgstr "एप्लिकेशन इंस्टॉल हो गया." -#: setup.py:493 +#: setup.py:581 #, fuzzy #| msgid "Upgrade Packages" msgid "Updating app packages" @@ -9100,6 +9133,12 @@ msgstr "अपडेट" msgid "Backup" msgstr "बैकअप" +#: templates/toolbar.html:53 +#, fuzzy +#| msgid "Start setup" +msgid "Re-run setup" +msgstr "सटअप शुरु करें" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 #, fuzzy diff --git a/plinth/locale/hu/LC_MESSAGES/django.po b/plinth/locale/hu/LC_MESSAGES/django.po index 947f8ac2a..9c62eb1b5 100644 --- a/plinth/locale/hu/LC_MESSAGES/django.po +++ b/plinth/locale/hu/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-10-24 18:39+0000\n" "Last-Translator: Sunil Mohan Adapa \n" "Language-Team: Hungarian kell állnia. Hagyd üresen ezt a mezőt, ha meg szeretnéd " "tartani a jelenlegi jelszót." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default app set" msgid "Default phone region" msgstr "Alapértelmezett alkalmazás beállítva" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8700,82 +8700,118 @@ msgstr "" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "A(z) {package_name} a legfrissebb verzió ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "telepítés" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "letöltés" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "adathordozó csere" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "konfigurációs fájl: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "" -#: setup.py:39 +#: setup.py:42 #, fuzzy #| msgid "Install Apps" msgid "Installing app" msgstr "Alkalmazások telepítése" -#: setup.py:41 +#: setup.py:44 #, fuzzy #| msgid "Updating..." msgid "Updating app" msgstr "Frissítés…" -#: setup.py:75 +#: setup.py:78 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error installing app: {error}" msgstr "Hiba lépett fel az alkalmazás telepítésekor: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error repairing app: {error}" +msgstr "Hiba lépett fel az alkalmazás telepítésekor: {error}" + +#: setup.py:84 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error updating app: {error}" msgstr "Hiba lépett fel az alkalmazás telepítésekor: {error}" -#: setup.py:82 +#: setup.py:88 #, fuzzy #| msgid "Application installed." msgid "App installed." msgstr "Alkalmazás telepítve." -#: setup.py:84 +#: setup.py:92 #, fuzzy #| msgid "Last update" msgid "App updated" msgstr "Legutolsó frissítés" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating..." +msgid "Repairing app" +msgstr "Frissítés…" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Hiba lépett fel az alkalmazás telepítésekor: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "Last update" +msgid "App repaired." +msgstr "Legutolsó frissítés" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 #, fuzzy #| msgid "Install Apps" msgid "Uninstalling app" msgstr "Alkalmazások telepítése" -#: setup.py:117 +#: setup.py:205 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error uninstalling app: {error}" msgstr "Hiba lépett fel az alkalmazás telepítésekor: {error}" -#: setup.py:120 +#: setup.py:208 #, fuzzy #| msgid "Application installed." msgid "App uninstalled." msgstr "Alkalmazás telepítve." -#: setup.py:493 +#: setup.py:581 #, fuzzy #| msgid "Upgrade Packages" msgid "Updating app packages" @@ -9153,6 +9189,12 @@ msgstr "Frissítés" msgid "Backup" msgstr "Biztonsági mentések" +#: templates/toolbar.html:53 +#, fuzzy +#| msgid "Start setup" +msgid "Re-run setup" +msgstr "Beállítás elindítása" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 #, fuzzy @@ -9185,6 +9227,28 @@ msgstr "" msgid "Gujarati" msgstr "Gudzsaráti" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Sikertelen tesztelés: Nincsenek konfigurált domainek." + +#~ msgid "Media streaming server" +#~ msgstr "Média streaming szerver" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Könyvtár, amelyet a MiniDLNA-szerver befog olvasni tartalomért. Ennek " +#~ "minden alkönyvtára szintén át lesz vizsgálva médiafájlok után. Ha " +#~ "megváltoztatod az alapértelmezést akkor győződj meg arról is, hogy az új " +#~ "könyvtár létezik és olvasható a \"minidlna\" felhasználó által. Általában " +#~ "bármely felhasználói médiakönyvtár (\"/home/felhasznalonev/\") működik." + +#~ msgid "Specified directory does not exist." +#~ msgstr "A megadott könyvtár nem létezik." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Tárhelypillanatképek konfigurációja frissítve" diff --git a/plinth/locale/id/LC_MESSAGES/django.po b/plinth/locale/id/LC_MESSAGES/django.po index c7a70b36a..18dee8a28 100644 --- a/plinth/locale/id/LC_MESSAGES/django.po +++ b/plinth/locale/id/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Indonesian (FreedomBox)\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-09-14 17:19+0000\n" "Last-Translator: ikmaak \n" "Language-Team: Indonesian \n" "Language-Team: Italian \n" "Language-Team: Japanese \n" "Language-Team: Kannada \n" "Language-Team: Lithuanian \n" "Language-Team: Latvian \n" "Language-Team: Norwegian Bokmål \n" "Language-Team: Dutch 10 tekens zijn, en niet makkelijk " "te raden. Laat dit veld leeg om het huidige wachtwoord te behouden." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default zone is external" msgid "Default phone region" msgstr "Standaardzone is extern" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8590,67 +8592,103 @@ msgstr "Pakket {package_expression} is niet beschikbaar voor installatie" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Pakket {package_name} is de nieuwste versie ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "installeren" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "downloaden" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "media wijzigen" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "configuratiebestand: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Time-out wachtend op pakketbeheerder" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Toepassing installeren" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Toepassing updaten" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Fout bij het installeren van de toepassing: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Fout bij het bijwerken van de toepassing: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Fout bij het bijwerken van de toepassing: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "De toepassing is geïnstalleerd." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Toepassing bijgewerkt" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Toepassing updaten" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Fout bij het verwijderen van de toepassing: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Toepassing bijgewerkt" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Toepassing wordt verwijderd" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Fout bij het verwijderen van de toepassing: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "De toepassing is verwijderd." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Toepassings-pakketten bijwerken" @@ -9022,6 +9060,10 @@ msgstr "Update" msgid "Backup" msgstr "Back-up" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Setup opnieuw uitvoeren" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9053,6 +9095,28 @@ msgstr "voor het verwijderen van {app_id}" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Kan niet testen: Er zijn geen domeinen ingesteld." + +#~ msgid "Media streaming server" +#~ msgstr "Mediastreaming server" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Map die de MiniDLNA Server zal doorzoeken. Alle submappen hiervan worden " +#~ "ook doorzocht op mediabestanden. Als de standaardmap verandert, zorg er " +#~ "dan voor dat de nieuwe map bestaat en dat deze leesbaar is voor de " +#~ "\"minidlna\" -gebruiker. Alle mediamappen van gebruikers binnen (\"/ " +#~ "home / gebruikersnaam /\") zullen meestal werken." + +#~ msgid "Specified directory does not exist." +#~ msgstr "De opgegeven map bestaat niet." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Opslag van Snapshots configuratie is bijgewerkt" diff --git a/plinth/locale/pl/LC_MESSAGES/django.po b/plinth/locale/pl/LC_MESSAGES/django.po index 88fcae407..7ad04f970 100644 --- a/plinth/locale/pl/LC_MESSAGES/django.po +++ b/plinth/locale/pl/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-09-14 17:19+0000\n" "Last-Translator: ikmaak \n" "Language-Team: Polish \n" "Language-Team: Portuguese \n" "Language-Team: Russian 10 символов. Оставьте это поле пустым, чтобы сохранить " "текущий пароль." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default app set" msgid "Default phone region" msgstr "Приложение по умолчанию настроено" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8710,82 +8710,118 @@ msgstr "Пакет {expression} недоступен для установки" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Пакет {package_name} последней версией ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "Установка" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "Загрузка" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "изменение медиа" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "Файл настроек: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "" -#: setup.py:39 +#: setup.py:42 #, fuzzy #| msgid "Install Apps" msgid "Installing app" msgstr "Установка приложений" -#: setup.py:41 +#: setup.py:44 #, fuzzy #| msgid "Updating..." msgid "Updating app" msgstr "Обновляется..." -#: setup.py:75 +#: setup.py:78 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error installing app: {error}" msgstr "Ошибка при установке приложения: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error repairing app: {error}" +msgstr "Ошибка при установке приложения: {error}" + +#: setup.py:84 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error updating app: {error}" msgstr "Ошибка при установке приложения: {error}" -#: setup.py:82 +#: setup.py:88 #, fuzzy #| msgid "Application installed." msgid "App installed." msgstr "Приложение установлено." -#: setup.py:84 +#: setup.py:92 #, fuzzy #| msgid "Last update" msgid "App updated" msgstr "Последнее обновление" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating..." +msgid "Repairing app" +msgstr "Обновляется..." + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Ошибка при установке приложения: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "Last update" +msgid "App repaired." +msgstr "Последнее обновление" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 #, fuzzy #| msgid "Install Apps" msgid "Uninstalling app" msgstr "Установка приложений" -#: setup.py:117 +#: setup.py:205 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error uninstalling app: {error}" msgstr "Ошибка при установке приложения: {error}" -#: setup.py:120 +#: setup.py:208 #, fuzzy #| msgid "Application installed." msgid "App uninstalled." msgstr "Приложение установлено." -#: setup.py:493 +#: setup.py:581 #, fuzzy #| msgid "Upgrade Packages" msgid "Updating app packages" @@ -9162,6 +9198,12 @@ msgstr "Обновление" msgid "Backup" msgstr "Резервные копии" +#: templates/toolbar.html:53 +#, fuzzy +#| msgid "Start setup" +msgid "Re-run setup" +msgstr "Запуск программы установки" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 #, fuzzy @@ -9194,6 +9236,31 @@ msgstr "" msgid "Gujarati" msgstr "Гуджарати" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Невозможно провести тестирование: Не настроены домены." + +#~ msgid "Media streaming server" +#~ msgstr "Сервер потоковой передачи мультимедиа" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Каталог, который MiniDLNA Server будет читать для содержимого. Все его " +#~ "подкаталоги также будут сканироваться на наличие файлов мультимедиа. Если " +#~ "вы измените значение по умолчанию, убедитесь, что новый каталог " +#~ "существует и доступен для чтения пользователем minidlna. Любые " +#~ "пользовательские медиа-каталоги (\"/home/username/\") обычно работают." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Указанный каталог не существует." + +#~ msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#~ msgstr "Примеры: «myfreedombox.example.org» или «example.onion»." + #~ msgid "To perform administrative actions, use the " #~ msgstr "Для выполнения административных действий, используйте " diff --git a/plinth/locale/si/LC_MESSAGES/django.po b/plinth/locale/si/LC_MESSAGES/django.po index f8d22995f..6f51cac86 100644 --- a/plinth/locale/si/LC_MESSAGES/django.po +++ b/plinth/locale/si/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2021-04-27 13:32+0000\n" "Last-Translator: HelaBasa \n" "Language-Team: Sinhala \n" "Language-Team: Slovenian \n" "Language-Team: Albanian 10 shenja. Që të " "mbani fjalëkalimin e tanishëm, lëreni të zbrazët këtë fushë." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "Zonë telefonike parazgjedhje" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8630,67 +8632,103 @@ msgid "Package {package_name} is the latest version ({latest_version})" msgstr "" "Paketa {package_name} gjendet nën versionin më të ri ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "po instalohet" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "po shkarkohet" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "ndryshim media" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "kartelë formësimi: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Mbaroi koha teksa pritej për përgjegjës paketash" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Po instalohet aplikacioni" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Po përditësohet aplikacioni" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Gabim në instalimin e aplikacionit: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Gabim në përditësimin e aplikacionit: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Gabim në përditësimin e aplikacionit: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "Aplikacioni u instalua." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Aplikacioni u përditësua" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Po përditësohet aplikacioni" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Gabim në çinstalimin e aplikacionit: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Aplikacioni u përditësua" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Po çinstalohet aplikacion" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Gabim në çinstalimin e aplikacionit: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Aplikacioni u çinstalua." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Po përditësohet paketa aplikacioni" @@ -9061,6 +9099,10 @@ msgstr "Përditësoje" msgid "Backup" msgstr "Kopjeruajtje" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Ribëni ujdisjen" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9092,6 +9134,31 @@ msgstr "para çinstalimit të {app_id}" msgid "Gujarati" msgstr "Gujaratase" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "S’mund të testojë: S’ka përkatësi të formësuara." + +#~ msgid "Media streaming server" +#~ msgstr "Shërbyes transmetimi mediash" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Drejtoria që Shërbyesi MiniDLNA do të lexojë për lëndë. Krejt " +#~ "nëndrejtoritë e saj do të skanohen gjithashtu për kartela media. Nëse " +#~ "ndryshoni parazgjedhjen, sigurohuni që drejtoria e re ekziston dhe se " +#~ "është e lexueshme nga përdoruesi “minidlna”. Çfarëdo drejtorish media " +#~ "përdoruesish (\"/home/emërpërdoruesi/\") zakonisht do të funksionojnë." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Drejtoria e dhënë s’ekziston." + +#~ msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#~ msgstr "Shembuj: “myfreedombox.example.org”, ose “example.onion”." + #~ msgid "To perform administrative actions, use the " #~ msgstr "Që të kryeni veprime administrative, përdorni " diff --git a/plinth/locale/sr/LC_MESSAGES/django.po b/plinth/locale/sr/LC_MESSAGES/django.po index 67135d148..cb507c53a 100644 --- a/plinth/locale/sr/LC_MESSAGES/django.po +++ b/plinth/locale/sr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2022-09-14 17:20+0000\n" "Last-Translator: ikmaak \n" "Language-Team: Serbian \n" "Language-Team: Swedish . Lämna det här fältet tomt om du vill behålla det nuvarande " "lösenordet." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default zone is external" msgid "Default phone region" msgstr "Standardzonen är extern" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8565,67 +8567,103 @@ msgstr "Paket {package_expression} är inte tillgänglig för installation" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Paketet {package_name} är den senaste versionen ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "Installera" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "ladda ner" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "Mediabyte" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "konfigurationsfil: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Timeout väntar på pakethanteraren" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Installera app" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Uppdatera app" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Fel vid installation av app: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Fel vid uppdatering av app: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Fel vid uppdatering av app: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "App installerad." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "App uppdaterad" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Uppdatera app" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Fel vid avinstallation av appen: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "App uppdaterad" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Avinstallera app" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Fel vid avinstallation av appen: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Appen avinstallerad." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Uppdatera appaket" @@ -8998,6 +9036,10 @@ msgstr "Uppdatera" msgid "Backup" msgstr "Säkerhetskopia" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Kör installation igen" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9029,6 +9071,28 @@ msgstr "innan du avinstallerar {app_id}" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Kan inte testa: Inga domäner är konfigurerade." + +#~ msgid "Media streaming server" +#~ msgstr "Media Streaming Server" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Katalog som MiniDLNA-servern kommer att läsa för innehåll. Alla " +#~ "underkataloger i detta kommer också att skannas för mediefiler. Om du " +#~ "ändrar standardvärdet se till att den nya katalogen finns och som är " +#~ "läsbar från \"minidlna\"-användaren. Alla användar mediekataloger (\"/" +#~ "Home/username/\") kommer vanligtvis att fungera." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Den angivna katalogen finns inte." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Lagring ögonblicksbildkonfiguration uppdaterad" diff --git a/plinth/locale/ta/LC_MESSAGES/django.po b/plinth/locale/ta/LC_MESSAGES/django.po index d57aa4527..a64ee58f6 100644 --- a/plinth/locale/ta/LC_MESSAGES/django.po +++ b/plinth/locale/ta/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -27,27 +27,27 @@ msgstr "" msgid "FreedomBox" msgstr "" -#: daemon.py:122 +#: daemon.py:124 #, python-brace-format msgid "Service {service_name} is running" msgstr "" -#: daemon.py:218 +#: daemon.py:222 #, python-brace-format msgid "Listening on {kind} port {listen_address}:{port}" msgstr "" -#: daemon.py:221 +#: daemon.py:225 #, python-brace-format msgid "Listening on {kind} port {port}" msgstr "" -#: daemon.py:291 +#: daemon.py:296 #, python-brace-format msgid "Connect to {host}:{port}" msgstr "" -#: daemon.py:299 +#: daemon.py:304 #, python-brace-format msgid "Cannot connect to {host}:{port}" msgstr "" @@ -134,12 +134,12 @@ msgstr "" msgid "{box_name} Web Interface (Plinth)" msgstr "" -#: modules/apache/components.py:159 +#: modules/apache/components.py:162 #, python-brace-format msgid "Access URL {url} on tcp{kind}" msgstr "" -#: modules/apache/components.py:162 +#: modules/apache/components.py:165 #, python-brace-format msgid "Access URL {url}" msgstr "" @@ -844,7 +844,7 @@ msgstr "" msgid "Admin" msgstr "" -#: modules/bepasty/views.py:88 modules/diagnostics/views.py:52 +#: modules/bepasty/views.py:88 modules/diagnostics/views.py:55 #: modules/nextcloud/views.py:62 modules/searx/views.py:35 #: modules/searx/views.py:46 modules/security/views.py:56 #: modules/snapshot/views.py:158 modules/tor/views.py:73 @@ -1448,8 +1448,7 @@ msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:20 #: modules/diagnostics/templates/diagnostics_full.html:48 -#: templates/toolbar.html:53 -msgid "Re-run setup" +msgid "Try to repair" msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:32 @@ -1489,10 +1488,15 @@ msgstr "" msgid "Result" msgstr "" -#: modules/diagnostics/views.py:111 +#: modules/diagnostics/views.py:114 msgid "Diagnostic Test" msgstr "" +#: modules/diagnostics/views.py:144 +#, python-brace-format +msgid "App {app_id} is not installed, cannot repair" +msgstr "" + #: modules/dynamicdns/__init__.py:28 #, python-brace-format msgid "" @@ -1650,7 +1654,7 @@ msgstr "" #: modules/dynamicdns/templates/dynamicdns.html:18 #: modules/email/templates/email.html:35 #: modules/letsencrypt/templates/letsencrypt.html:24 -#: modules/mediawiki/forms.py:64 modules/nextcloud/forms.py:26 +#: modules/mediawiki/forms.py:64 msgid "Domain" msgstr "" @@ -1998,12 +2002,12 @@ msgstr "" msgid "Port {name} ({details}) available for internal networks" msgstr "" -#: modules/firewall/components.py:153 +#: modules/firewall/components.py:154 #, python-brace-format msgid "Port {name} ({details}) available for external networks" msgstr "" -#: modules/firewall/components.py:159 +#: modules/firewall/components.py:160 #, python-brace-format msgid "Port {name} ({details}) unavailable for external networks" msgstr "" @@ -2921,10 +2925,6 @@ msgstr "" msgid "Certificates" msgstr "" -#: modules/letsencrypt/__init__.py:105 -msgid "Cannot test: No domains are configured." -msgstr "" - #: modules/letsencrypt/templates/letsencrypt.html:25 msgid "Certificate Status" msgstr "" @@ -3362,7 +3362,7 @@ msgstr "" msgid "Address" msgstr "" -#: modules/minidlna/__init__.py:22 +#: modules/minidlna/__init__.py:20 msgid "" "MiniDLNA is a simple media server software, with the aim of being fully " "compliant with DLNA/UPnP-AV clients. The MiniDLNA daemon serves media files " @@ -3373,28 +3373,22 @@ msgid "" "Kodi." msgstr "" -#: modules/minidlna/__init__.py:44 -msgid "Media streaming server" -msgstr "" - -#: modules/minidlna/__init__.py:47 +#: modules/minidlna/__init__.py:45 msgid "MiniDLNA" msgstr "" -#: modules/minidlna/__init__.py:48 +#: modules/minidlna/__init__.py:46 msgid "Simple Media Server" msgstr "" -#: modules/minidlna/forms.py:13 +#: modules/minidlna/forms.py:20 msgid "Media Files Directory" msgstr "" -#: modules/minidlna/forms.py:14 +#: modules/minidlna/forms.py:21 msgid "" "Directory that MiniDLNA Server will read for content. All sub-directories of " -"this will be also scanned for media files. If you change the default ensure " -"that the new directory exists and that is readable from the \"minidlna\" " -"user. Any user media directories (\"/home/username/\") will usually work." +"this will be also scanned for media files." msgstr "" #: modules/minidlna/manifest.py:10 @@ -3413,11 +3407,7 @@ msgstr "" msgid "totem" msgstr "" -#: modules/minidlna/views.py:35 -msgid "Specified directory does not exist." -msgstr "" - -#: modules/minidlna/views.py:38 +#: modules/minidlna/views.py:33 msgid "Updated media directory" msgstr "" @@ -4541,7 +4531,7 @@ msgstr "" msgid "Failed to delete connection: Connection not found." msgstr "" -#: modules/nextcloud/__init__.py:20 +#: modules/nextcloud/__init__.py:25 msgid "" "Nextcloud is a self-hosted productivity platform which provides private and " "secure functions for file sharing, collaborative work, and more. Nextcloud " @@ -4550,29 +4540,29 @@ msgid "" "interface." msgstr "" -#: modules/nextcloud/__init__.py:25 +#: modules/nextcloud/__init__.py:30 msgid "All users of FreedomBox can use Nextcloud. To perform administrative " msgstr "" -#: modules/nextcloud/__init__.py:29 +#: modules/nextcloud/__init__.py:34 #, python-brace-format msgid "" "Please note that Nextcloud is installed and run inside a container provided " -"by the Nextcloud project. Security, quality, privacy and legal reviews are " +"by the Nextcloud community. Security, quality, privacy and legal reviews are " "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" -#: modules/nextcloud/__init__.py:35 +#: modules/nextcloud/__init__.py:40 msgid "This app is experimental." msgstr "" -#: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 +#: modules/nextcloud/__init__.py:58 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 msgid "Nextcloud" msgstr "" -#: modules/nextcloud/__init__.py:55 +#: modules/nextcloud/__init__.py:60 msgid "File Storage & Collaboration" msgstr "" @@ -4580,15 +4570,22 @@ msgstr "" msgid "Not set" msgstr "" -#: modules/nextcloud/forms.py:27 -msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#: modules/nextcloud/forms.py:26 +msgid "Override domain" msgstr "" -#: modules/nextcloud/forms.py:30 +#: modules/nextcloud/forms.py:27 +msgid "" +"Set to the domain or IP address that Nextcloud should be forced to generate " +"URLs with. Should not be needed if a valid domain is used to access " +"Nextcloud. Examples: \"myfreedombox.example.org\" or \"example.onion\"." +msgstr "" + +#: modules/nextcloud/forms.py:33 msgid "Administrator password" msgstr "" -#: modules/nextcloud/forms.py:31 +#: modules/nextcloud/forms.py:34 msgid "" "Optional. Set a new password for Nextcloud's administrator account " "(nextcloud-admin). The password cannot be a common one and the minimum " @@ -4596,11 +4593,11 @@ msgid "" "keep the current password." msgstr "" -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -7428,67 +7425,97 @@ msgstr "" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "" -#: setup.py:75 -#, python-brace-format -msgid "Error installing app: {error}" -msgstr "" - #: setup.py:78 #, python-brace-format -msgid "Error updating app: {error}" +msgid "Error installing app: {error}" msgstr "" -#: setup.py:82 -msgid "App installed." +#: setup.py:81 setup.py:151 +#, python-brace-format +msgid "Error repairing app: {error}" msgstr "" #: setup.py:84 +#, python-brace-format +msgid "Error updating app: {error}" +msgstr "" + +#: setup.py:88 +msgid "App installed." +msgstr "" + +#: setup.py:92 msgid "App updated" msgstr "" -#: setup.py:101 +#: setup.py:110 +msgid "Repairing app" +msgstr "" + +#: setup.py:130 +#, python-brace-format +msgid "Error running diagnostics: {error}" +msgstr "" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +msgid "App repaired." +msgstr "" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "" -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "" @@ -7821,6 +7848,10 @@ msgstr "" msgid "Backup" msgstr "" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" diff --git a/plinth/locale/te/LC_MESSAGES/django.po b/plinth/locale/te/LC_MESSAGES/django.po index c5d8a7041..ba6589e48 100644 --- a/plinth/locale/te/LC_MESSAGES/django.po +++ b/plinth/locale/te/LC_MESSAGES/django.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: FreedomBox UI\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2024-02-11 20:14+0000\n" "Last-Translator: Sunil Mohan Adapa \n" "Language-Team: Telugu \n" "Language-Team: Turkish 10 karakterdir. Şu anki parolayı korumak için bu alanı boş " "bırakın." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "Varsayılan telefon bölgesi" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8568,67 +8575,103 @@ msgstr "{package_expression} paketi yükleme için mevcut değil" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "{package_name} paketi en son sürümdür ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "yükleniyor" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "indiriliyor" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "ortam değiştirme" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "yapılandırma dosyası: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Paket yöneticisini beklerken zaman aşımı oldu" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Uygulama yükleniyor" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Uygulama güncelleniyor" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Uygulama yüklenirken hata oldu: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Uygulama güncellenirken hata oldu: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Uygulama güncellenirken hata oldu: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "Uygulama yüklendi." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Uygulama güncellendi" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Uygulama güncelleniyor" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Uygulama kaldırılırken hata oldu: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Uygulama güncellendi" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Uygulama kaldırılıyor" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Uygulama kaldırılırken hata oldu: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Uygulama kaldırıldı." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Uygulama paketleri güncelleniyor" @@ -8999,6 +9042,10 @@ msgstr "Güncelle" msgid "Backup" msgstr "Yedekle" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Ayarlamayı yeniden çalıştır" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9030,6 +9077,31 @@ msgstr "{app_id} kaldırılmadan önce" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Denenemiyor: Hiçbir etki alanı yapılandırılmamış." + +#~ msgid "Media streaming server" +#~ msgstr "Ortam akış sunucusu" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "MiniDLNA Sunucusunun içerik için okuyacağı dizin. Bunun tüm alt dizinleri " +#~ "de ortam dosyaları için taranacaktır. Eğer varsayılanı değiştirirseniz, " +#~ "yeni dizinin var olduğundan ve \"minidlna\" kullanıcısının okuyabilir " +#~ "olduğundan emin olun. Herhangi bir kullanıcı ortam dizinleri (\"/home/" +#~ "kullanıcıadı/\") genellikle çalışacaktır." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Belirtilen dizin mevcut değil." + +#~ msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +#~ msgstr "Örnekler: \"freedomboxım.ornek.org\" veya \"ornek.onion\"." + #~ msgid "To perform administrative actions, use the " #~ msgstr "Yönetimsel eylemleri gerçekleştirmek için şunu kullanın: " diff --git a/plinth/locale/uk/LC_MESSAGES/django.po b/plinth/locale/uk/LC_MESSAGES/django.po index 1977efdf8..01d06ba9c 100644 --- a/plinth/locale/uk/LC_MESSAGES/django.po +++ b/plinth/locale/uk/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2024-02-05 00:01+0000\n" "Last-Translator: Ihor Hordiichuk \n" "Language-Team: Ukrainian 10 " "знаків. Залиште поле порожнім, щоб зберегти поточний пароль." -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 #, fuzzy #| msgid "Default zone is external" msgid "Default phone region" msgstr "Усталена зона – зовнішня" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -8576,67 +8577,103 @@ msgstr "Пакунок {package_expression} недоступний для вст msgid "Package {package_name} is the latest version ({latest_version})" msgstr "Пакунок {package_name} має останню версію ({latest_version})" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "встановлення" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "завантаження" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "зміна медія" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "файл конфіґурації: {file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "Час очікування менеджера пакунків" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "Встановлення застосунку" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "Оновлення застосунку" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "Помилка встановлення застосунку: {error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "Помилка оновлення застосунку: {error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "Помилка оновлення застосунку: {error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "Застосунок встановлено." -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "Застосунок оновлено" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "Updating app" +msgid "Repairing app" +msgstr "Оновлення застосунку" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "Помилка видалення застосунку: {error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "Застосунок оновлено" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "Видалення застосунку" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "Помилка видалення застосунку: {error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "Застосунок видалено." -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "Оновлення пакунків застосунків" @@ -9007,6 +9044,10 @@ msgstr "Оновити" msgid "Backup" msgstr "Резервна копія" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "Повторно розпочати налаштування" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" @@ -9038,6 +9079,29 @@ msgstr "перед видаленням {app_id}" msgid "Gujarati" msgstr "Gujarati" +#~ msgid "Cannot test: No domains are configured." +#~ msgstr "Тестування не можливе: Нема налаштованих доменів." + +#~ msgid "Media streaming server" +#~ msgstr "Сервер потокового медія" + +#~ msgid "" +#~ "Directory that MiniDLNA Server will read for content. All sub-directories " +#~ "of this will be also scanned for media files. If you change the default " +#~ "ensure that the new directory exists and that is readable from the " +#~ "\"minidlna\" user. Any user media directories (\"/home/username/\") will " +#~ "usually work." +#~ msgstr "" +#~ "Каталог, який MiniDLNA Server буде зчитувати на предмет вмісту. Всі " +#~ "підкаталоги цього каталогу також будуть проскановані на наявність " +#~ "мультимедійних файлів. Якщо ви змінюєте типові значення, переконайтеся, " +#~ "що новий каталог існує і що він доступний для читання від користувача " +#~ "\"minidlna\". Будь-які каталоги медіафайлів користувача (\"/home/ім'я " +#~ "користувача/\"), як правило, будуть працювати." + +#~ msgid "Specified directory does not exist." +#~ msgstr "Призначений каталог не існує." + #~ msgid "Storage snapshots configuration updated" #~ msgstr "Налаштування зрізів сховища оновлено" diff --git a/plinth/locale/vi/LC_MESSAGES/django.po b/plinth/locale/vi/LC_MESSAGES/django.po index efd3603cc..c2bb7215e 100644 --- a/plinth/locale/vi/LC_MESSAGES/django.po +++ b/plinth/locale/vi/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2021-07-28 08:34+0000\n" "Last-Translator: bruh \n" "Language-Team: Vietnamese \n" @@ -29,27 +29,27 @@ msgstr "" msgid "FreedomBox" msgstr "FreedomBox" -#: daemon.py:122 +#: daemon.py:124 #, python-brace-format msgid "Service {service_name} is running" msgstr "服务{service_name}正在运行" -#: daemon.py:218 +#: daemon.py:222 #, python-brace-format msgid "Listening on {kind} port {listen_address}:{port}" msgstr "正在侦听 {kind} 端口 {listen_address}:{port}" -#: daemon.py:221 +#: daemon.py:225 #, python-brace-format msgid "Listening on {kind} port {port}" msgstr "正在侦听 {kind} 端口 {port}" -#: daemon.py:291 +#: daemon.py:296 #, python-brace-format msgid "Connect to {host}:{port}" msgstr "连接到主机 {host}:{port}" -#: daemon.py:299 +#: daemon.py:304 #, python-brace-format msgid "Cannot connect to {host}:{port}" msgstr "不能连接到主机 {host}:{port}" @@ -136,12 +136,12 @@ msgstr "Web 服务器" msgid "{box_name} Web Interface (Plinth)" msgstr "{box_name} Web 界面(Plinth)" -#: modules/apache/components.py:159 +#: modules/apache/components.py:162 #, python-brace-format msgid "Access URL {url} on tcp{kind}" msgstr "在 tcp {kind} 访问 URL {url}" -#: modules/apache/components.py:162 +#: modules/apache/components.py:165 #, python-brace-format msgid "Access URL {url}" msgstr "访问 URL {url}" @@ -879,7 +879,7 @@ msgstr "删除" msgid "Admin" msgstr "管理员" -#: modules/bepasty/views.py:88 modules/diagnostics/views.py:52 +#: modules/bepasty/views.py:88 modules/diagnostics/views.py:55 #: modules/nextcloud/views.py:62 modules/searx/views.py:35 #: modules/searx/views.py:46 modules/security/views.py:56 #: modules/snapshot/views.py:158 modules/tor/views.py:73 @@ -1519,9 +1519,8 @@ msgstr "应用程序。%(app_name)s" #: modules/diagnostics/templates/diagnostics_app.html:20 #: modules/diagnostics/templates/diagnostics_full.html:48 -#: templates/toolbar.html:53 -msgid "Re-run setup" -msgstr "重新运行安装程序" +msgid "Try to repair" +msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:32 msgid "This app does not support diagnostics" @@ -1563,10 +1562,15 @@ msgstr "测试" msgid "Result" msgstr "结果" -#: modules/diagnostics/views.py:111 +#: modules/diagnostics/views.py:114 msgid "Diagnostic Test" msgstr "诊断测试" +#: modules/diagnostics/views.py:144 +#, python-brace-format +msgid "App {app_id} is not installed, cannot repair" +msgstr "" + #: modules/dynamicdns/__init__.py:28 #, python-brace-format msgid "" @@ -1744,7 +1748,7 @@ msgstr "状态" #: modules/dynamicdns/templates/dynamicdns.html:18 #: modules/email/templates/email.html:35 #: modules/letsencrypt/templates/letsencrypt.html:24 -#: modules/mediawiki/forms.py:64 modules/nextcloud/forms.py:26 +#: modules/mediawiki/forms.py:64 msgid "Domain" msgstr "域名" @@ -2110,12 +2114,12 @@ msgstr "" msgid "Port {name} ({details}) available for internal networks" msgstr "内网端口 {name}({details})可用" -#: modules/firewall/components.py:153 +#: modules/firewall/components.py:154 #, python-brace-format msgid "Port {name} ({details}) available for external networks" msgstr "外网端口 {name}({details})可用" -#: modules/firewall/components.py:159 +#: modules/firewall/components.py:160 #, python-brace-format msgid "Port {name} ({details}) unavailable for external networks" msgstr "" @@ -3074,10 +3078,6 @@ msgstr "Let's Encrypt" msgid "Certificates" msgstr "证书" -#: modules/letsencrypt/__init__.py:105 -msgid "Cannot test: No domains are configured." -msgstr "" - #: modules/letsencrypt/templates/letsencrypt.html:25 msgid "Certificate Status" msgstr "证书状态" @@ -3522,7 +3522,7 @@ msgstr "禁用后,玩家不能死或受到任何伤害。" msgid "Address" msgstr "地址" -#: modules/minidlna/__init__.py:22 +#: modules/minidlna/__init__.py:20 msgid "" "MiniDLNA is a simple media server software, with the aim of being fully " "compliant with DLNA/UPnP-AV clients. The MiniDLNA daemon serves media files " @@ -3533,28 +3533,22 @@ msgid "" "Kodi." msgstr "" -#: modules/minidlna/__init__.py:44 -msgid "Media streaming server" -msgstr "" - -#: modules/minidlna/__init__.py:47 +#: modules/minidlna/__init__.py:45 msgid "MiniDLNA" msgstr "" -#: modules/minidlna/__init__.py:48 +#: modules/minidlna/__init__.py:46 msgid "Simple Media Server" msgstr "简单媒体服务器" -#: modules/minidlna/forms.py:13 +#: modules/minidlna/forms.py:20 msgid "Media Files Directory" msgstr "" -#: modules/minidlna/forms.py:14 +#: modules/minidlna/forms.py:21 msgid "" "Directory that MiniDLNA Server will read for content. All sub-directories of " -"this will be also scanned for media files. If you change the default ensure " -"that the new directory exists and that is readable from the \"minidlna\" " -"user. Any user media directories (\"/home/username/\") will usually work." +"this will be also scanned for media files." msgstr "" #: modules/minidlna/manifest.py:10 @@ -3573,11 +3567,7 @@ msgstr "" msgid "totem" msgstr "" -#: modules/minidlna/views.py:35 -msgid "Specified directory does not exist." -msgstr "" - -#: modules/minidlna/views.py:38 +#: modules/minidlna/views.py:33 msgid "Updated media directory" msgstr "" @@ -4717,7 +4707,7 @@ msgstr "连接 {name} 已删除。" msgid "Failed to delete connection: Connection not found." msgstr "删除连接失败: 找不到连接。" -#: modules/nextcloud/__init__.py:20 +#: modules/nextcloud/__init__.py:25 msgid "" "Nextcloud is a self-hosted productivity platform which provides private and " "secure functions for file sharing, collaborative work, and more. Nextcloud " @@ -4726,29 +4716,29 @@ msgid "" "interface." msgstr "" -#: modules/nextcloud/__init__.py:25 +#: modules/nextcloud/__init__.py:30 msgid "All users of FreedomBox can use Nextcloud. To perform administrative " msgstr "" -#: modules/nextcloud/__init__.py:29 +#: modules/nextcloud/__init__.py:34 #, python-brace-format msgid "" "Please note that Nextcloud is installed and run inside a container provided " -"by the Nextcloud project. Security, quality, privacy and legal reviews are " +"by the Nextcloud community. Security, quality, privacy and legal reviews are " "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" -#: modules/nextcloud/__init__.py:35 +#: modules/nextcloud/__init__.py:40 msgid "This app is experimental." msgstr "" -#: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 +#: modules/nextcloud/__init__.py:58 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 msgid "Nextcloud" msgstr "" -#: modules/nextcloud/__init__.py:55 +#: modules/nextcloud/__init__.py:60 msgid "File Storage & Collaboration" msgstr "" @@ -4756,15 +4746,24 @@ msgstr "" msgid "Not set" msgstr "未设置" +#: modules/nextcloud/forms.py:26 +#, fuzzy +#| msgid "Server domain" +msgid "Override domain" +msgstr "服务器域" + #: modules/nextcloud/forms.py:27 -msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +msgid "" +"Set to the domain or IP address that Nextcloud should be forced to generate " +"URLs with. Should not be needed if a valid domain is used to access " +"Nextcloud. Examples: \"myfreedombox.example.org\" or \"example.onion\"." msgstr "" -#: modules/nextcloud/forms.py:30 +#: modules/nextcloud/forms.py:33 msgid "Administrator password" msgstr "管理员密码" -#: modules/nextcloud/forms.py:31 +#: modules/nextcloud/forms.py:34 msgid "" "Optional. Set a new password for Nextcloud's administrator account " "(nextcloud-admin). The password cannot be a common one and the minimum " @@ -4772,11 +4771,11 @@ msgid "" "keep the current password." msgstr "" -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "默认手机区域" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -7695,67 +7694,103 @@ msgstr "" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "安装" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "下载中" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "媒体改变" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "配置文件:{file}" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "安装应用中" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "" -#: setup.py:75 +#: setup.py:78 #, python-brace-format msgid "Error installing app: {error}" msgstr "安装应用出错:{error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error updating app: {error}" +msgid "Error repairing app: {error}" +msgstr "更新应用出错:{error}" + +#: setup.py:84 #, python-brace-format msgid "Error updating app: {error}" msgstr "更新应用出错:{error}" -#: setup.py:82 +#: setup.py:88 msgid "App installed." msgstr "应用已安装。" -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "应用已更新" -#: setup.py:101 +#: setup.py:110 +#, fuzzy +#| msgid "preparing" +msgid "Repairing app" +msgstr "正在准备" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error uninstalling app: {error}" +msgid "Error running diagnostics: {error}" +msgstr "卸载应用出错:{error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +#, fuzzy +#| msgid "App updated" +msgid "App repaired." +msgstr "应用已更新" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 msgid "Uninstalling app" msgstr "卸载应用" -#: setup.py:117 +#: setup.py:205 #, python-brace-format msgid "Error uninstalling app: {error}" msgstr "卸载应用出错:{error}" -#: setup.py:120 +#: setup.py:208 msgid "App uninstalled." msgstr "应用已卸载。" -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "更新软件包中" @@ -8102,6 +8137,10 @@ msgstr "更新" msgid "Backup" msgstr "备份" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "重新运行安装程序" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 msgid "Uninstall" diff --git a/plinth/locale/zh_Hant/LC_MESSAGES/django.po b/plinth/locale/zh_Hant/LC_MESSAGES/django.po index 7673e5572..ec24d69bc 100644 --- a/plinth/locale/zh_Hant/LC_MESSAGES/django.po +++ b/plinth/locale/zh_Hant/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-22 20:02-0400\n" +"POT-Creation-Date: 2024-05-06 20:18-0400\n" "PO-Revision-Date: 2024-04-24 07:07+0000\n" "Last-Translator: Ray Kuo \n" "Language-Team: Chinese (Traditional) 如果要備份還原到新的 %(box_name)s " -"您需要 SSH 認證和(如果您有設定)加密密碼。" +"此儲存庫的認證存在 %(box_name)s。
如果要備份還原到新的 %(box_name)s 您需" +"要 SSH 認證和(如果您有設定)加密密碼。" #: modules/backups/templates/backups_add_remote_repository.html:28 msgid "Create Location" @@ -630,8 +630,8 @@ msgid "" "one of the provided options. You can also use DSA, ECDSA, Ed25519 etc. " "instead of RSA, by choosing the corresponding file." msgstr "" -"在 SSH 主機執行下列指令。輸出結果應該符合提供選項之一。您也可以使用 " -"DSA、ECDSA、Ed25519 等等。要替換 RSA,可以選擇對應的檔案。" +"在 SSH 主機執行下列指令。輸出結果應該符合提供選項之一。您也可以使用 DSA、" +"ECDSA、Ed25519 等等。要替換 RSA,可以選擇對應的檔案。" #: modules/backups/templates/verify_ssh_hostkey.html:60 msgid "Verify Host" @@ -884,7 +884,7 @@ msgstr "刪除" msgid "Admin" msgstr "管理員" -#: modules/bepasty/views.py:88 modules/diagnostics/views.py:52 +#: modules/bepasty/views.py:88 modules/diagnostics/views.py:55 #: modules/nextcloud/views.py:62 modules/searx/views.py:35 #: modules/searx/views.py:46 modules/security/views.py:56 #: modules/snapshot/views.py:158 modules/tor/views.py:73 @@ -1038,7 +1038,8 @@ msgstr "新圖書館名稱" msgid "" "Only letters of the English alphabet, numbers and the characters _ . and - " "without spaces or special characters. Example: My_Library_2000" -msgstr "只有英文字母、數字和字符 _ . 和 - 不包含空格或特殊字符. 例如:My_Library_2000" +msgstr "" +"只有英文字母、數字和字符 _ . 和 - 不包含空格或特殊字符. 例如:My_Library_2000" #: modules/calibre/forms.py:28 msgid "A library with this name already exists." @@ -1523,8 +1524,7 @@ msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:20 #: modules/diagnostics/templates/diagnostics_full.html:48 -#: templates/toolbar.html:53 -msgid "Re-run setup" +msgid "Try to repair" msgstr "" #: modules/diagnostics/templates/diagnostics_app.html:32 @@ -1564,10 +1564,15 @@ msgstr "" msgid "Result" msgstr "" -#: modules/diagnostics/views.py:111 +#: modules/diagnostics/views.py:114 msgid "Diagnostic Test" msgstr "" +#: modules/diagnostics/views.py:144 +#, python-brace-format +msgid "App {app_id} is not installed, cannot repair" +msgstr "" + #: modules/dynamicdns/__init__.py:28 #, python-brace-format msgid "" @@ -1725,7 +1730,7 @@ msgstr "" #: modules/dynamicdns/templates/dynamicdns.html:18 #: modules/email/templates/email.html:35 #: modules/letsencrypt/templates/letsencrypt.html:24 -#: modules/mediawiki/forms.py:64 modules/nextcloud/forms.py:26 +#: modules/mediawiki/forms.py:64 msgid "Domain" msgstr "" @@ -2097,12 +2102,12 @@ msgstr "" msgid "Port {name} ({details}) available for internal networks" msgstr "" -#: modules/firewall/components.py:153 +#: modules/firewall/components.py:154 #, python-brace-format msgid "Port {name} ({details}) available for external networks" msgstr "" -#: modules/firewall/components.py:159 +#: modules/firewall/components.py:160 #, python-brace-format msgid "Port {name} ({details}) unavailable for external networks" msgstr "" @@ -3028,10 +3033,6 @@ msgstr "" msgid "Certificates" msgstr "" -#: modules/letsencrypt/__init__.py:105 -msgid "Cannot test: No domains are configured." -msgstr "" - #: modules/letsencrypt/templates/letsencrypt.html:25 msgid "Certificate Status" msgstr "" @@ -3479,7 +3480,7 @@ msgstr "" msgid "Address" msgstr "" -#: modules/minidlna/__init__.py:22 +#: modules/minidlna/__init__.py:20 msgid "" "MiniDLNA is a simple media server software, with the aim of being fully " "compliant with DLNA/UPnP-AV clients. The MiniDLNA daemon serves media files " @@ -3490,28 +3491,22 @@ msgid "" "Kodi." msgstr "" -#: modules/minidlna/__init__.py:44 -msgid "Media streaming server" -msgstr "" - -#: modules/minidlna/__init__.py:47 +#: modules/minidlna/__init__.py:45 msgid "MiniDLNA" msgstr "" -#: modules/minidlna/__init__.py:48 +#: modules/minidlna/__init__.py:46 msgid "Simple Media Server" msgstr "" -#: modules/minidlna/forms.py:13 +#: modules/minidlna/forms.py:20 msgid "Media Files Directory" msgstr "" -#: modules/minidlna/forms.py:14 +#: modules/minidlna/forms.py:21 msgid "" "Directory that MiniDLNA Server will read for content. All sub-directories of " -"this will be also scanned for media files. If you change the default ensure " -"that the new directory exists and that is readable from the \"minidlna\" " -"user. Any user media directories (\"/home/username/\") will usually work." +"this will be also scanned for media files." msgstr "" #: modules/minidlna/manifest.py:10 @@ -3530,11 +3525,7 @@ msgstr "" msgid "totem" msgstr "" -#: modules/minidlna/views.py:35 -msgid "Specified directory does not exist." -msgstr "" - -#: modules/minidlna/views.py:38 +#: modules/minidlna/views.py:33 msgid "Updated media directory" msgstr "" @@ -4660,7 +4651,7 @@ msgstr "" msgid "Failed to delete connection: Connection not found." msgstr "" -#: modules/nextcloud/__init__.py:20 +#: modules/nextcloud/__init__.py:25 msgid "" "Nextcloud is a self-hosted productivity platform which provides private and " "secure functions for file sharing, collaborative work, and more. Nextcloud " @@ -4669,29 +4660,29 @@ msgid "" "interface." msgstr "" -#: modules/nextcloud/__init__.py:25 +#: modules/nextcloud/__init__.py:30 msgid "All users of FreedomBox can use Nextcloud. To perform administrative " msgstr "" -#: modules/nextcloud/__init__.py:29 +#: modules/nextcloud/__init__.py:34 #, python-brace-format msgid "" "Please note that Nextcloud is installed and run inside a container provided " -"by the Nextcloud project. Security, quality, privacy and legal reviews are " +"by the Nextcloud community. Security, quality, privacy and legal reviews are " "done by the upstream project and not by Debian/{box_name}. Updates are " "performed following an independent cycle." msgstr "" -#: modules/nextcloud/__init__.py:35 +#: modules/nextcloud/__init__.py:40 msgid "This app is experimental." msgstr "" -#: modules/nextcloud/__init__.py:53 modules/nextcloud/manifest.py:11 +#: modules/nextcloud/__init__.py:58 modules/nextcloud/manifest.py:11 #: modules/nextcloud/manifest.py:18 msgid "Nextcloud" msgstr "" -#: modules/nextcloud/__init__.py:55 +#: modules/nextcloud/__init__.py:60 msgid "File Storage & Collaboration" msgstr "" @@ -4701,17 +4692,26 @@ msgstr "" msgid "Not set" msgstr "主機名稱設定" +#: modules/nextcloud/forms.py:26 +#, fuzzy +#| msgid "Invalid domain name" +msgid "Override domain" +msgstr "無效的網域名稱" + #: modules/nextcloud/forms.py:27 -msgid "Examples: \"myfreedombox.example.org\" or \"example.onion\"." +msgid "" +"Set to the domain or IP address that Nextcloud should be forced to generate " +"URLs with. Should not be needed if a valid domain is used to access " +"Nextcloud. Examples: \"myfreedombox.example.org\" or \"example.onion\"." msgstr "" -#: modules/nextcloud/forms.py:30 +#: modules/nextcloud/forms.py:33 #, fuzzy #| msgid "Server Administration" msgid "Administrator password" msgstr "伺服器管理" -#: modules/nextcloud/forms.py:31 +#: modules/nextcloud/forms.py:34 msgid "" "Optional. Set a new password for Nextcloud's administrator account " "(nextcloud-admin). The password cannot be a common one and the minimum " @@ -4719,11 +4719,11 @@ msgid "" "keep the current password." msgstr "" -#: modules/nextcloud/forms.py:38 +#: modules/nextcloud/forms.py:41 msgid "Default phone region" msgstr "" -#: modules/nextcloud/forms.py:39 +#: modules/nextcloud/forms.py:42 msgid "" "The default phone region is required to validate phone numbers in the " "profile settings without a country code." @@ -7572,76 +7572,108 @@ msgstr "" msgid "Package {package_name} is the latest version ({latest_version})" msgstr "" -#: package.py:419 +#: package.py:420 msgid "installing" msgstr "" -#: package.py:421 +#: package.py:422 msgid "downloading" msgstr "" -#: package.py:423 +#: package.py:424 msgid "media change" msgstr "" -#: package.py:425 +#: package.py:426 #, python-brace-format msgid "configuration file: {file}" msgstr "" -#: package.py:453 package.py:478 +#: package.py:454 package.py:479 msgid "Timeout waiting for package manager" msgstr "" -#: setup.py:39 +#: setup.py:42 msgid "Installing app" msgstr "" -#: setup.py:41 +#: setup.py:44 msgid "Updating app" msgstr "" -#: setup.py:75 +#: setup.py:78 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error installing app: {error}" msgstr "安裝應用遇到錯誤:{error}" -#: setup.py:78 +#: setup.py:81 setup.py:151 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error repairing app: {error}" +msgstr "安裝應用遇到錯誤:{error}" + +#: setup.py:84 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error updating app: {error}" msgstr "安裝應用遇到錯誤:{error}" -#: setup.py:82 +#: setup.py:88 #, fuzzy #| msgid "Application installed." msgid "App installed." msgstr "應用已完成安裝。" -#: setup.py:84 +#: setup.py:92 msgid "App updated" msgstr "" -#: setup.py:101 +#: setup.py:110 +msgid "Repairing app" +msgstr "" + +#: setup.py:130 +#, fuzzy, python-brace-format +#| msgid "Error installing application: {error}" +msgid "Error running diagnostics: {error}" +msgstr "安裝應用遇到錯誤:{error}" + +#: setup.py:143 +msgid "Skipping repair, no failed checks" +msgstr "" + +#: setup.py:157 +msgid "Re-running setup to complete repairs" +msgstr "" + +#: setup.py:165 +msgid "App repaired." +msgstr "" + +#: setup.py:169 +msgid "App repair completed with errors:\n" +msgstr "" + +#: setup.py:189 #, fuzzy #| msgid "Error installing application: {error}" msgid "Uninstalling app" msgstr "安裝應用遇到錯誤:{error}" -#: setup.py:117 +#: setup.py:205 #, fuzzy, python-brace-format #| msgid "Error installing application: {error}" msgid "Error uninstalling app: {error}" msgstr "安裝應用遇到錯誤:{error}" -#: setup.py:120 +#: setup.py:208 #, fuzzy #| msgid "Application installed." msgid "App uninstalled." msgstr "應用已完成安裝。" -#: setup.py:493 +#: setup.py:581 msgid "Updating app packages" msgstr "" @@ -7978,6 +8010,10 @@ msgstr "" msgid "Backup" msgstr "Backups 模組" +#: templates/toolbar.html:53 +msgid "Re-run setup" +msgstr "" + #: templates/toolbar.html:59 templates/toolbar.html:60 #: templates/uninstall.html:30 #, fuzzy @@ -8061,11 +8097,6 @@ msgstr "" #~ msgid "Enable" #~ msgstr "啟用域名系統安全擴充 DNSSEC" -#, fuzzy -#~| msgid "Invalid domain name" -#~ msgid "Enter a valid domain" -#~ msgstr "無效的網域名稱" - #, fuzzy #~| msgid "Delete files" #~ msgid "Delete selected" From 97bebf1a1ed376ba52dbd5b45040ba4d6cef1e00 Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Mon, 6 May 2024 20:58:30 -0400 Subject: [PATCH 35/36] doc: Fetch latest manual Signed-off-by: James Valleroy --- doc/manual/en/ReleaseNotes.raw.wiki | 37 ++++++++++++++++++++++++++++ doc/manual/es/GitWeb.raw.wiki | 2 ++ doc/manual/es/Ikiwiki.raw.wiki | 4 ++- doc/manual/es/Infinoted.raw.wiki | 12 ++++++--- doc/manual/es/JSXC.raw.wiki | 2 +- doc/manual/es/MatrixSynapse.raw.wiki | 7 ++++++ doc/manual/es/ReleaseNotes.raw.wiki | 37 ++++++++++++++++++++++++++++ 7 files changed, 96 insertions(+), 5 deletions(-) diff --git a/doc/manual/en/ReleaseNotes.raw.wiki b/doc/manual/en/ReleaseNotes.raw.wiki index 38943a7f4..0e7742c0d 100644 --- a/doc/manual/en/ReleaseNotes.raw.wiki +++ b/doc/manual/en/ReleaseNotes.raw.wiki @@ -8,6 +8,43 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f The following are the release notes for each !FreedomBox version. +== FreedomBox 24.10 (2024-05-06) == + +=== Highlights === + + * nextcloud: Enable app with experimental warning + * minidlna: Add media directory selection form + +=== Other Changes === + + * app, component: Add repair method + * diagnostics: Add optional component_id to !DiagnosticCheck + * diagnostics: Change "Re-run setup" to "Try to repair" + * letsencrypt: Re-obtain certificates during repair + * letsencrypt: Remove unused imports + * locale: Update translations for Chinese (Simplified), Chinese (Traditional), Czech, Spanish, Turkish + * minidlna: Do not proxy minidlna web interface over Apache + * minidlna: Explicitly include ssdp service to firewall configuration + * minidlna: Restart app when upgrading to reconfigure firewall + * nextcloud: Add fallback for when quadlet is not available + * nextcloud: Allow re-running setup + * nextcloud: Allow re-running setup when app is disabled + * nextcloud: Create network using systemd generator + * nextcloud: Drop network namespacing in container, use host network + * nextcloud: Enable pretty URLs without /index.php in them + * nextcloud: Implement enable/disable container + * nextcloud: Populated and maintain a list of trusted domains + * nextcloud: Pull the image separately before starting systemd unit + * nextcloud: Restart container when dependent services are restarted + * nextcloud: Ship instead of create cron timer related units + * nextcloud: Use php-fpm container instead of apache container + * nextcloud: Use systemd generator for creating container service + * nextcloud: Wait on init sync lock + * nextcloud: Warn that community provides the container not team + * notification: Handle more formatting errors + * setup: Add method to run app repair + * storage: Add an option to include help text to directory selection form + == FreedomBox 24.9 (2024-04-22) == * action_utils, nextcloud: Make podman util more generic diff --git a/doc/manual/es/GitWeb.raw.wiki b/doc/manual/es/GitWeb.raw.wiki index 742f95ddc..9884743ad 100644 --- a/doc/manual/es/GitWeb.raw.wiki +++ b/doc/manual/es/GitWeb.raw.wiki @@ -59,7 +59,9 @@ snapshot = tgz === Enlaces externos === + * Sitio web: https://git-scm.com * Documentación de uso: https://git-scm.com/docs/gitweb + * Wiki de Debian: https://wiki.debian.org/CategoryGit ## END_INCLUDE diff --git a/doc/manual/es/Ikiwiki.raw.wiki b/doc/manual/es/Ikiwiki.raw.wiki index 8d3ed4e3d..931764236 100644 --- a/doc/manual/es/Ikiwiki.raw.wiki +++ b/doc/manual/es/Ikiwiki.raw.wiki @@ -68,7 +68,9 @@ También se pueden instalar temas contribuidos por usuarios desde el Mercado de === Enlaces externos === * Sitio web: https://ikiwiki.info - * Mercado de temas: https://ikiwiki.info/theme_market/ + * Documentación de uso: https://ikiwiki.info + * Foro de usuarios: https://ikiwiki.info/forum/ + * Mercado de temas: https://ikiwiki.info/theme_market/ ## END_INCLUDE diff --git a/doc/manual/es/Infinoted.raw.wiki b/doc/manual/es/Infinoted.raw.wiki index 00d9be635..9c0716221 100644 --- a/doc/manual/es/Infinoted.raw.wiki +++ b/doc/manual/es/Infinoted.raw.wiki @@ -19,12 +19,18 @@ Si tu !FreedomBox está detras de un router necesitarás configurar la redirecci * TCP 6523 -=== Enlaces extenos === +=== Enlaces externos === * Sitio web: https://gobby.github.io/libinfinity - * Software cliente para Gobby: https://gobby.github.io - * Wiki de Gobby: https://github.com/gobby/gobby/wiki +==== Aplicaciones Cliente ==== + +!FreedomBox recomienda algunas aplicaciones cliente. Selecciona su icono en la página de ''Aplicaciones'' y haz clic en el botón'''> Aplicaciones Cliente'''. + + * El cliente principal de Infinoted se llama "Gobby: https://gobby.github.io + * Documentación de uso: https://github.com/gobby/gobby/wiki + * Wiki de Gobby: https://github.com/gobby/gobby/wiki + ## END_INCLUDE Volver a la [[es/FreedomBox/Features|descripción de Funcionalidades]] o a las páginas del [[es/FreedomBox/Manual|manual]]. diff --git a/doc/manual/es/JSXC.raw.wiki b/doc/manual/es/JSXC.raw.wiki index 6db1846d3..c9ed9dd13 100644 --- a/doc/manual/es/JSXC.raw.wiki +++ b/doc/manual/es/JSXC.raw.wiki @@ -46,7 +46,7 @@ Si tu !FreedomBox está detrás de un router y quieres conectarte a otros servid * Sitio web: https://www.jsxc.org * Documentación de uso: https://www.jsxc.org/manual.html - * Manual de !FreedomBox del servidor ejabberd: [[FreedomBox/Manual/ejabberd|https://wiki.debian.org/FreedomBox/Manual/ejabberd]] + * Manual de !FreedomBox del servidor ejabberd: [[es/FreedomBox/Manual/ejabberd|https://wiki.debian.org/es/FreedomBox/Manual/ejabberd]] ## END_INCLUDE diff --git a/doc/manual/es/MatrixSynapse.raw.wiki b/doc/manual/es/MatrixSynapse.raw.wiki index 76d3c9920..322ea8720 100644 --- a/doc/manual/es/MatrixSynapse.raw.wiki +++ b/doc/manual/es/MatrixSynapse.raw.wiki @@ -86,6 +86,13 @@ Si tu !FreedomBox está detrás de un router, necesitarás configurar la redirec * Matrix en el wiki de Debian : https://wiki.debian.org/Matrix * Video tutorial para instalar Matrix Synapse sobre una instancia en la nube: https://youtu.be/8snpMHHbymI + +==== Aplicaciones cliente ==== + +!FreedomBox recomienda algunas aplicaciones cliente. Selecciona su icono en la página de ''Aplicaciones'' y haz clic en el botón'''> Aplicaciones Cliente'''. + + * [[https://wiki.debian.org/Matrix#Clients|Software cliente para Matrix en el wiki de Debian]] + ## END_INCLUDE Volver a la [[es/FreedomBox/Features|descripción de Funcionalidades]] o a las páginas del [[es/FreedomBox/Manual|manual]]. diff --git a/doc/manual/es/ReleaseNotes.raw.wiki b/doc/manual/es/ReleaseNotes.raw.wiki index 38943a7f4..0e7742c0d 100644 --- a/doc/manual/es/ReleaseNotes.raw.wiki +++ b/doc/manual/es/ReleaseNotes.raw.wiki @@ -8,6 +8,43 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f The following are the release notes for each !FreedomBox version. +== FreedomBox 24.10 (2024-05-06) == + +=== Highlights === + + * nextcloud: Enable app with experimental warning + * minidlna: Add media directory selection form + +=== Other Changes === + + * app, component: Add repair method + * diagnostics: Add optional component_id to !DiagnosticCheck + * diagnostics: Change "Re-run setup" to "Try to repair" + * letsencrypt: Re-obtain certificates during repair + * letsencrypt: Remove unused imports + * locale: Update translations for Chinese (Simplified), Chinese (Traditional), Czech, Spanish, Turkish + * minidlna: Do not proxy minidlna web interface over Apache + * minidlna: Explicitly include ssdp service to firewall configuration + * minidlna: Restart app when upgrading to reconfigure firewall + * nextcloud: Add fallback for when quadlet is not available + * nextcloud: Allow re-running setup + * nextcloud: Allow re-running setup when app is disabled + * nextcloud: Create network using systemd generator + * nextcloud: Drop network namespacing in container, use host network + * nextcloud: Enable pretty URLs without /index.php in them + * nextcloud: Implement enable/disable container + * nextcloud: Populated and maintain a list of trusted domains + * nextcloud: Pull the image separately before starting systemd unit + * nextcloud: Restart container when dependent services are restarted + * nextcloud: Ship instead of create cron timer related units + * nextcloud: Use php-fpm container instead of apache container + * nextcloud: Use systemd generator for creating container service + * nextcloud: Wait on init sync lock + * nextcloud: Warn that community provides the container not team + * notification: Handle more formatting errors + * setup: Add method to run app repair + * storage: Add an option to include help text to directory selection form + == FreedomBox 24.9 (2024-04-22) == * action_utils, nextcloud: Make podman util more generic From 334f5f4af9e0dacdb7b0591450619f17a2f9db69 Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Mon, 6 May 2024 21:00:45 -0400 Subject: [PATCH 36/36] Release v24.10 to unstable Signed-off-by: James Valleroy --- debian/changelog | 55 ++++++++++++++++++++++++++++++++++++++++++++++ plinth/__init__.py | 2 +- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 089f05a19..e1b554933 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,58 @@ +freedombox (24.10) unstable; urgency=medium + + [ Veiko Aasa ] + * storage: Add an option to include help text to directory selection form + * minidlna: Add media directory selection form + * minidlna: Explicitly include ssdp service to firewall configuration + * minidlna: Do not proxy minidlna web interface over Apache + * minidlna: Restart app when upgrading to reconfigure firewall + + [ gallegonovato ] + * Translated using Weblate (Spanish) + + [ Burak Yavuz ] + * Translated using Weblate (Turkish) + + [ 大王叫我来巡山 ] + * Translated using Weblate (Chinese (Simplified)) + + [ Jiří Podhorecký ] + * Translated using Weblate (Czech) + + [ Ray Kuo ] + * Translated using Weblate (Chinese (Traditional)) + + [ James Valleroy ] + * diagnostics: Add optional component_id to DiagnosticCheck + * app, component: Add repair method + * setup: Add method to run app repair + * diagnostics: Change "Re-run setup" to "Try to repair" + * letsencrypt: Re-obtain certificates during repair + * locale: Update translation strings + * doc: Fetch latest manual + + [ Sunil Mohan Adapa ] + * letsencrypt: Remove unused imports + * nextcloud: Use systemd generator for creating container service + * nextcloud: Create network using systemd generator + * nextcloud: Drop network namespacing in container, use host network + * nextcloud: Use php-fpm container instead of apache container + * nextcloud: Wait on init sync lock + * nextcloud: Pull the image separately before starting systemd unit + * nextcloud: Ship instead of create cron timer related units + * nextcloud: Restart container when dependent services are restarted + * nextcloud: Allow re-running setup + * nextcloud: Implement enable/disable container + * nextcloud: Enable pretty URLs without /index.php in them + * notification: Handle more formatting errors + * nextcloud: Allow re-running setup when app is disabled + * nextcloud: Populated and maintain a list of trusted domains + * nextcloud: Enable app with experimental warning + * nextcloud: Warn that community provides the container not team + * nextcloud: Add fallback for when quadlet is not available + + -- James Valleroy Mon, 06 May 2024 21:00:03 -0400 + freedombox (24.9) unstable; urgency=medium [ Burak Yavuz ] diff --git a/plinth/__init__.py b/plinth/__init__.py index 6c6824e40..e96f61589 100644 --- a/plinth/__init__.py +++ b/plinth/__init__.py @@ -3,4 +3,4 @@ Package init file. """ -__version__ = '24.9' +__version__ = '24.10'