diff --git a/HACKING.md b/HACKING.md index 66860affd..7ac761a7f 100644 --- a/HACKING.md +++ b/HACKING.md @@ -589,6 +589,19 @@ installed. $ py.test-3 -n 4 --dist=loadfile --include-functional -m essential ``` +#### Enabling all apps + +When testing distribution upgrades from a stable release to next stable release +or testing, it is beneficial to install all available FreedomBox applications. +There is a script available to perform operation. + +In the VM or container, run the following command: + +``` +guest$ cd /freedombox +guest$ sudo make build install +guest$ plinth/tests/functional/enable-all-apps +``` [back to index](#hacking) diff --git a/container b/container index b568eab8c..7b87cea95 100755 --- a/container +++ b/container @@ -318,7 +318,7 @@ LIBVIRT_DOMAIN_XML_TEMPLATE = ''' /usr/bin/qemu-system-x86_64 - +
@@ -1356,10 +1356,10 @@ class VM(Machine): except subprocess.CalledProcessError: pass - image_file = _get_image_file(self.distribution) + qcow_image = self._create_qcow_image() domain_xml = LIBVIRT_DOMAIN_XML_TEMPLATE.format( domain_name=self.machine_name, memory_mib='2048', cpus='4', - image_file=image_file, source_dir=_get_project_folder()) + image_file=qcow_image, source_dir=_get_project_folder()) with tempfile.NamedTemporaryFile() as file_handle: file_handle.write(domain_xml.encode()) @@ -1389,12 +1389,22 @@ class VM(Machine): return logger.info('Running `virsh destroy %s`', self.machine_name) - self._virsh(['destroy', self.machine_name], stdout=subprocess.DEVNULL) + try: + self._virsh(['undefine', self.machine_name], + stdout=subprocess.DEVNULL) + except subprocess.CalledProcessError: + pass def destroy(self) -> None: """Remove all traces of the VM from the host.""" logger.info('Running `virsh undefine %s`', self.machine_name) - self._virsh(['undefine', self.machine_name], stdout=subprocess.DEVNULL) + try: + self._virsh(['undefine', self.machine_name], + stdout=subprocess.DEVNULL) + except subprocess.CalledProcessError: + pass + + self._destroy_qcow_image() def get_ip_address(self) -> str | None: """Return the IP address assigned to the VM.""" @@ -1426,6 +1436,27 @@ class VM(Machine): """Run virsh to control the virtual machine.""" return subprocess.run(['sudo', 'virsh'] + args, check=check, **kwargs) + def _create_qcow_image(self) -> pathlib.Path: + """Convert raw image into qcow2 image. + + qcow2 image format allows snapshots of VM. + """ + image_file = _get_image_file(self.distribution) + qcow_image = image_file.with_suffix('.qcow2') + logger.info('Creating qcow2 image: %s', qcow_image) + subprocess.run([ + 'sudo', 'qemu-img', 'convert', '-f', 'raw', '-O', 'qcow2', + image_file, qcow_image + ], check=True) + return qcow_image + + def _destroy_qcow_image(self) -> None: + """Delete the qcow image.""" + image_file = _get_image_file(self.distribution) + qcow_image = image_file.with_suffix('.qcow2') + logger.info('Removing qcow2 image: %s', qcow_image) + qcow_image.unlink(missing_ok=True) + def subcommand_up(arguments: argparse.Namespace): """Download, setup and bring up the container.""" diff --git a/debian/changelog b/debian/changelog index 91af9b7c9..a99e080c3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,29 @@ +freedombox (25.6) unstable; urgency=medium + + [ Sunil Mohan Adapa ] + * setup: Fix a minor flake8 complaint + * doc: Fetch latest manual + * container: Allow taking snapshots of VMs + * upgrades: Run dpkg/apt fixes before dist upgrade + * names: Store domains in kvstore instead of /etc/hosts + * container: Don't remove qcow2 image when terminating VM + * operation: Drop type annotations on enum members + * storage: tests: Fix a test failure for psutils >= 7.0 + + [ Dietmar ] + * Translated using Weblate (German) + + [ James Valleroy ] + * tests: functional: Add enable-all-apps script + * locale: Update translation strings + * doc: Fetch latest manual + + [ Benedek Nagy ] + * SOGo: add dpkg-dev to the package list + * syncthing: Extend setup process to recover from manual config errors + + -- James Valleroy Tue, 25 Mar 2025 10:09:07 -0400 + freedombox (25.5~bpo12+1) bookworm-backports; urgency=medium * Rebuild for bookworm-backports. diff --git a/doc/manual/en/Apache_userdir.raw.wiki b/doc/manual/en/Apache_userdir.raw.wiki index f96733161..8475ecd1a 100644 --- a/doc/manual/en/Apache_userdir.raw.wiki +++ b/doc/manual/en/Apache_userdir.raw.wiki @@ -76,7 +76,8 @@ Usually any Unix system, including Linux in all (most) of its flavours and Mac, Examples: Connect to !FreedomBox via SSH: - 1. (replacing `username` with a valid !FreedomBox user name and `freedombox.local` with your !FreedomBox's domain name or IP): {{{ + 1. (replacing `username` with a valid !FreedomBox user name and `freedombox.local` with your !FreedomBox's domain name or IP): + {{{ $ ssh username@freedombox.local }}} 1. If your data is ok and your !FreedomBox reachable, the first time you'll be asked to confirm its signature. @@ -85,36 +86,41 @@ $ ssh username@freedombox.local 1. The prompt changes to `username@freedombox:~$`. Once connected create your website folder with: - {{{ +{{{ username@freedombox:~$ mkdir ~/public_html }}} ...or one for another user: - 1. use the `sudo` prefix like {{{ + 1. use the `sudo` prefix like + {{{ username@freedombox:~$ sudo mkdir /home//public_html -}}}, and introduce your password. - 1. When you create a folder, by default it belongs to you no matter where it is created. Thus you'll then need to set its ownership to the other user: {{{ -username@freedombox:~$ sudo chown : /home//public_htm }}} - 1. Better check it before you disconnect that `public_html' is listed among the contents of the other user's home folder. + and introduce your password. + 1. When you create a folder, by default it belongs to you no matter where it is created. Thus you'll then need to set its ownership to the other user: + {{{ +username@freedombox:~$ sudo chown : /home//public_html +}}} + 1. Better check it before you disconnect that `public_html` is listed among the contents of the other user's home folder. {{{ username@freedombox:~$ ls -l /home/ ... drwxr-xr-x 2 4096 jan 29 17:39 public_html ... -}}}. The name of the other user must appear '''twice''' in the `public_html` line and its permissions should be '''`drwxr-xr-x`'''. +}}} + The name of the other user must appear '''twice''' in the `public_html` line and its permissions should be '''`drwxr-xr-x`'''. Then any user can upload their files to their respective folders with any of the graphical clients. Ask them to check it. It is a good security practice to exit instead of to just wait for the connection to time out: - {{{ +{{{ username@freedombox:~$ exit }}} If then you want to also upload the web content through the command line you can {{{ $ scp path/to/files username@freedombox.local:public_html/ -}}}. It will ask your password in !FreedomBox. You should then be able to navigate your browser to the corresponding url and see the files. +}}} +It will ask your password in !FreedomBox. You should then be able to navigate your browser to the corresponding url and see the files. Learn more about ssh, scp and sftp with `$ man ssh`, `$ man scp` and `$ man sftp`. diff --git a/doc/manual/en/ReleaseNotes.raw.wiki b/doc/manual/en/ReleaseNotes.raw.wiki index cba5e5465..7a6daec82 100644 --- a/doc/manual/en/ReleaseNotes.raw.wiki +++ b/doc/manual/en/ReleaseNotes.raw.wiki @@ -8,6 +8,26 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f The following are the release notes for each !FreedomBox version. +== FreedomBox 25.6 (2025-03-25) == + +=== Highlight === + + * names: Store domains in kvstore instead of /etc/hosts + +=== Other Changes === + + * container: Allow taking snapshots of VMs + * container: Don't remove qcow2 image when terminating VM + * doc: Fetch latest manual + * locale: Update translations for German + * operation: Drop type annotations on enum members + * setup: Fix a minor flake8 complaint + * SOGo: add dpkg-dev to the package list + * storage: tests: Fix a test failure for psutils >= 7.0 + * syncthing: Extend setup process to recover from manual config errors + * tests: functional: Add enable-all-apps script + * upgrades: Run dpkg/apt fixes before dist upgrade + == FreedomBox 25.5 (2025-03-10) == === Highlights === diff --git a/doc/manual/en/ejabberd.raw.wiki b/doc/manual/en/ejabberd.raw.wiki index aa2c2060e..2ff4c4816 100644 --- a/doc/manual/en/ejabberd.raw.wiki +++ b/doc/manual/en/ejabberd.raw.wiki @@ -59,7 +59,7 @@ If your !FreedomBox is behind a router, you will need to set up port forwarding * !FreedomBox provides a web client: [[FreedomBox/Manual/JSXC|JSXC]]. * [[https://xmpp.org/software/clients.html|XMPP clients]] are available for various desktop and mobile platforms. !FreedomBox links to the download sources of some of them. Feel free to include more [[https://wiki.debian.org/FreedomBox/Manual/ejabberd?action=edit&editor=text|here]] (needs free registration). We'll notice and might list them in !FreedomBox. - {{attachment:xmpp-clients_en_V01.png|XMPP clients}} + {{attachment:xmpp-clients_en_V02.png|XMPP clients}} ==== FreedomBox webclient ==== diff --git a/doc/manual/en/images/xmpp-clients_en_V01.png b/doc/manual/en/images/xmpp-clients_en_V01.png deleted file mode 100644 index 17ff60652..000000000 Binary files a/doc/manual/en/images/xmpp-clients_en_V01.png and /dev/null differ diff --git a/doc/manual/en/images/xmpp-clients_en_V02.png b/doc/manual/en/images/xmpp-clients_en_V02.png new file mode 100644 index 000000000..b24389c40 Binary files /dev/null and b/doc/manual/en/images/xmpp-clients_en_V02.png differ diff --git a/doc/manual/es/Apache_userdir.raw.wiki b/doc/manual/es/Apache_userdir.raw.wiki index c67d2b7d6..cd26ea4e8 100644 --- a/doc/manual/es/Apache_userdir.raw.wiki +++ b/doc/manual/es/Apache_userdir.raw.wiki @@ -70,7 +70,8 @@ Normalmente cualquier sistema Unix, incluyendo a Linux en (casi) todos sus sabor Ejemplos: Conectar con !FreedomBox mediante SSH: - 1. (reemplazando `usuario` por un nombre de usuario válido en !FreedomBox y `freedombox.local` por el nombre de dominio o la IP de tu !FreedomBox): {{{ + 1. (reemplazando `usuario` por un nombre de usuario válido en !FreedomBox y `freedombox.local` por el nombre de dominio o la IP de tu !FreedomBox): + {{{ $ ssh usuario@freedombox.local }}} 1. Si tus datos son correctos y tu !FreedomBox está accesible la primera vez se te pedirá confirmar su identificación. @@ -79,15 +80,18 @@ $ ssh usuario@freedombox.local 1. El prompt cambia a `usuario@freedombox:~$`. Una vez conectado crea la carpeta de tu sitio web mediante: - {{{ +{{{ usuario@freedombox:~$ mkdir ~/public_html }}} ...o crea la del sitio de otro usuario: - 1. Usa el prefijo `sudo` así: {{{ + 1. Usa el prefijo `sudo` así: + {{{ usuario@freedombox:~$ sudo mkdir /home//public_html -}}}, e introduce tu contraseña. - 1. Al crear una carpeta, por defecto te pertenece a tí la crees donde la crées. Por tanto, necesitarás poner como dueño al otro usuario: {{{ +}}} + e introduce tu contraseña. + 1. Al crear una carpeta, por defecto te pertenece a tí la crees donde la crées. Por tanto, necesitarás poner como dueño al otro usuario: + {{{ usuario@freedombox:~$ sudo chown : /home//public_htm }}} 1. Antes de desconectar mejor verificas que `public_html' se muestra entre los contenidos de la carpeta `home` del otro usuario. @@ -96,19 +100,21 @@ usuario@freedombox:~$ ls -l /home/ ... drwxr-xr-x 2 4096 jan 29 17:39 public_html ... -}}}. El nombre del otro usuario tiene que aparecer '''por duplicado''' en la línea de `public_html` y sus permisos deben ser '''`drwxr-xr-x`'''. +}}} + El nombre del otro usuario tiene que aparecer '''por duplicado''' en la línea de `public_html` y sus permisos deben ser '''`drwxr-xr-x`'''. Ahora el otro usuario puede subir sus archivos con cualquiera de los clientes gráficos. Pídeles que lo comprueben. Es una buena práctica de seguridad salir en vez de esperar simplemente a que la conexión caduque: - {{{ +{{{ usuario@freedombox:~$ exit }}} Si quieres emplear la linea de órdenes también para subir contenido puedes {{{ $ scp ruta/a/los/archivos usuario@freedombox.local:public_html/ -}}}. Te pedirá tu contraseña en !FreedomBox. A continuación deberías poder ir con tu navegador a la URL correspondiente y ver tus archivos. +}}} +Te pedirá tu contraseña en !FreedomBox. A continuación deberías poder ir con tu navegador a la URL correspondiente y ver tus archivos. Más acerca de ssh, scp y sftp con `$ man ssh`, `$ man scp` y `$ man sftp`. diff --git a/doc/manual/es/ReleaseNotes.raw.wiki b/doc/manual/es/ReleaseNotes.raw.wiki index cba5e5465..7a6daec82 100644 --- a/doc/manual/es/ReleaseNotes.raw.wiki +++ b/doc/manual/es/ReleaseNotes.raw.wiki @@ -8,6 +8,26 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f The following are the release notes for each !FreedomBox version. +== FreedomBox 25.6 (2025-03-25) == + +=== Highlight === + + * names: Store domains in kvstore instead of /etc/hosts + +=== Other Changes === + + * container: Allow taking snapshots of VMs + * container: Don't remove qcow2 image when terminating VM + * doc: Fetch latest manual + * locale: Update translations for German + * operation: Drop type annotations on enum members + * setup: Fix a minor flake8 complaint + * SOGo: add dpkg-dev to the package list + * storage: tests: Fix a test failure for psutils >= 7.0 + * syncthing: Extend setup process to recover from manual config errors + * tests: functional: Add enable-all-apps script + * upgrades: Run dpkg/apt fixes before dist upgrade + == FreedomBox 25.5 (2025-03-10) == === Highlights === diff --git a/doc/manual/es/Upgrades.raw.wiki b/doc/manual/es/Upgrades.raw.wiki index 9848933ec..01f2f22e6 100644 --- a/doc/manual/es/Upgrades.raw.wiki +++ b/doc/manual/es/Upgrades.raw.wiki @@ -69,15 +69,9 @@ General: * Toma una instantánea del sistema antes de empezar. * Hay disponibles [[DebianUpgrade|instrucciones]] generales para actualizar la distributción Debian. * Se sabe que algunos paquetes preguntan al usuario durante la actualización debido a cambios en los ficheros conf. Se recomienda no actualizar estos paquetes manualmente, sino permitir que !FreedomBox trate sus actualizaciones automaticamente. Nos referimos a los siguientes paquetes: - * bind9 * firewalld - * janus - * minetest-server * minidlna - * mumble-server * radicale - * roundcube-core - * tt-rss ## END_INCLUDE diff --git a/doc/manual/es/ejabberd.raw.wiki b/doc/manual/es/ejabberd.raw.wiki index 8d1b3790c..79ebb3700 100644 --- a/doc/manual/es/ejabberd.raw.wiki +++ b/doc/manual/es/ejabberd.raw.wiki @@ -57,7 +57,7 @@ Si tu !FreedomBox está detrás de un router tendrás que configurar en él la r * !FreedomBox proporciona un cliente web: [[es/FreedomBox/Manual/JSXC|JSXC]]. * Hay [[https://xmpp.org/software/clients.html|clientes XMPP]] disponibles para varias plataformas de escritorio y móviles. !FreedomBox enlaza a las fuentes de descarga de algunos. Eres libre de incluir más [[https://wiki.debian.org/FreedomBox/Manual/ejabberd?action=edit&editor=text|aquí]] (requiere registro libre). Nosotros lo notaremos y quizá lo listemos en !FreedomBox. - {{attachment:FreedomBox/Manual/ejabberd/xmpp-clients_en_V01.png|clientes XMPP}} + {{attachment:FreedomBox/Manual/ejabberd/xmpp-clients_en_V02.png|clientes XMPP}} ==== Cliente Web de FreedomBox ==== diff --git a/doc/manual/es/images/xmpp-clients_en_V01.png b/doc/manual/es/images/xmpp-clients_en_V01.png deleted file mode 100644 index 17ff60652..000000000 Binary files a/doc/manual/es/images/xmpp-clients_en_V01.png and /dev/null differ diff --git a/doc/manual/es/images/xmpp-clients_en_V02.png b/doc/manual/es/images/xmpp-clients_en_V02.png new file mode 100644 index 000000000..b24389c40 Binary files /dev/null and b/doc/manual/es/images/xmpp-clients_en_V02.png differ diff --git a/plinth/__init__.py b/plinth/__init__.py index 2e0e966f5..5cc33fb74 100644 --- a/plinth/__init__.py +++ b/plinth/__init__.py @@ -3,4 +3,4 @@ Package init file. """ -__version__ = '25.5' +__version__ = '25.6' diff --git a/plinth/locale/ar/LC_MESSAGES/django.po b/plinth/locale/ar/LC_MESSAGES/django.po index 750045620..062355609 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: 2025-03-10 20:08-0400\n" +"POT-Creation-Date: 2025-03-25 09:08-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" @@ -1888,13 +1888,13 @@ msgstr "" msgid "Use HTTP basic authentication" msgstr "" -#: modules/dynamicdns/forms.py:76 modules/names/forms.py:108 +#: modules/dynamicdns/forms.py:76 modules/names/forms.py:107 #: modules/names/templates/names.html:19 msgid "Domain Name" msgstr "Име на домейн" -#: modules/dynamicdns/forms.py:79 modules/names/forms.py:101 -#: modules/names/forms.py:119 +#: modules/dynamicdns/forms.py:79 modules/names/forms.py:100 +#: modules/names/forms.py:118 msgid "Invalid domain name" msgstr "Недопустимо име на домейн" @@ -1915,7 +1915,7 @@ msgstr "" msgid "This field is required." msgstr "" -#: modules/dynamicdns/forms.py:138 modules/names/forms.py:126 +#: modules/dynamicdns/forms.py:138 modules/names/forms.py:125 msgid "Domain already exists." msgstr "Домейнът вече съществува." @@ -4449,7 +4449,7 @@ msgstr "Паролата за присъединяване е променена msgid "Root channel name changed." msgstr "" -#: modules/names/__init__.py:33 +#: modules/names/__init__.py:34 #, python-brace-format msgid "" "Name Services provides an overview of the ways {box_name} can be reached " @@ -4462,19 +4462,19 @@ msgstr "" "Pagekite. За всеки тип име е показано дали услугите HTTP, HTTPS и SSH са " "разрешени или забранени за входящи връзки през даденото име." -#: modules/names/__init__.py:54 +#: modules/names/__init__.py:55 msgid "Name Services" msgstr "Услуги за имена" -#: modules/names/__init__.py:68 +#: modules/names/__init__.py:69 msgid "Domain (regular)" msgstr "" -#: modules/names/__init__.py:175 +#: modules/names/__init__.py:178 msgid "Package systemd-resolved is installed" msgstr "" -#: modules/names/__init__.py:199 +#: modules/names/__init__.py:232 #, python-brace-format msgid "Resolve domain name: {domain}" msgstr "" @@ -4491,19 +4491,19 @@ msgstr "Всички приложения за уеб" msgid "Secure Shell" msgstr "Secure Shell" -#: modules/names/forms.py:23 +#: modules/names/forms.py:22 msgid "Use DNS-over-TLS for resolving domains (global preference)" msgstr "" -#: modules/names/forms.py:51 +#: modules/names/forms.py:50 msgid "Use DNSSEC when resolving domains (global preference)" msgstr "" -#: modules/names/forms.py:86 modules/names/manifest.py:12 +#: modules/names/forms.py:85 modules/names/manifest.py:12 msgid "Hostname" msgstr "Име на хоста" -#: modules/names/forms.py:87 +#: modules/names/forms.py:86 #, python-brace-format msgid "" "Hostname is the local name by which other devices on the local network can " @@ -4515,11 +4515,11 @@ msgstr "" "Трябва да започва и завършва с буква или цифра, но може да съдържа и тире. " "Общата дължина не трябва да надхвърля 63 знака." -#: modules/names/forms.py:93 +#: modules/names/forms.py:92 msgid "Invalid hostname" msgstr "Недопустимо име на хост" -#: modules/names/forms.py:109 +#: modules/names/forms.py:108 #, python-brace-format msgid "" "Domain name is the global name by which other devices on the Internet can " @@ -7603,7 +7603,7 @@ msgstr "Кореновата файлова система е достъпна msgid "Go to Power" msgstr "Към Захранване" -#: modules/storage/__init__.py:447 modules/storage/tests/test_storage.py:396 +#: modules/storage/__init__.py:447 modules/storage/tests/test_storage.py:405 msgid "grub package is configured" msgstr "Пакетът grub е настроен" diff --git a/plinth/locale/bn/LC_MESSAGES/django.po b/plinth/locale/bn/LC_MESSAGES/django.po index dc81c6b8c..83ed18cae 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: 2025-03-10 20:08-0400\n" +"POT-Creation-Date: 2025-03-25 09:08-0400\n" "PO-Revision-Date: 2021-06-16 07:33+0000\n" "Last-Translator: Oymate \n" "Language-Team: Bengali \n" "Language-Team: Czech \n" "Language-Team: Danish \n" "Language-Team: German \n" @@ -19,7 +19,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.10.3-dev\n" #: config.py:103 #, python-brace-format @@ -1908,9 +1908,9 @@ msgid "" "used within the URL. For details see the update URL templates of the example " "providers." msgstr "" -"Die Variablen <Benutzer>, <Pass>, <IP>, <Domain> " -"können in der URL verwendet werden. Details finden Sie in den Update-URL-" -"Vorlagen der Beispielanbieter." +"Die Variablen <User>, <Pass>, <Ip>, <Domain> können " +"in der URL verwendet werden. Details finden Sie in den Update-URL-Vorlagen " +"der Beispielanbieter." #: modules/dynamicdns/forms.py:24 msgid "" @@ -1992,13 +1992,13 @@ msgstr "Alle SSL-Zertifikate akzeptieren" msgid "Use HTTP basic authentication" msgstr "HTTP-Basisauthentifizierung verwenden" -#: modules/dynamicdns/forms.py:76 modules/names/forms.py:108 +#: modules/dynamicdns/forms.py:76 modules/names/forms.py:107 #: modules/names/templates/names.html:19 msgid "Domain Name" msgstr "Domain-Name" -#: modules/dynamicdns/forms.py:79 modules/names/forms.py:101 -#: modules/names/forms.py:119 +#: modules/dynamicdns/forms.py:79 modules/names/forms.py:100 +#: modules/names/forms.py:118 msgid "Invalid domain name" msgstr "Ungültiger Domain-Name" @@ -2019,7 +2019,7 @@ msgstr "Verwenden Sie IPv6 statt IPv4" msgid "This field is required." msgstr "Dieses Feld ist erforderlich." -#: modules/dynamicdns/forms.py:138 modules/names/forms.py:126 +#: modules/dynamicdns/forms.py:138 modules/names/forms.py:125 msgid "Domain already exists." msgstr "Domain existiert bereits." @@ -4884,7 +4884,7 @@ msgstr "Beitrittspasswort geändert" msgid "Root channel name changed." msgstr "Name des Hauptkanals geändert." -#: modules/names/__init__.py:33 +#: modules/names/__init__.py:34 #, python-brace-format msgid "" "Name Services provides an overview of the ways {box_name} can be reached " @@ -4897,19 +4897,19 @@ msgstr "" "jeden Namens-Typ wird angezeigt, ob die HTTP-, HTTPS- und SSH-Dienste für " "eingehende Verbindungen eingeschaltet oder ausgeschaltet ist." -#: modules/names/__init__.py:54 +#: modules/names/__init__.py:55 msgid "Name Services" msgstr "Namen-Dienste" -#: modules/names/__init__.py:68 +#: modules/names/__init__.py:69 msgid "Domain (regular)" -msgstr "" +msgstr "Domäne (regulär)" -#: modules/names/__init__.py:175 +#: modules/names/__init__.py:178 msgid "Package systemd-resolved is installed" msgstr "Paket systemd-resolved ist installiert" -#: modules/names/__init__.py:199 +#: modules/names/__init__.py:232 #, python-brace-format msgid "Resolve domain name: {domain}" msgstr "Domainname auflösen: {domain}" @@ -4926,20 +4926,20 @@ msgstr "Alle Webanwendungen" msgid "Secure Shell" msgstr "Secure Shell" -#: modules/names/forms.py:23 +#: modules/names/forms.py:22 msgid "Use DNS-over-TLS for resolving domains (global preference)" msgstr "" "DNS-over-TLS für die Auflösung von Domänen verwenden (globale Präferenz)" -#: modules/names/forms.py:51 +#: modules/names/forms.py:50 msgid "Use DNSSEC when resolving domains (global preference)" msgstr "DNSSEC bei der Auflösung von Domains verwenden (globale Einstellung)" -#: modules/names/forms.py:86 modules/names/manifest.py:12 +#: modules/names/forms.py:85 modules/names/manifest.py:12 msgid "Hostname" msgstr "Hostname" -#: modules/names/forms.py:87 +#: modules/names/forms.py:86 #, python-brace-format msgid "" "Hostname is the local name by which other devices on the local network can " @@ -4952,11 +4952,11 @@ msgstr "" "darf als einziges Sonderzeichen das Minuszeichen enthalten. Die Gesamtlänge " "darf 63 Zeichen nicht überschreiten." -#: modules/names/forms.py:93 +#: modules/names/forms.py:92 msgid "Invalid hostname" msgstr "Ungültiger Hostname" -#: modules/names/forms.py:109 +#: modules/names/forms.py:108 #, python-brace-format msgid "" "Domain name is the global name by which other devices on the Internet can " @@ -8382,7 +8382,7 @@ msgstr "Schreibgeschütztes Root-Dateisystem" msgid "Go to Power" msgstr "Springe zur Stromversorgung" -#: modules/storage/__init__.py:447 modules/storage/tests/test_storage.py:396 +#: modules/storage/__init__.py:447 modules/storage/tests/test_storage.py:405 msgid "grub package is configured" msgstr "grub-Paket ist konfiguriert" diff --git a/plinth/locale/django.pot b/plinth/locale/django.pot index e446d67d9..fcc3d2a29 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: 2025-03-10 20:08-0400\n" +"POT-Creation-Date: 2025-03-25 09:08-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1779,13 +1779,13 @@ msgstr "" msgid "Use HTTP basic authentication" msgstr "" -#: modules/dynamicdns/forms.py:76 modules/names/forms.py:108 +#: modules/dynamicdns/forms.py:76 modules/names/forms.py:107 #: modules/names/templates/names.html:19 msgid "Domain Name" msgstr "" -#: modules/dynamicdns/forms.py:79 modules/names/forms.py:101 -#: modules/names/forms.py:119 +#: modules/dynamicdns/forms.py:79 modules/names/forms.py:100 +#: modules/names/forms.py:118 msgid "Invalid domain name" msgstr "" @@ -1806,7 +1806,7 @@ msgstr "" msgid "This field is required." msgstr "" -#: modules/dynamicdns/forms.py:138 modules/names/forms.py:126 +#: modules/dynamicdns/forms.py:138 modules/names/forms.py:125 msgid "Domain already exists." msgstr "" @@ -4250,7 +4250,7 @@ msgstr "" msgid "Root channel name changed." msgstr "" -#: modules/names/__init__.py:33 +#: modules/names/__init__.py:34 #, python-brace-format msgid "" "Name Services provides an overview of the ways {box_name} can be reached " @@ -4259,19 +4259,19 @@ msgid "" "enabled or disabled for incoming connections through the given name." msgstr "" -#: modules/names/__init__.py:54 +#: modules/names/__init__.py:55 msgid "Name Services" msgstr "" -#: modules/names/__init__.py:68 +#: modules/names/__init__.py:69 msgid "Domain (regular)" msgstr "" -#: modules/names/__init__.py:175 +#: modules/names/__init__.py:178 msgid "Package systemd-resolved is installed" msgstr "" -#: modules/names/__init__.py:199 +#: modules/names/__init__.py:232 #, python-brace-format msgid "Resolve domain name: {domain}" msgstr "" @@ -4288,19 +4288,19 @@ msgstr "" msgid "Secure Shell" msgstr "" -#: modules/names/forms.py:23 +#: modules/names/forms.py:22 msgid "Use DNS-over-TLS for resolving domains (global preference)" msgstr "" -#: modules/names/forms.py:51 +#: modules/names/forms.py:50 msgid "Use DNSSEC when resolving domains (global preference)" msgstr "" -#: modules/names/forms.py:86 modules/names/manifest.py:12 +#: modules/names/forms.py:85 modules/names/manifest.py:12 msgid "Hostname" msgstr "" -#: modules/names/forms.py:87 +#: modules/names/forms.py:86 #, python-brace-format msgid "" "Hostname is the local name by which other devices on the local network can " @@ -4309,11 +4309,11 @@ msgid "" "length must be 63 characters or less." msgstr "" -#: modules/names/forms.py:93 +#: modules/names/forms.py:92 msgid "Invalid hostname" msgstr "" -#: modules/names/forms.py:109 +#: modules/names/forms.py:108 #, python-brace-format msgid "" "Domain name is the global name by which other devices on the Internet can " @@ -7241,7 +7241,7 @@ msgstr "" msgid "Go to Power" msgstr "" -#: modules/storage/__init__.py:447 modules/storage/tests/test_storage.py:396 +#: modules/storage/__init__.py:447 modules/storage/tests/test_storage.py:405 msgid "grub package is configured" msgstr "" diff --git a/plinth/locale/el/LC_MESSAGES/django.po b/plinth/locale/el/LC_MESSAGES/django.po index 7bb6afd0a..02f765e8b 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: 2025-03-10 20:08-0400\n" +"POT-Creation-Date: 2025-03-25 09:08-0400\n" "PO-Revision-Date: 2022-09-14 17:20+0000\n" "Last-Translator: ikmaak \n" "Language-Team: Greek \n" "Language-Team: Spanish \n" "Language-Team: Persian \n" "Language-Team: Plinth Developers \n" "Language-Team: French \n" "Language-Team: Galician \n" "Language-Team: Gujarati \n" "Language-Team: Hindi \n" "Language-Team: Hungarian \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 \n" "Language-Team: Polish \n" "Language-Team: Portuguese \n" "Language-Team: Russian \n" "Language-Team: Sinhala \n" "Language-Team: Slovenian \n" "Language-Team: Albanian \n" "Language-Team: Serbian \n" "Language-Team: Swedish \n" "Language-Team: Tamil \n" "Language-Team: Telugu \n" "Language-Team: Turkish \n" "Language-Team: Ukrainian \n" "Language-Team: Vietnamese \n" @@ -1861,13 +1861,13 @@ msgstr "接受所有 SSL 证书" msgid "Use HTTP basic authentication" msgstr "使用 HTTP 基本身份验证" -#: modules/dynamicdns/forms.py:76 modules/names/forms.py:108 +#: modules/dynamicdns/forms.py:76 modules/names/forms.py:107 #: modules/names/templates/names.html:19 msgid "Domain Name" msgstr "域名" -#: modules/dynamicdns/forms.py:79 modules/names/forms.py:101 -#: modules/names/forms.py:119 +#: modules/dynamicdns/forms.py:79 modules/names/forms.py:100 +#: modules/names/forms.py:118 msgid "Invalid domain name" msgstr "无效的域名" @@ -1888,7 +1888,7 @@ msgstr "使用IPv6而不是IPv4" msgid "This field is required." msgstr "" -#: modules/dynamicdns/forms.py:138 modules/names/forms.py:126 +#: modules/dynamicdns/forms.py:138 modules/names/forms.py:125 msgid "Domain already exists." msgstr "域名已存在。" @@ -4398,7 +4398,7 @@ msgstr "加入密码已更改" msgid "Root channel name changed." msgstr "" -#: modules/names/__init__.py:33 +#: modules/names/__init__.py:34 #, python-brace-format msgid "" "Name Services provides an overview of the ways {box_name} can be reached " @@ -4407,19 +4407,19 @@ msgid "" "enabled or disabled for incoming connections through the given name." msgstr "" -#: modules/names/__init__.py:54 +#: modules/names/__init__.py:55 msgid "Name Services" msgstr "名称服务" -#: modules/names/__init__.py:68 +#: modules/names/__init__.py:69 msgid "Domain (regular)" msgstr "" -#: modules/names/__init__.py:175 +#: modules/names/__init__.py:178 msgid "Package systemd-resolved is installed" msgstr "" -#: modules/names/__init__.py:199 +#: modules/names/__init__.py:232 #, python-brace-format msgid "Resolve domain name: {domain}" msgstr "" @@ -4436,19 +4436,19 @@ msgstr "" msgid "Secure Shell" msgstr "安全 Shell" -#: modules/names/forms.py:23 +#: modules/names/forms.py:22 msgid "Use DNS-over-TLS for resolving domains (global preference)" msgstr "" -#: modules/names/forms.py:51 +#: modules/names/forms.py:50 msgid "Use DNSSEC when resolving domains (global preference)" msgstr "" -#: modules/names/forms.py:86 modules/names/manifest.py:12 +#: modules/names/forms.py:85 modules/names/manifest.py:12 msgid "Hostname" msgstr "主机名" -#: modules/names/forms.py:87 +#: modules/names/forms.py:86 #, python-brace-format msgid "" "Hostname is the local name by which other devices on the local network can " @@ -4460,11 +4460,11 @@ msgstr "" "数字结束和开始作为内部字符只有字母、数字和连字符。总长度必须为 63 个字符或更" "少。" -#: modules/names/forms.py:93 +#: modules/names/forms.py:92 msgid "Invalid hostname" msgstr "无效的主机名" -#: modules/names/forms.py:109 +#: modules/names/forms.py:108 #, python-brace-format msgid "" "Domain name is the global name by which other devices on the Internet can " @@ -7465,7 +7465,7 @@ msgstr "只读根文件系统" msgid "Go to Power" msgstr "转到电源" -#: modules/storage/__init__.py:447 modules/storage/tests/test_storage.py:396 +#: modules/storage/__init__.py:447 modules/storage/tests/test_storage.py:405 msgid "grub package is configured" msgstr "" diff --git a/plinth/locale/zh_Hant/LC_MESSAGES/django.po b/plinth/locale/zh_Hant/LC_MESSAGES/django.po index ac36f9992..752a25666 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: 2025-03-10 20:08-0400\n" +"POT-Creation-Date: 2025-03-25 09:08-0400\n" "PO-Revision-Date: 2025-02-07 12:01+0000\n" "Last-Translator: pesder \n" "Language-Team: Chinese (Traditional Han script) list[str]: + """Return a list of domains from configuration.""" + return json.loads(kvstore.get_default('domains', '[]')) + + +def domain_add(domain_name: str): + """Add a domain to configuration.""" + domains = domains_list() + if domain_name in domains: + return + + domains.append(domain_name) + _domains_set(domains) + + +def domain_delete(domain_name: str): + """Remove a domain from configuration.""" + domains = domains_list() + if domain_name not in domains: + return + + domains.remove(domain_name) + _domains_set(domains) + + +def _domains_set(domains: list[str]): + """Set the full list of domains in the configuration.""" + kvstore.set('domains', json.dumps(domains)) + + def install_systemd_resolved(_data): """Re-run setup on app to install systemd-resolved.""" if not is_resolved_installed(): diff --git a/plinth/modules/names/forms.py b/plinth/modules/names/forms.py index 611e6e0e5..64ede00ae 100644 --- a/plinth/modules/names/forms.py +++ b/plinth/modules/names/forms.py @@ -9,10 +9,9 @@ from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from plinth import cfg +from plinth.modules import names from plinth.utils import format_lazy -from . import privileged - HOSTNAME_REGEX = r'^[a-zA-Z0-9]([-a-zA-Z0-9]{,61}[a-zA-Z0-9])?$' @@ -122,7 +121,7 @@ class DomainAddForm(forms.Form): def clean_domain_name(self): """Check if the name is valid.""" domain_name = self.cleaned_data['domain_name'] - if domain_name in privileged.get_domains(): + if domain_name in names.domains_list(): raise ValidationError(_('Domain already exists.')) return domain_name diff --git a/plinth/modules/names/privileged.py b/plinth/modules/names/privileged.py index d889f4d15..37804eebc 100644 --- a/plinth/modules/names/privileged.py +++ b/plinth/modules/names/privileged.py @@ -39,8 +39,8 @@ def _load_augeas_hosts(): return aug -def get_domains(aug=None) -> list[str]: - """Return the list of domains.""" +def get_old_domains(aug=None) -> list[str]: + """Return the list of domains store in old /etc/hosts format.""" if not aug: aug = _load_augeas_hosts() @@ -69,55 +69,13 @@ def get_domains(aug=None) -> list[str]: @privileged -def domain_add(domain_name: str | None = None): - """Set system's static domain name in /etc/hosts.""" +def domain_delete_all(): + """Remove all static domain names from /etc/hosts.""" aug = _load_augeas_hosts() - domains = get_domains(aug) - if domain_name in domains: - return # Domain already present in /etc/hosts - - aug.set('./01/ipaddr', HOSTS_LOCAL_IP) - aug.set('./01/canonical', domain_name) - aug.save() - - -@privileged -def domain_delete(domain_name: str | None = None): - """Set system's static domain name in /etc/hosts.""" - aug = _load_augeas_hosts() - domains = get_domains(aug) - if domain_name not in domains: - return # Domain already not present in /etc/hosts - - for match in aug.match('*'): - if aug.get(match + '/ipaddr') == HOSTS_LOCAL_IP and \ - aug.get(match + '/canonical') == domain_name: - aug.remove(match) - - aug.save() - - -@privileged -def domains_migrate() -> None: - """Convert old style of adding domain names to /etc/hosts to new. - - Old format: - 127.0.1.1 . - - New format: - 127.0.1.1 - 127.0.1.1 - """ - aug = _load_augeas_hosts() - domains = get_domains(aug) for match in aug.match('*'): if aug.get(match + '/ipaddr') == HOSTS_LOCAL_IP: aug.remove(match) - for number, domain in enumerate(domains): - aug.set(f'./0{number}/ipaddr', HOSTS_LOCAL_IP) - aug.set(f'./0{number}/canonical', domain) - aug.save() diff --git a/plinth/modules/names/views.py b/plinth/modules/names/views.py index bbcd9646d..51e0b9745 100644 --- a/plinth/modules/names/views.py +++ b/plinth/modules/names/views.py @@ -163,7 +163,7 @@ def _domain_add(domain_name: str): domain_name = domain_name.lower() logger.info('Adding domain name - %s', domain_name) - privileged.domain_add(domain_name) + names.domain_add(domain_name) domain_added.send_robust(sender='names', domain_type='domain-type-static', name=domain_name, services='__all__') @@ -176,7 +176,7 @@ def _domain_delete(domain_name: str): domain_name = domain_name.lower() logger.info('Removing domain name - %s', domain_name) - privileged.domain_delete(domain_name) + names.domain_delete(domain_name) # Update domain registered with Name Services module. domain_removed.send_robust(sender='names', diff --git a/plinth/modules/sogo/__init__.py b/plinth/modules/sogo/__init__.py index 5ac09821f..0fdb5847c 100644 --- a/plinth/modules/sogo/__init__.py +++ b/plinth/modules/sogo/__init__.py @@ -60,8 +60,11 @@ class SOGoApp(app_module.App): clients=info.clients, tags=info.tags) self.add(shortcut) + # dpkg-dev is needed because plget command, part of + # gnustep-base-runtime package, uses dpkg-architecture command but + # there is no dependency on dpkg-dev. packages = Packages('packages-sogo', - ['sogo', 'postgresql', 'memcached']) + ['sogo', 'postgresql', 'memcached', 'dpkg-dev']) self.add(packages) dropin_configs = DropinConfigs('dropin-configs-sogo', [ diff --git a/plinth/modules/storage/tests/test_storage.py b/plinth/modules/storage/tests/test_storage.py index 2ad7fbf33..fcce70101 100644 --- a/plinth/modules/storage/tests/test_storage.py +++ b/plinth/modules/storage/tests/test_storage.py @@ -375,7 +375,16 @@ def test_validate_directory_creatable(path, error): @patch('psutil.disk_partitions') def test_is_partition_read_only(disk_partitions): """Test whether checking for ro partition works.""" - partition = psutil._common.sdiskpart # pylint: disable=protected-access + + def partition(*args): + try: + # psutil <= 5.9.8 + # pylint: disable=protected-access + return psutil._common.sdiskpart(*args) + except TypeError: + # psutil >= 7.0 + return psutil._common.sdiskpart(*args[:-2]) + disk_partitions.return_value = [ partition('/dev/root', '/', 'btrfs', 'rw,nosuid', 42, 42), partition('/dev/root', '/foo', 'btrfs', 'rw', 321, 321), diff --git a/plinth/modules/syncthing/privileged.py b/plinth/modules/syncthing/privileged.py index f6b505c53..6180c353d 100644 --- a/plinth/modules/syncthing/privileged.py +++ b/plinth/modules/syncthing/privileged.py @@ -73,16 +73,35 @@ def setup_config(): # disable authentication missing notification as FreedomBox itself # provides authentication - auth_conf = ('/configuration/options/unackedNotificationID' - '[#text="authenticationUserAndPassword"]') - conf_changed = bool(aug.remove('/files' + conf_file_in_use + auth_conf)) + # also make sure no authentication is required on top of FreedomBox's + configs_to_remove = ( + 'options/unackedNotificationID[#text="authenticationUserAndPassword"]', + 'gui/user/#text', 'gui/password/#text') + conf_changed = False + for config in configs_to_remove: + removed = bool( + aug.remove('/files' + conf_file_in_use + + f'/configuration/{config}')) + if removed: + conf_changed = True - # disable usage reporting notification by declining reporting - # if the user has not made a choice yet - usage_conf = '/configuration/options/urAccepted/#text' - if aug.get('/files' + conf_file_in_use + usage_conf) == '0': - aug.set('/files' + conf_file_in_use + usage_conf, '-1') - conf_changed = True + configs = { + # disable usage reporting notification by declining reporting + # if the user has not made a choice yet + 'options/urAccepted/#text': '-1', + # Set all the values that, misconfigured from the Syncthing UI, + # can make the UI inaccessible. Such misconfigurations can be + # corrected if the user re-runs the setup. + # https://discuss.freedombox.org/t/solved-cant-access-syncthing-administration-panel/2137 + 'gui/#attribute/tls': 'false', + 'gui/#attribute/enabled': 'true', + 'gui/address/#text': '127.0.0.1:8384' + } + for key, value in configs.items(): + config = f'/configuration/{key}' + if aug.get('/files' + conf_file_in_use + config) != value: + aug.set('/files' + conf_file_in_use + config, value) + conf_changed = True aug.save() diff --git a/plinth/modules/upgrades/distupgrade.py b/plinth/modules/upgrades/distupgrade.py index 5d4f346d0..706dee034 100644 --- a/plinth/modules/upgrades/distupgrade.py +++ b/plinth/modules/upgrades/distupgrade.py @@ -223,6 +223,13 @@ def _apt_update(): _apt_run(['update']) +def _apt_fix(): + """Try to fix any problems with apt/dpkg before the upgrade.""" + logger.info('Fixing any broken apt/dpkg states...') + subprocess.run(['dpkg', '--configure', '-a'], check=False) + _apt_run(['--fix-broken', 'install']) + + def _apt_autoremove(): """Run 'apt autoremove'.""" logger.info('Running apt autoremove...') @@ -298,6 +305,7 @@ def perform(): _services_disable(), \ _apt_hold_packages(): _apt_update() + _apt_fix() _debconf_set_selections() _packages_remove_obsolete() _apt_full_upgrade() diff --git a/plinth/modules/upgrades/tests/test_distupgrade.py b/plinth/modules/upgrades/tests/test_distupgrade.py index b1e2dedae..91ab44aa2 100644 --- a/plinth/modules/upgrades/tests/test_distupgrade.py +++ b/plinth/modules/upgrades/tests/test_distupgrade.py @@ -283,6 +283,17 @@ def test_apt_update(apt_run): apt_run.assert_called_with(['update']) +@patch('plinth.modules.upgrades.distupgrade._apt_run') +@patch('subprocess.run') +def test_apt_fix(run, apt_run): + """Test that apt fixes work.""" + distupgrade._apt_fix() + assert run.call_args_list == [ + call(['dpkg', '--configure', '-a'], check=False) + ] + assert apt_run.call_args_list == [call(['--fix-broken', 'install'])] + + @patch('plinth.modules.upgrades.distupgrade._apt_run') def test_apt_autoremove(apt_run): """Test that apt autoremove works.""" diff --git a/plinth/operation.py b/plinth/operation.py index 8ae517592..e6f9be658 100644 --- a/plinth/operation.py +++ b/plinth/operation.py @@ -20,9 +20,9 @@ class Operation: class State(enum.Enum): """Various states of an operation.""" - WAITING: str = 'waiting' - RUNNING: str = 'running' - COMPLETED: str = 'completed' + WAITING = 'waiting' + RUNNING = 'running' + COMPLETED = 'completed' def __init__(self, op_id: str, app_id: str, name: str, target: Callable, args: list | None = None, kwargs: dict | None = None, diff --git a/plinth/setup.py b/plinth/setup.py index faafc14f9..331a6eaed 100644 --- a/plinth/setup.py +++ b/plinth/setup.py @@ -647,8 +647,7 @@ class ForceUpgrader(): # App does not implement force upgrade continue - if (app.get_setup_state() == app_module.App.SetupState.NEEDS_SETUP - ): + if app.get_setup_state() == app_module.App.SetupState.NEEDS_SETUP: # If an app is not installed don't considered it. If an app # needs an update, it may have to do a force upgrade before # running app version update. This is because the app version diff --git a/plinth/tests/functional/enable-all-apps b/plinth/tests/functional/enable-all-apps new file mode 100755 index 000000000..f80e82d1b --- /dev/null +++ b/plinth/tests/functional/enable-all-apps @@ -0,0 +1,31 @@ +#!/usr/bin/python3 +# +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# Set FREEDOMBOX_URL to the URL of the FreedomBox on which +# all apps will be enabled. + +from splinter import Browser + +from plinth import app as app_module, module_loader +from plinth.tests.functional import app_enable, install, login + +module_loader.load_modules() +app_module.apps_init() + +browser = Browser('firefox') +login(browser) + +for app in app_module.App.list(): + app_name = app.app_id + try: + # Some apps cannot be installed. + install(browser, app_name) + except: + pass + + try: + app_enable(browser, app_name) + except: + # Some apps cannot be enabled. + pass diff --git a/pyproject.toml b/pyproject.toml index 7253f4d34..af8b4ae9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,7 +112,12 @@ version = {attr = "plinth.__version__"} include = ["plinth", "plinth.*"] [tool.setuptools.package-data] -"*" = ["templates/*", "static/**", "locale/*/LC_MESSAGES/*.mo"] +"*" = [ + "templates/*", + "static/**", + "locale/*/LC_MESSAGES/*.mo", + "tests/functional/config.ini" +] [tool.setuptools.exclude-package-data] "*" = ["*/data/*"]