From b93819af1fd48a2c28ae10bb9452a5d9c1d8f8f3 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 29 Mar 2026 20:52:35 -0500 Subject: T8445: add activation utils --- python/vyos/defaults.py | 4 ++ python/vyos/utils/activate.py | 103 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 python/vyos/utils/activate.py (limited to 'python') diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 6aa93ebed..78721c0d2 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -102,3 +102,7 @@ SSH_DSA_DEPRECATION_WARNING: str = \ 'ED25519) to avoid authentication failures after the upgrade.' reference_tree_cache = '/usr/share/vyos/reftree.cache' + +activation_list = os.path.join(directories['config'], 'activation-list') +activation_init = os.path.join(directories['data'], 'activation-init') +activation_hint = os.path.join(directories['data'], '.activation_hint') diff --git a/python/vyos/utils/activate.py b/python/vyos/utils/activate.py new file mode 100644 index 000000000..588dda733 --- /dev/null +++ b/python/vyos/utils/activate.py @@ -0,0 +1,103 @@ +# Copyright (C) VyOS Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . + + +import json +import typing +from pathlib import Path + +from vyos.defaults import activation_list +from vyos.defaults import activation_init +from vyos.defaults import activation_hint +from vyos.defaults import directories + + +ActiveOpt = typing.Literal['enabled', 'once', 'off', 'never'] + + +def get_activation_scripts() -> dict: + list_path = Path(activation_list) + return json.loads(list_path.read_text()) + + +def set_activation(file_name: str, value: ActiveOpt): + script_dict = get_activation_scripts() + file_key = Path(file_name).stem + script_dict[file_key] = value + list_path = Path(activation_list) + list_path.write_text(json.dumps(script_dict)) + + +def get_activation(file_name: str) -> ActiveOpt: + script_dict = get_activation_scripts() + file_key = Path(file_name).stem + return script_dict[file_key] + + +def is_active(file_name: str) -> bool: + script_dict = get_activation_scripts() + file_key = Path(file_name).stem + if script_dict[file_key] in ('enabled', 'once'): + return True + return False + + +def stable_update(new: dict, old: dict): + res = {} + for key in new.keys(): + res[key] = old[key] if key in old.keys() else new[key] + return res + + +def init_activation_list() -> bool: + """Init if activation_hint exists, left on install_image or if image was + built as raw_image""" + + init_hint = Path(activation_hint) + if not init_hint.exists(): + return False + + init_hint.unlink() + init_list = Path(activation_init) + data_list = Path(activation_list) + data_obj = json.loads(init_list.read_text()) + data_list.write_text(json.dumps(data_obj)) + + return True + + +def refresh_activation_list(): + """Refresh activation list, as will be needed after image update""" + + if init_activation_list(): + return + + new_list_path = Path(directories['data']).joinpath(Path(activation_list).name) + if not new_list_path.exists(): + return + + new_obj = json.loads(new_list_path.read_text()) + + orig_list_path = Path(activation_list) + if orig_list_path.exists(): + orig_obj = json.loads(orig_list_path.read_text()) + if orig_obj == new_obj: + return + + obj = stable_update(new_obj, orig_obj) + else: + obj = new_obj + + orig_list_path.write_text(json.dumps(obj)) -- cgit v1.2.3 From af86b86d1c85106dca6a8c9fa373fd36239dcc08 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 29 Mar 2026 21:01:36 -0500 Subject: T8445: add falsy callable This is useful as default value for a getattr of a function from a module. --- python/vyos/utils/func.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 python/vyos/utils/func.py (limited to 'python') diff --git a/python/vyos/utils/func.py b/python/vyos/utils/func.py new file mode 100644 index 000000000..830b76c94 --- /dev/null +++ b/python/vyos/utils/func.py @@ -0,0 +1,28 @@ +# Copyright (C) VyOS Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . + + +"""Module for functors and higher order functions.""" + + +class FalseCallable: + """Define falsy callable for use as default value for getattr of a + function from a module defined by vyos.utils.system.load_as_module""" + + def __call__(self, *args, **kwargs): + pass + + def __bool__(self): + return False -- cgit v1.2.3 From 47814a3f61f58dd79d4165cb75f358b93b289310 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 29 Mar 2026 21:17:44 -0500 Subject: T8445: add activation script to indicate first installed boot Add script to leave hint of first installed boot (as, say, distinguished from first live boot, or subsequent); this is useful as pre/post conditions and self-modification of activation scripts. --- python/vyos/utils/activate.py | 15 ++++++++++ src/activation-scripts/00-first-installed-boot.py | 35 +++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 src/activation-scripts/00-first-installed-boot.py (limited to 'python') diff --git a/python/vyos/utils/activate.py b/python/vyos/utils/activate.py index 588dda733..056bb2527 100644 --- a/python/vyos/utils/activate.py +++ b/python/vyos/utils/activate.py @@ -18,6 +18,7 @@ import json import typing from pathlib import Path +from vyos.base import Warning as Warn from vyos.defaults import activation_list from vyos.defaults import activation_init from vyos.defaults import activation_hint @@ -101,3 +102,17 @@ def refresh_activation_list(): obj = new_obj orig_list_path.write_text(json.dumps(obj)) + + +first_installed_boot_file = '/run/first_installed_boot' + + +def set_first_installed_boot(): + try: + Path(first_installed_boot_file).touch(exist_ok=False) + except FileExistsError: + Warn('redundant set of first_installed_boot') + + +def is_first_installed_boot(): + return Path(first_installed_boot_file).exists() diff --git a/src/activation-scripts/00-first-installed-boot.py b/src/activation-scripts/00-first-installed-boot.py new file mode 100644 index 000000000..ca02c2642 --- /dev/null +++ b/src/activation-scripts/00-first-installed-boot.py @@ -0,0 +1,35 @@ +# Copyright (C) VyOS Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + + +from vyos.configtree import ConfigTree +from vyos.system.image import is_live_boot +from vyos.utils.activate import set_activation +from vyos.utils.activate import set_first_installed_boot +from vyos.utils.activate import is_first_installed_boot + + +def pre_condition() -> bool: + return not is_live_boot() + + +def activate(_config: ConfigTree) -> None: + pass + + +def post_condition() -> None: + set_first_installed_boot() + if is_first_installed_boot(): + set_activation(__file__, 'never') -- cgit v1.2.3 From c9b4d635c0392cf5ee3fe55c3e7e516a6d4e66f3 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 29 Mar 2026 21:23:57 -0500 Subject: T8335: use activation script for installation of config path hint This is necessary for images produced from a raw_image build (qcow2), which no longer make use of the image_installer script. --- python/vyos/utils/activate.py | 8 ++++++ src/activation-scripts/01-set-config-path-hint.py | 35 +++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/activation-scripts/01-set-config-path-hint.py (limited to 'python') diff --git a/python/vyos/utils/activate.py b/python/vyos/utils/activate.py index 056bb2527..51b92a4aa 100644 --- a/python/vyos/utils/activate.py +++ b/python/vyos/utils/activate.py @@ -116,3 +116,11 @@ def set_first_installed_boot(): def is_first_installed_boot(): return Path(first_installed_boot_file).exists() + + +def set_config_path_hint(): + """The config hint allows subsequent installs to find previous disk + resident config data. It is traditionally added as part of the image + install procedure, however, for raw image builds an alternative is + needed.""" + Path(directories['config']).joinpath('.vyatta_config').touch(exist_ok=True) diff --git a/src/activation-scripts/01-set-config-path-hint.py b/src/activation-scripts/01-set-config-path-hint.py new file mode 100644 index 000000000..3146a7905 --- /dev/null +++ b/src/activation-scripts/01-set-config-path-hint.py @@ -0,0 +1,35 @@ +# Copyright (C) VyOS Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + + +from vyos.configtree import ConfigTree +from vyos.system.image import is_live_boot +from vyos.utils.activate import set_activation +from vyos.utils.activate import set_config_path_hint +from vyos.utils.activate import is_first_installed_boot + + +def pre_condition() -> bool: + return not is_live_boot() + + +def activate(_config: ConfigTree) -> None: + pass + + +def post_condition() -> None: + if is_first_installed_boot(): + set_config_path_hint() + set_activation(__file__, 'never') -- cgit v1.2.3