diff --git a/debian/changelog b/debian/changelog index f0ab77e0c..5091f5bc0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,33 @@ +freedombox (25.9.3) unstable; urgency=medium + + [ J AK ] + * Translated using Weblate (Albanian) + + [ Besnik Bleta ] + * Translated using Weblate (Albanian) + + [ kosagi ] + * Translated using Weblate (Catalan) + + [ தமிழ்நேரம் ] + * Translated using Weblate (Tamil) + + [ Benedek Nagy ] + * sogo: Fix typo in configuration for sieve server + * email: Add support for Dovecot 2.4 + + [ Sunil Mohan Adapa ] + * config: Allow overriding target path in dropin config component + * email: Start servers during re-setup if they are not running + + [ 109247019824 ] + * Translated using Weblate (Bulgarian) + + [ Priit Jõerüüt ] + * Translated using Weblate (Estonian) + + -- James Valleroy Mon, 21 Jul 2025 19:29:32 -0400 + freedombox (25.9.2~bpo12+1) bookworm-backports; urgency=medium * Rebuild for bookworm-backports. diff --git a/plinth/__init__.py b/plinth/__init__.py index 58c8bb5ef..d021a8289 100644 --- a/plinth/__init__.py +++ b/plinth/__init__.py @@ -3,4 +3,4 @@ Package init file. """ -__version__ = '25.9.2' +__version__ = '25.9.3' diff --git a/plinth/config.py b/plinth/config.py index 1b7f6c8f0..2cc0d1dc3 100644 --- a/plinth/config.py +++ b/plinth/config.py @@ -108,14 +108,12 @@ class DropinConfigs(app_module.FollowerComponent): return results - @staticmethod - def get_target_path(path): + def get_target_path(self, path): """Return Path object for a target path.""" - target = pathlib.Path(DropinConfigs.ROOT) - target /= DropinConfigs.DROPIN_CONFIG_ROOT.lstrip('/') + target = pathlib.Path(self.ROOT) + target /= self.DROPIN_CONFIG_ROOT.lstrip('/') return target / path.lstrip('/') - @staticmethod - def get_etc_path(path): + def get_etc_path(self, path): """Return Path object for etc path.""" - return pathlib.Path(DropinConfigs.ROOT) / path.lstrip('/') + return pathlib.Path(self.ROOT) / path.lstrip('/') diff --git a/plinth/locale/bg/LC_MESSAGES/django.po b/plinth/locale/bg/LC_MESSAGES/django.po index a704b98eb..dd340f603 100644 --- a/plinth/locale/bg/LC_MESSAGES/django.po +++ b/plinth/locale/bg/LC_MESSAGES/django.po @@ -8,9 +8,9 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-04-21 20:08-0400\n" -"PO-Revision-Date: 2025-02-25 21:04+0000\n" -"Last-Translator: 109247019824 <109247019824@users.noreply.hosted.weblate." -"org>\n" +"PO-Revision-Date: 2025-07-20 18:01+0000\n" +"Last-Translator: 109247019824 <109247019824@users.noreply.hosted.weblate.org>" +"\n" "Language-Team: Bulgarian \n" "Language: bg\n" @@ -18,7 +18,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.10.2-dev\n" +"X-Generator: Weblate 5.13-dev\n" #: config.py:103 #, python-brace-format @@ -199,7 +199,7 @@ msgstr "Домейн в местната мрежа" #: modules/avahi/manifest.py:14 msgid "Auto-discovery" -msgstr "" +msgstr "Автоматично откриване" #: modules/avahi/manifest.py:14 modules/backups/manifest.py:17 msgid "Local" @@ -207,7 +207,7 @@ msgstr "Местно" #: modules/avahi/manifest.py:14 msgid "mDNS" -msgstr "" +msgstr "mDNS" #: modules/backups/__init__.py:24 msgid "Backups allows creating and managing backup archives." @@ -8203,10 +8203,8 @@ msgstr "" #: modules/upgrades/templates/upgrades-dist-upgrade-notification.html:9 #: modules/upgrades/templates/upgrades-dist-upgrade.html:11 #: modules/upgrades/templates/upgrades_configure.html:16 -#, fuzzy -#| msgid "Distribution update started" msgid "Distribution Update" -msgstr "Започнато е обновяване на дистрибуцията" +msgstr "Начало на бновяване на дистрибуцията" #: modules/upgrades/__init__.py:396 msgid "Check for package holds" @@ -8267,10 +8265,8 @@ msgid "Next" msgstr "Напред" #: modules/upgrades/templates/upgrades-dist-upgrade-confirm.html:11 -#, fuzzy -#| msgid "Could not start distribution update" msgid "Confirm Distribution Update?" -msgstr "Обновяването на дистрибуцията не може да бъде стартирано" +msgstr "Потвърждавате ли бновяване на дистрибуцията?" #: modules/upgrades/templates/upgrades-dist-upgrade-confirm.html:21 #, python-format @@ -8303,10 +8299,8 @@ msgid "If the process is interrupted, you should be able to continue it." msgstr "" #: modules/upgrades/templates/upgrades-dist-upgrade-confirm.html:66 -#, fuzzy -#| msgid "Could not start distribution update" msgid "Confirm & Start Distribution Update" -msgstr "Обновяването на дистрибуцията не може да бъде стартирано" +msgstr "Потвърждаване и обновяване на дистрибуцията" #: modules/upgrades/templates/upgrades-dist-upgrade-notification.html:15 msgid "" @@ -8336,10 +8330,8 @@ msgid "" msgstr "" #: modules/upgrades/templates/upgrades-dist-upgrade-notification.html:42 -#, fuzzy -#| msgid "Test Distribution Upgrade" msgid "Go to Distribution Update" -msgstr "Надграждане на дистрибуцията до тестова" +msgstr "Към обновяване на дистрибуцията" #: modules/upgrades/templates/upgrades-dist-upgrade-notification.html:46 #: modules/upgrades/templates/upgrades-new-release.html:22 @@ -8368,16 +8360,12 @@ msgid "" msgstr "" #: modules/upgrades/templates/upgrades-dist-upgrade.html:50 -#, fuzzy -#| msgid "Frequent feature updates are activated." msgid "Automatic updates are disabled." -msgstr "Честото обновяване на пакети е включено." +msgstr "Автоматичното обновяване е изключено." #: modules/upgrades/templates/upgrades-dist-upgrade.html:54 -#, fuzzy -#| msgid "Distribution update started" msgid "Distribution upgrades are disabled." -msgstr "Започнато е обновяване на дистрибуцията" +msgstr "Обновяванията на дистрибуцията са изключени." #: modules/upgrades/templates/upgrades-dist-upgrade.html:58 msgid "" @@ -8390,10 +8378,8 @@ msgid "Your current distribution is mixed or not understood." msgstr "" #: modules/upgrades/templates/upgrades-dist-upgrade.html:72 -#, fuzzy -#| msgid "Test Distribution Upgrade" msgid "Current Distribution:" -msgstr "Надграждане на дистрибуцията до тестова" +msgstr "Текуща дистрибуция:" #: modules/upgrades/templates/upgrades-dist-upgrade.html:74 msgid "Unknown or mixed" @@ -8409,10 +8395,8 @@ msgid "Released: %(date)s." msgstr "" #: modules/upgrades/templates/upgrades-dist-upgrade.html:91 -#, fuzzy -#| msgid "Test Distribution Upgrade" msgid "Next Stable Distribution:" -msgstr "Надграждане на дистрибуцията до тестова" +msgstr "Следваща стабилна дистрибуция:" #: modules/upgrades/templates/upgrades-dist-upgrade.html:93 msgid "Unknown" @@ -8467,22 +8451,16 @@ msgstr "" #: modules/upgrades/templates/upgrades-dist-upgrade.html:157 #: modules/upgrades/templates/upgrades-dist-upgrade.html:172 -#, fuzzy -#| msgid "Test Distribution Upgrade" msgid "Start Distribution Update" -msgstr "Надграждане на дистрибуцията до тестова" +msgstr "Начало на обновяване на дистрибуцията" #: modules/upgrades/templates/upgrades-dist-upgrade.html:162 -#, fuzzy -#| msgid "Test Distribution Upgrade" msgid "Continue Distribution Update" -msgstr "Надграждане на дистрибуцията до тестова" +msgstr "Продължаване обновяването на дистрибуцията" #: modules/upgrades/templates/upgrades-dist-upgrade.html:167 -#, fuzzy -#| msgid "Starting distribution upgrade test." msgid "Start Distribution Update (for testing)" -msgstr "Начало на опит за обновяване на дистрибуцията." +msgstr "Начало на обновяване на дистрибуцията (за проба)" #: modules/upgrades/templates/upgrades-new-release.html:9 #, python-format @@ -8562,10 +8540,8 @@ msgid "Error when configuring unattended-upgrades" msgstr "Грешка при настройка на unattended-upgrades" #: modules/upgrades/views.py:117 -#, fuzzy -#| msgid "Starting distribution upgrade test." msgid "Started distribution update." -msgstr "Начало на опит за обновяване на дистрибуцията." +msgstr "Обновяване на дистрибуцията е започнато." #: modules/upgrades/views.py:153 msgid "Upgrade process started." diff --git a/plinth/locale/ca/LC_MESSAGES/django.po b/plinth/locale/ca/LC_MESSAGES/django.po index daf25dda4..f7eac9e93 100644 --- a/plinth/locale/ca/LC_MESSAGES/django.po +++ b/plinth/locale/ca/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-04-21 20:08-0400\n" -"PO-Revision-Date: 2025-05-29 10:01+0000\n" +"PO-Revision-Date: 2025-06-25 22:04+0000\n" "Last-Translator: kosagi \n" "Language-Team: Catalan \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.12-dev\n" +"X-Generator: Weblate 5.13-dev\n" #: config.py:103 #, python-brace-format @@ -2599,26 +2599,28 @@ msgid "" "Wiki title and description can be set from within the wiki. This file name " "is independent of the wiki title." msgstr "" +"El títol i la descripció del wiki es poden establir des de dins del wiki. " +"Aquest nom de fitxer és independent del títol del wiki." #: modules/featherwiki/forms.py:23 modules/tiddlywiki/forms.py:23 msgid "New name for the wiki file, with file extension \".html\"" -msgstr "" +msgstr "Nou nom per al fitxer del wiki, amb l'extensió de fitxer \".html\"" #: modules/featherwiki/forms.py:25 modules/tiddlywiki/forms.py:25 msgid "Renaming the file has no effect on the title of the wiki." -msgstr "" +msgstr "Canviar el nom del fitxer no té cap efecte sobre el títol del wiki." #: modules/featherwiki/forms.py:32 msgid "A Feather Wiki file with .html file extension" -msgstr "" +msgstr "Un fitxer Feather Wiki amb l'extensió de fitxer .html" #: modules/featherwiki/forms.py:35 msgid "Feather Wiki files must be in HTML format" -msgstr "" +msgstr "Els fitxers Feather Wiki han d'estar en format HTML" #: modules/featherwiki/forms.py:37 msgid "Upload an existing Feather Wiki file from this computer." -msgstr "" +msgstr "Carrega un fitxer Feather Wiki existent des d'aquest ordinador." #: modules/featherwiki/manifest.py:18 #: modules/help/templates/help_about.html:108 modules/ikiwiki/manifest.py:15 @@ -2629,7 +2631,7 @@ msgstr "Wiki" #: modules/featherwiki/manifest.py:18 modules/infinoted/manifest.py:46 #: modules/tiddlywiki/manifest.py:20 msgid "Note taking" -msgstr "" +msgstr "Prendre notes" #: modules/featherwiki/manifest.py:18 modules/ikiwiki/manifest.py:15 #: modules/mediawiki/manifest.py:25 modules/tiddlywiki/manifest.py:21 @@ -2639,17 +2641,17 @@ msgstr "Pàgina Web" #: modules/featherwiki/manifest.py:18 modules/tiddlywiki/manifest.py:25 msgid "Quine" -msgstr "" +msgstr "Quine" #: modules/featherwiki/manifest.py:18 modules/nextcloud/manifest.py:56 #: modules/tiddlywiki/manifest.py:26 msgid "Non-Debian" -msgstr "" +msgstr "No-Debian" #: modules/featherwiki/templates/featherwiki_configure.html:12 #: modules/tiddlywiki/templates/tiddlywiki_configure.html:12 msgid "Manage Wikis" -msgstr "" +msgstr "Gestiona les Wikis" #: modules/featherwiki/templates/featherwiki_configure.html:16 #: modules/featherwiki/templates/featherwiki_configure.html:18 @@ -2658,100 +2660,102 @@ msgstr "" #: modules/tiddlywiki/templates/tiddlywiki_configure.html:18 #: modules/tiddlywiki/views.py:47 msgid "Create Wiki" -msgstr "" +msgstr "Crea una Wiki" #: modules/featherwiki/templates/featherwiki_configure.html:21 #: modules/featherwiki/templates/featherwiki_configure.html:23 #: modules/tiddlywiki/templates/tiddlywiki_configure.html:21 #: modules/tiddlywiki/templates/tiddlywiki_configure.html:23 msgid "Upload Wiki" -msgstr "" +msgstr "Penja una Wiki" #: modules/featherwiki/templates/featherwiki_configure.html:30 #: modules/tiddlywiki/templates/tiddlywiki_configure.html:30 msgid "No wikis available." -msgstr "" +msgstr "No hi ha wikis disponibles." #: modules/featherwiki/templates/featherwiki_configure.html:36 #: modules/tiddlywiki/templates/tiddlywiki_configure.html:36 #, python-format msgid "Go to wiki %(wiki)s" -msgstr "" +msgstr "Ves a la wiki %(wiki)s" #: modules/featherwiki/templates/featherwiki_configure.html:43 #: modules/tiddlywiki/templates/tiddlywiki_configure.html:43 #, python-format msgid "Rename wiki %(wiki)s" -msgstr "" +msgstr "Reanomena la wiki %(wiki)s" #: modules/featherwiki/templates/featherwiki_configure.html:50 #: modules/tiddlywiki/templates/tiddlywiki_configure.html:50 #, python-format msgid "Delete wiki %(wiki)s" -msgstr "" +msgstr "Elimina la wiki %(wiki)s" #: modules/featherwiki/templates/featherwiki_delete.html:12 #: modules/tiddlywiki/templates/tiddlywiki_delete.html:12 #, python-format msgid "Delete wiki %(name)s" -msgstr "" +msgstr "Elimina la wiki %(name)s" #: modules/featherwiki/templates/featherwiki_delete.html:18 msgid "" "Hint: You can download a copy of this wiki from within " "Feather Wiki before deleting it." msgstr "" +"Consell: Pots descarregar una còpia d'aquest wiki des de " +"dins de Feather Wiki abans d'eliminar-lo." #: modules/featherwiki/templates/featherwiki_delete.html:25 #: modules/tiddlywiki/templates/tiddlywiki_delete.html:25 msgid "Delete this wiki file permanently?" -msgstr "" +msgstr "Eliminar aquest fitxer de wiki de manera permanent?" #: modules/featherwiki/templates/featherwiki_upload_file.html:20 #: modules/tiddlywiki/templates/tiddlywiki_upload_file.html:20 msgid "Upload" -msgstr "" +msgstr "Penja" #: modules/featherwiki/views.py:20 modules/tiddlywiki/views.py:20 msgid "A wiki file with the given name already exists." -msgstr "" +msgstr "Ja hi ha un fitxer de wiki amb aquest nom." #: modules/featherwiki/views.py:54 modules/tiddlywiki/views.py:54 msgid "Wiki created." -msgstr "" +msgstr "Wiki creada." #: modules/featherwiki/views.py:59 modules/tiddlywiki/views.py:59 msgid "An error occurred while creating the wiki." -msgstr "" +msgstr "Hi ha hagut un error al crear la wiki." #: modules/featherwiki/views.py:76 modules/tiddlywiki/views.py:76 msgid "Rename Wiki" -msgstr "" +msgstr "Reanomena la Wiki" #: modules/featherwiki/views.py:84 modules/tiddlywiki/views.py:84 msgid "Wiki renamed." -msgstr "" +msgstr "Nom de la wiki actualitzat." #: modules/featherwiki/views.py:89 modules/tiddlywiki/views.py:89 msgid "An error occurred while renaming the wiki." -msgstr "" +msgstr "Hi ha hagut un error al reanomenar la wiki." #: modules/featherwiki/views.py:106 modules/tiddlywiki/views.py:106 msgid "Upload Wiki File" -msgstr "" +msgstr "Penja un fitxer de Wiki" #: modules/featherwiki/views.py:115 modules/tiddlywiki/views.py:116 msgid "Wiki file added." -msgstr "" +msgstr "Fitxer de Wiki afegit." #: modules/featherwiki/views.py:119 modules/tiddlywiki/views.py:120 msgid "Failed to add wiki file." -msgstr "" +msgstr "No s'ha pogut afegir el fitxer de wiki." #: modules/featherwiki/views.py:138 modules/tiddlywiki/views.py:139 #, python-brace-format msgid "Could not delete {name}" -msgstr "" +msgstr "No s'ha pogut eliminar {name}" #: modules/firewall/__init__.py:25 #, python-brace-format @@ -2760,83 +2764,87 @@ msgid "" "network traffic on your {box_name}. Keeping a firewall enabled and properly " "configured reduces risk of security threat from the Internet." msgstr "" +"El tallafocs és un sistema de seguretat que controla el trànsit de xarxa " +"entrant i sortint del teu {box_name}. Mantenir el tallafocs activat i " +"configurat correctament redueix el risc d’amenaces de seguretat des " +"d’Internet." #: modules/firewall/__init__.py:61 msgid "Firewall" -msgstr "" +msgstr "Tallafocs" #: modules/firewall/__init__.py:262 msgid "Default zone is external" -msgstr "" +msgstr "La zona per defecte és externa" #: modules/firewall/__init__.py:272 msgid "Firewall backend is nftables" -msgstr "" +msgstr "El backend del tallafocs és nftables" #: modules/firewall/__init__.py:286 msgid "Direct passthrough rules exist" -msgstr "" +msgstr "Existeixen regles de pas directe" #: modules/firewall/components.py:139 #, python-brace-format msgid "Port {name} ({details}) available for internal networks" -msgstr "" +msgstr "Port {name} ({details}) disponible per a xarxes internes" #: modules/firewall/components.py:154 #, python-brace-format msgid "Port {name} ({details}) available for external networks" -msgstr "" +msgstr "Port {name} ({details}) disponible per a xarxes externes" #: modules/firewall/components.py:160 #, python-brace-format msgid "Port {name} ({details}) unavailable for external networks" -msgstr "" +msgstr "Port {name} ({details}) no disponible per a xarxes externes" #: modules/firewall/manifest.py:10 modules/tor/templates/tor.html:25 msgid "Ports" -msgstr "" +msgstr "Ports" #: modules/firewall/manifest.py:10 msgid "Blocking" -msgstr "" +msgstr "Bloquejant" #: modules/firewall/manifest.py:10 modules/networks/forms.py:319 #: modules/upgrades/manifest.py:10 msgid "Automatic" -msgstr "" +msgstr "Automàtic" #: modules/firewall/templates/firewall.html:21 msgid "Service/Port" -msgstr "" +msgstr "Servei/Port" #: modules/firewall/templates/firewall.html:40 #: modules/letsencrypt/templates/letsencrypt.html:69 #: modules/snapshot/forms.py:23 modules/snapshot/forms.py:29 msgid "Enabled" -msgstr "" +msgstr "Habilitat" #: modules/firewall/templates/firewall.html:43 #: modules/letsencrypt/templates/letsencrypt.html:71 #: modules/snapshot/forms.py:23 modules/snapshot/forms.py:29 #: templates/cards.html:38 msgid "Disabled" -msgstr "" +msgstr "Inhabilitat" #: modules/firewall/templates/firewall.html:58 msgid "Permitted" -msgstr "" +msgstr "Permès" #: modules/firewall/templates/firewall.html:61 msgid "Permitted (internal only)" -msgstr "" +msgstr "Permès (només intern)" #: modules/firewall/templates/firewall.html:64 msgid "Permitted (external only)" -msgstr "" +msgstr "Permès (només extern)" #: modules/firewall/templates/firewall.html:67 msgid "Blocked" -msgstr "" +msgstr "Bloquejat" #: modules/firewall/templates/firewall.html:80 msgid "" @@ -2844,22 +2852,28 @@ msgid "" "also permitted in the firewall and when you disable a service it is also " "disabled in the firewall." msgstr "" +"L'operació del tallafocs és automàtica. Quan actives un servei, també està " +"permès al tallafocs, i quan desactives un servei, també es desactiva al " +"tallafocs." #: modules/firewall/templates/firewall.html:88 #: modules/networks/templates/networks_configuration.html:22 #: modules/storage/templates/storage.html:93 msgid "Advanced" -msgstr "" +msgstr "Avançat" #: modules/firewall/templates/firewall.html:90 msgid "" "Advanced firewall operations such as opening custom ports are provided by " "the Cockpit app." msgstr "" +"Les operacions avançades del tallafocs, com ara obrir ports personalitzats, " +"les proporciona l'aplicació Cockpit." #: modules/first_boot/__init__.py:61 msgid "Setup complete! Next steps:" -msgstr "" +msgstr "Configuració completada! Properes passes:" #: modules/first_boot/__init__.py:63 #, python-brace-format @@ -2867,14 +2881,16 @@ msgid "" "Initial setup has been completed. Perform the next steps to make your " "{box_name} operational." msgstr "" +"La configuració inicial s’ha completat. Realitza els següents passos per fer " +"que el teu {box_name} estigui operatiu." #: modules/first_boot/__init__.py:66 msgid "Next steps" -msgstr "" +msgstr "Propers passos" #: modules/first_boot/__init__.py:73 msgid "See next steps" -msgstr "" +msgstr "Veure els passos següents" #: modules/first_boot/forms.py:14 #, python-brace-format @@ -2883,14 +2899,17 @@ msgid "" "also be obtained by running the command \"sudo cat /var/lib/plinth/firstboot-" "wizard-secret\" on your {box_name}" msgstr "" +"Introdueix el secret generat durant la instal·lació de FreedomBox. Aquest " +"secret també es pot obtenir executant la comanda " +"\"sudo cat /var/lib/plinth/firstboot-wizard-secret\" al teu {box_name}" #: modules/first_boot/forms.py:19 msgid "Firstboot Wizard Secret" -msgstr "" +msgstr "Secret de l'Assistent de Primera Execució" #: modules/first_boot/templates/firstboot_complete.html:14 msgid "Setup Complete! Next Steps:" -msgstr "" +msgstr "Configuració completada! Passos següents:" #: modules/first_boot/templates/firstboot_complete.html:21 #, python-format @@ -2898,17 +2917,22 @@ msgid "" "Automatic software update " "runs daily by default. For the first time, manually run it now." msgstr "" +"L’actualització automàtica de " +"programari s’executa diàriament per defecte. Per primera vegada, executa-" +"la manualment ara." #: modules/first_boot/templates/firstboot_complete.html:30 #: modules/upgrades/templates/upgrades_configure.html:119 msgid "Update now" -msgstr "" +msgstr "Actualitza-ho ara" #: modules/first_boot/templates/firstboot_complete.html:39 #, python-format msgid "" "Review privacy options." msgstr "" +"Revisa les opcions de " +"privadesa." #: modules/first_boot/templates/firstboot_complete.html:49 #, python-format @@ -2916,12 +2940,16 @@ msgid "" "Review and setup network " "connections. Change the default Wi-Fi password, if applicable." msgstr "" +"Revisa i configura les connexions de xarxa. Canvia la contrasenya Wi-Fi per defecte, si " +"s’escau." #: modules/first_boot/templates/firstboot_complete.html:60 #, python-format msgid "" "Configure a domain name." msgstr "" +"Configura un nom de domini." #: modules/first_boot/templates/firstboot_complete.html:70 #, python-format @@ -2929,6 +2957,8 @@ msgid "" "Configure and schedule remote backups." msgstr "" +"Configura i programa còpies de seguretat remotes." #: modules/first_boot/templates/firstboot_complete.html:81 #, python-format @@ -2936,14 +2966,16 @@ msgid "" "Put %(box_name)s to use by installing apps." msgstr "" +"Posa %(box_name)s en funcionament instal·lant aplicacions." #: modules/first_boot/templates/firstboot_welcome.html:29 msgid "Start Setup" -msgstr "" +msgstr "Inicia la configuració" #: modules/first_boot/views.py:41 msgid "Setup Complete" -msgstr "" +msgstr "Configuració completada" #: modules/gitweb/__init__.py:22 msgid "" @@ -2955,16 +2987,25 @@ msgid "" "available graphical clients. And you can share your code with people around " "the world." msgstr "" +"Git és un sistema de control de versions distribuït per fer el seguiment de " +"canvis en el codi font durant el desenvolupament de programari. Gitweb " +"proporciona una interfície web als repositoris Git. Pots navegar per " +"l'historial i el contingut del codi font, utilitzar la cerca per trobar " +"commits i codi rellevants. També pots clonar repositoris i pujar canvis de " +"codi amb un client Git de línia de comandes o amb diversos clients gràfics " +"disponibles. I pots compartir el teu codi amb persones de tot el món." #: modules/gitweb/__init__.py:29 msgid "" "To learn more on how to use Git visit Git tutorial." msgstr "" +"Per aprendre més sobre com utilitzar Git, visita tutorial de Git." #: modules/gitweb/__init__.py:45 msgid "Read-write access to Git repositories" -msgstr "" +msgstr "Accés de lectura i escriptura als repositoris Git" #: modules/gitweb/__init__.py:48 modules/gitweb/manifest.py:10 msgid "Gitweb" @@ -2972,128 +3013,130 @@ msgstr "Gitweb" #: modules/gitweb/forms.py:59 msgid "Invalid repository URL." -msgstr "" +msgstr "URL del repositori no vàlida." #: modules/gitweb/forms.py:69 msgid "Invalid repository name." -msgstr "" +msgstr "Nom del repositori no vàlid." #: modules/gitweb/forms.py:77 msgid "Name of a new repository or URL to import an existing repository." -msgstr "" +msgstr "Nom d’un repositori nou o URL per importar un repositori existent." #: modules/gitweb/forms.py:83 msgid "Description of the repository" -msgstr "" +msgstr "Descripció del repositori" #: modules/gitweb/forms.py:84 modules/gitweb/forms.py:88 msgid "Optional, for displaying on Gitweb." -msgstr "" +msgstr "Opcional, per mostrar a Gitweb." #: modules/gitweb/forms.py:86 msgid "Repository's owner name" -msgstr "" +msgstr "Nom del propietari del Repositori" #: modules/gitweb/forms.py:91 msgid "Private repository" -msgstr "" +msgstr "Repositori privat" #: modules/gitweb/forms.py:92 msgid "Allow only authorized users to access this repository." -msgstr "" +msgstr "Permet que només usuaris autoritzats accedeixin a aquest repositori." #: modules/gitweb/forms.py:113 modules/gitweb/forms.py:155 msgid "A repository with this name already exists." -msgstr "" +msgstr "Ja existeix un repositori amb aquest nom." #: modules/gitweb/forms.py:126 msgid "Name of the repository" -msgstr "" +msgstr "Nom del repositori" #: modules/gitweb/forms.py:130 msgid "An alpha-numeric string that uniquely identifies a repository." -msgstr "" +msgstr "Una cadena alfanumèrica que identifica de manera única un repositori." #: modules/gitweb/forms.py:134 msgid "Default branch" -msgstr "" +msgstr "Branca principal" #: modules/gitweb/forms.py:135 msgid "Gitweb displays this as a default branch." -msgstr "" +msgstr "La Gitweb sempre mostra això com a branca principal." #: modules/gitweb/manifest.py:18 msgid "Git" -msgstr "" +msgstr "Git" #: modules/gitweb/manifest.py:37 msgid "Git hosting" -msgstr "" +msgstr "Allotjament Git" #: modules/gitweb/manifest.py:37 msgid "Version control" -msgstr "" +msgstr "Control de versions" #: modules/gitweb/manifest.py:37 msgid "Developer tool" -msgstr "" +msgstr "Eines de desenvolupador" #: modules/gitweb/templates/gitweb_configure.html:13 msgid "Manage Repositories" -msgstr "" +msgstr "Gestiona Repositoris" #: modules/gitweb/templates/gitweb_configure.html:17 #: modules/gitweb/templates/gitweb_configure.html:19 msgid "Create repository" -msgstr "" +msgstr "Crea un repositori" #: modules/gitweb/templates/gitweb_configure.html:26 msgid "No repositories available." -msgstr "" +msgstr "No hi ha repositoris disponibles." #: modules/gitweb/templates/gitweb_configure.html:35 #, python-format msgid "Go to repository %(repo.name)s" -msgstr "" +msgstr "Ves al repositori %(repo.name)s" #: modules/gitweb/templates/gitweb_configure.html:42 msgid "Cloning…" -msgstr "" +msgstr "Clonant…" #: modules/gitweb/templates/gitweb_configure.html:59 #, python-format msgid "Delete repository %(repo.name)s" -msgstr "" +msgstr "Elimina el repositori %(repo.name)s" #: modules/gitweb/templates/gitweb_delete.html:12 #, python-format msgid "Delete Git Repository %(name)s" -msgstr "" +msgstr "Elimina el repositori Git %(name)s" #: modules/gitweb/templates/gitweb_delete.html:18 msgid "Delete this repository permanently?" -msgstr "" +msgstr "Vols eliminar aquest repositori permanentment?" #: modules/gitweb/views.py:46 msgid "Repository created." -msgstr "" +msgstr "Repositori creat." #: modules/gitweb/views.py:69 msgid "An error occurred while creating the repository." -msgstr "" +msgstr "Hi ha hagut un error al crear el repositori." #: modules/gitweb/views.py:84 msgid "Repository edited." -msgstr "" +msgstr "Repositori editat." #: modules/gitweb/views.py:89 msgid "Edit repository" -msgstr "" +msgstr "Edita el repositori" #: modules/gnome/__init__.py:18 msgid "" "GNOME is a desktop environment that focuses on simplicity and ease of use." msgstr "" +"GNOME és un entorn d'escriptori que se centra en la simplicitat i la " +"facilitat d'ús." #: modules/gnome/__init__.py:21 #, python-brace-format @@ -3103,12 +3146,20 @@ msgid "" "suite, and other basic utilities are available. You may install further " "graphical applications using the software center provided within." msgstr "" +"Aquesta aplicació transforma el teu {box_name} en un ordinador d’escriptori " +"si hi connectes físicament un monitor, un teclat i un ratolí. Tens " +"disponible un navegador, una suite d’ofimàtica i altres utilitats bàsiques. " +"També pots instal·lar més aplicacions gràfiques mitjançant el centre de " +"programari que s’ofereix a l’interior." #: modules/gnome/__init__.py:26 msgid "" "This app is not suitable for low-end hardware. It requires at least 4GiB of " "RAM, 4GiB of disk space and a GPU capable of basic 3D acceleration." msgstr "" +"Aquesta aplicació no és adequada per a maquinari de baix rendiment. " +"Requereix com a mínim 4 GiB de RAM, 4 GiB d'espai al disc i una GPU capaç " +"d'acceleració 3D bàsica." #: modules/gnome/__init__.py:30 #, python-brace-format @@ -3117,45 +3168,48 @@ msgid "" "need to restart the machine for changes to take " "effect." msgstr "" +"Després d’instal·lar, activar, desactivar o desinstal·lar l’aplicació, " +"hauràs de reiniciar la màquina perquè els canvis " +"tinguin efecte." #: modules/gnome/__init__.py:48 msgid "GNOME" -msgstr "" +msgstr "GNOME" #: modules/gnome/manifest.py:9 templates/clients.html:42 msgid "Desktop" -msgstr "" +msgstr "Escriptori" #: modules/gnome/manifest.py:10 msgid "Browser" -msgstr "" +msgstr "Navegador" #: modules/gnome/manifest.py:11 msgid "Office suite" -msgstr "" +msgstr "Suite d’ofimàtica" #: modules/gnome/manifest.py:12 msgid "Software store" -msgstr "" +msgstr "Botiga d'aplicacions" #: modules/gnome/manifest.py:13 msgid "GUI" -msgstr "" +msgstr "Interfície Gràfica d'Usuari" #: modules/gnome/manifest.py:14 msgid "Graphical apps" -msgstr "" +msgstr "Aplicacions gràfiques" #: modules/help/__init__.py:33 modules/help/templates/help_index.html:14 #: templates/help-menu.html:8 templates/help-menu.html:14 msgid "Help" -msgstr "" +msgstr "Ajuda" #: modules/help/__init__.py:37 modules/help/templates/help_about.html:104 #: templates/help-menu.html:20 templates/help-menu.html:21 msgctxt "User guide" msgid "Manual" -msgstr "" +msgstr "Manual" #: modules/help/__init__.py:41 modules/help/templates/help_support.html:9 #: modules/help/views.py:93 templates/help-menu.html:27 @@ -3167,7 +3221,7 @@ msgstr "Rep Suport" #: modules/help/views.py:87 templates/help-menu.html:33 #: templates/help-menu.html:34 msgid "Submit Feedback" -msgstr "" +msgstr "Enviar comentaris" #: modules/help/__init__.py:49 modules/help/templates/help_about.html:113 #: modules/help/templates/help_contribute.html:9 modules/help/views.py:76 @@ -3183,13 +3237,13 @@ msgstr "Sobre" #: modules/help/templates/help_about.html:25 templates/messages.html:23 msgid "Success:" -msgstr "" +msgstr "Reeixit:" #: modules/help/templates/help_about.html:29 #: modules/upgrades/templates/upgrades_configure.html:31 #, python-format msgid "You are running %(os_release)s and %(box_name)s version %(version)s." -msgstr "" +msgstr "Estàs executant %(os_release)s i la versió %(version)s de %(box_name)s." #: modules/help/templates/help_about.html:35 #, python-format @@ -3197,12 +3251,14 @@ msgid "" "There is a new %(box_name)s version available." msgstr "" +"Hi ha una nova versió de %(box_name)s disponible." #: modules/help/templates/help_about.html:40 #: modules/upgrades/templates/upgrades_configure.html:42 #, python-format msgid "%(box_name)s is up to date." -msgstr "" +msgstr "%(box_name)s està actualitzat." #: modules/help/templates/help_about.html:49 #, python-format @@ -3215,6 +3271,14 @@ msgid "" "and a Tor relay, on a device that can replace your Wi-Fi router, so that " "your data stays with you." msgstr "" +"%(box_name)s és un projecte comunitari per desenvolupar, dissenyar i " +"promoure servidors personals que executen programari lliure per a " +"comunicacions privades i personals. És un dispositiu de xarxa dissenyat " +"per permetre la interacció amb la resta d’Internet sota condicions de " +"privadesa i seguretat de dades protegides. Allotja aplicacions com bloc, " +"wiki, lloc web, xarxa social, correu electrònic, proxy web i un relé Tor, en " +"un dispositiu que pot substituir el teu router Wi-Fi, perquè les teves dades " +"es quedin amb tu." #: modules/help/templates/help_about.html:62 msgid "" @@ -3225,6 +3289,12 @@ msgid "" "giving back power to the users over their networks and machines, we are " "returning the Internet to its intended peer-to-peer architecture." msgstr "" +"Vivim en un món on l’ús que fem de la xarxa està mediatitzat per aquells que " +"sovint vetllen pels nostres interessos. Construint programari que no " +"depengui d’un servei central, podem recuperar el control i la privadesa. " +"Guardant les nostres dades a casa, obtenim proteccions legals útils sobre " +"elles. Tornant el poder als usuaris sobre les seves xarxes i màquines, " +"estem retornant Internet a la seva arquitectura peer-to-peer prevista." #: modules/help/templates/help_about.html:75 #, python-format @@ -3233,6 +3303,9 @@ msgid "" "services; %(box_name)s aims to bring them all together in a convenient " "package." msgstr "" +"Hi ha diversos projectes que treballen per fer realitat un futur de serveis " +"distribuïts; %(box_name)s té com a objectiu reunir-los tots en un paquet " +"còmode." #: modules/help/templates/help_about.html:83 #, python-format @@ -3244,6 +3317,13 @@ msgid "" "href=\"https://sources.debian.org/\">Debian Sources site, or by running " "\"apt source package_name\" in a terminal (using Cockpit or SSH)." msgstr "" +"%(box_name)s és programari lliure, llicenciat sota la GNU Affero General " +"Public License. El codi font està disponible en línia al repositori de " +"%(box_name)s. A més, el codi font de qualsevol paquet Debian es pot " +"obtenir des del lloc Debian Sources, o executant \"apt source nom\\_paquet\" en un terminal (" +"utilitzant Cockpit o SSH)." #: modules/help/templates/help_about.html:97 msgid "Learn" @@ -3255,11 +3335,11 @@ msgstr "Fes una Donació" #: modules/help/templates/help_about.html:119 msgid "Join project" -msgstr "" +msgstr "Suma't al projecte" #: modules/help/templates/help_about.html:123 msgid "Translate" -msgstr "" +msgstr "Tradueix" #: modules/help/templates/help_about.html:129 msgid "Support" @@ -3271,21 +3351,21 @@ msgstr "Forum" #: modules/help/templates/help_about.html:138 msgid "IRC Chatroom" -msgstr "" +msgstr "Sala de xat IRC" #: modules/help/templates/help_about.html:143 msgid "Mailing list" -msgstr "" +msgstr "Llista de correu electrònic" #: modules/help/templates/help_base.html:21 #: modules/help/templates/help_index.html:63 #, python-format msgid "%(box_name)s Setup" -msgstr "" +msgstr "Configuració de %(box_name)s" #: modules/help/templates/help_contribute.html:12 msgid "The FreedomBox project welcomes contributions of all kinds." -msgstr "" +msgstr "El projecte FreedomBox accepta contribucions de qualsevol tipus." #: modules/help/templates/help_contribute.html:18 msgid "" @@ -3295,6 +3375,10 @@ msgid "" "into your language, hosting hackathons or install fests, and by spreading " "the word." msgstr "" +"Pots contribuir escrivint codi, provant i reportant errors, debatent nous " +"casos d’ús i aplicacions, dissenyant logotips i obres d’art, oferint suport " +"als teus companys usuaris, traduint FreedomBox i les seves aplicacions al " +"teu idioma, organitzant hackathons o install fests, i difonent la iniciativa." #: modules/help/templates/help_contribute.html:28 msgid "" @@ -3307,52 +3391,63 @@ msgid "" "throughout the world. The FreedomBox Foundation would not exist without its " "supporters." msgstr "" +"També pots ajudar el projecte econòmicament fent una donació a la " +"FreedomBox Foundation, una organització sense ànim de lucre. Fundada el " +"2011, la FreedomBox Foundation és una entitat sense ànim de lucre amb " +"estatus 501(c)(3) amb seu a Nova York que existeix per donar suport a " +"FreedomBox. Proporciona infraestructura tècnica i serveis legals pel " +"projecte, cerca aliances i defensa FreedomBox arreu del món. La FreedomBox " +"Foundation no existiria sense els seus col·laboradors." #: modules/help/templates/help_contribute.html:42 #: modules/power/templates/power_restart.html:27 #: modules/power/templates/power_shutdown.html:26 templates/app-header.html:65 msgid "Learn more..." -msgstr "" +msgstr "Aprèn-ne més..." #: modules/help/templates/help_contribute.html:46 msgid "How can I help?" -msgstr "" +msgstr "Com puc ajudar?" #: modules/help/templates/help_contribute.html:48 msgid "" "Below is a list of opportunities for contributing to Debian. It has been " "filtered to only show packages that are installed on this system." msgstr "" +"A continuació hi ha una llista de maneres de contribuir a Debian. S’ha " +"filtrat per mostrar només els paquets que estan instal·lats en aquest " +"sistema." #: modules/help/templates/help_contribute.html:59 msgid "Show issues" -msgstr "" +msgstr "Mostra incidències" #: modules/help/templates/help_contribute.html:63 msgid "Packages that will be removed from Debian testing" -msgstr "" +msgstr "Paquets que seran eliminats de Debian testing" #: modules/help/templates/help_contribute.html:69 #: modules/help/templates/help_contribute.html:85 msgid "source package:" -msgstr "" +msgstr "paquet font:" #: modules/help/templates/help_contribute.html:80 msgid "Packages that are not in Debian testing" -msgstr "" +msgstr "Paquets que no estan a Debian testing" #: modules/help/templates/help_contribute.html:92 msgid "Good first issues for beginners" -msgstr "" +msgstr "Bons primers reptes per a principiants" #: modules/help/templates/help_contribute.html:104 msgid "Issues for which the package maintainer has requested help" -msgstr "" +msgstr "Incidències per a les quals qui manté del paquet ha sol·licitat ajuda" #: modules/help/templates/help_feedback.html:12 #, python-format msgid "Your feedback will help us improve %(box_name)s!" -msgstr "" +msgstr "Els teus comentaris ens ajudaran a millorar %(box_name)s!" #: modules/help/templates/help_feedback.html:18 msgid "" @@ -3360,6 +3455,9 @@ msgid "" "improve them on our discussion forum." msgstr "" +"Fes-nos saber les funcions que falten, les teves aplicacions preferides i " +"com podem millorar-les al nostre fòrum de discussió." #: modules/help/templates/help_feedback.html:26 msgid "" @@ -3368,10 +3466,15 @@ msgid "" "tracker to let our developers know. To report, first check if the issue " "is already reported and then use the \"New issue\" button." msgstr "" +"Si trobes algun error o incidència, si us plau utilitza el seguiment d’incidències per informar els nostres " +"desenvolupadors. Per informar, primer comprova si l’incidència ja està " +"reportada i després utilitza el botó \"Nova incidència\"." #: modules/help/templates/help_feedback.html:36 msgid "Thank you!" -msgstr "" +msgstr "Gràcies!" #: modules/help/templates/help_index.html:18 #, python-format @@ -3379,6 +3482,8 @@ msgid "" "The %(box_name)s Manual is the best place to " "start for information regarding %(box_name)s." msgstr "" +"El Manual de %(box_name)s és el millor lloc " +"per començar a trobar informació sobre %(box_name)s." #: modules/help/templates/help_index.html:25 #, python-format @@ -3386,6 +3491,8 @@ msgid "" " " "%(box_name)s project wiki contains further information." msgstr "" +"L’wiki del " +"projecte %(box_name)s conté més informació." #: modules/help/templates/help_index.html:32 #, python-format @@ -3395,6 +3502,11 @@ msgid "" "discuss\"> mailing list. The list archives also contain information " "about problems faced by other users and possible solutions." msgstr "" +"Per sol·licitar ajuda a la comunitat de %(box_name)s, es poden publicar " +"consultes a la " +"llista de correu. Els arxius de la llista també contenen informació " +"sobre problemes que han tingut altres usuaris i possibles solucions." #: modules/help/templates/help_index.html:42 #, python-format @@ -3404,10 +3516,14 @@ msgid "" "oftc.net/?randomnick=1&channels=freedombox&prompt=1\"> #freedombox " "channel using the IRC web interface." msgstr "" +"Molts contribuïdors i usuaris de %(box_name)s també estan disponibles a la " +"xarxa IRC irc.oftc.net. Uneix-te i sol·licita ajuda al canal " +"#freedombox mitjançant la interfície web IRC." #: modules/help/templates/help_manual.html:18 msgid "Download as PDF" -msgstr "" +msgstr "Descarrega-ho com a PDF" #: modules/help/templates/help_support.html:12 #, python-format @@ -3416,12 +3532,17 @@ msgid "" "using %(box_name)s, you can ask for help from our community of users and " "contributors." msgstr "" +"Si necessites ajuda per fer alguna cosa o si tens problemes utilitzant " +"%(box_name)s, pots demanar ajuda a la nostra comunitat d’usuaris i " +"contribuïdors." #: modules/help/templates/help_support.html:20 msgid "" "Search for past discussions or post a new query on our discussion forum." msgstr "" +"Cerca discussions passades o publica una nova consulta al nostre fòrum de discussió." #: modules/help/templates/help_support.html:27 msgid "" @@ -3430,6 +3551,10 @@ msgid "" "Or send an email to our mailing list." msgstr "" +"També pots xatejar amb nosaltres als nostres canals IRC i Matrix " +"(connectats):
  • #freedombox a irc.oftc.net
  • #freedombox\\" +":matrix.org
O enviar un correu electrònic a la nostra llista de correu." #: modules/help/templates/statuslog.html:10 msgid "Status Log" diff --git a/plinth/locale/et/LC_MESSAGES/django.po b/plinth/locale/et/LC_MESSAGES/django.po index f4a9beb9e..a3b499d76 100644 --- a/plinth/locale/et/LC_MESSAGES/django.po +++ b/plinth/locale/et/LC_MESSAGES/django.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-04-21 20:08-0400\n" -"PO-Revision-Date: 2025-06-19 22:01+0000\n" -"Last-Translator: Priit Jõerüüt \n" +"PO-Revision-Date: 2025-07-20 18:01+0000\n" +"Last-Translator: Priit Jõerüüt \n" "Language-Team: Estonian \n" "Language: et\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.12.1\n" +"X-Generator: Weblate 5.13-dev\n" #: config.py:103 #, python-brace-format @@ -921,7 +921,7 @@ msgstr "" #: modules/miniflux/forms.py:14 modules/networks/forms.py:282 #: modules/shadowsocks/forms.py:32 modules/shadowsocksserver/forms.py:37 msgid "Password" -msgstr "" +msgstr "Salasõna" #: modules/bepasty/views.py:19 msgid "admin" diff --git a/plinth/locale/sq/LC_MESSAGES/django.po b/plinth/locale/sq/LC_MESSAGES/django.po index 69ccb2e97..8b5a8ea1b 100644 --- a/plinth/locale/sq/LC_MESSAGES/django.po +++ b/plinth/locale/sq/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-04-21 20:08-0400\n" -"PO-Revision-Date: 2025-04-09 13:07+0000\n" +"PO-Revision-Date: 2025-06-25 22:04+0000\n" "Last-Translator: Besnik Bleta \n" "Language-Team: Albanian \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.11-dev\n" +"X-Generator: Weblate 5.13-dev\n" #: config.py:103 #, python-brace-format diff --git a/plinth/locale/ta/LC_MESSAGES/django.po b/plinth/locale/ta/LC_MESSAGES/django.po index 70af55dea..887c8a0ae 100644 --- a/plinth/locale/ta/LC_MESSAGES/django.po +++ b/plinth/locale/ta/LC_MESSAGES/django.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-04-21 20:08-0400\n" -"PO-Revision-Date: 2024-12-27 01:03+0000\n" -"Last-Translator: James Valleroy \n" +"PO-Revision-Date: 2025-06-30 09:01+0000\n" +"Last-Translator: தமிழ்நேரம் \n" "Language-Team: Tamil \n" "Language: ta\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.10-dev\n" +"X-Generator: Weblate 5.13-dev\n" #: config.py:103 #, python-brace-format @@ -26,7 +26,7 @@ msgstr "நிலையான உள்ளமைவு {etc_path} சரிய #: context_processors.py:21 views.py:168 msgid "FreedomBox" -msgstr "" +msgstr "ஃப்ரீடோம்பாக்ச்" #: daemon.py:124 #, python-brace-format @@ -101,7 +101,7 @@ msgstr "உலாவியில் உள்ள மொழி விருப் #: menu.py:116 templates/base.html:120 msgid "Home" -msgstr "" +msgstr "வீடு" #: menu.py:117 templates/base.html:129 msgid "Apps" @@ -681,7 +681,7 @@ msgstr "காப்புப்பிரதி இருப்பிடத் #: modules/backups/templates/backups_repository.html:88 msgid "Download" -msgstr "" +msgstr "பதிவிறக்கம்" #: modules/backups/templates/backups_repository.html:114 msgid "No archives currently exist." @@ -2637,7 +2637,7 @@ msgstr "இந்த கணினியிலிருந்து ஏற்க #: modules/help/templates/help_about.html:108 modules/ikiwiki/manifest.py:15 #: modules/mediawiki/manifest.py:25 modules/tiddlywiki/manifest.py:19 msgid "Wiki" -msgstr "" +msgstr "விக்கி" #: modules/featherwiki/manifest.py:18 modules/infinoted/manifest.py:46 #: modules/tiddlywiki/manifest.py:20 @@ -2648,7 +2648,7 @@ msgstr "குறிப்பு" #: modules/mediawiki/manifest.py:25 modules/tiddlywiki/manifest.py:21 #: modules/wordpress/manifest.py:26 msgid "Website" -msgstr "" +msgstr "வலைத்தளம்" #: modules/featherwiki/manifest.py:18 modules/tiddlywiki/manifest.py:25 msgid "Quine" @@ -3213,7 +3213,7 @@ msgstr "கையேடு" #: modules/help/views.py:93 templates/help-menu.html:27 #: templates/help-menu.html:28 msgid "Get Support" -msgstr "" +msgstr "ஆதரவை பெறு" #: modules/help/__init__.py:45 modules/help/templates/help_feedback.html:9 #: modules/help/views.py:87 templates/help-menu.html:33 @@ -3225,13 +3225,13 @@ msgstr "கருத்துக்களை சமர்ப்பிக்க #: modules/help/templates/help_contribute.html:9 modules/help/views.py:76 #: templates/help-menu.html:39 templates/help-menu.html:40 msgid "Contribute" -msgstr "" +msgstr "பங்களிப்பு" #: modules/help/__init__.py:53 templates/base.html:216 templates/base.html:219 #: templates/help-menu.html:46 templates/help-menu.html:47 #: templates/index.html:96 msgid "About" -msgstr "" +msgstr "பற்றி" #: modules/help/templates/help_about.html:25 templates/messages.html:23 msgid "Success:" @@ -3322,11 +3322,11 @@ msgstr "" #: modules/help/templates/help_about.html:97 msgid "Learn" -msgstr "" +msgstr "அறிக" #: modules/help/templates/help_about.html:116 templates/toolbar.html:19 msgid "Donate" -msgstr "" +msgstr "நன்கொடை" #: modules/help/templates/help_about.html:119 msgid "Join project" @@ -3338,7 +3338,7 @@ msgstr "மொழிபெயர்த்திடு" #: modules/help/templates/help_about.html:129 msgid "Support" -msgstr "" +msgstr "உதவி" #: modules/help/templates/help_about.html:133 msgid "Forum" @@ -6943,7 +6943,7 @@ msgstr "நாட்காட்டி" #: modules/radicale/manifest.py:91 modules/roundcube/manifest.py:23 msgid "Contacts" -msgstr "" +msgstr "தொடர்புகள்" #: modules/radicale/manifest.py:91 modules/sogo/manifest.py:75 msgid "CalDAV" @@ -7018,7 +7018,7 @@ msgstr "" #: modules/roundcube/manifest.py:23 msgid "Email" -msgstr "" +msgstr "மின்னஞ்சல்" #: modules/rssbridge/__init__.py:21 msgid "" @@ -10248,7 +10248,7 @@ msgstr "விரலிடைத் தோல்" #: templates/clients.html:28 msgid "Launch" -msgstr "" +msgstr "ஏவுதல்" #: templates/clients.html:53 msgid "GNU/Linux" diff --git a/plinth/modules/email/__init__.py b/plinth/modules/email/__init__.py index e3678963b..721976d98 100644 --- a/plinth/modules/email/__init__.py +++ b/plinth/modules/email/__init__.py @@ -20,7 +20,7 @@ from plinth.privileged import service as service_privileged from plinth.signals import domain_added, domain_removed from plinth.utils import format_lazy, gettext_noop -from . import aliases, manifest, privileged +from . import aliases, dovecot, manifest, privileged _description = [ _('This is a complete email server solution using Postfix, Dovecot, ' @@ -52,7 +52,7 @@ class EmailApp(plinth.app.App): app_id = 'email' - _version = 6 + _version = 7 def __init__(self) -> None: """Initialize the email app.""" @@ -95,21 +95,12 @@ class EmailApp(plinth.app.App): 'dovecot-lmtpd', 'dovecot-managesieved', 'dovecot-ldap', 'rspamd', 'redis-server', 'openssl' ], conflicts=['exim4-base', 'exim4-config', 'exim4-daemon-light'], - conflicts_action=Packages.ConflictsAction.REMOVE) + conflicts_action=Packages.ConflictsAction.REMOVE, + rerun_setup_on_upgrade=True) self.add(packages) dropin_configs = DropinConfigs('dropin-configs-email', [ '/etc/apache2/conf-available/email-freedombox.conf', - '/etc/dovecot/conf.d/05-freedombox-passdb.conf', - '/etc/dovecot/conf.d/05-freedombox-userdb.conf', - '/etc/dovecot/conf.d/15-freedombox-auth.conf', - '/etc/dovecot/conf.d/15-freedombox-mail.conf', - '/etc/dovecot/conf.d/90-freedombox-imap.conf', - '/etc/dovecot/conf.d/90-freedombox-lmtp.conf', - '/etc/dovecot/conf.d/90-freedombox-mailboxes.conf', - '/etc/dovecot/conf.d/90-freedombox-master.conf', - '/etc/dovecot/conf.d/90-freedombox-tls.conf', - '/etc/dovecot/conf.d/freedombox-ldap.conf.ext', '/etc/fail2ban/jail.d/dovecot-freedombox.conf', '/etc/postfix/freedombox-aliases.cf', '/etc/rspamd/local.d/freedombox-logging.inc', @@ -121,10 +112,24 @@ class EmailApp(plinth.app.App): dropin_configs_sieve = DropinConfigs('dropin-configs-email-sieve', [ '/etc/dovecot/freedombox-sieve/learn-ham.sieve', '/etc/dovecot/freedombox-sieve/learn-spam.sieve', - '/etc/dovecot/freedombox-sieve-after/sort-spam.sieve', - '/etc/dovecot/conf.d/95-freedombox-sieve.conf' + '/etc/dovecot/freedombox-sieve-after/sort-spam.sieve' ]) self.add(dropin_configs_sieve) + dropin_configs_dovecot = DovecotDropinConfigs( + 'dropin-configs-email-dovecot', [ + '/etc/dovecot/conf.d/05-freedombox-passdb.conf', + '/etc/dovecot/conf.d/05-freedombox-userdb.conf', + '/etc/dovecot/conf.d/15-freedombox-auth.conf', + '/etc/dovecot/conf.d/15-freedombox-mail.conf', + '/etc/dovecot/conf.d/90-freedombox-imap.conf', + '/etc/dovecot/conf.d/90-freedombox-lmtp.conf', + '/etc/dovecot/conf.d/90-freedombox-mailboxes.conf', + '/etc/dovecot/conf.d/90-freedombox-master.conf', + '/etc/dovecot/conf.d/90-freedombox-tls.conf', + '/etc/dovecot/conf.d/95-freedombox-sieve.conf', + '/etc/dovecot/conf.d/freedombox-ldap.conf.ext' + ]) + self.add(dropin_configs_dovecot) listen_ports = [(25, 'tcp4'), (25, 'tcp6'), (465, 'tcp4'), (465, 'tcp6'), (587, 'tcp4'), (587, 'tcp6')] @@ -212,13 +217,15 @@ class EmailApp(plinth.app.App): # Enable drop-in configuration files component for sieve (temporarily) # to ensure that sievec can compile. self.get_component('dropin-configs-email-sieve').enable() + self.get_component('dropin-configs-email-dovecot').enable() service_privileged.try_restart('dovecot') privileged.setup_spam() # Restart daemons - service_privileged.try_restart('postfix') - service_privileged.try_restart('dovecot') - service_privileged.try_restart('rspamd') + if self.is_enabled(): + service_privileged.restart('postfix') + service_privileged.restart('dovecot') + service_privileged.restart('rspamd') # Expose to public internet if old_version == 0: @@ -228,6 +235,20 @@ class EmailApp(plinth.app.App): service_privileged.try_restart('rspamd') +class DovecotDropinConfigs(DropinConfigs): + """Configure dovecot based on its package version.""" + + def get_target_path(self, path): + """Return Path object for a target path.""" + version = '2.3' + if dovecot.is_version_24(): + version = '2.4' + + target_path = super().get_target_path(path) + target_path = target_path.parent / version / target_path.name + return target_path + + def _get_first_admin(): """Return an admin user in the system or None if non exist.""" from django.contrib.auth.models import User diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/05-freedombox-passdb.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/05-freedombox-passdb.conf similarity index 100% rename from plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/05-freedombox-passdb.conf rename to plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/05-freedombox-passdb.conf diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/05-freedombox-userdb.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/05-freedombox-userdb.conf similarity index 100% rename from plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/05-freedombox-userdb.conf rename to plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/05-freedombox-userdb.conf diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/15-freedombox-auth.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/15-freedombox-auth.conf similarity index 100% rename from plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/15-freedombox-auth.conf rename to plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/15-freedombox-auth.conf diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/15-freedombox-mail.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/15-freedombox-mail.conf similarity index 100% rename from plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/15-freedombox-mail.conf rename to plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/15-freedombox-mail.conf diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/90-freedombox-imap.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/90-freedombox-imap.conf similarity index 100% rename from plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/90-freedombox-imap.conf rename to plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/90-freedombox-imap.conf diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/90-freedombox-lmtp.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/90-freedombox-lmtp.conf similarity index 100% rename from plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/90-freedombox-lmtp.conf rename to plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/90-freedombox-lmtp.conf diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/90-freedombox-mailboxes.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/90-freedombox-mailboxes.conf similarity index 100% rename from plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/90-freedombox-mailboxes.conf rename to plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/90-freedombox-mailboxes.conf diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/90-freedombox-master.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/90-freedombox-master.conf similarity index 100% rename from plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/90-freedombox-master.conf rename to plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/90-freedombox-master.conf diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/90-freedombox-tls.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/90-freedombox-tls.conf similarity index 100% rename from plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/90-freedombox-tls.conf rename to plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/90-freedombox-tls.conf diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/95-freedombox-sieve.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/95-freedombox-sieve.conf similarity index 100% rename from plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/95-freedombox-sieve.conf rename to plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/95-freedombox-sieve.conf diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/freedombox-ldap.conf.ext b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/freedombox-ldap.conf.ext similarity index 100% rename from plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/freedombox-ldap.conf.ext rename to plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.3/freedombox-ldap.conf.ext diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/05-freedombox-passdb.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/05-freedombox-passdb.conf new file mode 100644 index 000000000..3483a14de --- /dev/null +++ b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/05-freedombox-passdb.conf @@ -0,0 +1,20 @@ +# Do not edit this file. Manage your settings on FreedomBox. + +# See: +# https://doc.dovecot.org/main/core/config/auth/passdb.html +# https://doc.dovecot.org/main/howto/active_directory.html +# +# For passdb, the passwd driver looks up using NSS. In FreedomBox, NSS is +# configured to lookup LDAP with the help of libnss-ldapd. Lookup using passdb +# would have been sufficient if FreedomBox allowed all its users to login using +# pam. However, by default, FreedomBox disallows all users but 'admin' group to +# login. Hence, the need for LDAP lookup. +# +passdb freedombox-ldap { + driver = ldap + ldap_uris = ldapi:/// + ldap_base = dc=thisbox + ldap_bind = yes + ldap_bind_userdn = uid=%{user},ou=users,dc=thisbox + ldap_filter = (&(objectClass=posixAccount)(uid=%{user})) +} diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/05-freedombox-userdb.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/05-freedombox-userdb.conf new file mode 100644 index 000000000..81cf28a2a --- /dev/null +++ b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/05-freedombox-userdb.conf @@ -0,0 +1,32 @@ +# Do not edit this file. Manage your settings on FreedomBox. + +# See: +# https://doc.dovecot.org/main/core/config/auth/userdb.html +# +# Users in FreedomBox are not expected to access mail by logging into the +# system. Storing the mail in single location instead of home directories and +# with single UID/GID simplifies security reasoning and backup/restore +# operations. +# +# When FreedomBox has multiple domains a user is expected to get a mailbox that +# is same across the domains. Changing an domain name is not uncommon in +# FreedomBox. So, authenticate and store mails based on username only instead of +# including domain names in storage path. +# +# Directories are created under /var/mail as necessary by dovecot. Permissions +# for newly created directories are inherited from parent directory. FreedomBox +# will remove all permissions for 'others' from /var/mail to ensure that mail is +# not read by non-root users. +# +# userdb provides lookup for three parameters after authentication of a user. +# These parameters are uid, gid, and home directory of the user. If these do not +# change from user to user, a 'static' database type with fixed values is +# sufficient as userdb. +userdb freedombox-static { + driver = static + fields { + uid=mail + gid=mail + home=/var/mail/%{user | username | lower} + } +} diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/15-freedombox-auth.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/15-freedombox-auth.conf new file mode 100644 index 000000000..655de1de6 --- /dev/null +++ b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/15-freedombox-auth.conf @@ -0,0 +1,10 @@ +# Do not edit this file. Manage your settings on FreedomBox. + +# See: +# https://doc.dovecot.org/main/core/config/auth/basic.html +# https://doc.dovecot.org/main/core/config/auth/databases/ldap.html#username + +# Outlook and Windows Mail work only with LOGIN mechanism, not the standard PLAIN +auth_mechanisms = plain login + +auth_username_format = %{user | lower} diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/15-freedombox-mail.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/15-freedombox-mail.conf new file mode 100644 index 000000000..d5b475b9d --- /dev/null +++ b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/15-freedombox-mail.conf @@ -0,0 +1,18 @@ +# Do not edit this file. Manage your settings on FreedomBox. + +# See: https://doc.dovecot.org/main/core/config/mail_location.html + +# Use sdbox, a format specific to dovecot, for storing mails. The format allows +# better performance with some IMAP queries. When this is combined with Full +# Text Search (FTS), users will get optimal web and desktop mail experience. +# Don't pick mdbox format because is requires regular expunge maintenance. We +# have enabled btrfs filesystem compression by default. +mail_driver = sdbox +mail_path = ~/mail + +# We try to deliver all mail using a single UID 'mail' and a single GID 'mail'. +# In Debian, UID of mail user is 8 and GID of mail user is 8 as set in +# /usr/share/base-passwd/{passwd|group}.master. By default first valid UID in +# dovecot is 500. +first_valid_uid = 8 +last_valid_uid = 8 diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-imap.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-imap.conf new file mode 100644 index 000000000..5f138602e --- /dev/null +++ b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-imap.conf @@ -0,0 +1,11 @@ +# Do not edit this file. Manage your settings on FreedomBox. + +# Make rspamd learn spam/ham when the user marks mails as junk or not junk. +# https://doc.dovecot.org/main/core/config/sieve/overview.html +# https://doc.dovecot.org/main/core/plugins/sieve.html + +protocol imap { + mail_plugins { + imap_sieve = yes + } +} diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-lmtp.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-lmtp.conf new file mode 100644 index 000000000..0a34b9ff9 --- /dev/null +++ b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-lmtp.conf @@ -0,0 +1,12 @@ +# Do not edit this file. Manage your settings on FreedomBox. + +# See: +# https://doc.dovecot.org/main/core/config/sieve/overview.html +# https://doc.dovecot.org/main/core/plugins/sieve.html + +# Enable the sieve plugin to sort mail during delivery using sieve scripts. +protocol lmtp { + mail_plugins { + sieve = yes + } +} diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-mailboxes.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-mailboxes.conf new file mode 100644 index 000000000..a2400a33c --- /dev/null +++ b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-mailboxes.conf @@ -0,0 +1,72 @@ +# Do not edit this file. Manage your settings on FreedomBox. + +# Mark various mailboxes with special use flags (RFC 6154). Various names used +# in mail clients for mailboxes: https://www.imapwiki.org/SpecialUse +# See: +# https://doc.dovecot.org/main/core/config/mail_location.html#custom-namespace-location + +namespace inbox { + # Archive + mailbox Archive { + auto = subscribe + special_use = \Archive + } + mailbox Archives { # Thunderbird + auto = no + special_use = \Archive + } + + # Drafts + mailbox Drafts { + auto = subscribe + special_use = \Drafts + } + + # Sent + mailbox Sent { + auto = subscribe + special_use = \Sent + } + mailbox "Sent Items" { # Outlook 2010/2013 + auto = no + special_use = \Sent + } + mailbox "Sent Messages" { # iOS + auto = no + special_use = \Sent + } + + # Junk + mailbox Junk { + auto = subscribe + autoexpunge = 60d + special_use = \Junk + } + mailbox Spam { # KMail, K-9 Mail + auto = no + autoexpunge = 60d + special_use = \Junk + } + mailbox "Junk E-mail" { # Outlook 2010 + auto = no + autoexpunge = 60d + special_use = \Junk + } + mailbox INBOX.Junk { + auto = no + autoexpunge = 60d + special_use = \Junk + } + + # Trash + mailbox Trash { + auto = subscribe + autoexpunge = 60d + special_use = \Trash + } + mailbox INBOX.Trash { + auto = no + autoexpunge = 60d + special_use = \Trash + } +} diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-master.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-master.conf new file mode 100644 index 000000000..63c6c8202 --- /dev/null +++ b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-master.conf @@ -0,0 +1,21 @@ +# Do not edit this file. Manage your settings on FreedomBox. + +# Listen on Unix domain sockets for postfix to use dovecot SASL authentication +# and for postfix to deliver mail using dovecot to local mailboxes. See: +# https://doc.dovecot.org/main/howto/sasl/postfix.html#postfix-and-dovecot-sasl + +service auth { + unix_listener /var/spool/postfix/private/auth { + mode = 0600 + user = postfix + group = postfix + } +} + +service lmtp { + unix_listener /var/spool/postfix/private/dovecot-lmtp { + mode = 0600 + user = postfix + group = postfix + } +} diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-tls.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-tls.conf new file mode 100644 index 000000000..92fde04b7 --- /dev/null +++ b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/90-freedombox-tls.conf @@ -0,0 +1,12 @@ +# Do not edit this file. Manage your settings on FreedomBox. + +# Mozilla Guideline v5.7, Dovecot 2.3.21, OpenSSL 3.4.0, intermediate. +# Generated 2025-07-16: https://ssl-config.mozilla.org/ +# See: https://doc.dovecot.org/main/core/config/ssl.html +ssl = required + +ssl_min_protocol = TLSv1.2 +ssl_server_prefer_ciphers = client + +ssl_curve_list = X25519:prime256v1:secp384r1 +ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305 diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/95-freedombox-sieve.conf b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/95-freedombox-sieve.conf new file mode 100644 index 000000000..a50a630f2 --- /dev/null +++ b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/95-freedombox-sieve.conf @@ -0,0 +1,43 @@ +# Do not edit this file. Manage your settings on FreedomBox. + +# Default sieve scripts applied for delivery to all users. To move mail to Junk +# folder based on classification headers set by rspamd. See: +# https://doc.dovecot.org/main/core/plugins/sieve.html + +sieve_script freedombox-after { + type = after + driver = file + path = /etc/dovecot/freedombox-sieve-after +} + +sieve_plugins { + sieve_imapsieve = yes + sieve_extprograms = yes +} + +sieve_global_extensions { + vnd.dovecot.pipe = yes + vnd.dovecot.environment = yes +} + +# Make rspamd learn spam/ham when the user marks mails as junk or not junk. +# https://doc.dovecot.org/main/core/config/spam_reporting.html +sieve_pipe_bin_dir = /usr/bin + +# When moving a mail from to Junk folder from elsewhere +mailbox Junk { + sieve_script learn-spam { + type = before + cause = copy + path = /etc/dovecot/freedombox-sieve/learn-spam.sieve + } +} + +# When moving a mail from from Junk folder to elsewhere +imapsieve_from Junk { + sieve_script learn-ham { + type = before + cause = copy + path = /etc/dovecot/freedombox-sieve/learn-ham.sieve + } +} diff --git a/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/freedombox-ldap.conf.ext b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/freedombox-ldap.conf.ext new file mode 100644 index 000000000..668ed3e43 --- /dev/null +++ b/plinth/modules/email/data/usr/share/freedombox/etc/dovecot/conf.d/2.4/freedombox-ldap.conf.ext @@ -0,0 +1,4 @@ +# Do not edit this file. Manage your settings on FreedomBox. + +# This file is not needed for Dovecot >= 2.4. It is only needed for simplifying +# compatibility with Dovecot 2.3. diff --git a/plinth/modules/email/dovecot.py b/plinth/modules/email/dovecot.py new file mode 100644 index 000000000..6aa462830 --- /dev/null +++ b/plinth/modules/email/dovecot.py @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Utilities to configure Dovecot.""" + +import apt + +from plinth.utils import Version + + +def is_version_24(): + """Return the currently installed version of Dovecot.""" + cache = apt.Cache() + try: + version = cache['dovecot-core'].installed.version + except KeyError: + return True + + return Version(version) >= Version('1:2.4') diff --git a/plinth/modules/email/privileged/__init__.py b/plinth/modules/email/privileged/__init__.py index 028c71157..76e5c88e0 100644 --- a/plinth/modules/email/privileged/__init__.py +++ b/plinth/modules/email/privileged/__init__.py @@ -2,8 +2,7 @@ """Provides privileged actions that run as root.""" from .aliases import setup_aliases -from .dkim import (get_dkim_public_key, setup_dkim, - fix_incorrect_key_ownership) +from .dkim import fix_incorrect_key_ownership, get_dkim_public_key, setup_dkim from .domain import set_domains from .home import setup_home from .postfix import setup_postfix diff --git a/plinth/modules/email/privileged/tls.py b/plinth/modules/email/privileged/tls.py index b540b5a69..11f4e49aa 100644 --- a/plinth/modules/email/privileged/tls.py +++ b/plinth/modules/email/privileged/tls.py @@ -10,6 +10,7 @@ See: https://doc.dovecot.org/configuration_manual/dovecot_ssl_configuration/ import pathlib from .. import postfix +from ..dovecot import is_version_24 # Mozilla Guideline v5.6, Postfix 1.17.7, OpenSSL 1.1.1d, intermediate # Generated 2021-08 @@ -68,15 +69,27 @@ def set_postfix_config(primary_domain, all_domains): def set_dovecot_config(primary_domain, all_domains): """Set dovecot configuration for TLS certificates.""" + is_new_version = is_version_24() + + # Determine whether to prefix file paths with '<' based on version + prefix = '' + cert_naming = 'ssl_server_cert_file' + key_naming = 'ssl_server_key_file' + if not is_new_version: + prefix = '<' + cert_naming = 'ssl_cert' + key_naming = 'ssl_key' + content = f'''# This file is managed by FreedomBox -ssl_cert = None: """Create components for the app.""" diff --git a/plinth/modules/sogo/privileged.py b/plinth/modules/sogo/privileged.py index 0de0f8076..7d7a13f95 100644 --- a/plinth/modules/sogo/privileged.py +++ b/plinth/modules/sogo/privileged.py @@ -81,7 +81,7 @@ def _create_config(db_password: str): SOGoTrashFolderName = "Trash"; SOGoJunkFolderName = "Junk"; SOGoIMAPServer = "imap://127.0.0.1:143/?tls=YES&tlsVerifyMode=allowInsecureLocalhost"; - SOGoSieveServer = "sieve://127.0.0.14190/?tls=YES&tlsVerifyMode=allowInsecureLocalhost"; + SOGoSieveServer = "sieve://127.0.0.1:4190/?tls=YES&tlsVerifyMode=allowInsecureLocalhost"; /* LDAP */ SOGoUserSources = ({{ diff --git a/plinth/privileged/config.py b/plinth/privileged/config.py index 57bd922a6..403b82e4b 100644 --- a/plinth/privileged/config.py +++ b/plinth/privileged/config.py @@ -10,7 +10,7 @@ from plinth import module_loader from plinth.actions import privileged -def _assert_managed_dropin_config(app_id: str, path: str): +def _get_managed_dropin_config(app_id: str, path: str): """Check that this is a path managed by the specified app.""" module_path = module_loader.get_module_import_path(app_id) module = importlib.import_module(module_path) @@ -25,7 +25,7 @@ def _assert_managed_dropin_config(app_id: str, path: str): components = app.get_components_of_type(DropinConfigs) for component in components: if path in component.etc_paths: - return + return component raise AssertionError('Not a managed drop-in config') @@ -37,10 +37,9 @@ def dropin_is_valid(app_id: str, path: str, copy_only: bool, Optionally, drop the link if it is invalid. """ - _assert_managed_dropin_config(app_id, path) - from plinth.config import DropinConfigs - etc_path = DropinConfigs.get_etc_path(path) - target = DropinConfigs.get_target_path(path) + component = _get_managed_dropin_config(app_id, path) + etc_path = component.get_etc_path(path) + target = component.get_target_path(path) if etc_path.exists() or etc_path.is_symlink(): if (not copy_only and etc_path.is_symlink() and etc_path.readlink() == target): @@ -59,10 +58,9 @@ def dropin_is_valid(app_id: str, path: str, copy_only: bool, @privileged def dropin_link(app_id: str, path: str, copy_only: bool): """Create a symlink from /etc/ to /usr/share/freedombox/etc.""" - _assert_managed_dropin_config(app_id, path) - from plinth.config import DropinConfigs - target = DropinConfigs.get_target_path(path) - etc_path = DropinConfigs.get_etc_path(path) + component = _get_managed_dropin_config(app_id, path) + target = component.get_target_path(path) + etc_path = component.get_etc_path(path) etc_path.parent.mkdir(parents=True, exist_ok=True) if copy_only: shutil.copyfile(target, etc_path) @@ -73,7 +71,6 @@ def dropin_link(app_id: str, path: str, copy_only: bool): @privileged def dropin_unlink(app_id: str, path: str, missing_ok: bool = False): """Remove a symlink in /etc/.""" - _assert_managed_dropin_config(app_id, path) - from plinth.config import DropinConfigs - etc_path = DropinConfigs.get_etc_path(path) + component = _get_managed_dropin_config(app_id, path) + etc_path = component.get_etc_path(path) etc_path.unlink(missing_ok=missing_ok) diff --git a/plinth/tests/test_config.py b/plinth/tests/test_config.py index f0dc12d82..86e235410 100644 --- a/plinth/tests/test_config.py +++ b/plinth/tests/test_config.py @@ -31,9 +31,10 @@ def fixture_dropin_configs(): @pytest.fixture(autouse=True) -def fixture_assert_dropin_config(): +def fixture_assert_dropin_config(dropin_configs): """Mock asserting dropin config path.""" - with patch('plinth.privileged.config._assert_managed_dropin_config'): + with patch('plinth.privileged.config._get_managed_dropin_config') as mock: + mock.return_value = dropin_configs yield @@ -95,7 +96,7 @@ def test_dropin_configs_enable_disable_symlinks(dropin_configs, tmp_path): # Enable when a file already exists dropin_configs.disable() - etc_path = DropinConfigs.get_etc_path('/etc/test/path1') + etc_path = dropin_configs.get_etc_path('/etc/test/path1') etc_path.touch() dropin_configs.enable() _assert_symlinks(dropin_configs, tmp_path, should_exist=True) @@ -108,7 +109,7 @@ def test_dropin_configs_enable_disable_symlinks(dropin_configs, tmp_path): # When symlink already exists to correct location dropin_configs.disable() - target_path = DropinConfigs.get_target_path('/etc/test/path1') + target_path = dropin_configs.get_target_path('/etc/test/path1') etc_path.symlink_to(target_path) dropin_configs.enable() _assert_symlinks(dropin_configs, tmp_path, should_exist=True) @@ -119,7 +120,7 @@ def test_dropin_configs_enable_disable_copy_only(dropin_configs, tmp_path): with patch('plinth.config.DropinConfigs.ROOT', new=tmp_path): dropin_configs.copy_only = True for path in ['/etc/test/path1', '/etc/path2']: - target = DropinConfigs.get_target_path(path) + target = dropin_configs.get_target_path(path) target.parent.mkdir(parents=True, exist_ok=True) target.write_text('test-config-content') @@ -135,7 +136,7 @@ def test_dropin_configs_enable_disable_copy_only(dropin_configs, tmp_path): # Enable when a file already exists with wrong content dropin_configs.disable() - etc_path = DropinConfigs.get_etc_path('/etc/test/path1') + etc_path = dropin_configs.get_etc_path('/etc/test/path1') etc_path.write_text('x-invalid-content') dropin_configs.enable() _assert_symlinks(dropin_configs, tmp_path, should_exist=True, @@ -182,7 +183,7 @@ def test_dropin_config_diagnose_symlinks(dropin_configs, tmp_path): # A file exists instead of symlink dropin_configs.disable() - etc_path = DropinConfigs.get_etc_path('/etc/test/path1') + etc_path = dropin_configs.get_etc_path('/etc/test/path1') etc_path.touch() results = dropin_configs.diagnose() assert results[0].result == 'failed' @@ -204,7 +205,7 @@ def test_dropin_config_diagnose_copy_only(dropin_configs, tmp_path): with patch('plinth.config.DropinConfigs.ROOT', new=tmp_path): dropin_configs.copy_only = True for path in ['/etc/test/path1', '/etc/path2']: - target = DropinConfigs.get_target_path(path) + target = dropin_configs.get_target_path(path) target.parent.mkdir(parents=True, exist_ok=True) target.write_text('test-config-content') @@ -221,7 +222,7 @@ def test_dropin_config_diagnose_copy_only(dropin_configs, tmp_path): # A symlink exists instead of a copied file dropin_configs.disable() - etc_path = DropinConfigs.get_etc_path('/etc/test/path1') + etc_path = dropin_configs.get_etc_path('/etc/test/path1') etc_path.symlink_to('/blah') results = dropin_configs.diagnose() assert results[0].result == 'failed'