We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[2.3.0-RC1] --config file parsing regressions
When specifying a config file generated from /var/log/archinstall/user_configuration.json to attempt to retry the same installation, I ran into hardware related import errors until I dropped gfx_driver from the config file, and then once I did, the installer was prompting me to re-enter a lot of entries (not imported properly), even though they seemed to be in the configuration file.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `archinstall/__init__.py`
Content:
```
1 """Arch Linux installer - guided, templates etc."""
2 import urllib.error
3 import urllib.parse
4 import urllib.request
5 from argparse import ArgumentParser
6 
7 from .lib.disk import *
8 from .lib.exceptions import *
9 from .lib.general import *
10 from .lib.hardware import *
11 from .lib.installer import __packages__, Installer
12 from .lib.locale_helpers import *
13 from .lib.luks import *
14 from .lib.mirrors import *
15 from .lib.networking import *
16 from .lib.output import *
17 from .lib.packages import *
18 from .lib.profiles import *
19 from .lib.services import *
20 from .lib.storage import *
21 from .lib.systemd import *
22 from .lib.user_interaction import *
23 
24 parser = ArgumentParser()
25 
26 __version__ = "2.3.0.dev0"
27 storage['__version__'] = __version__
28 
29 
30 def initialize_arguments():
31 	config = {}
32 	parser.add_argument("--config", nargs="?", help="JSON configuration file or URL")
33 	parser.add_argument("--creds", nargs="?", help="JSON credentials configuration file")
34 	parser.add_argument("--silent", action="store_true",
35 						help="WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored")
36 	parser.add_argument("--dry-run", action="store_true",
37 						help="Generates a configuration file and then exits instead of performing an installation")
38 	parser.add_argument("--script", default="guided", nargs="?", help="Script to run for installation", type=str)
39 	args, unknowns = parser.parse_known_args()
40 	if args.config is not None:
41 		try:
42 			# First, let's check if this is a URL scheme instead of a filename
43 			parsed_url = urllib.parse.urlparse(args.config)
44 
45 			if not parsed_url.scheme:  # The Profile was not a direct match on a remote URL, it must be a local file.
46 				with open(args.config) as file:
47 					config = json.load(file)
48 			else:  # Attempt to load the configuration from the URL.
49 				with urllib.request.urlopen(urllib.request.Request(args.config, headers={'User-Agent': 'ArchInstall'})) as response:
50 					config = json.loads(response.read())
51 		except Exception as e:
52 			print(e)
53 		if args.creds is not None:
54 			with open(args.creds) as file:
55 				config.update(json.load(file))
56 		# Installation can't be silent if config is not passed
57 		config["silent"] = args.silent
58 	for arg in unknowns:
59 		if '--' == arg[:2]:
60 			if '=' in arg:
61 				key, val = [x.strip() for x in arg[2:].split('=', 1)]
62 			else:
63 				key, val = arg[2:], True
64 			config[key] = val
65 	config["script"] = args.script
66 	if args.dry_run is not None:
67 		config["dry-run"] = args.dry_run
68 	return config
69 
70 
71 arguments = initialize_arguments()
72 storage['arguments'] = arguments
73 if arguments.get('debug'):
74 	log(f"Warning: --debug mode will write certain credentials to {storage['LOG_PATH']}/{storage['LOG_FILE']}!", fg="red", level=logging.WARNING)
75 if arguments.get('mount-point'):
76 	storage['MOUNT_POINT'] = arguments['mount-point']
77 
78 from .lib.plugins import plugins, load_plugin # This initiates the plugin loading ceremony
79 
80 if arguments.get('plugin', None):
81 	load_plugin(arguments['plugin'])
82 
83 # TODO: Learn the dark arts of argparse... (I summon thee dark spawn of cPython)
84 
85 
86 def run_as_a_module():
87 	"""
88 	Since we're running this as a 'python -m archinstall' module OR
89 	a nuitka3 compiled version of the project.
90 	This function and the file __main__ acts as a entry point.
91 	"""
92 
93 	# Add another path for finding profiles, so that list_profiles() in Script() can find guided.py, unattended.py etc.
94 	storage['PROFILE_PATH'].append(os.path.abspath(f'{os.path.dirname(__file__)}/examples'))
95 	try:
96 		script = Script(arguments.get('script', None))
97 	except ProfileNotFound as err:
98 		print(f"Couldn't find file: {err}")
99 		sys.exit(1)
100 
101 	os.chdir(os.path.abspath(os.path.dirname(__file__)))
102 
103 	# Remove the example directory from the PROFILE_PATH, to avoid guided.py etc shows up in user input questions.
104 	storage['PROFILE_PATH'].pop()
105 	script.execute()
106
```

