diff --git a/archinstall/__init__.py b/archinstall/__init__.py index f13fa16956..e69de29bb2 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -1,3 +0,0 @@ -from archinstall.lib.plugins import plugin - -__all__ = ['plugin'] diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index c61bc5a15f..3d46b76c7f 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -25,7 +25,6 @@ from archinstall.lib.models.profile import ProfileConfiguration from archinstall.lib.models.users import Password, User, UserSerialization from archinstall.lib.output import debug, error, logger, warn -from archinstall.lib.plugins import load_plugin from archinstall.lib.translationhandler import Language, tr, translation_handler from archinstall.lib.version import get_version from archinstall.tui.ui.components import tui @@ -48,7 +47,6 @@ class Arguments: debug: bool = False offline: bool = False no_pkg_lookups: bool = False - plugin: str | None = None skip_version_check: bool = False skip_wifi_check: bool = False advanced: bool = False @@ -395,13 +393,6 @@ def _define_arguments(self) -> ArgumentParser: default=False, help='Disabled package validation specifically prior to starting installation.', ) - parser.add_argument( - '--plugin', - nargs='?', - type=str, - default=None, - help='File path to a plugin to load', - ) parser.add_argument( '--skip-version-check', action='store_true', @@ -441,10 +432,6 @@ def _parse_args(self) -> Arguments: if args.debug: warn(f'Warning: --debug mode will write certain credentials to {logger.path}!') - if args.plugin: - plugin_path = Path(args.plugin) - load_plugin(plugin_path) - if args.creds_decryption_key is None: if os.environ.get('ARCHINSTALL_CREDS_DECRYPTION_KEY'): args.creds_decryption_key = os.environ.get('ARCHINSTALL_CREDS_DECRYPTION_KEY') diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index ff56e2e11b..aaa019a275 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -54,7 +54,6 @@ from archinstall.lib.pacman.config import PacmanConfig from archinstall.lib.pacman.pacman import Pacman from archinstall.lib.pathnames import MIRRORLIST, PACMAN_CONF -from archinstall.lib.plugins import plugins from archinstall.lib.translationhandler import tr # Any package that the Installer() is responsible for (optional and the default ones) @@ -560,11 +559,6 @@ def set_mirrors( """ debug('Setting mirrors on ' + ('target' if on_target else 'live system')) - for plugin in plugins.values(): - if hasattr(plugin, 'on_mirrors'): - if result := plugin.on_mirrors(mirror_config): - mirror_config = result - if on_target: mirrorlist_config = self.target / MIRRORLIST.relative_to_root() pacman_config = self.target / PACMAN_CONF.relative_to_root() @@ -606,11 +600,6 @@ def genfstab(self, flags: str = '-pU') -> None: if not fstab_path.is_file(): raise RequirementError('Could not create fstab file') - for plugin in plugins.values(): - if hasattr(plugin, 'on_genfstab'): - if plugin.on_genfstab(self) is True: - break - with open(fstab_path, 'a') as fp: for entry in self._fstab_entries: fp.write(f'{entry}\n') @@ -673,11 +662,6 @@ def set_timezone(self, zone: str) -> bool: if not len(zone): return True # Redundant - for plugin in plugins.values(): - if hasattr(plugin, 'on_timezone'): - if result := plugin.on_timezone(zone): - zone = result - if (Path('/usr') / 'share' / 'zoneinfo' / zone).exists(): (Path(self.target) / 'etc' / 'localtime').unlink(missing_ok=True) self.arch_chroot(f'ln -s /usr/share/zoneinfo/{zone} /etc/localtime') @@ -713,10 +697,6 @@ def enable_service(self, services: str | list[str]) -> None: except SysCallError as err: raise ServiceException(f'Unable to start service {service}: {err}') - for plugin in plugins.values(): - if hasattr(plugin, 'on_service'): - plugin.on_service(service) - def disable_service(self, services_disable: str | list[str]) -> None: if isinstance(services_disable, str): services_disable = [services_disable] @@ -744,19 +724,6 @@ def drop_to_shell(self) -> None: def configure_nic(self, nic: Nic) -> None: conf = nic.as_systemd_config() - for plugin in plugins.values(): - if hasattr(plugin, 'on_configure_nic'): - conf = ( - plugin.on_configure_nic( - nic.iface, - nic.dhcp, - nic.ip, - nic.gateway, - nic.dns, - ) - or conf - ) - with open(f'{self.target}/etc/systemd/network/10-{nic.iface}.network', 'a') as netconf: netconf.write(str(conf)) @@ -818,12 +785,6 @@ def post_install_enable_networkd_resolved(*args: str, **kwargs: str) -> None: return True def mkinitcpio(self, flags: list[str]) -> bool: - for plugin in plugins.values(): - if hasattr(plugin, 'on_mkinitcpio'): - # Allow plugins to override the usage of mkinitcpio altogether. - if plugin.on_mkinitcpio(self): - return True - with open(f'{self.target}/etc/mkinitcpio.conf', 'r+') as mkinit: content = mkinit.read() content = re.sub('\nMODULES=(.*)', f'\nMODULES=({" ".join(self._modules)})', content) @@ -963,10 +924,6 @@ def minimal_installation( info(f'Running post-installation hook: {function}') function(self) - for plugin in plugins.values(): - if hasattr(plugin, 'on_install'): - plugin.on_install(self) - def setup_btrfs_snapshot( self, snapshot_type: SnapshotType, @@ -1812,14 +1769,6 @@ def add_bootloader(self, bootloader: Bootloader, uki_enabled: bool = False, boot :param uki_enabled: Whether to use unified kernel images :param bootloader_removable: Whether to install to removable media location (UEFI only, for GRUB and Limine) """ - - for plugin in plugins.values(): - if hasattr(plugin, 'on_add_bootloader'): - # Allow plugins to override the boot-loader handling. - # This allows for boot configuring and installing bootloaders. - if plugin.on_add_bootloader(self): - return - efi_partition = self._get_efi_partition() boot_partition = self._get_boot_partition() root = self._get_root() @@ -1902,33 +1851,19 @@ def create_users(self, users: User | list[User]) -> None: self._create_user(user) def _create_user(self, user: User) -> None: - # This plugin hook allows for the plugin to handle the creation of the user. - # Password and Group management is still handled by user_create() - handled_by_plugin = False - for plugin in plugins.values(): - if hasattr(plugin, 'on_user_create'): - if result := plugin.on_user_create(self, user): - handled_by_plugin = result - - if not handled_by_plugin: - info(f'Creating user {user.username}') + info(f'Creating user {user.username}') - cmd = 'useradd -m' + cmd = 'useradd -m' - if user.sudo: - cmd += ' -G wheel' + if user.sudo: + cmd += ' -G wheel' - cmd += f' {user.username}' + cmd += f' {user.username}' - try: - self.arch_chroot(cmd) - except SysCallError as err: - raise SystemError(f'Could not create user inside installation: {err}') - - for plugin in plugins.values(): - if hasattr(plugin, 'on_user_created'): - if result := plugin.on_user_created(self, user): - handled_by_plugin = result + try: + self.arch_chroot(cmd) + except SysCallError as err: + raise SystemError(f'Could not create user inside installation: {err}') self.set_user_password(user) diff --git a/archinstall/lib/pacman/pacman.py b/archinstall/lib/pacman/pacman.py index 5427214ca7..970e37d336 100644 --- a/archinstall/lib/pacman/pacman.py +++ b/archinstall/lib/pacman/pacman.py @@ -7,7 +7,6 @@ from archinstall.lib.exceptions import RequirementError from archinstall.lib.output import error, info, warn from archinstall.lib.pathnames import PACMAN_CONF -from archinstall.lib.plugins import plugins from archinstall.lib.translationhandler import tr @@ -67,11 +66,6 @@ def strap(self, packages: str | list[str]) -> None: if isinstance(packages, str): packages = [packages] - for plugin in plugins.values(): - if hasattr(plugin, 'on_pacstrap'): - if result := plugin.on_pacstrap(packages): - packages = result - info(f'Installing packages: {packages}') self.ask( diff --git a/archinstall/lib/plugins.py b/archinstall/lib/plugins.py deleted file mode 100644 index 919fcf2387..0000000000 --- a/archinstall/lib/plugins.py +++ /dev/null @@ -1,120 +0,0 @@ -import hashlib -import importlib.util -import os -import sys -import urllib.parse -import urllib.request -from importlib import metadata -from pathlib import Path - -from archinstall.lib.output import error, info, warn -from archinstall.lib.version import get_version - -plugins = {} - - -# 1: List archinstall.plugin definitions -# 2: Load the plugin entrypoint -# 3: Initiate the plugin and store it as .name in plugins -for plugin_definition in metadata.entry_points().select(group='archinstall.plugin'): - plugin_entrypoint = plugin_definition.load() - - try: - plugins[plugin_definition.name] = plugin_entrypoint() - except Exception as err: - error( - f'Error: {err}', - f'The above error was detected when loading the plugin: {plugin_definition}', - ) - - -# @archinstall.plugin decorator hook to programmatically add -# plugins in runtime. Useful in profiles_bck and other things. -def plugin(f, *args, **kwargs) -> None: # type: ignore[no-untyped-def] - plugins[f.__name__] = f - - -def _localize_path(path: Path) -> Path: - """ - Support structures for load_plugin() - """ - url = urllib.parse.urlparse(str(path)) - - if url.scheme and url.scheme in ('https', 'http'): - converted_path = Path(f'/tmp/{path.stem}_{hashlib.md5(os.urandom(12)).hexdigest()}.py') - - with open(converted_path, 'w') as temp_file: - temp_file.write(urllib.request.urlopen(url.geturl()).read().decode('utf-8')) - - return converted_path - else: - return path - - -def _import_via_path(path: Path, namespace: str | None = None) -> str: - if not namespace: - namespace = os.path.basename(path) - - if namespace == '__init__.py': - namespace = path.parent.name - - try: - spec = importlib.util.spec_from_file_location(namespace, path) - if spec and spec.loader: - imported = importlib.util.module_from_spec(spec) - sys.modules[namespace] = imported - spec.loader.exec_module(sys.modules[namespace]) - - return namespace - except Exception as err: - error( - f'Error: {err}', - f'The above error was detected when loading the plugin: {path}', - ) - - try: - del sys.modules[namespace] - except Exception: - pass - - return namespace - - -def load_plugin(path: Path) -> None: - namespace: str | None = None - parsed_url = urllib.parse.urlparse(str(path)) - info(f'Loading plugin from url {parsed_url}') - - # The Profile was not a direct match on a remote URL - if not parsed_url.scheme: - # Path was not found in any known examples, check if it's an absolute path - if os.path.isfile(path): - namespace = _import_via_path(path) - elif parsed_url.scheme in ('https', 'http'): - localized = _localize_path(path) - namespace = _import_via_path(localized) - - if namespace and namespace in sys.modules: - # Version dependency via __archinstall__version__ variable (if present) in the plugin - # Any errors in version inconsistency will be handled through normal error handling if not defined. - version = get_version() - - if version is not None: - version_major_and_minor = version.rsplit('.', 1)[0] - - if sys.modules[namespace].__archinstall__version__ < float(version_major_and_minor): - error(f'Plugin {sys.modules[namespace]} does not support the current Archinstall version.') - - # Locate the plugin entry-point called Plugin() - # This in accordance with the entry_points() from setup.cfg above - if hasattr(sys.modules[namespace], 'Plugin'): - try: - plugins[namespace] = sys.modules[namespace].Plugin() - info(f'Plugin {plugins[namespace]} has been loaded.') - except Exception as err: - error( - f'Error: {err}', - f'The above error was detected when initiating the plugin: {path}', - ) - else: - warn(f"Plugin '{path}' is missing a valid entry-point or is corrupt.") diff --git a/docs/archinstall/plugins.rst b/docs/archinstall/plugins.rst deleted file mode 100644 index 1022ab6f6b..0000000000 --- a/docs/archinstall/plugins.rst +++ /dev/null @@ -1,57 +0,0 @@ -.. _archinstall.Plugins: - -Python Plugins -============== - -``archinstall`` supports plugins via two methods. - -First method is directly via the ``--plugin`` parameter when running as a CLI tool. This will load a specific plugin locally or remotely via a path. - -The second method is via Python's built in `plugin discovery`_ using `entry points`_ categorized as ``archinstall.plugin``. - -``--plugin`` parameter ----------------------- - -The parameter has the benefit of being stored in the ``--conf`` state, meaning when re-running an installation — the plugin will automatically be loaded. -It's limitation is that it requires an initial path to be known and written and be cumbersome. - -Plugin Discovery ----------------- - -This method allows for multiple plugins to be loaded with the drawback that they have to be installed beforehand on the system running ``archinstall``. -This mainly targets those who build their own ISO's and package specific setups for their needs. - - -What's supported? ------------------ - -Currently the documentation for this is scarse. Until that is resolved, the best way to find supported features is to search the source code for `plugin.on_ `_ as this will give a clear indication of which calls are made to plugins. - -How does it work? ------------------ - -``archinstall`` plugins use a discovery-driven approach where plugins are queried for certain functions. -As an example, if a plugin has the following function: - -.. code-block:: python - - def on_pacstrap(*packages): - ... - -The function :code:`archinstall.Pacman().strap(["some packages"])` is hardcoded to iterate plugins and look for :code:`on_pacstrap` in the plugin. -If the function exists, :code:`.strap()` will call the plugin's function and replace the initial package list with the result from the plugin. - -The best way to document these calls is currently undecided, as it's hard to document this behavior dynamically. - -Writing your own? ------------------ - -The simplest way currently is to look at a reference implementation or the community. Two of these are: - -* `torxed/archinstall-aur `_ -* `phisch/archinstall-aur `_ - -And search for `plugin.on_ `_ in the code base to find what ``archinstall`` will look for. PR's are welcome to widen the support for this. - -.. _plugin discovery: https://packaging.python.org/en/latest/specifications/entry-points/ -.. _entry points: https://docs.python.org/3/library/importlib.metadata.html#entry-points diff --git a/docs/help/known_issues.rst b/docs/help/known_issues.rst index bf039dcbc4..53a764ac4b 100644 --- a/docs/help/known_issues.rst +++ b/docs/help/known_issues.rst @@ -100,26 +100,6 @@ This is also a catch-all issue. This means that feature requests like supporting filesystems such as `ZFS`_ can not be added, and issues cannot be solved by using AUR packages either. -.. note:: - - But in spirit of giving the community options, ``archinstall`` supports :ref:`archinstall.Plugins`, which means you can run ``archinstall --plugin `` and source an AUR plugin. - - `torxed/archinstall-aur `_ is a reference implementation for plugins: - - .. code-block:: console - - # archinstall --plugin https://archlinux.life/aur-plugin - - `phisch/archinstall-aur `_ is another alternative: - - .. code-block:: console - - # archinstall --plugin https://raw.githubusercontent.com/phisch/archinstall-aur/master/archinstall-aur.py - - .. warning:: - - This will allow for unsupported usage of AUR during installation. - .. _#1686: https://github.com/archlinux/archinstall/issues/1686 .. _#2002: https://github.com/archlinux/archinstall/issues/2002 .. _#2144: https://github.com/archlinux/archinstall/issues/2144 diff --git a/docs/index.rst b/docs/index.rst index 9759524bac..72f9aa8a68 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,7 +32,6 @@ Some of the features of Archinstall are: installing/python examples/python - archinstall/plugins .. toctree:: :maxdepth: 3 diff --git a/tests/test_args.py b/tests/test_args.py index 1b052ec0a1..ff53c51e16 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -48,7 +48,6 @@ def test_default_args(monkeypatch: MonkeyPatch) -> None: debug=False, offline=False, no_pkg_lookups=False, - plugin=None, skip_version_check=False, advanced=False, ) @@ -79,8 +78,6 @@ def test_correct_parsing_args( '--debug', '--offline', '--no-pkg-lookups', - '--plugin', - 'pytest_plugin.py', '--skip-version-check', '--advanced', '--dry-run', @@ -105,7 +102,6 @@ def test_correct_parsing_args( debug=True, offline=True, no_pkg_lookups=True, - plugin='pytest_plugin.py', skip_version_check=True, advanced=True, )