mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-27 10:44:33 +00:00
upgrades: Overhaul detection of distribution
- Move some utilities to utils.py from distupgrade.py and __init__.py. - This fixes issues with apt preferences being set on unstable distribution (despite code that tries to prevent it). - There is no way to distinguish between 'testing' and 'unstable' distributions in Debian using commands like lsb_release (powered by /etc/os-release). See: https://lwn.net/Articles/984635/ . So, use the value set in /etc/apt/sources.list. Tests: (tested entire patchset) - Deluge can be installed in trixie. - Auto-distribution upgrade button is checked during setup on stable and oldstable but not on testing and unstable. - Auto-distribution upgrade button is enabled in the form on stable and oldstable but not on testing and unstable. - Backports wizard step is skipped on unstable (non-develop mode), but not on oldstable, stable, testing, and unstable (develop mode). - If backports are not activated during first wizard, then backports can be activated on upgrades app page if distribution is oldstable, stable, testing, or unstable (non-develop mode) but not unstable (develop mode). - During re-run of setup, setting up backport sources is skipped if already setup. - Backports sources files are not added in testing (non-develop) and unstable (non-develop) distributions. Backports sources are added to oldstable, stable, testing (develop) and unstable (develop). Unstable sources sources are not added to unstable but added to oldstable, stable, and testing. - Backports sources file is added with correct code name bookworm/trixie for oldstable, stable, and testing distributions. - When backports sources is set to 'bookworm-backports' on Trixie distribution, re-running setup updates them to 'trixie-backports'. - Preferences files are added in oldstable, stable, and testing distributions but not unstable. - If unstable and another distro is present in apt sources, then it is treated as unstable as shown in the distribution upgrade page. - Current codename is shown properly from sources.list in oldstable, stable, testing, and unstable in distribution upgrade page. - NOT TESTED: If distribution upgrade is interrupted, then continue page is shown. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
4a0e35f806
commit
fecd6a3577
@ -11,7 +11,7 @@ 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,
|
||||||
FirewallLocalProtection)
|
FirewallLocalProtection)
|
||||||
from plinth.modules.upgrades import get_current_release
|
from plinth.modules.upgrades.utils import get_current_release
|
||||||
from plinth.modules.users import add_user_to_share_group
|
from plinth.modules.users import add_user_to_share_group
|
||||||
from plinth.modules.users.components import UsersAndGroups
|
from plinth.modules.users.components import UsersAndGroups
|
||||||
from plinth.package import Packages
|
from plinth.package import Packages
|
||||||
|
|||||||
@ -20,7 +20,7 @@ from plinth.diagnostic_check import DiagnosticCheck, Result
|
|||||||
from plinth.modules.backups.components import BackupRestore
|
from plinth.modules.backups.components import BackupRestore
|
||||||
from plinth.package import Packages
|
from plinth.package import Packages
|
||||||
|
|
||||||
from . import distupgrade, manifest, privileged
|
from . import distupgrade, manifest, privileged, utils
|
||||||
|
|
||||||
first_boot_steps = [
|
first_boot_steps = [
|
||||||
{
|
{
|
||||||
@ -350,21 +350,12 @@ def is_backports_enabled():
|
|||||||
return os.path.exists(privileged.BACKPORTS_SOURCES_LIST)
|
return os.path.exists(privileged.BACKPORTS_SOURCES_LIST)
|
||||||
|
|
||||||
|
|
||||||
def get_current_release():
|
|
||||||
"""Return current release and codename as a tuple."""
|
|
||||||
output = subprocess.check_output(
|
|
||||||
['lsb_release', '--release', '--codename',
|
|
||||||
'--short']).decode().strip()
|
|
||||||
lines = output.split('\n')
|
|
||||||
return lines[0], lines[1]
|
|
||||||
|
|
||||||
|
|
||||||
def is_backports_current():
|
def is_backports_current():
|
||||||
"""Return whether backports are enabled for the current release."""
|
"""Return whether backports are enabled for the current release."""
|
||||||
if not is_backports_enabled():
|
if not is_backports_enabled():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
_, dist = get_current_release()
|
_, dist = utils.get_current_release()
|
||||||
dist += '-backports'
|
dist += '-backports'
|
||||||
sources = sourceslist.SourcesList()
|
sources = sourceslist.SourcesList()
|
||||||
for source in sources:
|
for source in sources:
|
||||||
@ -379,15 +370,12 @@ def can_activate_backports():
|
|||||||
if cfg.develop:
|
if cfg.develop:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Release will be 'n/a' in latest unstable and testing distributions.
|
return not utils.is_distribution_rolling()
|
||||||
release, _ = get_current_release()
|
|
||||||
return release not in ['unstable', 'testing', 'n/a']
|
|
||||||
|
|
||||||
|
|
||||||
def can_enable_dist_upgrade():
|
def can_enable_dist_upgrade():
|
||||||
"""Return whether dist upgrade can be enabled."""
|
"""Return whether dist upgrade can be enabled."""
|
||||||
release, _ = get_current_release()
|
return not utils.is_distribution_rolling()
|
||||||
return release not in ['unstable', 'testing', 'n/a']
|
|
||||||
|
|
||||||
|
|
||||||
def _diagnose_held_packages():
|
def _diagnose_held_packages():
|
||||||
|
|||||||
@ -105,30 +105,6 @@ def _sources_list_update(old_codename: str, new_codename: str):
|
|||||||
aug_path.rename(temp_sources_list)
|
aug_path.rename(temp_sources_list)
|
||||||
|
|
||||||
|
|
||||||
def _get_sources_list_codename() -> str | None:
|
|
||||||
"""Return the codename set in the /etc/apt/sources.list file."""
|
|
||||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
|
||||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
|
||||||
aug.transform('aptsources', str(sources_list))
|
|
||||||
aug.set('/augeas/context', '/files' + str(sources_list))
|
|
||||||
aug.load()
|
|
||||||
|
|
||||||
dists = set()
|
|
||||||
for match_ in aug.match('*'):
|
|
||||||
dist = aug.get(match_ + '/distribution')
|
|
||||||
if not dist:
|
|
||||||
continue
|
|
||||||
|
|
||||||
dist = dist.removesuffix('-updates')
|
|
||||||
dist = dist.removesuffix('-security')
|
|
||||||
dists.add(dist)
|
|
||||||
|
|
||||||
if len(dists) != 1:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return dists.pop()
|
|
||||||
|
|
||||||
|
|
||||||
def get_status() -> dict[str, bool | str | None]:
|
def get_status() -> dict[str, bool | str | None]:
|
||||||
"""Check if a distribution upgrade be performed.
|
"""Check if a distribution upgrade be performed.
|
||||||
|
|
||||||
@ -158,7 +134,7 @@ def get_status() -> dict[str, bool | str | None]:
|
|||||||
has_free_space = utils.is_sufficient_free_space()
|
has_free_space = utils.is_sufficient_free_space()
|
||||||
running = action_utils.service_is_running('freedombox-dist-upgrade')
|
running = action_utils.service_is_running('freedombox-dist-upgrade')
|
||||||
|
|
||||||
current_codename = _get_sources_list_codename()
|
current_codename = utils.get_sources_list_codename()
|
||||||
status = {
|
status = {
|
||||||
'updates_enabled': updates_enabled,
|
'updates_enabled': updates_enabled,
|
||||||
'dist_upgrade_enabled': dist_upgrade_enabled,
|
'dist_upgrade_enabled': dist_upgrade_enabled,
|
||||||
@ -177,7 +153,7 @@ def get_status() -> dict[str, bool | str | None]:
|
|||||||
if current_codename in (None, 'testing', 'unstable'):
|
if current_codename in (None, 'testing', 'unstable'):
|
||||||
return status
|
return status
|
||||||
|
|
||||||
_, base_files_codename = upgrades.get_current_release()
|
_, base_files_codename = utils.get_current_release()
|
||||||
if current_codename == 'stable':
|
if current_codename == 'stable':
|
||||||
current_codename = base_files_codename
|
current_codename = base_files_codename
|
||||||
|
|
||||||
|
|||||||
@ -158,8 +158,7 @@ def _check_and_backports_sources(develop=False):
|
|||||||
if os.path.exists(old_sources_list):
|
if os.path.exists(old_sources_list):
|
||||||
os.remove(old_sources_list)
|
os.remove(old_sources_list)
|
||||||
|
|
||||||
from plinth.modules.upgrades import (get_current_release,
|
from plinth.modules.upgrades import is_backports_current
|
||||||
is_backports_current)
|
|
||||||
if is_backports_current():
|
if is_backports_current():
|
||||||
logging.info('Repositories list up-to-date. Skipping update.')
|
logging.info('Repositories list up-to-date. Skipping update.')
|
||||||
return
|
return
|
||||||
@ -181,15 +180,16 @@ def _check_and_backports_sources(develop=False):
|
|||||||
'backports.')
|
'backports.')
|
||||||
return
|
return
|
||||||
|
|
||||||
release, dist = get_current_release()
|
if utils.is_distribution_rolling() and not develop:
|
||||||
if release in ['unstable', 'testing', 'n/a'] and not develop:
|
logging.info(
|
||||||
logging.info(f'System release is {release}. Skip enabling backports.')
|
'System release is unstable/testing. Skip enabling backports.')
|
||||||
return
|
return
|
||||||
|
|
||||||
protocol = utils.get_http_protocol()
|
protocol = utils.get_http_protocol()
|
||||||
if protocol == 'tor+http':
|
if protocol == 'tor+http':
|
||||||
logging.info('Package download over Tor is enabled.')
|
logging.info('Package download over Tor is enabled.')
|
||||||
|
|
||||||
|
_, dist = utils.get_current_release()
|
||||||
if not utils.is_release_file_available(protocol, dist, backports=True):
|
if not utils.is_release_file_available(protocol, dist, backports=True):
|
||||||
logging.info(
|
logging.info(
|
||||||
f'Release file for {dist}-backports is not available yet.')
|
f'Release file for {dist}-backports is not available yet.')
|
||||||
@ -212,13 +212,12 @@ def _add_apt_preferences():
|
|||||||
# Don't try to remove 50freedombox3.pref as this file is shipped with the
|
# Don't try to remove 50freedombox3.pref as this file is shipped with the
|
||||||
# Debian package and is removed using maintainer scripts.
|
# Debian package and is removed using maintainer scripts.
|
||||||
|
|
||||||
from plinth.modules.upgrades import get_current_release
|
if utils.is_distribution_unstable():
|
||||||
_, dist = get_current_release()
|
|
||||||
if dist == 'sid':
|
|
||||||
logging.info(
|
logging.info(
|
||||||
f'System distribution is {dist}. Skip setting apt preferences '
|
'System distribution is "unstable". Skip setting apt preferences '
|
||||||
'for backports.')
|
'for backports.')
|
||||||
else:
|
else:
|
||||||
|
_, dist = utils.get_current_release()
|
||||||
logging.info(f'Setting apt preferences for {dist}-backports.')
|
logging.info(f'Setting apt preferences for {dist}-backports.')
|
||||||
with open(base_path / '50freedombox4.pref', 'w',
|
with open(base_path / '50freedombox4.pref', 'w',
|
||||||
encoding='utf-8') as file_handle:
|
encoding='utf-8') as file_handle:
|
||||||
|
|||||||
@ -80,43 +80,9 @@ deb https://deb.debian.org/debian bookwormish main
|
|||||||
assert temp_sources_list.read_text() == modified
|
assert temp_sources_list.read_text() == modified
|
||||||
|
|
||||||
|
|
||||||
def test_get_sources_list_codename(tmp_path):
|
|
||||||
"""Test retrieving codename from sources.list file."""
|
|
||||||
list1 = '''
|
|
||||||
deb http://deb.debian.org/debian bookworm main non-free-firmware
|
|
||||||
deb-src http://deb.debian.org/debian bookworm main non-free-firmware
|
|
||||||
|
|
||||||
deb http://deb.debian.org/debian bookworm-updates main non-free-firmware
|
|
||||||
deb-src http://deb.debian.org/debian bookworm-updates main non-free-firmware
|
|
||||||
|
|
||||||
deb http://security.debian.org/debian-security/ bookworm-security main non-free-firmware
|
|
||||||
deb-src http://security.debian.org/debian-security/ bookworm-security main non-free-firmware
|
|
||||||
''' # noqa: E501
|
|
||||||
|
|
||||||
list2 = '''
|
|
||||||
deb http://deb.debian.org/debian stable main non-free-firmware
|
|
||||||
deb-src http://deb.debian.org/debian stable main non-free-firmware
|
|
||||||
|
|
||||||
deb http://deb.debian.org/debian bookworm-updates main non-free-firmware
|
|
||||||
deb-src http://deb.debian.org/debian bookworm-updates main non-free-firmware
|
|
||||||
|
|
||||||
deb http://security.debian.org/debian-security/ bookworm-security main non-free-firmware
|
|
||||||
deb-src http://security.debian.org/debian-security/ bookworm-security main non-free-firmware
|
|
||||||
''' # noqa: E501
|
|
||||||
|
|
||||||
sources_list = tmp_path / 'sources.list'
|
|
||||||
module = 'plinth.modules.upgrades.distupgrade'
|
|
||||||
with patch(f'{module}.sources_list', sources_list):
|
|
||||||
sources_list.write_text(list1)
|
|
||||||
assert distupgrade._get_sources_list_codename() == 'bookworm'
|
|
||||||
|
|
||||||
sources_list.write_text(list2)
|
|
||||||
assert distupgrade._get_sources_list_codename() is None
|
|
||||||
|
|
||||||
|
|
||||||
@patch('datetime.datetime')
|
@patch('datetime.datetime')
|
||||||
@patch('plinth.modules.upgrades.get_current_release')
|
@patch('plinth.modules.upgrades.utils.get_current_release')
|
||||||
@patch('plinth.modules.upgrades.distupgrade._get_sources_list_codename')
|
@patch('plinth.modules.upgrades.distupgrade.utils.get_sources_list_codename')
|
||||||
@patch('plinth.action_utils.service_is_running')
|
@patch('plinth.action_utils.service_is_running')
|
||||||
@patch('plinth.modules.upgrades.utils.is_sufficient_free_space')
|
@patch('plinth.modules.upgrades.utils.is_sufficient_free_space')
|
||||||
@patch('plinth.modules.upgrades.is_dist_upgrade_enabled')
|
@patch('plinth.modules.upgrades.is_dist_upgrade_enabled')
|
||||||
|
|||||||
95
plinth/modules/upgrades/tests/test_utils.py
Normal file
95
plinth/modules/upgrades/tests/test_utils.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
"""
|
||||||
|
Test various upgrade related utilities.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from .. import utils
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_sources_list_codename(tmp_path):
|
||||||
|
"""Test retrieving codename from sources.list file."""
|
||||||
|
list1 = '''
|
||||||
|
deb http://deb.debian.org/debian bookworm main non-free-firmware
|
||||||
|
deb-src http://deb.debian.org/debian bookworm main non-free-firmware
|
||||||
|
|
||||||
|
deb http://deb.debian.org/debian bookworm-updates main non-free-firmware
|
||||||
|
deb-src http://deb.debian.org/debian bookworm-updates main non-free-firmware
|
||||||
|
|
||||||
|
deb http://security.debian.org/debian-security/ bookworm-security main non-free-firmware
|
||||||
|
deb-src http://security.debian.org/debian-security/ bookworm-security main non-free-firmware
|
||||||
|
''' # noqa: E501
|
||||||
|
|
||||||
|
list2 = '''
|
||||||
|
deb http://deb.debian.org/debian stable main non-free-firmware
|
||||||
|
deb-src http://deb.debian.org/debian stable main non-free-firmware
|
||||||
|
|
||||||
|
deb http://deb.debian.org/debian bookworm-updates main non-free-firmware
|
||||||
|
deb-src http://deb.debian.org/debian bookworm-updates main non-free-firmware
|
||||||
|
|
||||||
|
deb http://security.debian.org/debian-security/ bookworm-security main non-free-firmware
|
||||||
|
deb-src http://security.debian.org/debian-security/ bookworm-security main non-free-firmware
|
||||||
|
''' # noqa: E501
|
||||||
|
|
||||||
|
list3 = '''
|
||||||
|
deb http://deb.debian.org/debian unstable main
|
||||||
|
deb http://deb.debian.org/debian trixie main
|
||||||
|
'''
|
||||||
|
list4 = '''
|
||||||
|
deb http://deb.debian.org/debian sid main
|
||||||
|
deb http://deb.debian.org/debian trixie main
|
||||||
|
'''
|
||||||
|
list5 = '''
|
||||||
|
deb http://deb.debian.org/debian testing main
|
||||||
|
deb http://deb.debian.org/debian trixie main
|
||||||
|
'''
|
||||||
|
|
||||||
|
sources_list = tmp_path / 'sources.list'
|
||||||
|
module = 'plinth.modules.upgrades.utils'
|
||||||
|
with patch(f'{module}.sources_list', sources_list):
|
||||||
|
sources_list.write_text(list1)
|
||||||
|
assert utils.get_sources_list_codename() == 'bookworm'
|
||||||
|
|
||||||
|
sources_list.write_text(list2)
|
||||||
|
assert utils.get_sources_list_codename() is None
|
||||||
|
|
||||||
|
sources_list.write_text(list3)
|
||||||
|
assert utils.get_sources_list_codename() == 'unstable'
|
||||||
|
|
||||||
|
sources_list.write_text(list4)
|
||||||
|
assert utils.get_sources_list_codename() == 'unstable'
|
||||||
|
|
||||||
|
sources_list.write_text(list5)
|
||||||
|
assert utils.get_sources_list_codename() == 'testing'
|
||||||
|
|
||||||
|
|
||||||
|
@patch('subprocess.check_output')
|
||||||
|
def test_get_current_release(check_output):
|
||||||
|
"""Test that getting current release works."""
|
||||||
|
check_output.return_value = b'test-release\ntest-codename\n\n'
|
||||||
|
assert utils.get_current_release() == ('test-release', 'test-codename')
|
||||||
|
|
||||||
|
|
||||||
|
@patch('plinth.modules.upgrades.utils.get_sources_list_codename')
|
||||||
|
def test_is_distribution_unstable(get_sources_list_codename):
|
||||||
|
"""Test that checking for unstable distribution works."""
|
||||||
|
get_sources_list_codename.return_value = 'unstable'
|
||||||
|
assert utils.is_distribution_unstable()
|
||||||
|
|
||||||
|
get_sources_list_codename.return_value = 'sid'
|
||||||
|
assert utils.is_distribution_unstable()
|
||||||
|
|
||||||
|
get_sources_list_codename.return_value = 'testing'
|
||||||
|
assert not utils.is_distribution_unstable()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('plinth.modules.upgrades.utils.get_current_release')
|
||||||
|
def test_is_distribution_rolling(get_current_release):
|
||||||
|
"""Test that checking for testing/unstable distribution works."""
|
||||||
|
for value in ['unstable', 'testing', 'n/a']:
|
||||||
|
get_current_release.return_value = (value, None)
|
||||||
|
assert utils.is_distribution_rolling()
|
||||||
|
|
||||||
|
get_current_release.return_value = ('forky', None)
|
||||||
|
assert not utils.is_distribution_rolling()
|
||||||
@ -1,9 +1,12 @@
|
|||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
"""Utilities for regular updates and dist-upgrades."""
|
"""Utilities for regular updates and dist-upgrades."""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
import augeas
|
||||||
|
|
||||||
from plinth.modules.apache.components import check_url
|
from plinth.modules.apache.components import check_url
|
||||||
|
|
||||||
RELEASE_FILE_URL = \
|
RELEASE_FILE_URL = \
|
||||||
@ -11,6 +14,8 @@ RELEASE_FILE_URL = \
|
|||||||
|
|
||||||
DIST_UPGRADE_REQUIRED_FREE_SPACE = 5000000
|
DIST_UPGRADE_REQUIRED_FREE_SPACE = 5000000
|
||||||
|
|
||||||
|
sources_list = pathlib.Path('/etc/apt/sources.list')
|
||||||
|
|
||||||
|
|
||||||
def check_auto() -> bool:
|
def check_auto() -> bool:
|
||||||
"""Return whether automatic updates are enabled."""
|
"""Return whether automatic updates are enabled."""
|
||||||
@ -60,3 +65,62 @@ def is_sufficient_free_space() -> bool:
|
|||||||
output = subprocess.check_output(['df', '--output=avail', '/'])
|
output = subprocess.check_output(['df', '--output=avail', '/'])
|
||||||
free_space = int(output.decode().split('\n')[1])
|
free_space = int(output.decode().split('\n')[1])
|
||||||
return free_space >= DIST_UPGRADE_REQUIRED_FREE_SPACE
|
return free_space >= DIST_UPGRADE_REQUIRED_FREE_SPACE
|
||||||
|
|
||||||
|
|
||||||
|
def get_sources_list_codename() -> str | None:
|
||||||
|
"""Return the codename set in the /etc/apt/sources.list file."""
|
||||||
|
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||||
|
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||||
|
aug.transform('aptsources', str(sources_list))
|
||||||
|
aug.set('/augeas/context', '/files' + str(sources_list))
|
||||||
|
aug.load()
|
||||||
|
|
||||||
|
dists = set()
|
||||||
|
for match_ in aug.match('*'):
|
||||||
|
dist = aug.get(match_ + '/distribution')
|
||||||
|
if not dist:
|
||||||
|
continue
|
||||||
|
|
||||||
|
dist = dist.removesuffix('-updates')
|
||||||
|
dist = dist.removesuffix('-security')
|
||||||
|
dists.add(dist)
|
||||||
|
|
||||||
|
if 'unstable' in dists or 'sid' in dists:
|
||||||
|
return 'unstable'
|
||||||
|
|
||||||
|
if 'testing' in dists:
|
||||||
|
return 'testing'
|
||||||
|
|
||||||
|
# Multiple distributions are not understood.
|
||||||
|
if len(dists) != 1:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return dists.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_release():
|
||||||
|
"""Return current release and codename as a tuple."""
|
||||||
|
output = subprocess.check_output(
|
||||||
|
['lsb_release', '--release', '--codename',
|
||||||
|
'--short']).decode().strip()
|
||||||
|
lines = output.split('\n')
|
||||||
|
return lines[0], lines[1]
|
||||||
|
|
||||||
|
|
||||||
|
def is_distribution_unstable() -> bool:
|
||||||
|
"""Return whether the current distribution is unstable.
|
||||||
|
|
||||||
|
There is no way to distinguish between 'testing' and 'unstable'
|
||||||
|
distributions in Debian using commands like lsb_release (powered by
|
||||||
|
/etc/os-release). See: https://lwn.net/Articles/984635/ . So, use the value
|
||||||
|
set in /etc/apt/sources.list.
|
||||||
|
"""
|
||||||
|
codename = get_sources_list_codename()
|
||||||
|
return codename in ['unstable', 'sid']
|
||||||
|
|
||||||
|
|
||||||
|
def is_distribution_rolling() -> bool:
|
||||||
|
"""Return whether the current distribution is testing or unstable."""
|
||||||
|
# Release will be 'n/a' in latest unstable and testing distributions.
|
||||||
|
release, _ = get_current_release()
|
||||||
|
return release in ['unstable', 'testing', 'n/a']
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user