Path: `archinstall/lib/systemd.py`
Content:
```
1 import logging
2 import time
3 from .exceptions import SysCallError
4 from .general import SysCommand, SysCommandWorker, locate_binary
5 from .installer import Installer
6 from .output import log
7 from .storage import storage
8 
9 
10 class Ini:
11 	def __init__(self, *args, **kwargs):
12 		"""
13 		Limited INI handler for now.
14 		Supports multiple keywords through dictionary list items.
15 		"""
16 		self.kwargs = kwargs
17 
18 	def __str__(self):
19 		result = ''
20 		first_row_done = False
21 		for top_level in self.kwargs:
22 			if first_row_done:
23 				result += f"\n[{top_level}]\n"
24 			else:
25 				result += f"[{top_level}]\n"
26 				first_row_done = True
27 
28 			for key, val in self.kwargs[top_level].items():
29 				if type(val) == list:
30 					for item in val:
31 						result += f"{key}={item}\n"
32 				else:
33 					result += f"{key}={val}\n"
34 
35 		return result
36 
37 
38 class Systemd(Ini):
39 	"""
40 	Placeholder class to do systemd specific setups.
41 	"""
42 
43 
44 class Networkd(Systemd):
45 	"""
46 	Placeholder class to do systemd-network specific setups.
47 	"""
48 
49 
50 class Boot:
51 	def __init__(self, installation: Installer):
52 		self.instance = installation
53 		self.container_name = 'archinstall'
54 		self.session = None
55 		self.ready = False
56 
57 	def __enter__(self):
58 		if (existing_session := storage.get('active_boot', None)) and existing_session.instance != self.instance:
59 			raise KeyError("Archinstall only supports booting up one instance, and a active session is already active and it is not this one.")
60 
61 		if existing_session:
62 			self.session = existing_session.session
63 			self.ready = existing_session.ready
64 		else:
65 			self.session = SysCommandWorker([
66 				'/usr/bin/systemd-nspawn',
67 				'-D', self.instance.target,
68 				'--timezone=off',
69 				'-b',
70 				'--no-pager',
71 				'--machine', self.container_name
72 			])
73 			# '-P' or --console=pipe  could help us not having to do a bunch of os.write() calls, but instead use pipes (stdin, stdout and stderr) as usual.
74 
75 		if not self.ready:
76 			while self.session.is_alive():
77 				if b' login:' in self.session:
78 					self.ready = True
79 					break
80 
81 		storage['active_boot'] = self
82 		return self
83 
84 	def __exit__(self, *args, **kwargs):
85 		# b''.join(sys_command('sync')) # No need to, since the underlying fs() object will call sync.
86 		# TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager
87 
88 		if len(args) >= 2 and args[1]:
89 			log(args[1], level=logging.ERROR, fg='red')
90 			log(f"The error above occured in a temporary boot-up of the installation {self.instance}", level=logging.ERROR, fg="red")
91 
92 		shutdown = SysCommand(f'systemd-run --machine={self.container_name} --pty /bin/bash -c "shutdown now"')
93 		while self.session.is_alive():
94 			time.sleep(0.25)
95 
96 		if shutdown.exit_code == 0:
97 			storage['active_boot'] = None
98 		else:
99 			raise SysCallError(f"Could not shut down temporary boot of {self.instance}", level=logging.ERROR, fg="red")
100 
101 	def __iter__(self):
102 		if self.session:
103 			for value in self.session:
104 				yield value
105 
106 	def __contains__(self, key: bytes):
107 		if self.session is None:
108 			return False
109 
110 		return key in self.session
111 
112 	def is_alive(self):
113 		if self.session is None:
114 			return False
115 
116 		return self.session.is_alive()
117 
118 	def SysCommand(self, cmd: list, *args, **kwargs):
119 		if cmd[0][0] != '/' and cmd[0][:2] != './':
120 			# This check is also done in SysCommand & SysCommandWorker.
121 			# However, that check is done for `machinectl` and not for our chroot command.
122 			# So this wrapper for SysCommand will do this additionally.
123 
124 			cmd[0] = locate_binary(cmd[0])
125 
126 		return SysCommand(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs)
127 
128 	def SysCommandWorker(self, cmd: list, *args, **kwargs):
129 		if cmd[0][0] != '/' and cmd[0][:2] != './':
130 			cmd[0] = locate_binary(cmd[0])
131 
132 		return SysCommandWorker(["systemd-run", f"--machine={self.container_name}", "--pty", *cmd], *args, **kwargs)
133
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/archinstall/__init__.py b/archinstall/__init__.py
--- a/archinstall/__init__.py
+++ b/archinstall/__init__.py
@@ -49,12 +49,15 @@
 				with urllib.request.urlopen(urllib.request.Request(args.config, headers={'User-Agent': 'ArchInstall'})) as response:
 					config = json.loads(response.read())
 		except Exception as e:
-			print(e)
+			raise ValueError(f"Could not load --config because: {e}")
+	
 		if args.creds is not None:
 			with open(args.creds) as file:
 				config.update(json.load(file))
+	
 		# Installation can't be silent if config is not passed
 		config["silent"] = args.silent
+	
 	for arg in unknowns:
 		if '--' == arg[:2]:
 			if '=' in arg:
@@ -62,9 +65,12 @@
 			else:
 				key, val = arg[2:], True
 			config[key] = val
+	
 	config["script"] = args.script
+	
 	if args.dry_run is not None:
 		config["dry-run"] = args.dry_run
+
 	return config
 
 
diff --git a/archinstall/lib/systemd.py b/archinstall/lib/systemd.py
--- a/archinstall/lib/systemd.py
+++ b/archinstall/lib/systemd.py
@@ -96,7 +96,7 @@
 		if shutdown.exit_code == 0:
 			storage['active_boot'] = None
 		else:
-			raise SysCallError(f"Could not shut down temporary boot of {self.instance}", level=logging.ERROR, fg="red")
+			raise SysCallError(f"Could not shut down temporary boot of {self.instance}: {shutdown}", exit_code=shutdown.exit_code)
 
 	def __iter__(self):
 		if self.session:

{"golden_diff": "diff --git a/archinstall/__init__.py b/archinstall/__init__.py\n--- a/archinstall/__init__.py\n+++ b/archinstall/__init__.py\n@@ -49,12 +49,15 @@\n \t\t\t\twith urllib.request.urlopen(urllib.request.Request(args.config, headers={'User-Agent': 'ArchInstall'})) as response:\n \t\t\t\t\tconfig = json.loads(response.read())\n \t\texcept Exception as e:\n-\t\t\tprint(e)\n+\t\t\traise ValueError(f\"Could not load --config because: {e}\")\n+\t\n \t\tif args.creds is not None:\n \t\t\twith open(args.creds) as file:\n \t\t\t\tconfig.update(json.load(file))\n+\t\n \t\t# Installation can't be silent if config is not passed\n \t\tconfig[\"silent\"] = args.silent\n+\t\n \tfor arg in unknowns:\n \t\tif '--' == arg[:2]:\n \t\t\tif '=' in arg:\n@@ -62,9 +65,12 @@\n \t\t\telse:\n \t\t\t\tkey, val = arg[2:], True\n \t\t\tconfig[key] = val\n+\t\n \tconfig[\"script\"] = args.script\n+\t\n \tif args.dry_run is not None:\n \t\tconfig[\"dry-run\"] = args.dry_run\n+\n \treturn config\n \n \ndiff --git a/archinstall/lib/systemd.py b/archinstall/lib/systemd.py\n--- a/archinstall/lib/systemd.py\n+++ b/archinstall/lib/systemd.py\n@@ -96,7 +96,7 @@\n \t\tif shutdown.exit_code == 0:\n \t\t\tstorage['active_boot'] = None\n \t\telse:\n-\t\t\traise SysCallError(f\"Could not shut down temporary boot of {self.instance}\", level=logging.ERROR, fg=\"red\")\n+\t\t\traise SysCallError(f\"Could not shut down temporary boot of {self.instance}: {shutdown}\", exit_code=shutdown.exit_code)\n \n \tdef __iter__(self):\n \t\tif self.session:\n", "issue": "[2.3.0-RC1] --config file parsing regressions\nWhen specifying a config file generated from /var/log/archinstall/user_configuration.json to attempt to retry the same installation, I ran into hardware related import errors until I dropped gfx_driver from the config file, and then once I did, the installer was prompting me to re-enter a lot of entries (not imported properly), even though they seemed to be in the configuration file.\n", "before_files": [{"content": "\"\"\"Arch Linux installer - guided, templates etc.\"\"\"\nimport urllib.error\nimport urllib.parse\nimport urllib.request\nfrom argparse import ArgumentParser\n\nfrom .lib.disk import *\nfrom .lib.exceptions import *\nfrom .lib.general import *\nfrom .lib.hardware import *\nfrom .lib.installer import __packages__, Installer\nfrom .lib.locale_helpers import *\nfrom .lib.luks import *\nfrom .lib.mirrors import *\nfrom .lib.networking import *\nfrom .lib.output import *\nfrom .lib.packages import *\nfrom .lib.profiles import *\nfrom .lib.services import *\nfrom .lib.storage import *\nfrom .lib.systemd import *\nfrom .lib.user_interaction import *\n\nparser = ArgumentParser()\n\n__version__ = \"2.3.0.dev0\"\nstorage['__version__'] = __version__\n\n\ndef initialize_arguments():\n\tconfig = {}\n\tparser.add_argument(\"--config\", nargs=\"?\", help=\"JSON configuration file or URL\")\n\tparser.add_argument(\"--creds\", nargs=\"?\", help=\"JSON credentials configuration file\")\n\tparser.add_argument(\"--silent\", action=\"store_true\",\n\t\t\t\t\t\thelp=\"WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored\")\n\tparser.add_argument(\"--dry-run\", action=\"store_true\",\n\t\t\t\t\t\thelp=\"Generates a configuration file and then exits instead of performing an installation\")\n\tparser.add_argument(\"--script\", default=\"guided\", nargs=\"?\", help=\"Script to run for installation\", type=str)\n\targs, unknowns = parser.parse_known_args()\n\tif args.config is not None:\n\t\ttry:\n\t\t\t# First, let's check if this is a URL scheme instead of a filename\n\t\t\tparsed_url = urllib.parse.urlparse(args.config)\n\n\t\t\tif not parsed_url.scheme:  # The Profile was not a direct match on a remote URL, it must be a local file.\n\t\t\t\twith open(args.config) as file:\n\t\t\t\t\tconfig = json.load(file)\n\t\t\telse:  # Attempt to load the configuration from the URL.\n\t\t\t\twith urllib.request.urlopen(urllib.request.Request(args.config, headers={'User-Agent': 'ArchInstall'})) as response:\n\t\t\t\t\tconfig = json.loads(response.read())\n\t\texcept Exception as e:\n\t\t\tprint(e)\n\t\tif args.creds is not None:\n\t\t\twith open(args.creds) as file:\n\t\t\t\tconfig.update(json.load(file))\n\t\t# Installation can't be silent if config is not passed\n\t\tconfig[\"silent\"] = args.silent\n\tfor arg in unknowns:\n\t\tif '--' == arg[:2]:\n\t\t\tif '=' in arg:\n\t\t\t\tkey, val = [x.strip() for x in arg[2:].split('=', 1)]\n\t\t\telse:\n\t\t\t\tkey, val = arg[2:], True\n\t\t\tconfig[key] = val\n\tconfig[\"script\"] = args.script\n\tif args.dry_run is not None:\n\t\tconfig[\"dry-run\"] = args.dry_run\n\treturn config\n\n\narguments = initialize_arguments()\nstorage['arguments'] = arguments\nif arguments.get('debug'):\n\tlog(f\"Warning: --debug mode will write certain credentials to {storage['LOG_PATH']}/{storage['LOG_FILE']}!\", fg=\"red\", level=logging.WARNING)\nif arguments.get('mount-point'):\n\tstorage['MOUNT_POINT'] = arguments['mount-point']\n\nfrom .lib.plugins import plugins, load_plugin # This initiates the plugin loading ceremony\n\nif arguments.get('plugin', None):\n\tload_plugin(arguments['plugin'])\n\n# TODO: Learn the dark arts of argparse... (I summon thee dark spawn of cPython)\n\n\ndef run_as_a_module():\n\t\"\"\"\n\tSince we're running this as a 'python -m archinstall' module OR\n\ta nuitka3 compiled version of the project.\n\tThis function and the file __main__ acts as a entry point.\n\t\"\"\"\n\n\t# Add another path for finding profiles, so that list_profiles() in Script() can find guided.py, unattended.py etc.\n\tstorage['PROFILE_PATH'].append(os.path.abspath(f'{os.path.dirname(__file__)}/examples'))\n\ttry:\n\t\tscript = Script(arguments.get('script', None))\n\texcept ProfileNotFound as err:\n\t\tprint(f\"Couldn't find file: {err}\")\n\t\tsys.exit(1)\n\n\tos.chdir(os.path.abspath(os.path.dirname(__file__)))\n\n\t# Remove the example directory from the PROFILE_PATH, to avoid guided.py etc shows up in user input questions.\n\tstorage['PROFILE_PATH'].pop()\n\tscript.execute()\n", "path": "archinstall/__init__.py"}, {"content": "import logging\nimport time\nfrom .exceptions import SysCallError\nfrom .general import SysCommand, SysCommandWorker, locate_binary\nfrom .installer import Installer\nfrom .output import log\nfrom .storage import storage\n\n\nclass Ini:\n\tdef __init__(self, *args, **kwargs):\n\t\t\"\"\"\n\t\tLimited INI handler for now.\n\t\tSupports multiple keywords through dictionary list items.\n\t\t\"\"\"\n\t\tself.kwargs = kwargs\n\n\tdef __str__(self):\n\t\tresult = ''\n\t\tfirst_row_done = False\n\t\tfor top_level in self.kwargs:\n\t\t\tif first_row_done:\n\t\t\t\tresult += f\"\\n[{top_level}]\\n\"\n\t\t\telse:\n\t\t\t\tresult += f\"[{top_level}]\\n\"\n\t\t\t\tfirst_row_done = True\n\n\t\t\tfor key, val in self.kwargs[top_level].items():\n\t\t\t\tif type(val) == list:\n\t\t\t\t\tfor item in val:\n\t\t\t\t\t\tresult += f\"{key}={item}\\n\"\n\t\t\t\telse:\n\t\t\t\t\tresult += f\"{key}={val}\\n\"\n\n\t\treturn result\n\n\nclass Systemd(Ini):\n\t\"\"\"\n\tPlaceholder class to do systemd specific setups.\n\t\"\"\"\n\n\nclass Networkd(Systemd):\n\t\"\"\"\n\tPlaceholder class to do systemd-network specific setups.\n\t\"\"\"\n\n\nclass Boot:\n\tdef __init__(self, installation: Installer):\n\t\tself.instance = installation\n\t\tself.container_name = 'archinstall'\n\t\tself.session = None\n\t\tself.ready = False\n\n\tdef __enter__(self):\n\t\tif (existing_session := storage.get('active_boot', None)) and existing_session.instance != self.instance:\n\t\t\traise KeyError(\"Archinstall only supports booting up one instance, and a active session is already active and it is not this one.\")\n\n\t\tif existing_session:\n\t\t\tself.session = existing_session.session\n\t\t\tself.ready = existing_session.ready\n\t\telse:\n\t\t\tself.session = SysCommandWorker([\n\t\t\t\t'/usr/bin/systemd-nspawn',\n\t\t\t\t'-D', self.instance.target,\n\t\t\t\t'--timezone=off',\n\t\t\t\t'-b',\n\t\t\t\t'--no-pager',\n\t\t\t\t'--machine', self.container_name\n\t\t\t])\n\t\t\t# '-P' or --console=pipe  could help us not having to do a bunch of os.write() calls, but instead use pipes (stdin, stdout and stderr) as usual.\n\n\t\tif not self.ready:\n\t\t\twhile self.session.is_alive():\n\t\t\t\tif b' login:' in self.session:\n\t\t\t\t\tself.ready = True\n\t\t\t\t\tbreak\n\n\t\tstorage['active_boot'] = self\n\t\treturn self\n\n\tdef __exit__(self, *args, **kwargs):\n\t\t# b''.join(sys_command('sync')) # No need to, since the underlying fs() object will call sync.\n\t\t# TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager\n\n\t\tif len(args) >= 2 and args[1]:\n\t\t\tlog(args[1], level=logging.ERROR, fg='red')\n\t\t\tlog(f\"The error above occured in a temporary boot-up of the installation {self.instance}\", level=logging.ERROR, fg=\"red\")\n\n\t\tshutdown = SysCommand(f'systemd-run --machine={self.container_name} --pty /bin/bash -c \"shutdown now\"')\n\t\twhile self.session.is_alive():\n\t\t\ttime.sleep(0.25)\n\n\t\tif shutdown.exit_code == 0:\n\t\t\tstorage['active_boot'] = None\n\t\telse:\n\t\t\traise SysCallError(f\"Could not shut down temporary boot of {self.instance}\", level=logging.ERROR, fg=\"red\")\n\n\tdef __iter__(self):\n\t\tif self.session:\n\t\t\tfor value in self.session:\n\t\t\t\tyield value\n\n\tdef __contains__(self, key: bytes):\n\t\tif self.session is None:\n\t\t\treturn False\n\n\t\treturn key in self.session\n\n\tdef is_alive(self):\n\t\tif self.session is None:\n\t\t\treturn False\n\n\t\treturn self.session.is_alive()\n\n\tdef SysCommand(self, cmd: list, *args, **kwargs):\n\t\tif cmd[0][0] != '/' and cmd[0][:2] != './':\n\t\t\t# This check is also done in SysCommand & SysCommandWorker.\n\t\t\t# However, that check is done for `machinectl` and not for our chroot command.\n\t\t\t# So this wrapper for SysCommand will do this additionally.\n\n\t\t\tcmd[0] = locate_binary(cmd[0])\n\n\t\treturn SysCommand([\"systemd-run\", f\"--machine={self.container_name}\", \"--pty\", *cmd], *args, **kwargs)\n\n\tdef SysCommandWorker(self, cmd: list, *args, **kwargs):\n\t\tif cmd[0][0] != '/' and cmd[0][:2] != './':\n\t\t\tcmd[0] = locate_binary(cmd[0])\n\n\t\treturn SysCommandWorker([\"systemd-run\", f\"--machine={self.container_name}\", \"--pty\", *cmd], *args, **kwargs)\n", "path": "archinstall/lib/systemd.py"}], "after_files": [{"content": "\"\"\"Arch Linux installer - guided, templates etc.\"\"\"\nimport urllib.error\nimport urllib.parse\nimport urllib.request\nfrom argparse import ArgumentParser\n\nfrom .lib.disk import *\nfrom .lib.exceptions import *\nfrom .lib.general import *\nfrom .lib.hardware import *\nfrom .lib.installer import __packages__, Installer\nfrom .lib.locale_helpers import *\nfrom .lib.luks import *\nfrom .lib.mirrors import *\nfrom .lib.networking import *\nfrom .lib.output import *\nfrom .lib.packages import *\nfrom .lib.profiles import *\nfrom .lib.services import *\nfrom .lib.storage import *\nfrom .lib.systemd import *\nfrom .lib.user_interaction import *\n\nparser = ArgumentParser()\n\n__version__ = \"2.3.0.dev0\"\nstorage['__version__'] = __version__\n\n\ndef initialize_arguments():\n\tconfig = {}\n\tparser.add_argument(\"--config\", nargs=\"?\", help=\"JSON configuration file or URL\")\n\tparser.add_argument(\"--creds\", nargs=\"?\", help=\"JSON credentials configuration file\")\n\tparser.add_argument(\"--silent\", action=\"store_true\",\n\t\t\t\t\t\thelp=\"WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored\")\n\tparser.add_argument(\"--dry-run\", action=\"store_true\",\n\t\t\t\t\t\thelp=\"Generates a configuration file and then exits instead of performing an installation\")\n\tparser.add_argument(\"--script\", default=\"guided\", nargs=\"?\", help=\"Script to run for installation\", type=str)\n\targs, unknowns = parser.parse_known_args()\n\tif args.config is not None:\n\t\ttry:\n\t\t\t# First, let's check if this is a URL scheme instead of a filename\n\t\t\tparsed_url = urllib.parse.urlparse(args.config)\n\n\t\t\tif not parsed_url.scheme:  # The Profile was not a direct match on a remote URL, it must be a local file.\n\t\t\t\twith open(args.config) as file:\n\t\t\t\t\tconfig = json.load(file)\n\t\t\telse:  # Attempt to load the configuration from the URL.\n\t\t\t\twith urllib.request.urlopen(urllib.request.Request(args.config, headers={'User-Agent': 'ArchInstall'})) as response:\n\t\t\t\t\tconfig = json.loads(response.read())\n\t\texcept Exception as e:\n\t\t\traise ValueError(f\"Could not load --config because: {e}\")\n\t\n\t\tif args.creds is not None:\n\t\t\twith open(args.creds) as file:\n\t\t\t\tconfig.update(json.load(file))\n\t\n\t\t# Installation can't be silent if config is not passed\n\t\tconfig[\"silent\"] = args.silent\n\t\n\tfor arg in unknowns:\n\t\tif '--' == arg[:2]:\n\t\t\tif '=' in arg:\n\t\t\t\tkey, val = [x.strip() for x in arg[2:].split('=', 1)]\n\t\t\telse:\n\t\t\t\tkey, val = arg[2:], True\n\t\t\tconfig[key] = val\n\t\n\tconfig[\"script\"] = args.script\n\t\n\tif args.dry_run is not None:\n\t\tconfig[\"dry-run\"] = args.dry_run\n\n\treturn config\n\n\narguments = initialize_arguments()\nstorage['arguments'] = arguments\nif arguments.get('debug'):\n\tlog(f\"Warning: --debug mode will write certain credentials to {storage['LOG_PATH']}/{storage['LOG_FILE']}!\", fg=\"red\", level=logging.WARNING)\nif arguments.get('mount-point'):\n\tstorage['MOUNT_POINT'] = arguments['mount-point']\n\nfrom .lib.plugins import plugins, load_plugin # This initiates the plugin loading ceremony\n\nif arguments.get('plugin', None):\n\tload_plugin(arguments['plugin'])\n\n# TODO: Learn the dark arts of argparse... (I summon thee dark spawn of cPython)\n\n\ndef run_as_a_module():\n\t\"\"\"\n\tSince we're running this as a 'python -m archinstall' module OR\n\ta nuitka3 compiled version of the project.\n\tThis function and the file __main__ acts as a entry point.\n\t\"\"\"\n\n\t# Add another path for finding profiles, so that list_profiles() in Script() can find guided.py, unattended.py etc.\n\tstorage['PROFILE_PATH'].append(os.path.abspath(f'{os.path.dirname(__file__)}/examples'))\n\ttry:\n\t\tscript = Script(arguments.get('script', None))\n\texcept ProfileNotFound as err:\n\t\tprint(f\"Couldn't find file: {err}\")\n\t\tsys.exit(1)\n\n\tos.chdir(os.path.abspath(os.path.dirname(__file__)))\n\n\t# Remove the example directory from the PROFILE_PATH, to avoid guided.py etc shows up in user input questions.\n\tstorage['PROFILE_PATH'].pop()\n\tscript.execute()\n", "path": "archinstall/__init__.py"}, {"content": "import logging\nimport time\nfrom .exceptions import SysCallError\nfrom .general import SysCommand, SysCommandWorker, locate_binary\nfrom .installer import Installer\nfrom .output import log\nfrom .storage import storage\n\n\nclass Ini:\n\tdef __init__(self, *args, **kwargs):\n\t\t\"\"\"\n\t\tLimited INI handler for now.\n\t\tSupports multiple keywords through dictionary list items.\n\t\t\"\"\"\n\t\tself.kwargs = kwargs\n\n\tdef __str__(self):\n\t\tresult = ''\n\t\tfirst_row_done = False\n\t\tfor top_level in self.kwargs:\n\t\t\tif first_row_done:\n\t\t\t\tresult += f\"\\n[{top_level}]\\n\"\n\t\t\telse:\n\t\t\t\tresult += f\"[{top_level}]\\n\"\n\t\t\t\tfirst_row_done = True\n\n\t\t\tfor key, val in self.kwargs[top_level].items():\n\t\t\t\tif type(val) == list:\n\t\t\t\t\tfor item in val:\n\t\t\t\t\t\tresult += f\"{key}={item}\\n\"\n\t\t\t\telse:\n\t\t\t\t\tresult += f\"{key}={val}\\n\"\n\n\t\treturn result\n\n\nclass Systemd(Ini):\n\t\"\"\"\n\tPlaceholder class to do systemd specific setups.\n\t\"\"\"\n\n\nclass Networkd(Systemd):\n\t\"\"\"\n\tPlaceholder class to do systemd-network specific setups.\n\t\"\"\"\n\n\nclass Boot:\n\tdef __init__(self, installation: Installer):\n\t\tself.instance = installation\n\t\tself.container_name = 'archinstall'\n\t\tself.session = None\n\t\tself.ready = False\n\n\tdef __enter__(self):\n\t\tif (existing_session := storage.get('active_boot', None)) and existing_session.instance != self.instance:\n\t\t\traise KeyError(\"Archinstall only supports booting up one instance, and a active session is already active and it is not this one.\")\n\n\t\tif existing_session:\n\t\t\tself.session = existing_session.session\n\t\t\tself.ready = existing_session.ready\n\t\telse:\n\t\t\tself.session = SysCommandWorker([\n\t\t\t\t'/usr/bin/systemd-nspawn',\n\t\t\t\t'-D', self.instance.target,\n\t\t\t\t'--timezone=off',\n\t\t\t\t'-b',\n\t\t\t\t'--no-pager',\n\t\t\t\t'--machine', self.container_name\n\t\t\t])\n\t\t\t# '-P' or --console=pipe  could help us not having to do a bunch of os.write() calls, but instead use pipes (stdin, stdout and stderr) as usual.\n\n\t\tif not self.ready:\n\t\t\twhile self.session.is_alive():\n\t\t\t\tif b' login:' in self.session:\n\t\t\t\t\tself.ready = True\n\t\t\t\t\tbreak\n\n\t\tstorage['active_boot'] = self\n\t\treturn self\n\n\tdef __exit__(self, *args, **kwargs):\n\t\t# b''.join(sys_command('sync')) # No need to, since the underlying fs() object will call sync.\n\t\t# TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager\n\n\t\tif len(args) >= 2 and args[1]:\n\t\t\tlog(args[1], level=logging.ERROR, fg='red')\n\t\t\tlog(f\"The error above occured in a temporary boot-up of the installation {self.instance}\", level=logging.ERROR, fg=\"red\")\n\n\t\tshutdown = SysCommand(f'systemd-run --machine={self.container_name} --pty /bin/bash -c \"shutdown now\"')\n\t\twhile self.session.is_alive():\n\t\t\ttime.sleep(0.25)\n\n\t\tif shutdown.exit_code == 0:\n\t\t\tstorage['active_boot'] = None\n\t\telse:\n\t\t\traise SysCallError(f\"Could not shut down temporary boot of {self.instance}: {shutdown}\", exit_code=shutdown.exit_code)\n\n\tdef __iter__(self):\n\t\tif self.session:\n\t\t\tfor value in self.session:\n\t\t\t\tyield value\n\n\tdef __contains__(self, key: bytes):\n\t\tif self.session is None:\n\t\t\treturn False\n\n\t\treturn key in self.session\n\n\tdef is_alive(self):\n\t\tif self.session is None:\n\t\t\treturn False\n\n\t\treturn self.session.is_alive()\n\n\tdef SysCommand(self, cmd: list, *args, **kwargs):\n\t\tif cmd[0][0] != '/' and cmd[0][:2] != './':\n\t\t\t# This check is also done in SysCommand & SysCommandWorker.\n\t\t\t# However, that check is done for `machinectl` and not for our chroot command.\n\t\t\t# So this wrapper for SysCommand will do this additionally.\n\n\t\t\tcmd[0] = locate_binary(cmd[0])\n\n\t\treturn SysCommand([\"systemd-run\", f\"--machine={self.container_name}\", \"--pty\", *cmd], *args, **kwargs)\n\n\tdef SysCommandWorker(self, cmd: list, *args, **kwargs):\n\t\tif cmd[0][0] != '/' and cmd[0][:2] != './':\n\t\t\tcmd[0] = locate_binary(cmd[0])\n\n\t\treturn SysCommandWorker([\"systemd-run\", f\"--machine={self.container_name}\", \"--pty\", *cmd], *args, **kwargs)\n", "path": "archinstall/lib/systemd.py"}]}