Remove plugin system

This commit is contained in:
codefiles 2026-04-11 20:36:17 -04:00
parent d6de03ab76
commit 1b92b93f97
9 changed files with 9 additions and 298 deletions

View File

@ -1,3 +0,0 @@
from archinstall.lib.plugins import plugin
__all__ = ['plugin']

View File

@ -25,7 +25,6 @@ from archinstall.lib.models.packages import Repository
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 @@ class ArchConfigHandler:
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 @@ class ArchConfigHandler:
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')

View File

@ -54,7 +54,6 @@ from archinstall.lib.packages.packages import installed_package
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 @@ class Installer:
"""
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 @@ class Installer:
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 @@ class Installer:
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 @@ class Installer:
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 @@ class Installer:
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 @@ class Installer:
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 @@ class Installer:
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 @@ class Installer:
: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 @@ class Installer:
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
info(f'Creating user {user.username}')
if not handled_by_plugin:
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)

View File

@ -7,7 +7,6 @@ from archinstall.lib.command import SysCommand
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 @@ class Pacman:
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(

View File

@ -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.")

View File

@ -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_ <https://github.com/search?q=repo%3Aarchlinux%2Farchinstall+%22plugin.on_%22&type=code>`_ 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 <https://github.com/torxed/archinstall-aur>`_
* `phisch/archinstall-aur <https://github.com/phisch/archinstall-aur>`_
And search for `plugin.on_ <https://github.com/search?q=repo%3Aarchlinux%2Farchinstall+%22plugin.on_%22&type=code>`_ 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

View File

@ -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 <url>`` and source an AUR plugin.
`torxed/archinstall-aur <https://github.com/torxed/archinstall-aur>`_ is a reference implementation for plugins:
.. code-block:: console
# archinstall --plugin https://archlinux.life/aur-plugin
`phisch/archinstall-aur <https://github.com/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

View File

@ -32,7 +32,6 @@ Some of the features of Archinstall are:
installing/python
examples/python
archinstall/plugins
.. toctree::
:maxdepth: 3

View File

@ -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,
)