From 0009d0a790db181e087208a9f0113cd4d00d9eba Mon Sep 17 00:00:00 2001 From: h8d13 Date: Mon, 8 Dec 2025 14:05:02 +0100 Subject: [PATCH 1/4] Add host-to-target (H2T) installation mode detection - Add running_from_host() function to detect if running from installed system vs ISO - Function checks for /run/archiso existence (ISO mode) vs host mode - Add clear logging of installation mode on startup - Skip keyboard layout changes when running from host system - Fix Pyright type error in jsonify() by using Any instead of object - Update README to mention installation from existing system This enables archinstall to be run from an existing Arch installation to perform host-to-target installs on other disks/partitions. --- README.md | 2 +- archinstall/__init__.py | 7 +++++++ archinstall/lib/general.py | 19 ++++++++++++++++--- archinstall/lib/locale/utils.py | 7 ++++++- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 370e81a2f9..b6d5708503 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Lint Python and Find Syntax Errors](https://github.com/archlinux/archinstall/actions/workflows/flake8.yaml/badge.svg)](https://github.com/archlinux/archinstall/actions/workflows/flake8.yaml) Just another guided/automated [Arch Linux](https://wiki.archlinux.org/index.php/Arch_Linux) installer with a twist. -The installer also doubles as a python library to install Arch Linux and manage services, packages, and other things inside the installed system *(Usually from a live medium)*. +The installer also doubles as a python library to install Arch Linux and manage services, packages, and other things inside the installed system *(Usually from a live medium or from an existing installation)*. * archinstall [discord](https://discord.gg/aDeMffrxNg) server * archinstall [#archinstall:matrix.org](https://matrix.to/#/#archinstall:matrix.org) Matrix channel diff --git a/archinstall/__init__.py b/archinstall/__init__.py index ebba9510f7..371dcdf4d4 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -13,6 +13,7 @@ from archinstall.lib.packages.packages import check_package_upgrade from archinstall.tui.ui.components import tui as ttui +from .lib.general import running_from_host from .lib.hardware import SysInfo from .lib.output import FormattedOutput, debug, error, info, log, warn from .lib.pacman import Pacman @@ -110,6 +111,12 @@ def main() -> int: info(new_version) time.sleep(3) + if running_from_host(): + # log which mode we are using + info('Running from host (H2T Mode)') + else: + info('Running from ISO (Live Mode)') + script = arch_config_handler.get_script() mod_name = f'archinstall.scripts.{script}' diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 66d8ee03dd..8c5ce026f5 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -27,6 +27,19 @@ _VT100_ESCAPE_REGEX_BYTES = _VT100_ESCAPE_REGEX.encode() +def running_from_host() -> bool: + """ + Check if running from an installed system. + + Returns True if running from installed system (host mode) for host-to-target install. + Returns False if /run/archiso exists (ISO mode). + """ + is_host = not Path('/run/archiso').exists() + mode = 'Host mode (H2T)' if is_host else 'ISO mode (Live)' + debug(f'Installation mode detected: {mode} (/run/archiso {"does not exist" if is_host else "exists"})') + return is_host + + def generate_password(length: int = 64) -> str: haystack = string.printable # digits, ascii_letters, punctuation (!"#$[] etc) and whitespace return ''.join(secrets.choice(haystack) for _ in range(length)) @@ -46,7 +59,7 @@ def clear_vt100_escape_codes_from_str(data: str) -> str: return re.sub(_VT100_ESCAPE_REGEX, '', data) -def jsonify(obj: object, safe: bool = True) -> object: +def jsonify(obj: Any, safe: bool = True) -> Any: """ Converts objects into json.dumps() compatible nested dictionaries. Setting safe to True skips dictionary keys starting with a bang (!) @@ -84,7 +97,7 @@ class JSON(json.JSONEncoder, json.JSONDecoder): """ @override - def encode(self, o: object) -> str: + def encode(self, o: Any) -> str: return super().encode(jsonify(o)) @@ -94,7 +107,7 @@ class UNSAFE_JSON(json.JSONEncoder, json.JSONDecoder): """ @override - def encode(self, o: object) -> str: + def encode(self, o: Any) -> str: return super().encode(jsonify(o, safe=False)) diff --git a/archinstall/lib/locale/utils.py b/archinstall/lib/locale/utils.py index e1e25a0fc4..f89b865d31 100644 --- a/archinstall/lib/locale/utils.py +++ b/archinstall/lib/locale/utils.py @@ -1,5 +1,5 @@ from ..exceptions import ServiceException, SysCallError -from ..general import SysCommand +from ..general import SysCommand, running_from_host from ..output import error @@ -79,6 +79,11 @@ def get_kb_layout() -> str: def set_kb_layout(locale: str) -> bool: + if running_from_host(): + # Skip when running from host - no need to change host keymap + # The target installation keymap is set via installer.set_keyboard_language() + return True + if len(locale.strip()): if not verify_keyboard_layout(locale): error(f'Invalid keyboard locale specified: {locale}') From 6811121b9f308f1d48cebabf6360aa002d1502b5 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Mon, 8 Dec 2025 14:09:21 +0100 Subject: [PATCH 2/4] match existing style --- archinstall/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/archinstall/__init__.py b/archinstall/__init__.py index 371dcdf4d4..146a4761d2 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -113,9 +113,9 @@ def main() -> int: if running_from_host(): # log which mode we are using - info('Running from host (H2T Mode)') + info('Running from host (H2T Mode)...') else: - info('Running from ISO (Live Mode)') + info('Running from ISO (Live Mode)...') script = arch_config_handler.get_script() From e888cc95fb5a0f47f42a35a5372c6a79640260cb Mon Sep 17 00:00:00 2001 From: h8d13 Date: Mon, 8 Dec 2025 14:32:43 +0100 Subject: [PATCH 3/4] rem debug --- archinstall/lib/general.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 8c5ce026f5..057f94a220 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -35,8 +35,6 @@ def running_from_host() -> bool: Returns False if /run/archiso exists (ISO mode). """ is_host = not Path('/run/archiso').exists() - mode = 'Host mode (H2T)' if is_host else 'ISO mode (Live)' - debug(f'Installation mode detected: {mode} (/run/archiso {"does not exist" if is_host else "exists"})') return is_host From bf7ce91a7ca2ea65326d15e2e4e0d165a6d21230 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Mon, 15 Dec 2025 22:48:49 +0100 Subject: [PATCH 4/4] info -> debug --- archinstall/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/archinstall/__init__.py b/archinstall/__init__.py index 146a4761d2..f96c840980 100644 --- a/archinstall/__init__.py +++ b/archinstall/__init__.py @@ -113,9 +113,9 @@ def main() -> int: if running_from_host(): # log which mode we are using - info('Running from host (H2T Mode)...') + debug('Running from Host (H2T Mode)...') else: - info('Running from ISO (Live Mode)...') + debug('Running from ISO (Live Mode)...') script = arch_config_handler.get_script()