|
1 | 1 | """Arch Linux installer - guided, templates etc.""" |
| 2 | + |
2 | 3 | import curses |
3 | 4 | import importlib |
4 | 5 | import os |
|
9 | 10 | from pathlib import Path |
10 | 11 | from typing import TYPE_CHECKING, Any |
11 | 12 |
|
12 | | -from . import default_profiles |
13 | | -from .lib import disk, exceptions, interactions, locale, luks, mirrors, models, networking, packages, profile |
14 | | -from .lib.boot import Boot |
15 | | -from .lib.configuration import ConfigurationOutput |
16 | | -from .lib.general import ( |
17 | | - JSON, |
18 | | - UNSAFE_JSON, |
19 | | - SysCommand, |
20 | | - SysCommandWorker, |
21 | | - clear_vt100_escape_codes, |
22 | | - generate_password, |
23 | | - json_stream_to_structure, |
24 | | - locate_binary, |
25 | | - run_custom_user_commands, |
26 | | - secret, |
27 | | -) |
28 | | -from .lib.global_menu import GlobalMenu |
29 | | -from .lib.hardware import GfxDriver, SysInfo |
30 | | -from .lib.installer import Installer, accessibility_tools_in_use |
| 13 | +from archinstall.lib.args import arch_config_handler |
| 14 | +from archinstall.lib.disk.utils import disk_layouts |
| 15 | + |
| 16 | +from .lib.hardware import SysInfo |
31 | 17 | from .lib.output import FormattedOutput, debug, error, info, log, warn |
32 | 18 | from .lib.pacman import Pacman |
33 | 19 | from .lib.plugins import load_plugin, plugins |
34 | | -from .lib.storage import storage |
35 | 20 | from .lib.translationhandler import DeferredTranslation, Language, translation_handler |
36 | 21 | from .tui import Tui |
37 | 22 |
|
|
41 | 26 | _: Callable[[str], DeferredTranslation] |
42 | 27 |
|
43 | 28 |
|
44 | | -__version__ = "3.0.2" |
45 | | -storage['__version__'] = __version__ |
46 | | - |
47 | 29 | # add the custom _ as a builtin, it can now be used anywhere in the |
48 | 30 | # project to mark strings as translatable with _('translate me') |
49 | 31 | DeferredTranslation.install() |
|
56 | 38 | debug(f"Graphics devices detected: {SysInfo._graphics_devices().keys()}") |
57 | 39 |
|
58 | 40 | # For support reasons, we'll log the disk layout pre installation to match against post-installation layout |
59 | | -debug(f"Disk states before installing:\n{disk.disk_layouts()}") |
60 | | - |
61 | | -parser = ArgumentParser() |
62 | | - |
63 | | - |
64 | | -def define_arguments() -> None: |
65 | | - """ |
66 | | - Define which explicit arguments do we allow. |
67 | | - Refer to https://docs.python.org/3/library/argparse.html for documentation and |
68 | | - https://docs.python.org/3/howto/argparse.html for a tutorial |
69 | | - Remember that the property/entry name python assigns to the parameters is the first string defined as argument and |
70 | | - dashes inside it '-' are changed to '_' |
71 | | - """ |
72 | | - parser.add_argument("-v", "--version", action="version", version="%(prog)s " + __version__) |
73 | | - parser.add_argument("--config", nargs="?", help="JSON configuration file or URL") |
74 | | - parser.add_argument("--creds", nargs="?", help="JSON credentials configuration file") |
75 | | - parser.add_argument("--silent", action="store_true", |
76 | | - help="WARNING: Disables all prompts for input and confirmation. If no configuration is provided, this is ignored") |
77 | | - parser.add_argument("--dry-run", "--dry_run", action="store_true", |
78 | | - help="Generates a configuration file and then exits instead of performing an installation") |
79 | | - parser.add_argument("--script", default="guided", nargs="?", help="Script to run for installation", type=str) |
80 | | - parser.add_argument("--mount-point", "--mount_point", default=Path("/mnt/archinstall"), nargs="?", type=Path, |
81 | | - help="Define an alternate mount point for installation") |
82 | | - parser.add_argument("--skip-ntp", action="store_true", help="Disables NTP checks during installation", default=False) |
83 | | - parser.add_argument("--debug", action="store_true", default=False, help="Adds debug info into the log") |
84 | | - parser.add_argument("--offline", action="store_true", default=False, |
85 | | - help="Disabled online upstream services such as package search and key-ring auto update.") |
86 | | - parser.add_argument("--no-pkg-lookups", action="store_true", default=False, |
87 | | - help="Disabled package validation specifically prior to starting installation.") |
88 | | - parser.add_argument("--plugin", nargs="?", type=str) |
89 | | - parser.add_argument("--skip-version-check", action="store_true", |
90 | | - help="Skip the version check when running archinstall") |
| 41 | +debug(f"Disk states before installing:\n{disk_layouts()}") |
91 | 42 |
|
92 | 43 |
|
93 | 44 | if 'sphinx' not in sys.modules and 'pylint' not in sys.modules: |
94 | 45 | if '--help' in sys.argv or '-h' in sys.argv: |
95 | | - define_arguments() |
96 | | - parser.print_help() |
| 46 | + arch_config_handler.print_help() |
97 | 47 | exit(0) |
98 | 48 | if os.getuid() != 0: |
99 | 49 | print(_("Archinstall requires root privileges to run. See --help for more.")) |
100 | 50 | exit(1) |
101 | 51 |
|
102 | 52 |
|
103 | | -def parse_unspecified_argument_list(unknowns: list, multiple: bool = False, err: bool = False) -> dict: # type: ignore[type-arg] |
104 | | - """We accept arguments not defined to the parser. (arguments "ad hoc"). |
105 | | - Internally argparse return to us a list of words so we have to parse its contents, manually. |
106 | | - We accept following individual syntax for each argument |
107 | | - --argument value |
108 | | - --argument=value |
109 | | - --argument = value |
110 | | - --argument (boolean as default) |
111 | | - the optional parameters to the function alter a bit its behaviour: |
112 | | - * multiple allows multivalued arguments, each value separated by whitespace. They're returned as a list |
113 | | - * error. If set any non correctly specified argument-value pair to raise an exception. Else, simply notifies the |
114 | | - existence of a problem and continues processing. |
115 | | -
|
116 | | - To a certain extent, multiple and error are incompatible. In fact, the only error this routine can catch, as of now, |
117 | | - is the event argument value value ... |
118 | | - which isn't am error if multiple is specified |
119 | | - """ |
120 | | - tmp_list = [arg for arg in unknowns if arg != "="] # wastes a few bytes, but avoids any collateral effect of the destructive nature of the pop method() |
121 | | - config = {} |
122 | | - key = None |
123 | | - last_key = None |
124 | | - while tmp_list: |
125 | | - element = tmp_list.pop(0) # retrieve an element of the list |
126 | | - |
127 | | - if element.startswith('--'): # is an argument ? |
128 | | - if '=' in element: # uses the arg=value syntax ? |
129 | | - key, value = [x.strip() for x in element[2:].split('=', 1)] |
130 | | - config[key] = value |
131 | | - last_key = key # for multiple handling |
132 | | - key = None # we have the kwy value pair we need |
133 | | - else: |
134 | | - key = element[2:] |
135 | | - config[key] = True # every argument starts its lifecycle as boolean |
136 | | - elif key: |
137 | | - config[key] = element |
138 | | - last_key = key # multiple |
139 | | - key = None |
140 | | - elif multiple and last_key: |
141 | | - if isinstance(config[last_key], str): |
142 | | - config[last_key] = [config[last_key], element] |
143 | | - else: |
144 | | - config[last_key].append(element) |
145 | | - elif err: |
146 | | - raise ValueError(f"Entry {element} is not related to any argument") |
147 | | - else: |
148 | | - print(f" We ignore the entry {element} as it isn't related to any argument") |
149 | | - return config |
150 | | - |
151 | | - |
152 | | -def cleanup_empty_args(args: Namespace | dict) -> dict: # type: ignore[type-arg] |
153 | | - """ |
154 | | - Takes arguments (dictionary or argparse Namespace) and removes any |
155 | | - None values. This ensures clean mergers during dict.update(args) |
156 | | - """ |
157 | | - if type(args) is Namespace: |
158 | | - args = vars(args) |
159 | | - |
160 | | - clean_args = {} |
161 | | - for key, val in args.items(): |
162 | | - if isinstance(val, dict): |
163 | | - val = cleanup_empty_args(val) |
164 | | - |
165 | | - if val is not None: |
166 | | - clean_args[key] = val |
167 | | - |
168 | | - return clean_args |
169 | | - |
170 | | - |
171 | | -def get_arguments() -> dict[str, Any]: |
172 | | - """ The handling of parameters from the command line |
173 | | - Is done on following steps: |
174 | | - 0) we create a dict to store the arguments and their values |
175 | | - 1) preprocess. |
176 | | - We take those arguments which use JSON files, and read them into the argument dict. So each first level entry |
177 | | - becomes an argument on its own right |
178 | | - 2) Load. |
179 | | - We convert the predefined argument list directly into the dict via the vars() function. Non specified arguments |
180 | | - are loaded with value None or false if they are booleans (action="store_true"). The name is chosen according to |
181 | | - argparse conventions. See above (the first text is used as argument name, but underscore substitutes dash). We |
182 | | - then load all the undefined arguments. In this case the names are taken as written. |
183 | | - Important. This way explicit command line arguments take precedence over configuration files. |
184 | | - 3) Amend |
185 | | - Change whatever is needed on the configuration dictionary (it could be done in post_process_arguments but this |
186 | | - ougth to be left to changes anywhere else in the code, not in the arguments dictionary |
187 | | - """ |
188 | | - config: dict[str, Any] = {} |
189 | | - args, unknowns = parser.parse_known_args() |
190 | | - # preprocess the JSON files. |
191 | | - # TODO Expand the url access to the other JSON file arguments ? |
192 | | - if args.config is not None: |
193 | | - if not json_stream_to_structure('--config', args.config, config): |
194 | | - exit(1) |
195 | | - |
196 | | - if args.creds is not None: |
197 | | - if not json_stream_to_structure('--creds', args.creds, config): |
198 | | - exit(1) |
199 | | - |
200 | | - # load the parameters. first the known, then the unknowns |
201 | | - clean_args = cleanup_empty_args(args) |
202 | | - config.update(clean_args) |
203 | | - config.update(parse_unspecified_argument_list(unknowns)) |
204 | | - # amend the parameters (check internal consistency) |
205 | | - # Installation can't be silent if config is not passed |
206 | | - if clean_args.get('config') is None: |
207 | | - config["silent"] = False |
208 | | - else: |
209 | | - config["silent"] = clean_args.get('silent') |
210 | | - |
211 | | - # avoiding a compatibility issue |
212 | | - if 'dry-run' in config: |
213 | | - del config['dry-run'] |
214 | | - |
215 | | - return config |
216 | | - |
217 | | - |
218 | | -def load_config() -> None: |
219 | | - """ |
220 | | - refine and set some arguments. Formerly at the scripts |
221 | | - """ |
222 | | - from .lib.models import NetworkConfiguration |
223 | | - |
224 | | - arguments['locale_config'] = locale.LocaleConfiguration.parse_arg(arguments) |
225 | | - |
226 | | - if (archinstall_lang := arguments.get('archinstall-language', None)) is not None: |
227 | | - arguments['archinstall-language'] = translation_handler.get_language_by_name(archinstall_lang) |
228 | | - |
229 | | - if disk_config := arguments.get('disk_config', {}): |
230 | | - arguments['disk_config'] = disk.DiskLayoutConfiguration.parse_arg(disk_config) |
231 | | - |
232 | | - if profile_config := arguments.get('profile_config', None): |
233 | | - arguments['profile_config'] = profile.ProfileConfiguration.parse_arg(profile_config) |
234 | | - |
235 | | - if mirror_config := arguments.get('mirror_config', None): |
236 | | - arguments['mirror_config'] = mirrors.MirrorConfiguration.parse_args(mirror_config) |
237 | | - |
238 | | - if arguments.get('servers', None) is not None: |
239 | | - storage['_selected_servers'] = arguments.get('servers', None) |
240 | | - |
241 | | - if (net_config := arguments.get('network_config', None)) is not None: |
242 | | - config = NetworkConfiguration.parse_arg(net_config) |
243 | | - arguments['network_config'] = config |
244 | | - |
245 | | - if arguments.get('!users', None) is not None or arguments.get('!superusers', None) is not None: |
246 | | - users = arguments.get('!users', None) |
247 | | - superusers = arguments.get('!superusers', None) |
248 | | - arguments['!users'] = models.User.parse_arguments(users, superusers) |
249 | | - |
250 | | - if arguments.get('bootloader', None) is not None: |
251 | | - arguments['bootloader'] = models.Bootloader.from_arg(arguments['bootloader']) |
252 | | - |
253 | | - if arguments.get('uki') and not arguments['bootloader'].has_uki_support(): |
254 | | - arguments['uki'] = False |
255 | | - |
256 | | - if arguments.get('audio_config', None) is not None: |
257 | | - arguments['audio_config'] = models.AudioConfiguration.parse_arg(arguments['audio_config']) |
258 | | - |
259 | | - if arguments.get('disk_encryption', None) is not None and disk_config is not None: |
260 | | - arguments['disk_encryption'] = disk.DiskEncryption.parse_arg( |
261 | | - arguments['disk_config'], |
262 | | - arguments['disk_encryption'], |
263 | | - arguments.get('encryption_password', '') |
264 | | - ) |
265 | | - |
266 | | - |
267 | | -def post_process_arguments(args: dict[str, Any]) -> None: |
268 | | - storage['arguments'] = args |
269 | | - |
270 | | - if args.get('debug'): |
271 | | - warn(f"Warning: --debug mode will write certain credentials to {storage['LOG_PATH']}/{storage['LOG_FILE']}!") |
272 | | - |
273 | | - if args.get('plugin'): |
274 | | - path = args['plugin'] |
275 | | - load_plugin(path) |
276 | | - |
277 | | - try: |
278 | | - load_config() |
279 | | - except ValueError as err: |
280 | | - warn(str(err)) |
281 | | - exit(1) |
282 | | - |
283 | | - |
284 | | -define_arguments() |
285 | | -arguments: dict[str, Any] = get_arguments() |
286 | | -post_process_arguments(arguments) |
287 | | - |
288 | | - |
289 | 53 | # @archinstall.plugin decorator hook to programmatically add |
290 | 54 | # plugins in runtime. Useful in profiles_bck and other things. |
291 | 55 | def plugin(f, *args, **kwargs) -> None: # type: ignore[no-untyped-def] |
@@ -321,13 +85,10 @@ def main() -> None: |
321 | 85 | OR straight as a module: python -m archinstall |
322 | 86 | In any case we will be attempting to load the provided script to be run from the scripts/ folder |
323 | 87 | """ |
324 | | - if not arguments.get('skip_version_check'): |
| 88 | + if not arch_config_handler.args.skip_version_check: |
325 | 89 | _check_new_version() |
326 | 90 |
|
327 | | - script = arguments.get('script', None) |
328 | | - |
329 | | - if script is None: |
330 | | - print('No script to run provided') |
| 91 | + script = arch_config_handler.args.script |
331 | 92 |
|
332 | 93 | mod_name = f'archinstall.scripts.{script}' |
333 | 94 | # by loading the module we'll automatically run the script |
|
0 commit comments