mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-20 10:34:30 +00:00
freedombox Debian release 24.5
-----BEGIN PGP SIGNATURE----- iQJKBAABCgA0FiEEfWrbdQ+RCFWJSEvmd8DHXntlCAgFAmXdU4cWHGp2YWxsZXJv eUBtYWlsYm94Lm9yZwAKCRB3wMdee2UICMstD/9zF868IP5XxLe2+ZV8QNkHeBpX ElOWKA6uK7jrcR39oMX7JJnRDWFNE3IG44f3GWDfKQ8Vpann3XNGhcRvR9rhlsq6 TEU9y518WsCLvfkFZ7TVOrx3CVXpBzrJClt3kvIGoT/UB4z2IQ4p5Vsuei1TiD1K R5hBBQBSTDxKIIqv+whHLaaWnUI/d2vlZeZuddfI8BPT2juhvOTxlaLlMIxXdzhU IMbsFMbOaFy9+F/OyWs/euLNHhGoNEwqruFL/JuACBY5UtfQpnyHozY7dq/T09Ho 6djoLpvhVK808Vx6tNicXeC1XpARsQ6brBPasB96A7q9rYuDY++QP9Mexj7YLZJ/ Q4TRAKsRGtIMbg411QoRO5bQqag67PLZ/b+0UwJDgwbQrzsnKI8iC4LOrxvU2rMI Uo1yWcnFxJZ8P7xZ+DStQU830ssddm78w38ja2R8qoNdxUS/RODz9uZxLSNoF9+v RKt6pf72SlJXXOiZdTgNIiuh2o2n4J/SdKxZtjeFeqHS9+5HoD4LgA28rE5ElGTQ fP5KGzp+X6v6TcvgIbhJ1yX2SEm5/SiuAnmPwGQ5ItjNh9SXPEU/7ekt7u5iDkf4 x7ZyD43dmk8y6cO4v6zQFOqCyd7m0+hgFv5UwDonpOd2e6CqI7xQzMyWFofZdQbv PseKweEOq9y0frebYg== =iF1H -----END PGP SIGNATURE----- gpgsig -----BEGIN PGP SIGNATURE----- iQJKBAABCgA0FiEEfWrbdQ+RCFWJSEvmd8DHXntlCAgFAmXjWscWHGp2YWxsZXJv eUBtYWlsYm94Lm9yZwAKCRB3wMdee2UICL/ZD/oC5JkU8BA6lIbhxoP4Z4Vgg5Dw hL9Gbh0VPyV87ZNChxvp9JUFiVrrj44WMydtv8KE+GD5Dc8byp2Ipr4cPtAcMy36 f40DSbxPwD59ku+BlyOhQpCk5C0wgG0hageWFg27X77FTedNo7PQGpVryrZY/MTY aM9bL5hmlJoFvOoC6oqiF11yRdZJ24cUNSIn/JXcBs7bMhepW/6y/sDb719ZBHwG QtTPMYSWlrKsUIeKYkO8v3rBht2gGw851PhwmnqZ8oTEhnYc6XU/kdcyon6hFEiG fDSEw5X3OwJdxzR7CTXPD7qN2lZsA84GS7iyNafEDVa6hZmAV88T1xbD0+QoPz+V 6S149azfG7QFMx/UVs5Cpf73pIKZvRH1oxHjhX55DOWk9a0B4IAwR3sRFFsfZPsd A0QL6A9qmdoT7+eWy9VTqyqkKFMm0hbyTVNDIemxhVkQHiUqveK1XmzdQ+RNBerL g14T1XBePwxy8oAFsY7uw4PP8QLv7vk4F4/cuCrpGYtqrU/+iTIZMp57kO15LIwT 81+L+F7KaN2XvWBdEgPB6Xt3vqbR+sr6EJmhp+4BKxevkXRtSCpBkC7GHP/p7ktO yeoz/4HtI6q3o+1mqbllR7a4tBEM8qc+D5CzwPz6MCGCHdQqx5cMC+xgbo5EAduT hICdutjwRgmJ4QeDnQ== =1fNa -----END PGP SIGNATURE----- Merge tag 'v24.5' into debian/bookworm-backports freedombox Debian release 24.5
This commit is contained in:
commit
14d69e2cf7
@ -159,6 +159,7 @@ set -xe pipefail
|
|||||||
|
|
||||||
cd /freedombox/
|
cd /freedombox/
|
||||||
|
|
||||||
|
sudo apt-get -y install make
|
||||||
sudo make provision-dev
|
sudo make provision-dev
|
||||||
|
|
||||||
echo 'alias freedombox-develop="cd /freedombox; sudo -u plinth /freedombox/run --develop"' \
|
echo 'alias freedombox-develop="cd /freedombox; sudo -u plinth /freedombox/run --develop"' \
|
||||||
|
|||||||
27
debian/changelog
vendored
27
debian/changelog
vendored
@ -1,3 +1,30 @@
|
|||||||
|
freedombox (24.5) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Sunil Mohan Adapa ]
|
||||||
|
* container: Fix issue with missing make command on stable image
|
||||||
|
* setup: Minor refactoring of force upgrader class instantiation
|
||||||
|
* setup: Ensure that force upgrade won't run when app is not installed
|
||||||
|
* setup: Ensure that apt is updated before checking force upgrade
|
||||||
|
* firewalld: Implement force upgrading to any 2.x versions
|
||||||
|
* backups: tests: Don't use pytest marks on fixtures
|
||||||
|
* tor: tests: Fix issue with pytest 8.x versions
|
||||||
|
* tor: tests: Convert to pytest style tests from class based tests
|
||||||
|
* pyproject.toml: Exclude the build directory from mypy checks
|
||||||
|
* gitweb, users: Minor fixes for newer pycodestyle
|
||||||
|
* daemon: Add new component for daemons shared across apps
|
||||||
|
* wordpress: Add shared daemon component for mariadb/mysql
|
||||||
|
* zoph: Add shared daemon component for mariadb/mysql
|
||||||
|
|
||||||
|
[ James Valleroy ]
|
||||||
|
* setup: Try force upgrade before running app setup
|
||||||
|
* tests: Patch apps_init for enable/disable daemon test
|
||||||
|
* doc: Fetch latest manual
|
||||||
|
|
||||||
|
[ Olaf Schaf ]
|
||||||
|
* Translated using Weblate (German)
|
||||||
|
|
||||||
|
-- James Valleroy <jvalleroy@mailbox.org> Mon, 26 Feb 2024 20:58:45 -0500
|
||||||
|
|
||||||
freedombox (24.4~bpo12+1) bookworm-backports; urgency=medium
|
freedombox (24.4~bpo12+1) bookworm-backports; urgency=medium
|
||||||
|
|
||||||
* Rebuild for bookworm-backports.
|
* Rebuild for bookworm-backports.
|
||||||
|
|||||||
@ -8,3 +8,6 @@ Daemon
|
|||||||
|
|
||||||
.. autoclass:: plinth.daemon.RelatedDaemon
|
.. autoclass:: plinth.daemon.RelatedDaemon
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: plinth.daemon.SharedDaemon
|
||||||
|
:members:
|
||||||
|
|||||||
@ -8,6 +8,25 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f
|
|||||||
|
|
||||||
The following are the release notes for each !FreedomBox version.
|
The following are the release notes for each !FreedomBox version.
|
||||||
|
|
||||||
|
== FreedomBox 24.5 (2024-02-26) ==
|
||||||
|
|
||||||
|
* backups: tests: Don't use pytest marks on fixtures
|
||||||
|
* container: Fix issue with missing make command on stable image
|
||||||
|
* daemon: Add new component for daemons shared across apps
|
||||||
|
* firewalld: Implement force upgrading to any 2.x versions
|
||||||
|
* gitweb, users: Minor fixes for newer pycodestyle
|
||||||
|
* locale: Update translations for German
|
||||||
|
* pyproject.toml: Exclude the build directory from mypy checks
|
||||||
|
* setup: Ensure that apt is updated before checking force upgrade
|
||||||
|
* setup: Ensure that force upgrade won't run when app is not installed
|
||||||
|
* setup: Minor refactoring of force upgrader class instantiation
|
||||||
|
* setup: Try force upgrade before running app setup
|
||||||
|
* tests: Patch apps_init for enable/disable daemon test
|
||||||
|
* tor: tests: Convert to pytest style tests from class based tests
|
||||||
|
* tor: tests: Fix issue with pytest 8.x versions
|
||||||
|
* wordpress: Add shared daemon component for mariadb/mysql
|
||||||
|
* zoph: Add shared daemon component for mariadb/mysql
|
||||||
|
|
||||||
== FreedomBox 24.4 (2024-02-12) ==
|
== FreedomBox 24.4 (2024-02-12) ==
|
||||||
|
|
||||||
=== Highlights ===
|
=== Highlights ===
|
||||||
|
|||||||
@ -8,6 +8,25 @@ For more technical details, see the [[https://salsa.debian.org/freedombox-team/f
|
|||||||
|
|
||||||
The following are the release notes for each !FreedomBox version.
|
The following are the release notes for each !FreedomBox version.
|
||||||
|
|
||||||
|
== FreedomBox 24.5 (2024-02-26) ==
|
||||||
|
|
||||||
|
* backups: tests: Don't use pytest marks on fixtures
|
||||||
|
* container: Fix issue with missing make command on stable image
|
||||||
|
* daemon: Add new component for daemons shared across apps
|
||||||
|
* firewalld: Implement force upgrading to any 2.x versions
|
||||||
|
* gitweb, users: Minor fixes for newer pycodestyle
|
||||||
|
* locale: Update translations for German
|
||||||
|
* pyproject.toml: Exclude the build directory from mypy checks
|
||||||
|
* setup: Ensure that apt is updated before checking force upgrade
|
||||||
|
* setup: Ensure that force upgrade won't run when app is not installed
|
||||||
|
* setup: Minor refactoring of force upgrader class instantiation
|
||||||
|
* setup: Try force upgrade before running app setup
|
||||||
|
* tests: Patch apps_init for enable/disable daemon test
|
||||||
|
* tor: tests: Convert to pytest style tests from class based tests
|
||||||
|
* tor: tests: Fix issue with pytest 8.x versions
|
||||||
|
* wordpress: Add shared daemon component for mariadb/mysql
|
||||||
|
* zoph: Add shared daemon component for mariadb/mysql
|
||||||
|
|
||||||
== FreedomBox 24.4 (2024-02-12) ==
|
== FreedomBox 24.4 (2024-02-12) ==
|
||||||
|
|
||||||
=== Highlights ===
|
=== Highlights ===
|
||||||
|
|||||||
@ -3,4 +3,4 @@
|
|||||||
Package init file.
|
Package init file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '24.4'
|
__version__ = '24.5'
|
||||||
|
|||||||
@ -135,6 +135,41 @@ class RelatedDaemon(app.FollowerComponent):
|
|||||||
self.unit = unit
|
self.unit = unit
|
||||||
|
|
||||||
|
|
||||||
|
class SharedDaemon(Daemon):
|
||||||
|
"""Component to manage a daemon that is used by multiple apps.
|
||||||
|
|
||||||
|
Daemons such as a database server are a hard requirement for an app.
|
||||||
|
However, there may be multiple apps using that server. This component
|
||||||
|
ensures that server is enabled and running when app is enabled. It runs
|
||||||
|
diagnostics on the daemon when app is diagnosed. The primary difference
|
||||||
|
from the Daemon component is that when the app is disabled the daemon must
|
||||||
|
only be disabled if there is no other app using this daemon.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# A shared daemon may be running even when an app is disabled because
|
||||||
|
# another app might be using the daemon. Hence, the enabled/disabled state
|
||||||
|
# of this component can't be used to determine the enabled/disabled state
|
||||||
|
# of the app.
|
||||||
|
is_leader = False
|
||||||
|
|
||||||
|
def set_enabled(self, enabled):
|
||||||
|
"""Do nothing. Enabled state is still determined by unit status."""
|
||||||
|
|
||||||
|
def disable(self):
|
||||||
|
"""Disable the daemon iff this is the last app using the daemon."""
|
||||||
|
other_apps_enabled = False
|
||||||
|
for other_app in app.App.list():
|
||||||
|
if other_app.app_id == self.app_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for component in other_app.get_components_of_type(SharedDaemon):
|
||||||
|
if component.unit == self.unit and other_app.is_enabled():
|
||||||
|
other_apps_enabled = True
|
||||||
|
|
||||||
|
if not other_apps_enabled:
|
||||||
|
super().disable()
|
||||||
|
|
||||||
|
|
||||||
def app_is_running(app_):
|
def app_is_running(app_):
|
||||||
"""Return whether all the daemons in the app are running."""
|
"""Return whether all the daemons in the app are running."""
|
||||||
for component in app_.components.values():
|
for component in app_.components.values():
|
||||||
|
|||||||
@ -10,8 +10,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: FreedomBox UI\n"
|
"Project-Id-Version: FreedomBox UI\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-02-12 20:35-0500\n"
|
"POT-Creation-Date: 2024-02-12 20:35-0500\n"
|
||||||
"PO-Revision-Date: 2024-02-11 20:14+0000\n"
|
"PO-Revision-Date: 2024-02-22 14:02+0000\n"
|
||||||
"Last-Translator: Dietmar <sagen@permondes.de>\n"
|
"Last-Translator: Olaf Schaf <pakuti123000@gmail.com>\n"
|
||||||
"Language-Team: German <https://hosted.weblate.org/projects/freedombox/"
|
"Language-Team: German <https://hosted.weblate.org/projects/freedombox/"
|
||||||
"freedombox/de/>\n"
|
"freedombox/de/>\n"
|
||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
@ -19,7 +19,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 5.4-dev\n"
|
"X-Generator: Weblate 5.5-dev\n"
|
||||||
|
|
||||||
#: config.py:120
|
#: config.py:120
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -7042,10 +7042,8 @@ msgid "Read-only root filesystem"
|
|||||||
msgstr "Schreibgeschütztes Root-Dateisystem"
|
msgstr "Schreibgeschütztes Root-Dateisystem"
|
||||||
|
|
||||||
#: modules/storage/__init__.py:391
|
#: modules/storage/__init__.py:391
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Go to Networks"
|
|
||||||
msgid "Go to Power"
|
msgid "Go to Power"
|
||||||
msgstr "Zur Stromversorgung"
|
msgstr "Springe zur Stromversorgung"
|
||||||
|
|
||||||
#: modules/storage/forms.py:63
|
#: modules/storage/forms.py:63
|
||||||
msgid "Invalid directory name."
|
msgid "Invalid directory name."
|
||||||
|
|||||||
@ -59,9 +59,8 @@ def fixture_create_temp_user(temp_home, password, needs_root):
|
|||||||
subprocess.check_call(['sudo', 'userdel', username])
|
subprocess.check_call(['sudo', 'userdel', username])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('needs_sudo')
|
|
||||||
@pytest.fixture(name='has_ssh_key', scope='module', autouse=True)
|
@pytest.fixture(name='has_ssh_key', scope='module', autouse=True)
|
||||||
def fixture_ssh_key(temp_home, temp_user, password, needs_root):
|
def fixture_ssh_key(temp_home, temp_user, password, needs_root, needs_sudo):
|
||||||
subprocess.check_call([
|
subprocess.check_call([
|
||||||
'sudo', '-n', '-u', temp_user, 'ssh-keygen', '-t', 'rsa', '-b', '1024',
|
'sudo', '-n', '-u', temp_user, 'ssh-keygen', '-t', 'rsa', '-b', '1024',
|
||||||
'-N', '', '-f', f'{temp_home}/.ssh/id_rsa', '-q'
|
'-N', '', '-f', f'{temp_home}/.ssh/id_rsa', '-q'
|
||||||
|
|||||||
@ -87,9 +87,9 @@ class FirewallApp(app_module.App):
|
|||||||
if 'firewalld' not in packages:
|
if 'firewalld' not in packages:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Allow upgrade from any version to any version below 2.0
|
# Allow upgrade from any version to any version below 3.0
|
||||||
package = packages['firewalld']
|
package = packages['firewalld']
|
||||||
if Version(package['new_version']) > Version('2~'):
|
if Version(package['new_version']) > Version('3~'):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
install(['firewalld'], force_configuration='new')
|
install(['firewalld'], force_configuration='new')
|
||||||
|
|||||||
@ -53,7 +53,7 @@ def gitweb_patch():
|
|||||||
with patch('plinth.modules.gitweb.get_repo_list') as get_repo_list, \
|
with patch('plinth.modules.gitweb.get_repo_list') as get_repo_list, \
|
||||||
patch('plinth.app.App.get') as app_get, \
|
patch('plinth.app.App.get') as app_get, \
|
||||||
patch(f'{privileged}.create_repo'), \
|
patch(f'{privileged}.create_repo'), \
|
||||||
patch(f'{privileged}.repo_exists') as repo_exists,\
|
patch(f'{privileged}.repo_exists') as repo_exists, \
|
||||||
patch(f'{privileged}.repo_info') as repo_info, \
|
patch(f'{privileged}.repo_info') as repo_info, \
|
||||||
patch(f'{privileged}.rename_repo'), \
|
patch(f'{privileged}.rename_repo'), \
|
||||||
patch(f'{privileged}.set_repo_description'), \
|
patch(f'{privileged}.set_repo_description'), \
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from django.utils.translation import gettext_noop
|
|||||||
from plinth import action_utils
|
from plinth import action_utils
|
||||||
from plinth import app as app_module
|
from plinth import app as app_module
|
||||||
from plinth import cfg, kvstore, menu
|
from plinth import cfg, kvstore, menu
|
||||||
from plinth import setup as setup_module
|
from plinth import setup as setup_module_ # Not setup_module, for pytest
|
||||||
from plinth.daemon import (Daemon, app_is_running, diagnose_netcat,
|
from plinth.daemon import (Daemon, app_is_running, diagnose_netcat,
|
||||||
diagnose_port_listening)
|
diagnose_port_listening)
|
||||||
from plinth.modules import torproxy
|
from plinth.modules import torproxy
|
||||||
@ -209,7 +209,7 @@ class TorApp(app_module.App):
|
|||||||
kvstore.set(torproxy.PREINSTALL_CONFIG_KEY, json.dumps(config))
|
kvstore.set(torproxy.PREINSTALL_CONFIG_KEY, json.dumps(config))
|
||||||
# This creates the operation, which will run after the current
|
# This creates the operation, which will run after the current
|
||||||
# operation (Tor setup) is completed.
|
# operation (Tor setup) is completed.
|
||||||
setup_module.run_setup_on_app('torproxy')
|
setup_module_.run_setup_on_app('torproxy')
|
||||||
|
|
||||||
if not old_version:
|
if not old_version:
|
||||||
logger.info('Enabling Tor app')
|
logger.info('Enabling Tor app')
|
||||||
|
|||||||
@ -11,72 +11,64 @@ from django.core.exceptions import ValidationError
|
|||||||
from plinth.modules.tor import forms, utils
|
from plinth.modules.tor import forms, utils
|
||||||
|
|
||||||
|
|
||||||
class TestTor:
|
@patch('plinth.app.App.get')
|
||||||
"""Test cases for testing the Tor module."""
|
@pytest.mark.usefixtures('needs_root', 'load_cfg')
|
||||||
|
def test_get_status(_app_get):
|
||||||
|
"""Test that get_status does not raise any unhandled exceptions.
|
||||||
|
|
||||||
@staticmethod
|
This should work regardless of whether tor is installed, or
|
||||||
@patch('plinth.app.App.get')
|
/etc/tor/instances/plinth/torrc exists.
|
||||||
@pytest.mark.usefixtures('needs_root', 'load_cfg')
|
"""
|
||||||
def test_get_status(_app_get):
|
utils.get_status()
|
||||||
"""Test that get_status does not raise any unhandled exceptions.
|
|
||||||
|
|
||||||
This should work regardless of whether tor is installed, or
|
|
||||||
/etc/tor/instances/plinth/torrc exists.
|
|
||||||
"""
|
|
||||||
utils.get_status()
|
|
||||||
|
|
||||||
|
|
||||||
class TestTorForm:
|
def test_bridge_validator():
|
||||||
"""Test whether Tor configration form works."""
|
"""Test upstream bridges' form field validator."""
|
||||||
|
validator = forms.bridges_validator
|
||||||
|
|
||||||
@staticmethod
|
# Just IP:port
|
||||||
def test_bridge_validator():
|
validator('73.237.165.184:9001')
|
||||||
"""Test upstream bridges' form field validator."""
|
validator('73.237.165.184')
|
||||||
validator = forms.bridges_validator
|
validator('[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443')
|
||||||
|
validator('[2001:db8:85a3:8d3:1319:8a2e:370:7348]')
|
||||||
|
|
||||||
# Just IP:port
|
# With fingerprint
|
||||||
validator('73.237.165.184:9001')
|
validator('73.237.165.184:9001 '
|
||||||
validator('73.237.165.184')
|
'0D04F10F497E68D2AF32375BB763EC3458A908C8')
|
||||||
validator('[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443')
|
|
||||||
validator('[2001:db8:85a3:8d3:1319:8a2e:370:7348]')
|
|
||||||
|
|
||||||
# With fingerprint
|
# With transport type
|
||||||
validator('73.237.165.184:9001 '
|
validator('obfs4 73.237.165.184:9001 '
|
||||||
'0D04F10F497E68D2AF32375BB763EC3458A908C8')
|
'0D04F10F497E68D2AF32375BB763EC3458A908C8')
|
||||||
|
|
||||||
# With transport type
|
# With transport type and extra options
|
||||||
validator('obfs4 73.237.165.184:9001 '
|
validator('obfs4 10.1.1.1:30000 '
|
||||||
'0D04F10F497E68D2AF32375BB763EC3458A908C8')
|
'0123456789ABCDEF0123456789ABCDEF01234567 '
|
||||||
|
'cert=A/b+1 iat-mode=0')
|
||||||
|
|
||||||
# With transport type and extra options
|
# Leading, trailing spaces and empty lines
|
||||||
validator('obfs4 10.1.1.1:30000 '
|
validator('\n'
|
||||||
'0123456789ABCDEF0123456789ABCDEF01234567 '
|
' \n'
|
||||||
'cert=A/b+1 iat-mode=0')
|
'73.237.165.184:9001 '
|
||||||
|
'0D04F10F497E68D2AF32375BB763EC3458A908C8'
|
||||||
|
' \n'
|
||||||
|
'73.237.165.184:9001 '
|
||||||
|
'0D04F10F497E68D2AF32375BB763EC3458A908C8'
|
||||||
|
' \n'
|
||||||
|
'\n')
|
||||||
|
|
||||||
# Leading, trailing spaces and empty lines
|
# Invalid number for parts
|
||||||
validator('\n'
|
with pytest.raises(ValidationError):
|
||||||
' \n'
|
validator(' ')
|
||||||
'73.237.165.184:9001 '
|
|
||||||
'0D04F10F497E68D2AF32375BB763EC3458A908C8'
|
|
||||||
' \n'
|
|
||||||
'73.237.165.184:9001 '
|
|
||||||
'0D04F10F497E68D2AF32375BB763EC3458A908C8'
|
|
||||||
' \n'
|
|
||||||
'\n')
|
|
||||||
|
|
||||||
# Invalid number for parts
|
# Invalid IP address/port
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
validator(' ')
|
validator('73.237.165.384:9001')
|
||||||
|
|
||||||
# Invalid IP address/port
|
with pytest.raises(ValidationError):
|
||||||
with pytest.raises(ValidationError):
|
validator('73.237.165.184:90001')
|
||||||
validator('73.237.165.384:9001')
|
|
||||||
|
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
validator('73.237.165.184:90001')
|
validator('[a2001:db8:85a3:8d3:1319:8a2e:370:7348]:443')
|
||||||
|
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
validator('[a2001:db8:85a3:8d3:1319:8a2e:370:7348]:443')
|
validator('[2001:db8:85a3:8d3:1319:8a2e:370:7348]:90443')
|
||||||
|
|
||||||
with pytest.raises(ValidationError):
|
|
||||||
validator('[2001:db8:85a3:8d3:1319:8a2e:370:7348]:90443')
|
|
||||||
|
|||||||
@ -72,9 +72,9 @@ def make_request(request, view, as_admin=True, **kwargs):
|
|||||||
request.user = admin_user if as_admin else user
|
request.user = admin_user if as_admin else user
|
||||||
|
|
||||||
with patch('plinth.modules.users.forms.is_user_admin',
|
with patch('plinth.modules.users.forms.is_user_admin',
|
||||||
return_value=as_admin),\
|
return_value=as_admin), \
|
||||||
patch('plinth.modules.users.views.is_user_admin',
|
patch('plinth.modules.users.views.is_user_admin',
|
||||||
return_value=as_admin),\
|
return_value=as_admin), \
|
||||||
patch('plinth.modules.users.views.update_session_auth_hash'):
|
patch('plinth.modules.users.views.update_session_auth_hash'):
|
||||||
|
|
||||||
response = view(request, **kwargs)
|
response = view(request, **kwargs)
|
||||||
|
|||||||
@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from plinth import app as app_module
|
from plinth import app as app_module
|
||||||
from plinth import cfg, frontpage, menu
|
from plinth import cfg, frontpage, menu
|
||||||
from plinth.config import DropinConfigs
|
from plinth.config import DropinConfigs
|
||||||
from plinth.daemon import Daemon
|
from plinth.daemon import Daemon, SharedDaemon
|
||||||
from plinth.modules.apache.components import Webserver
|
from plinth.modules.apache.components import Webserver
|
||||||
from plinth.modules.backups.components import BackupRestore
|
from plinth.modules.backups.components import BackupRestore
|
||||||
from plinth.modules.firewall.components import Firewall
|
from plinth.modules.firewall.components import Firewall
|
||||||
@ -106,6 +106,9 @@ class WordPressApp(app_module.App):
|
|||||||
daemon = Daemon('daemon-wordpress', 'wordpress-freedombox.timer')
|
daemon = Daemon('daemon-wordpress', 'wordpress-freedombox.timer')
|
||||||
self.add(daemon)
|
self.add(daemon)
|
||||||
|
|
||||||
|
daemon = SharedDaemon('shared-daemon-wordpress-mysql', 'mysql')
|
||||||
|
self.add(daemon)
|
||||||
|
|
||||||
backup_restore = WordPressBackupRestore('backup-restore-wordpress',
|
backup_restore = WordPressBackupRestore('backup-restore-wordpress',
|
||||||
**manifest.backup)
|
**manifest.backup)
|
||||||
self.add(backup_restore)
|
self.add(backup_restore)
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from plinth import app as app_module
|
from plinth import app as app_module
|
||||||
from plinth import cfg, frontpage, menu
|
from plinth import cfg, frontpage, menu
|
||||||
from plinth.config import DropinConfigs
|
from plinth.config import DropinConfigs
|
||||||
|
from plinth.daemon import SharedDaemon
|
||||||
from plinth.modules.apache.components import Webserver
|
from plinth.modules.apache.components import Webserver
|
||||||
from plinth.modules.backups.components import BackupRestore
|
from plinth.modules.backups.components import BackupRestore
|
||||||
from plinth.modules.firewall.components import Firewall
|
from plinth.modules.firewall.components import Firewall
|
||||||
@ -89,6 +90,9 @@ class ZophApp(app_module.App):
|
|||||||
webserver = Webserver('webserver-zoph-freedombox', 'zoph-freedombox')
|
webserver = Webserver('webserver-zoph-freedombox', 'zoph-freedombox')
|
||||||
self.add(webserver)
|
self.add(webserver)
|
||||||
|
|
||||||
|
daemon = SharedDaemon('shared-daemon-zoph-mysql', 'mysql')
|
||||||
|
self.add(daemon)
|
||||||
|
|
||||||
backup_restore = ZophBackupRestore('backup-restore-zoph',
|
backup_restore = ZophBackupRestore('backup-restore-zoph',
|
||||||
**manifest.backup)
|
**manifest.backup)
|
||||||
self.add(backup_restore)
|
self.add(backup_restore)
|
||||||
|
|||||||
@ -26,8 +26,6 @@ _is_first_setup = False
|
|||||||
is_first_setup_running = False
|
is_first_setup_running = False
|
||||||
_is_shutting_down = False
|
_is_shutting_down = False
|
||||||
|
|
||||||
_force_upgrader = None
|
|
||||||
|
|
||||||
|
|
||||||
def run_setup_on_app(app_id, allow_install=True, rerun=False):
|
def run_setup_on_app(app_id, allow_install=True, rerun=False):
|
||||||
"""Execute the setup process in a thread."""
|
"""Execute the setup process in a thread."""
|
||||||
@ -59,6 +57,15 @@ def _run_setup_on_app(app, current_version):
|
|||||||
message = None
|
message = None
|
||||||
try:
|
try:
|
||||||
current_version = app.get_setup_version()
|
current_version = app.get_setup_version()
|
||||||
|
|
||||||
|
if current_version != 0:
|
||||||
|
# Check if this app needs force_upgrade. If it is needed, but not
|
||||||
|
# yet supported for the new version of the package, then an
|
||||||
|
# exception will be raised, so that we do not run setup.
|
||||||
|
package.refresh_package_lists()
|
||||||
|
force_upgrader = ForceUpgrader.get_instance()
|
||||||
|
force_upgrader.attempt_upgrade_for_app(app.app_id)
|
||||||
|
|
||||||
app.setup(old_version=current_version)
|
app.setup(old_version=current_version)
|
||||||
app.set_setup_version(app.info.version)
|
app.set_setup_version(app.info.version)
|
||||||
post_setup.send_robust(sender=app.__class__, module_name=app.app_id)
|
post_setup.send_robust(sender=app.__class__, module_name=app.app_id)
|
||||||
@ -143,8 +150,8 @@ def stop():
|
|||||||
global _is_shutting_down
|
global _is_shutting_down
|
||||||
_is_shutting_down = True
|
_is_shutting_down = True
|
||||||
|
|
||||||
if _force_upgrader:
|
force_upgrader = ForceUpgrader.get_instance()
|
||||||
_force_upgrader.shutdown()
|
force_upgrader.shutdown()
|
||||||
|
|
||||||
|
|
||||||
def setup_apps(app_ids=None, essential=False, allow_install=True):
|
def setup_apps(app_ids=None, essential=False, allow_install=True):
|
||||||
@ -229,8 +236,8 @@ def _get_apps_for_regular_setup():
|
|||||||
1. essential apps that are not up-to-date
|
1. essential apps that are not up-to-date
|
||||||
2. non-essential app that are installed and need updates
|
2. non-essential app that are installed and need updates
|
||||||
"""
|
"""
|
||||||
if (app.info.is_essential and
|
if (app.info.is_essential and app.get_setup_state()
|
||||||
app.get_setup_state() != app_module.App.SetupState.UP_TO_DATE):
|
!= app_module.App.SetupState.UP_TO_DATE):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if app.get_setup_state() == app_module.App.SetupState.NEEDS_UPDATE:
|
if app.get_setup_state() == app_module.App.SetupState.NEEDS_UPDATE:
|
||||||
@ -335,6 +342,7 @@ class ForceUpgrader():
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_instance = None
|
||||||
_run_lock = threading.Lock()
|
_run_lock = threading.Lock()
|
||||||
_wait_event = threading.Event()
|
_wait_event = threading.Event()
|
||||||
|
|
||||||
@ -349,6 +357,14 @@ class ForceUpgrader():
|
|||||||
"""Raised when upgrade fails and there is nothing more we wish to do.
|
"""Raised when upgrade fails and there is nothing more we wish to do.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_instance(cls):
|
||||||
|
"""Return a single instance of a the class."""
|
||||||
|
if not cls._instance:
|
||||||
|
cls._instance = ForceUpgrader()
|
||||||
|
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize the force upgrader."""
|
"""Initialize the force upgrader."""
|
||||||
if plinth.cfg.develop:
|
if plinth.cfg.develop:
|
||||||
@ -401,7 +417,7 @@ class ForceUpgrader():
|
|||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
"""If we are sleeping for next attempt, cancel it.
|
"""If we are sleeping for next attempt, cancel it.
|
||||||
|
|
||||||
If we are actually upgrading packages, don nothing.
|
If we are actually upgrading packages, do nothing.
|
||||||
"""
|
"""
|
||||||
self._wait_event.set()
|
self._wait_event.set()
|
||||||
|
|
||||||
@ -448,6 +464,50 @@ class ForceUpgrader():
|
|||||||
if need_retry:
|
if need_retry:
|
||||||
raise self.TemporaryFailure('Some apps failed to force upgrade.')
|
raise self.TemporaryFailure('Some apps failed to force upgrade.')
|
||||||
|
|
||||||
|
def attempt_upgrade_for_app(self, app_id):
|
||||||
|
"""Attempt to perform an upgrade for specified app.
|
||||||
|
|
||||||
|
Raise TemporaryFailure if upgrade can't be performed now.
|
||||||
|
|
||||||
|
Raise PermanentFailure if upgrade can't be performed until something
|
||||||
|
with the system state changes. We don't want to try again until
|
||||||
|
notified of further package cache changes.
|
||||||
|
|
||||||
|
Return True if upgrade was performed successfully.
|
||||||
|
|
||||||
|
Return False if upgrade is not needed.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if _is_shutting_down:
|
||||||
|
raise self.PermanentFailure('Service is shutting down')
|
||||||
|
|
||||||
|
if packages_privileged.is_package_manager_busy():
|
||||||
|
raise self.TemporaryFailure('Package manager is busy')
|
||||||
|
|
||||||
|
apps = self._get_list_of_apps_to_force_upgrade()
|
||||||
|
if app_id not in apps:
|
||||||
|
logger.info('App %s does not need force upgrade', app_id)
|
||||||
|
return False
|
||||||
|
|
||||||
|
packages = apps[app_id]
|
||||||
|
app = app_module.App.get(app_id)
|
||||||
|
try:
|
||||||
|
logger.info('Force upgrading app: %s', app.info.name)
|
||||||
|
if app.force_upgrade(packages):
|
||||||
|
logger.info('Successfully force upgraded app: %s',
|
||||||
|
app.info.name)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warning('Ignored force upgrade for app: %s',
|
||||||
|
app.info.name)
|
||||||
|
raise self.TemporaryFailure(
|
||||||
|
'Force upgrade is needed, but not yet implemented for new '
|
||||||
|
f'version of app: {app_id}')
|
||||||
|
except Exception as exception:
|
||||||
|
logger.exception('Error running force upgrade: %s', exception)
|
||||||
|
raise self.TemporaryFailure(
|
||||||
|
f'App {app_id} failed to force upgrade.')
|
||||||
|
|
||||||
def _run_force_upgrade_as_operation(self, app, packages):
|
def _run_force_upgrade_as_operation(self, app, packages):
|
||||||
"""Start an operation for force upgrading."""
|
"""Start an operation for force upgrading."""
|
||||||
name = gettext_noop('Updating app packages')
|
name = gettext_noop('Updating app packages')
|
||||||
@ -523,8 +583,5 @@ class ForceUpgrader():
|
|||||||
|
|
||||||
def on_package_cache_updated():
|
def on_package_cache_updated():
|
||||||
"""Called by D-Bus service when apt package cache is updated."""
|
"""Called by D-Bus service when apt package cache is updated."""
|
||||||
global _force_upgrader
|
force_upgrader = ForceUpgrader.get_instance()
|
||||||
if not _force_upgrader:
|
force_upgrader.on_package_cache_updated()
|
||||||
_force_upgrader = ForceUpgrader()
|
|
||||||
|
|
||||||
_force_upgrader.on_package_cache_updated()
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from unittest.mock import Mock, call, patch
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from plinth.app import App, FollowerComponent, Info
|
from plinth.app import App, FollowerComponent, Info
|
||||||
from plinth.daemon import (Daemon, RelatedDaemon, app_is_running,
|
from plinth.daemon import (Daemon, RelatedDaemon, SharedDaemon, app_is_running,
|
||||||
diagnose_netcat, diagnose_port_listening)
|
diagnose_netcat, diagnose_port_listening)
|
||||||
from plinth.modules.diagnostics.check import DiagnosticCheck, Result
|
from plinth.modules.diagnostics.check import DiagnosticCheck, Result
|
||||||
|
|
||||||
@ -77,10 +77,11 @@ def test_is_enabled(service_is_enabled, daemon):
|
|||||||
service_is_enabled.assert_has_calls([call('test-unit', strict_check=True)])
|
service_is_enabled.assert_has_calls([call('test-unit', strict_check=True)])
|
||||||
|
|
||||||
|
|
||||||
|
@patch('plinth.app.apps_init')
|
||||||
@patch('subprocess.run')
|
@patch('subprocess.run')
|
||||||
@patch('subprocess.call')
|
@patch('subprocess.call')
|
||||||
def test_enable(subprocess_call, subprocess_run, app_list, mock_privileged,
|
def test_enable(subprocess_call, subprocess_run, apps_init, app_list,
|
||||||
daemon):
|
mock_privileged, daemon):
|
||||||
"""Test that enabling the daemon works."""
|
"""Test that enabling the daemon works."""
|
||||||
daemon.enable()
|
daemon.enable()
|
||||||
subprocess_call.assert_has_calls(
|
subprocess_call.assert_has_calls(
|
||||||
@ -101,9 +102,11 @@ def test_enable(subprocess_call, subprocess_run, app_list, mock_privileged,
|
|||||||
stdout=subprocess.DEVNULL, check=False)
|
stdout=subprocess.DEVNULL, check=False)
|
||||||
|
|
||||||
|
|
||||||
|
@patch('plinth.app.apps_init')
|
||||||
@patch('subprocess.run')
|
@patch('subprocess.run')
|
||||||
@patch('subprocess.call')
|
@patch('subprocess.call')
|
||||||
def test_disable(subprocess_call, subprocess_run, mock_privileged, daemon):
|
def test_disable(subprocess_call, subprocess_run, apps_init, app_list,
|
||||||
|
mock_privileged, daemon):
|
||||||
"""Test that disabling the daemon works."""
|
"""Test that disabling the daemon works."""
|
||||||
daemon.disable()
|
daemon.disable()
|
||||||
subprocess_call.assert_has_calls(
|
subprocess_call.assert_has_calls(
|
||||||
@ -329,3 +332,56 @@ def test_related_daemon_initialization():
|
|||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
RelatedDaemon(None, 'test-daemon')
|
RelatedDaemon(None, 'test-daemon')
|
||||||
|
|
||||||
|
|
||||||
|
def test_shared_daemon_leader():
|
||||||
|
"""Test that shared daemon is not a leader component."""
|
||||||
|
component1 = SharedDaemon('test-component1', 'test-daemon')
|
||||||
|
assert not component1.is_leader
|
||||||
|
|
||||||
|
|
||||||
|
@patch('plinth.action_utils.service_is_enabled')
|
||||||
|
def test_shared_daemon_set_enabled(service_is_enabled):
|
||||||
|
"""Test that enabled status is determined by unit status."""
|
||||||
|
component = SharedDaemon('test-component', 'test-daemon')
|
||||||
|
|
||||||
|
service_is_enabled.return_value = False
|
||||||
|
component.set_enabled(False)
|
||||||
|
assert not component.is_enabled()
|
||||||
|
component.set_enabled(True)
|
||||||
|
assert not component.is_enabled()
|
||||||
|
|
||||||
|
service_is_enabled.return_value = True
|
||||||
|
component.set_enabled(False)
|
||||||
|
assert component.is_enabled()
|
||||||
|
component.set_enabled(True)
|
||||||
|
assert component.is_enabled()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('plinth.privileged.service.disable')
|
||||||
|
def test_shared_daemon_disable(disable_method):
|
||||||
|
"""Test that shared daemon disables service correctly."""
|
||||||
|
|
||||||
|
class AppTest2(App):
|
||||||
|
"""Test application class."""
|
||||||
|
app_id = 'test-app-2'
|
||||||
|
|
||||||
|
component1 = SharedDaemon('test-component1', 'test-daemon')
|
||||||
|
app1 = AppTest()
|
||||||
|
app1.add(component1)
|
||||||
|
app1.is_enabled = Mock()
|
||||||
|
|
||||||
|
component2 = SharedDaemon('test-component2', 'test-daemon')
|
||||||
|
app2 = AppTest2()
|
||||||
|
app2.add(component2)
|
||||||
|
|
||||||
|
# When another app is enabled, service should not be disabled
|
||||||
|
app1.is_enabled.return_value = True
|
||||||
|
app2.disable()
|
||||||
|
assert disable_method.mock_calls == []
|
||||||
|
|
||||||
|
# When all other apps are disabled, service should be disabled
|
||||||
|
disable_method.reset_mock()
|
||||||
|
app1.is_enabled.return_value = False
|
||||||
|
app2.disable()
|
||||||
|
assert disable_method.mock_calls == [call('test-daemon')]
|
||||||
|
|||||||
@ -201,6 +201,9 @@ disable = [
|
|||||||
"too-many-ancestors", # Easy to hit when using Django
|
"too-many-ancestors", # Easy to hit when using Django
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
exclude = "build/"
|
||||||
|
|
||||||
# Ignore missing type stubs for some libraries. Try to keep this list minimal
|
# Ignore missing type stubs for some libraries. Try to keep this list minimal
|
||||||
# and use type annotations where available.
|
# and use type annotations where available.
|
||||||
[[tool.mypy.overrides]]
|
[[tool.mypy.overrides]]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user