Tiny clone of pacman to fetch arch packages binary cache. Best used with something like LFN or LFS
Note: mount and su executables are setuid, but you will get weird permission errors with them unless you chown :root ./root/usr/bin/su or similar.
Last active
October 29, 2025 17:13
-
-
Save InfiniteCoder01/17de784f8ff9647dd623d8f0c25a2fa2 to your computer and use it in GitHub Desktop.
Tiny clone of pacman in python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| # Printing | |
| def print_replace(text: str, end: str = '\n'): | |
| print('\x1b[?7l', end='', flush=True) # Disable wrap | |
| print('\x1b[2K', end='', flush=True) # Clear line | |
| print(text, end=end, flush=True) # Print line | |
| print('\x1b[?7h', end='', flush=True) # Enable wrap | |
| def sizeof_fmt(size: int, suffix: str = 'B'): | |
| # Source: https://stackoverflow.com/questions/1094841/get-a-human-readable-version-of-a-file-size | |
| num = size | |
| for unit in ('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi'): | |
| if abs(num) < 1024.0: | |
| return f'{num:3.1f}{unit}{suffix}' | |
| num /= 1024.0 | |
| return f'{num:.1f}Yi{suffix}' | |
| def progress_bar(value: int, max: int, label: str, filesize: bool = True): | |
| WIDTH = 20 | |
| chars = int(value / max * WIDTH) | |
| def fmt(val: int) -> str: | |
| if filesize: return sizeof_fmt(val) | |
| else: return str(val) | |
| bar = f'[{'=' * chars}{' ' * (WIDTH - chars)}]' | |
| print_replace(f'{fmt(value)} / {fmt(max)} {bar} {label}', end='\r') | |
| # Download | |
| from typing import IO | |
| def download(url: str, file: IO[bytes]): | |
| # Source: https://stackoverflow.com/questions/15644964/python-progress-bar-and-downloads (slightly modified) | |
| import requests | |
| response = requests.get(url, stream=True) | |
| content_length = response.headers.get('content-length') | |
| if content_length is None: | |
| file.write(response.content) | |
| else: | |
| content_length = int(content_length) | |
| downloaded = 0 | |
| for data in response.iter_content(chunk_size=4096): | |
| file.write(data) | |
| downloaded += len(data) | |
| progress_bar(downloaded, content_length, f'Downloading {url}') | |
| print_replace(f'Downloaded {url}') | |
| def extract(file: IO[bytes], target_dir: str): | |
| # Source: https://stackoverflow.com/questions/3667865/python-tarfile-progress-output | |
| import tarfile | |
| def track_progress(members: list[tarfile.TarInfo]): | |
| for idx, member in enumerate(members): | |
| progress_bar(idx + 1, len(members), f'Extracting {member.name}') | |
| yield member | |
| with tarfile.open(fileobj=file) as tarball: | |
| tarball.extractall(target_dir, members=track_progress(tarball.getmembers())) | |
| print_replace(f'Extracted {os.path.basename(target_dir)}') | |
| def download_and_extract(url: str, target_dir: str, zst=False): | |
| from io import BytesIO | |
| with BytesIO() as pipe1: | |
| download(url, pipe1) | |
| pipe1.seek(0) | |
| if zst: | |
| with BytesIO() as pipe2: | |
| import zstandard | |
| unzstd = zstandard.ZstdDecompressor() | |
| unzstd.copy_stream(pipe1, pipe2) | |
| pipe2.seek(0) | |
| extract(pipe2, target_dir) | |
| else: | |
| extract(pipe1, target_dir) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import platform | |
| import os | |
| import cache | |
| ARCH = platform.uname().machine | |
| MIRROR = 'https://mirrors.edge.kernel.org/archlinux' | |
| PACKAGE_CACHE_PATH = './packages' | |
| if not os.path.isdir(PACKAGE_CACHE_PATH): | |
| for repo in ['core', 'extra']: | |
| path = os.path.join(PACKAGE_CACHE_PATH, repo) | |
| cache.download_and_extract(f'{MIRROR}/{repo}/os/{ARCH}/{repo}.db.tar.gz', path) | |
| def split_version(package): | |
| split = len(package) | |
| split = min(split, package.index('<') if '<' in package else len(package)) | |
| split = min(split, package.index('>') if '>' in package else len(package)) | |
| split = min(split, package.index('=') if '=' in package else len(package)) | |
| package, version = package[:split], package[split:] | |
| return package, version | |
| package_info = {} | |
| for repo in os.listdir(PACKAGE_CACHE_PATH): | |
| for package in os.listdir(os.path.join(PACKAGE_CACHE_PATH, repo)): | |
| with open(os.path.join(PACKAGE_CACHE_PATH, repo, package, 'desc'), 'r') as file: | |
| info = {} | |
| info['repo'] = repo | |
| section = None | |
| for line in file.readlines(): | |
| line = line.strip() | |
| if line == '': continue | |
| if line[0] == '%' and line[-1] == '%': | |
| section = line[1:-1] | |
| info[section] = [] | |
| else: | |
| info[section].append(line) | |
| for entry in info['NAME'] + (info['PROVIDES'] if 'PROVIDES' in info else []): | |
| entry, _ = split_version(entry) | |
| if entry not in package_info: package_info[entry] = [] | |
| package_info[entry].append(info) | |
| pending_packages = set() | |
| def install(name, target_dir, mirror=MIRROR, arch=ARCH): | |
| if not name in package_info: | |
| raise RuntimeError(f'package {name} not found') | |
| packages = package_info[name] | |
| for package in packages: | |
| if package['NAME'][0] in pending_packages: return | |
| if os.path.isfile(os.path.join(target_dir, 'installed', package['NAME'][0])): return | |
| if len(packages) == 1: | |
| package = packages[0] | |
| else: | |
| names = [package['NAME'][0] for package in packages] | |
| while True: | |
| print(f'Select a package for {name}:\n{'\n'.join([f' {idx + 1}) {name}' for idx, name in enumerate(names)])}') | |
| choice = input('Input (default 1): ') | |
| if choice.strip() == '': | |
| package = packages[0] | |
| break | |
| try: | |
| package = packages[int(choice) - 1] | |
| break | |
| except ValueError: pass | |
| pending_packages.add(package['NAME'][0]) | |
| for dep in (package['DEPENDS'] if 'DEPENDS' in package else []): | |
| dep, _ = split_version(dep) | |
| try: | |
| install(dep, target_dir, mirror, arch) | |
| except RuntimeError as e: | |
| e.add_note(f'while evaluating dependencies of {name}') | |
| raise e | |
| url = f'{mirror}/{package['repo']}/os/{arch}/{package['FILENAME'][0]}' | |
| cache.download_and_extract(url, target_dir, True) | |
| open(os.path.join(target_dir, 'installed', package['NAME'][0]), 'a').close() | |
| pending_packages.remove(package['NAME'][0]) | |
| import argparse | |
| parser = argparse.ArgumentParser(description='Arch linux from scratch :)') | |
| parser.add_argument('target', type=str, help='target path') | |
| parser.add_argument('packages', metavar='N', type=str, nargs='+', help='packages to install') | |
| args = parser.parse_args() | |
| os.makedirs(os.path.join(args.target, 'installed'), exist_ok=True) | |
| for package in args.packages: | |
| install(package, args.target) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment