From 4cbbf9c75068f387ed856ff4f34b8daadfb326b7 Mon Sep 17 00:00:00 2001 From: Korney Gedert Date: Fri, 29 Aug 2025 15:06:22 +0400 Subject: [PATCH 1/2] Implement apt1_runner --- gpoa/apt1_runner | 155 ++++++++++++++++++++++++++ gpoa/locale/ru_RU/LC_MESSAGES/gpoa.po | 3 + gpoa/messages/__init__.py | 1 + gpupdate.spec | 4 + 4 files changed, 163 insertions(+) create mode 100755 gpoa/apt1_runner diff --git a/gpoa/apt1_runner b/gpoa/apt1_runner new file mode 100755 index 00000000..25499905 --- /dev/null +++ b/gpoa/apt1_runner @@ -0,0 +1,155 @@ +#!/usr/bin/python3 +# +# GPOA - GPO Applier for Linux +# +# Copyright (C) 2025 BaseALT Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import rpm +from gpoa.storage import registry_factory +from util.util import get_uid_by_username, string_to_literal_eval +import logging +from util.logging import log +import argparse +import gettext +import locale + +import dbus +import dbus.mainloop.glib + + +def is_rpm_installed(rpm_name): + """ + Check if the package named 'rpm_name' is installed + """ + ts = rpm.TransactionSet() + pm = ts.dbMatch('name', rpm_name) + if pm.count() > 0: + return True + + return False + + +def is_rpm_notinstalled(rpm_name): + """ + Check if the package named 'rpm_name' is not installed + """ + return not is_rpm_installed(rpm_name) + + +def remove_suffix(item): + return '-' + str(item) + + +class Apt_applier: + def __init__(self, user=None): + """ + Initialize the DBus client for org.altlinux.alterator.apt1 + + Throws: + dbus.exceptions.DBusException on error while connecting to the DBus + """ + dbus_interface = 'org.altlinux.alterator.apt1' + bus_name = 'org.altlinux.alterator' + object_path = '/org/altlinux/alterator/apt' + + apt_obj = dbus.SystemBus().get_object(bus_name, object_path) + self.apt_iface = dbus.Interface(apt_obj, dbus_interface) + + install_key_name = 'Install' + remove_key_name = 'Remove' + hklm_branch = 'Software/BaseALT/Policies/Packages' + self.storage = registry_factory() + if user: + uid = get_uid_by_username(user) + dict_dconf_db = self.storage.get_dictionary_from_dconf_file_db(uid) + else: + dict_dconf_db = self.storage.get_dictionary_from_dconf_file_db() + dict_packages = dict_dconf_db.get(hklm_branch, {}) + + self.remove_packages = set(map(str.strip, string_to_literal_eval(dict_packages.get(remove_key_name, [])))) + self.install_packages = map(str.strip, string_to_literal_eval(dict_packages.get(install_key_name, []))) + self.install_packages = set( + filter(is_rpm_notinstalled, [item for item in self.install_packages if item not in self.remove_packages])) + self.remove_packages = filter(is_rpm_installed, self.remove_packages) + + def apply(self): + """ + Call the ApplyAsync method for current packages policy + Throws: + dbus.exceptions.DBusException on error while connecting to the DBus + Returns: + response + """ + log('D235') + + log('D148', {'names': self.install_packages}) + log('D149', {'names': self.remove_packages}) + response = self.apt_iface.ApplyAsync("' '", + "'" + " ".join(map(str, self.install_packages)) + " ".join( + map(remove_suffix, self.remove_packages)) + "'") + if response != 0: + remove_packages = filter(is_rpm_installed, self.remove_packages) + install_packages = filter(is_rpm_notinstalled, self.install_packages) + for package in remove_packages: + if self.apt_iface.ApplyAsync("' '", remove_suffix(package)) != 0: + log('E58', {'name': package}) + for package in install_packages: + if self.apt_iface.ApplyAsync("' '", package) != 0: + log('E57', {'name': package}) + + return response + + def update(self): + """ + Call the UpdateAsync method + Throws: + dbus.exceptions.DBusException on error while connecting to the DBus + Returns: + response + """ + response = self.apt_iface.UpdateAsync() + log('D143') + # TODO: Write output into log + return response + + +if __name__ == '__main__': + locale.bindtextdomain('gpoa', '/usr/lib/python3/site-packages/gpoa/locale') + gettext.bindtextdomain('gpoa', '/usr/lib/python3/site-packages/gpoa/locale') + gettext.textdomain('gpoa') + logger = logging.getLogger() + parser = argparse.ArgumentParser(description='Package applier') + parser.add_argument('--user', type=str, help='user', nargs='?', default=None) + parser.add_argument('--loglevel', type=int, help='loglevel', nargs='?', default=30) + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + args = parser.parse_args() + logger.setLevel(args.loglevel) + + try: + if args.user: + applier = Apt_applier(args.user) + else: + applier = Apt_applier() + except dbus.exceptions.DBusException as e: + exit(1) + + try: + if applier.apply() != 0: + exit(1) + except dbus.exceptions.DBusException as exc: + exit(1) diff --git a/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.po b/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.po index 2076065d..9828fd2a 100644 --- a/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.po +++ b/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.po @@ -958,6 +958,9 @@ msgstr "Расчет времени с момента первого входа msgid "No logins found after password change" msgstr "Не найдены входы после изменения пароля" +msgid "Running apt1_runner to install and remove packages" +msgstr "Запуск apt1_runner для установки и удаления пакетов" + msgid "Unknown message type, no message assigned" msgstr "Неизвестный тип сообщения" diff --git a/gpoa/messages/__init__.py b/gpoa/messages/__init__.py index 717f51ea..2517395e 100644 --- a/gpoa/messages/__init__.py +++ b/gpoa/messages/__init__.py @@ -349,6 +349,7 @@ def debug_code(code): debug_ids[232] = 'No user login records found' debug_ids[233] = 'Calculating time since the first user login after their password change' debug_ids[234] = 'No logins found after password change' + debug_ids[235] = 'Running apt1_runner to install and remove packages' return debug_ids.get(code, 'Unknown debug code') diff --git a/gpupdate.spec b/gpupdate.spec index a60dbc24..4034bacb 100644 --- a/gpupdate.spec +++ b/gpupdate.spec @@ -106,6 +106,8 @@ ln -s %python3_sitelibdir/gpoa/gpupdate-setup \ mkdir -p \ %buildroot%_prefix/libexec/%name +ln -s %python3_sitelibdir/gpoa/apt1_runner \ + %buildroot%_prefix/libexec/%name/apt1_runner ln -s %python3_sitelibdir/gpoa/pkcon_runner \ %buildroot%_prefix/libexec/%name/pkcon_runner ln -s %python3_sitelibdir/gpoa/scripts_runner \ @@ -166,11 +168,13 @@ fi %_bindir/gpupdate %_prefix/libexec/%name/scripts_runner %_prefix/libexec/%name/pkcon_runner +%_prefix/libexec/%name/apt1_runner %attr(755,root,root) %python3_sitelibdir/gpoa/gpoa %attr(755,root,root) %python3_sitelibdir/gpoa/gpupdate %attr(755,root,root) %python3_sitelibdir/gpoa/gpupdate-setup %attr(755,root,root) %python3_sitelibdir/gpoa/scripts_runner %attr(755,root,root) %python3_sitelibdir/gpoa/pkcon_runner +%attr(755,root,root) %python3_sitelibdir/gpoa/apt1_runner %python3_sitelibdir/gpoa %_datadir/%name %_unitdir/%name.service From 99279709e73f702634895f4744968f1b963fe7ad Mon Sep 17 00:00:00 2001 From: Korney Gedert Date: Fri, 29 Aug 2025 15:07:57 +0400 Subject: [PATCH 2/2] Implement selecting runner(apt1 default) --- gpoa/frontend/package_applier.py | 53 ++++++++++++++-- gpoa/locale/ru_RU/LC_MESSAGES/gpoa.po | 18 ++++++ gpoa/messages/__init__.py | 7 +++ gpoa/select_runner | 91 +++++++++++++++++++++++++++ gpupdate.spec | 4 ++ 5 files changed, 167 insertions(+), 6 deletions(-) create mode 100755 gpoa/select_runner diff --git a/gpoa/frontend/package_applier.py b/gpoa/frontend/package_applier.py index 3687f812..d07beaab 100644 --- a/gpoa/frontend/package_applier.py +++ b/gpoa/frontend/package_applier.py @@ -24,6 +24,20 @@ applier_frontend , check_enabled ) +import os + +def select_runner() -> None | str: + """ + Select runner to use for package applier + + Returns: + str if backend is selected + None otherwise + """ + proc = subprocess.run(['/usr/libexec/gpupdate/select_runner'], stdout=subprocess.PIPE) + if proc.returncode != 0: + return None + return proc.stdout.decode('utf-8').strip() class package_applier(applier_frontend): __module_name = 'PackagesApplier' @@ -40,8 +54,16 @@ def __init__(self, storage): install_branch = '{}\\{}%'.format(self.__hklm_branch, self.__install_key_name) remove_branch = '{}\\{}%'.format(self.__hklm_branch, self.__remove_key_name) sync_branch = '{}\\{}%'.format(self.__hklm_branch, self.__sync_key_name) + + runner = select_runner() + if runner is None: + log('E81') + raise Exception('No package runner selected') + log('D236', {'runner': runner}) + self.runner = runner + '_runner' + self.fulcmd = [] - self.fulcmd.append('/usr/libexec/gpupdate/pkcon_runner') + self.fulcmd.append('/usr/libexec/gpupdate/' + self.runner) self.fulcmd.append('--loglevel') logger = logging.getLogger() self.fulcmd.append(str(logger.level)) @@ -65,13 +87,19 @@ def run(self): subprocess.check_call(self.fulcmd) except Exception as exc: logdata = {'msg': str(exc)} - log('E55', logdata) + if self.runner == 'pkcon_runner': + log('E55', logdata) + elif self.runner == 'apt1_runner': + log('E79', logdata) else: try: subprocess.Popen(self.fulcmd,close_fds=False) except Exception as exc: logdata = {'msg': str(exc)} - log('E61', logdata) + if self.runner == 'pkcon_runner': + log('E61', logdata) + elif self.runner == 'apt1_runner': + log('E80', logdata) def apply(self): if self.__module_enabled: @@ -91,10 +119,17 @@ class package_applier_user(applier_frontend): __hkcu_branch = 'Software\\BaseALT\\Policies\\Packages' def __init__(self, storage, username): + runner = select_runner() + if runner is None: + log('E81') + raise Exception('No package applier runner found') + log('D236', {'runner': runner}) + self.runner = runner + '_runner' + self.storage = storage self.username = username self.fulcmd = [] - self.fulcmd.append('/usr/libexec/gpupdate/pkcon_runner') + self.fulcmd.append('/usr/libexec/gpupdate/' + self.runner) self.fulcmd.append('--user') self.fulcmd.append(self.username) self.fulcmd.append('--loglevel') @@ -129,13 +164,19 @@ def run(self): subprocess.check_call(self.fulcmd) except Exception as exc: logdata = {'msg': str(exc)} - log('E60', logdata) + if self.runner == 'pkcon_runner': + log('E60', logdata) + elif self.runner == 'apt1_runner': + log('E77', logdata) else: try: subprocess.Popen(self.fulcmd,close_fds=False) except Exception as exc: logdata = {'msg': str(exc)} - log('E62', logdata) + if self.runner == 'pkcon_runner': + log('E62', logdata) + elif self.runner == 'apt1_runner': + log('E78', logdata) def admin_context_apply(self): ''' diff --git a/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.po b/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.po index 9828fd2a..9796d203 100644 --- a/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.po +++ b/gpoa/locale/ru_RU/LC_MESSAGES/gpoa.po @@ -268,6 +268,21 @@ msgstr "Не удалось обновить LDAP новыми данными п msgid "Failed to change local user password" msgstr "Не удалось изменить пароль локального пользователя" +msgid "Error running apt1_runner sync for user" +msgstr "Ошибка при запуске apt1_runner синхронно для пользователя" + +msgid "Error running apt1_runner async for user" +msgstr "Ошибка при запуске apt1_runner асинхронно для пользователя" + +msgid "Error running apt1_runner sync for machine" +msgstr "Ошибка при запуске apt1_runner синхронно для компьютера" + +msgid "Error running apt1_runner async for machine" +msgstr "Ошибка при запуске apt1_runner асинхронно для компьютера" + +msgid "Error no package applier runner found" +msgstr "Ошибка не найден исполнитель применения пакетов" + # Error_end # Debug @@ -961,6 +976,9 @@ msgstr "Не найдены входы после изменения парол msgid "Running apt1_runner to install and remove packages" msgstr "Запуск apt1_runner для установки и удаления пакетов" +msgid "Package applier runner found" +msgstr "Найдено средство применения пакета" + msgid "Unknown message type, no message assigned" msgstr "Неизвестный тип сообщения" diff --git a/gpoa/messages/__init__.py b/gpoa/messages/__init__.py index 2517395e..13cb3464 100644 --- a/gpoa/messages/__init__.py +++ b/gpoa/messages/__init__.py @@ -112,6 +112,12 @@ def error_code(code): error_ids[74] = 'Autofs restart failed' error_ids[75] = 'Failed to update LDAP with new password data' error_ids[76] = 'Failed to change local user password' + error_ids[77] = 'Error running apt1_runner sync for user' + error_ids[78] = 'Error running apt1_runner async for user' + error_ids[79] = 'Error running apt1_runner sync for machine' + error_ids[80] = 'Error running apt1_runner async for machine' + error_ids[81] = 'Error no package applier runner found' + return error_ids.get(code, 'Unknown error code') def debug_code(code): @@ -350,6 +356,7 @@ def debug_code(code): debug_ids[233] = 'Calculating time since the first user login after their password change' debug_ids[234] = 'No logins found after password change' debug_ids[235] = 'Running apt1_runner to install and remove packages' + debug_ids[236] = 'Package applier runner found' return debug_ids.get(code, 'Unknown debug code') diff --git a/gpoa/select_runner b/gpoa/select_runner new file mode 100755 index 00000000..51d8c50d --- /dev/null +++ b/gpoa/select_runner @@ -0,0 +1,91 @@ +#!/usr/bin/python3 + +import dbus +import logging +from util.logging import log +import argparse +import gettext +import locale + +import os +import dbus +import dbus.mainloop.glib + + +def check_apt1() -> bool: + ''' + Check if apt1 is running and has ApplyAsync method + + Returns: + True if apt1 is running and has ApplyAsync method + ''' + dbus_interface = 'org.altlinux.alterator.apt1' + bus_name = 'org.altlinux.alterator' + object_path = '/org/altlinux/alterator/apt' + try: + bus = dbus.SystemBus() + + if not bus.name_has_owner(bus_name): + bus.close() + return False + + if not bus.get_object(bus_name, object_path).has_method(dbus_interface, 'ApplyAsync'): + bus.close() + return False + + bus.close() + return True + except dbus.exceptions.DBusException as e: + return False + +def check_pkcon() -> bool: + """ + Check if pkcon is installed and executable + + Returns: + True if pkcon is installed and executable + """ + if os.path.isfile('/usr/bin/pkcon') and os.access('/usr/bin/pkcon', os.X_OK): + return True + return False + +def main() -> int: + """ + Check if apt1 or pkcon is running and return the name of the backend + + Returns: + 0 if apt1 or pkcon is running, 1 otherwise + """ + + backends = { "apt1": check_apt1,"pkcon": check_pkcon } + + locale.bindtextdomain('gpoa', '/usr/lib/python3/site-packages/gpoa/locale') + gettext.bindtextdomain('gpoa', '/usr/lib/python3/site-packages/gpoa/locale') + gettext.textdomain('gpoa') + logger = logging.getLogger() + parser = argparse.ArgumentParser(description='Package applier') + parser.add_argument('-b', '--backend', type=str, choices=backends.keys(), default='apt1', help='priority backend (default: %(default)s)') + parser.add_argument('--loglevel', type=int, help='loglevel', nargs='?', default=30) + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + args = parser.parse_args() + logger.setLevel(args.loglevel) + + if backends[args.backend](): + print(args.backend) + return 0 + + for el in backends: + if el == args.backend: + continue + if backends[el](): + print(el) + return 0 + return 1 + +if __name__ == '__main__': + try: + exit(main()) + except Exception as exc: + exit(1) \ No newline at end of file diff --git a/gpupdate.spec b/gpupdate.spec index 4034bacb..5ebdf270 100644 --- a/gpupdate.spec +++ b/gpupdate.spec @@ -108,6 +108,8 @@ mkdir -p \ ln -s %python3_sitelibdir/gpoa/apt1_runner \ %buildroot%_prefix/libexec/%name/apt1_runner +ln -s %python3_sitelibdir/gpoa/select_runner \ + %buildroot%_prefix/libexec/%name/select_runner ln -s %python3_sitelibdir/gpoa/pkcon_runner \ %buildroot%_prefix/libexec/%name/pkcon_runner ln -s %python3_sitelibdir/gpoa/scripts_runner \ @@ -169,12 +171,14 @@ fi %_prefix/libexec/%name/scripts_runner %_prefix/libexec/%name/pkcon_runner %_prefix/libexec/%name/apt1_runner +%_prefix/libexec/%name/select_runner %attr(755,root,root) %python3_sitelibdir/gpoa/gpoa %attr(755,root,root) %python3_sitelibdir/gpoa/gpupdate %attr(755,root,root) %python3_sitelibdir/gpoa/gpupdate-setup %attr(755,root,root) %python3_sitelibdir/gpoa/scripts_runner %attr(755,root,root) %python3_sitelibdir/gpoa/pkcon_runner %attr(755,root,root) %python3_sitelibdir/gpoa/apt1_runner +%attr(755,root,root) %python3_sitelibdir/gpoa/select_runner %python3_sitelibdir/gpoa %_datadir/%name %_unitdir/%name.service