Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/archinstall/lib/pacman
diff options
context:
space:
mode:
authorAndreas Baumann <mail@andreasbaumann.cc>2024-05-10 15:56:28 +0200
committerAndreas Baumann <mail@andreasbaumann.cc>2024-05-10 15:56:28 +0200
commit683da22298abbd90f51d4dd38a7ec4b0dfb04555 (patch)
treeec2ac04967f9277df038edc362201937b331abe5 /archinstall/lib/pacman
parentaf7ab9833c9f9944874f0162ae0975175ddc628d (diff)
parent3381cd55673e5105697d354cf4a1be9a7bcef062 (diff)
merged with upstreamHEADmaster
Diffstat (limited to 'archinstall/lib/pacman')
-rw-r--r--archinstall/lib/pacman/__init__.py88
-rw-r--r--archinstall/lib/pacman/config.py44
-rw-r--r--archinstall/lib/pacman/repo.py5
3 files changed, 137 insertions, 0 deletions
diff --git a/archinstall/lib/pacman/__init__.py b/archinstall/lib/pacman/__init__.py
new file mode 100644
index 00000000..6478f0cc
--- /dev/null
+++ b/archinstall/lib/pacman/__init__.py
@@ -0,0 +1,88 @@
+from pathlib import Path
+import time
+import re
+from typing import TYPE_CHECKING, Any, List, Callable, Union
+from shutil import copy2
+
+from ..general import SysCommand
+from ..output import warn, error, info
+from .repo import Repo
+from .config import Config
+from ..exceptions import RequirementError
+from ..plugins import plugins
+
+if TYPE_CHECKING:
+ _: Any
+
+
+class Pacman:
+
+ def __init__(self, target: Path, silent: bool = False):
+ self.synced = False
+ self.silent = silent
+ self.target = target
+
+ @staticmethod
+ def run(args :str, default_cmd :str = 'pacman') -> SysCommand:
+ """
+ A centralized function to call `pacman` from.
+ It also protects us from colliding with other running pacman sessions (if used locally).
+ The grace period is set to 10 minutes before exiting hard if another pacman instance is running.
+ """
+ pacman_db_lock = Path('/var/lib/pacman/db.lck')
+
+ if pacman_db_lock.exists():
+ warn(_('Pacman is already running, waiting maximum 10 minutes for it to terminate.'))
+
+ started = time.time()
+ while pacman_db_lock.exists():
+ time.sleep(0.25)
+
+ if time.time() - started > (60 * 10):
+ error(_('Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall.'))
+ exit(1)
+
+ return SysCommand(f'{default_cmd} {args}')
+
+ def ask(self, error_message: str, bail_message: str, func: Callable, *args, **kwargs):
+ while True:
+ try:
+ func(*args, **kwargs)
+ break
+ except Exception as err:
+ error(f'{error_message}: {err}')
+ if not self.silent and input('Would you like to re-try this download? (Y/n): ').lower().strip() in 'y':
+ continue
+ raise RequirementError(f'{bail_message}: {err}')
+
+ def sync(self):
+ if self.synced:
+ return
+ self.ask(
+ 'Could not sync a new package database',
+ 'Could not sync mirrors',
+ self.run,
+ '-Syy',
+ default_cmd='/usr/bin/pacman'
+ )
+ self.synced = True
+
+ def strap(self, packages: Union[str, List[str]]):
+ self.sync()
+ 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(
+ 'Could not strap in packages',
+ 'Pacstrap failed. See /var/log/archinstall/install.log or above message for error details',
+ SysCommand,
+ f'/usr/bin/pacstrap -C /etc/pacman.conf -K {self.target} {" ".join(packages)} --noconfirm',
+ peek_output=True
+ )
diff --git a/archinstall/lib/pacman/config.py b/archinstall/lib/pacman/config.py
new file mode 100644
index 00000000..6686f4a9
--- /dev/null
+++ b/archinstall/lib/pacman/config.py
@@ -0,0 +1,44 @@
+import re
+from pathlib import Path
+from shutil import copy2
+from typing import List
+
+from .repo import Repo
+
+
+class Config:
+ def __init__(self, target: Path):
+ self.path = Path("/etc") / "pacman.conf"
+ self.chroot_path = target / "etc" / "pacman.conf"
+ self.repos: List[Repo] = []
+
+ def enable(self, repo: Repo):
+ self.repos.append(repo)
+
+ def apply(self):
+ if not self.repos:
+ return
+
+ if Repo.Testing in self.repos:
+ if Repo.Multilib in self.repos:
+ repos_pattern = f'({Repo.Multilib.value}|.+-{Repo.Testing.value})'
+ else:
+ repos_pattern = f'(?!{Repo.Multilib.value}).+-{Repo.Testing.value}'
+ else:
+ repos_pattern = Repo.Multilib.value
+
+ pattern = re.compile(rf"^#\s*\[{repos_pattern}\]$")
+
+ lines = iter(self.path.read_text().splitlines(keepends=True))
+ with open(self.path, 'w') as f:
+ for line in lines:
+ if pattern.match(line):
+ # Uncomment this line and the next.
+ f.write(line.lstrip('#'))
+ f.write(next(lines).lstrip('#'))
+ else:
+ f.write(line)
+
+ def persist(self):
+ if self.repos:
+ copy2(self.path, self.chroot_path)
diff --git a/archinstall/lib/pacman/repo.py b/archinstall/lib/pacman/repo.py
new file mode 100644
index 00000000..7a461431
--- /dev/null
+++ b/archinstall/lib/pacman/repo.py
@@ -0,0 +1,5 @@
+from enum import Enum
+
+class Repo(Enum):
+ Multilib = "multilib"
+ Testing = "testing"