From 23365d2d8e33a3967e13ae6aa579e52a4d8ac349 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 18 Oct 2020 20:00:53 +0200 Subject: Added a test version trying to solve #43. --- examples/guided.py | 55 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index 30514a71..8233a205 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -1,4 +1,4 @@ -import archinstall, getpass, time +import archinstall, getpass, time, json def perform_installation(device, boot_partition, language, mirrors): """ @@ -50,8 +50,13 @@ archinstall.sys_command(f'cryptsetup close /dev/mapper/luksloop', surpress_error keyboard_language = archinstall.select_language(archinstall.list_keyboard_languages()) archinstall.set_keyboard_language(keyboard_language) +# Create a storage structure for all our information. +# We'll print this right before the user gets informed about the formatting timer. +archinstall.storage['_guided'] = {} + # Set which region to download packages from during the installation mirror_regions = archinstall.select_mirror_regions(archinstall.list_mirrors()) +archinstall.storage['_guided']['mirrors'] = mirror_regions # Ask which harddrive/block-device we will install to harddrive = archinstall.select_disk(archinstall.all_disks()) @@ -61,10 +66,12 @@ while (disk_password := getpass.getpass(prompt='Enter disk encryption password ( archinstall.log(' * Passwords did not match * ', bg='black', fg='red') continue break +archinstall.storage['_guided']['harddrive'] = harddrive # Ask for a hostname hostname = input('Desired hostname for the installation: ') if len(hostname) == 0: hostname = 'ArchInstall' +archinstall.storage['_guided']['hostname'] = hostname # Ask for a root password (optional, but triggers requirement for super-user if skipped) while (root_pw := getpass.getpass(prompt='Enter root password (leave blank to leave root disabled): ')): @@ -87,6 +94,10 @@ while 1: archinstall.log(' * Since root is disabled, you need to create a least one (super) user!', bg='black', fg='red') continue break + + if 'users' not in archinstall.storage['_guided']: archinstall.storage['_guided']['users'] = [] + archinstall.storage['_guided']['users'].append(new_user) + new_user_passwd = getpass.getpass(prompt=f'Password for user {new_user}: ') new_user_passwd_verify = getpass.getpass(prompt=f'Enter password again for verification: ') if new_user_passwd != new_user_passwd_verify: @@ -99,12 +110,16 @@ while 1: # Ask for archinstall-specific profiles (such as desktop environments etc) while 1: profile = archinstall.select_profile(archinstall.list_profiles()) - if profile and type(profile) != str: # Got a imported profile - if not profile[1]._prep_function(): - archinstall.log(' * Profile\'s preperation requirements was not fulfilled.', bg='black', fg='red') - continue - profile = profile[0]._path # Once the prep is done, replace the selected profile with the profile name ("path") given from select_profile() - break + if profile: + archinstall.storage['_guided']['profile'] = profile + + if type(profile) != str: # Got a imported profile + if not profile[1]._prep_function(): + archinstall.log(' * Profile\'s preperation requirements was not fulfilled.', bg='black', fg='red') + continue + + profile = profile[0]._path # Once the prep is done, replace the selected profile with the profile name ("path") given from select_profile() + break else: break @@ -117,28 +132,26 @@ while 1: try: if archinstall.validate_package_list(packages): + archinstall.storage['_guided']['packages'] = packages break except archinstall.RequirementError as e: print(e) -# TODO: Print a summary here of all the options chosen. -# Ideally, archinstall should keep track of this internally -# and there should be something like print(archinstall.config). +print(json.dumps(archinstall.storage['_guided'], indent=4, sort_keys=True)) """ Issue a final warning before we continue with something un-revertable. + We mention the drive one last time, and count from 5 to 0. """ -print(f' ! Formatting {harddrive} in 5...') -time.sleep(1) -print(f' ! Formatting {harddrive} in 4...') -time.sleep(1) -print(f' ! Formatting {harddrive} in 3...') -time.sleep(1) -print(f' ! Formatting {harddrive} in 2...') -time.sleep(1) -print(f' ! Formatting {harddrive} in 1...') -time.sleep(1) - +print(f' ! Formatting {harddrive} in ', end='') +for i in range(5, 0, -1): + print(f"{i}", end='') + + for x in range(4): + sys.stdout.flush() + time.sleep(0.25) + print(".", end='') +print() """ Setup the blockdevice, filesystem (and optionally encryption). Once that's done, we'll hand over to perform_installation() -- cgit v1.2.3-70-g09d2 From d5effa744f23472d328f71d7bda9ebd46540c265 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 18 Oct 2020 20:17:45 +0200 Subject: Added a JSON serializer for certain non-json objects. --- archinstall/lib/general.py | 41 +++++++++++++++++++++++++++++++++++++++++ examples/guided.py | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 393bf69a..10d22c31 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -1,5 +1,6 @@ import os, json, hashlib, shlex, sys import time, pty +from datetime import datetime from subprocess import Popen, STDOUT, PIPE, check_output from select import epoll, EPOLLIN, EPOLLHUP from .exceptions import * @@ -30,6 +31,46 @@ def locate_binary(name): return os.path.join(root, file) break # Don't recurse +def to_json(dictionary): + return json.dumps(dictionary, cls=) + +class JSON_Encoder: + def _encode(obj): + if isinstance(obj, dict): + ## We'll need to iterate not just the value that default() usually gets passed + ## But also iterate manually over each key: value pair in order to trap the keys. + + for key, val in list(obj.items()): + if isinstance(val, dict): + val = json.loads(json.dumps(val, cls=JSON)) # This, is a EXTREMELY ugly hack.. + # But it's the only quick way I can think of to + # trigger a encoding of sub-dictionaries. + else: + val = JSON_Encoder._encode(val) + del(obj[key]) + obj[JSON_Encoder._encode(key)] = val + return obj + elif hasattr(obj, 'json'): + return obj.json() + elif hasattr(obj, '__dump__'): + return obj.__dump__() + elif isinstance(obj, (datetime, date)): + return obj.isoformat() + elif isinstance(obj, (list, set, tuple)): + r = [] + for item in obj: + r.append(json.loads(json.dumps(item, cls=JSON))) + return r + else: + return obj + +class JSON(json.JSONEncoder, json.JSONDecoder): + def _encode(self, obj): + return JSON_Encoder._encode(obj) + + def encode(self, obj): + return super(JSON, self).encode(self._encode(obj)) + class sys_command():#Thread): """ Stolen from archinstall_gui diff --git a/examples/guided.py b/examples/guided.py index 8233a205..00d2565c 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -137,7 +137,7 @@ while 1: except archinstall.RequirementError as e: print(e) -print(json.dumps(archinstall.storage['_guided'], indent=4, sort_keys=True)) +print(json.dumps(archinstall.storage['_guided'], indent=4, sort_keys=True, cls=archinstall.JSON)) """ Issue a final warning before we continue with something un-revertable. -- cgit v1.2.3-70-g09d2 From 17a70eb9f459230e7e11beb724a68051b2f1c667 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 18 Oct 2020 20:18:20 +0200 Subject: Removed a stub function --- archinstall/lib/general.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 10d22c31..2f4713d8 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -31,9 +31,6 @@ def locate_binary(name): return os.path.join(root, file) break # Don't recurse -def to_json(dictionary): - return json.dumps(dictionary, cls=) - class JSON_Encoder: def _encode(obj): if isinstance(obj, dict): -- cgit v1.2.3-70-g09d2 From 3d4eaec4662197e15b8f8c05fcefc2a5fa487da5 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 18 Oct 2020 20:19:21 +0200 Subject: Forgot an import --- archinstall/lib/general.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 2f4713d8..ff834241 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -1,6 +1,6 @@ import os, json, hashlib, shlex, sys import time, pty -from datetime import datetime +from datetime import datetime, date from subprocess import Popen, STDOUT, PIPE, check_output from select import epoll, EPOLLIN, EPOLLHUP from .exceptions import * -- cgit v1.2.3-70-g09d2 From fa4be63e483a0a692f26103b45ad6abe73879f37 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 18 Oct 2020 20:26:09 +0200 Subject: Forgot an import, as well as made BlockDevice() have less verbose output on json.dumps. --- archinstall/lib/disk.py | 10 ++++++++++ examples/guided.py | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index 7cd8dd0f..b11f2318 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -25,6 +25,16 @@ class BlockDevice(): raise KeyError(f'{self} does not contain information: "{key}"') return self.info[key] + def json(self): + """ + json() has precedence over __dump__, so this is a way + to give less/partial information for user readability. + """ + return { + 'path' : self.path, + 'size' : self.info['size'] if 'size' in self.info else '' + } + def __dump__(self): return { 'path' : self.path, diff --git a/examples/guided.py b/examples/guided.py index 00d2565c..958b1e5f 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -1,4 +1,5 @@ -import archinstall, getpass, time, json +import archinstall +import getpass, time, json, sys def perform_installation(device, boot_partition, language, mirrors): """ -- cgit v1.2.3-70-g09d2 From 1bd6a19dc46d693ab55d25a0275db8fcf83e20d9 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 18 Oct 2020 20:27:40 +0200 Subject: Made Profile() json-dumpable --- archinstall/lib/profiles.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/archinstall/lib/profiles.py b/archinstall/lib/profiles.py index 47e6dd36..e018f753 100644 --- a/archinstall/lib/profiles.py +++ b/archinstall/lib/profiles.py @@ -61,6 +61,9 @@ class Profile(): self._cache = None self.args = args + def __dump__(self, *args, **kwargs): + return {'path' : self._path} + def __repr__(self, *args, **kwargs): return f'Profile({self._path} <"{self.path}">)' -- cgit v1.2.3-70-g09d2 From 58201d723465517dbe467715d57e46f73241a0dd Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 18 Oct 2020 20:32:26 +0200 Subject: Tweaked the _guided storage. One variable was a loaded module, which is hard to convert to json in a meaningful way. --- examples/guided.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/guided.py b/examples/guided.py index 958b1e5f..a96934e5 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -115,6 +115,7 @@ while 1: archinstall.storage['_guided']['profile'] = profile if type(profile) != str: # Got a imported profile + archinstall.storage['_guided']['profile'] = profile[0] # The second return is a module, and not a handle/object. if not profile[1]._prep_function(): archinstall.log(' * Profile\'s preperation requirements was not fulfilled.', bg='black', fg='red') continue -- cgit v1.2.3-70-g09d2 From 479881a5a25576725a822d805d556a7f318a5e48 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 18 Oct 2020 20:36:14 +0200 Subject: Tweaked the output a little. --- examples/guided.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/guided.py b/examples/guided.py index a96934e5..12d34194 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -139,7 +139,10 @@ while 1: except archinstall.RequirementError as e: print(e) +print() +print('This is your chosen configuration:') print(json.dumps(archinstall.storage['_guided'], indent=4, sort_keys=True, cls=archinstall.JSON)) +print() """ Issue a final warning before we continue with something un-revertable. -- cgit v1.2.3-70-g09d2 From 2df4347b44ff6e45ed6b4a66332efae51056c2ef Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 18 Oct 2020 20:49:44 +0200 Subject: Added a slightly convoluted but non-intrusive 'are you sure?' on Ctrl+C/any input. --- examples/guided.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index 12d34194..067d591f 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -1,5 +1,20 @@ import archinstall -import getpass, time, json, sys +import getpass, time, json, sys, signal +from select import epoll, EPOLLIN + +""" +This signal-handler chain (and global variable) +is used to trigger the "Are you sure you want to abort?" question. +""" +SIG_TRIGGER = False +def kill_handler(sig, frame): + print() + exit(0) +def sig_handler(sig, frame): + global SIG_TRIGGER + SIG_TRIGGER = True + signal.signal(signal.SIGINT, kill_handler) +signal.signal(signal.SIGINT, sig_handler) def perform_installation(device, boot_partition, language, mirrors): """ @@ -148,7 +163,11 @@ print() Issue a final warning before we continue with something un-revertable. We mention the drive one last time, and count from 5 to 0. """ + print(f' ! Formatting {harddrive} in ', end='') + +poller = epoll() +poller.register(sys.stdin.fileno(), EPOLLIN) for i in range(5, 0, -1): print(f"{i}", end='') @@ -156,6 +175,16 @@ for i in range(5, 0, -1): sys.stdout.flush() time.sleep(0.25) print(".", end='') + + if list(poller.poll(0.25)) or SIG_TRIGGER: + abort = input('\nDo you really want to abort (y/n)? ') + if abort.strip() != 'n': + exit(0) + + if SIG_TRIGGER is False: + sys.stdin.read() + SIG_TRIGGER = False + signal.signal(signal.SIGINT, sig_handler) print() """ Setup the blockdevice, filesystem (and optionally encryption). -- cgit v1.2.3-70-g09d2 From ad2adec8343a1cf8dfcd68e472d8dc4720717038 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 18 Oct 2020 21:19:08 +0200 Subject: Reverted some changes. It was to 'complicated' to get status of the sys.stdin buffer while it did not contain a new-line character. So reverted that code. Here is an interesting read, but a bit to much code for a simple guided template: https://stackoverflow.com/a/41459565/929999 --- examples/guided.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/guided.py b/examples/guided.py index 067d591f..b3df07d7 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -1,6 +1,5 @@ import archinstall import getpass, time, json, sys, signal -from select import epoll, EPOLLIN """ This signal-handler chain (and global variable) @@ -166,8 +165,6 @@ print() print(f' ! Formatting {harddrive} in ', end='') -poller = epoll() -poller.register(sys.stdin.fileno(), EPOLLIN) for i in range(5, 0, -1): print(f"{i}", end='') @@ -176,7 +173,7 @@ for i in range(5, 0, -1): time.sleep(0.25) print(".", end='') - if list(poller.poll(0.25)) or SIG_TRIGGER: + if SIG_TRIGGER: abort = input('\nDo you really want to abort (y/n)? ') if abort.strip() != 'n': exit(0) -- cgit v1.2.3-70-g09d2 From db2e5d721cbfc9009b617a4c4b0bc3d54b158f65 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 18 Oct 2020 21:22:05 +0200 Subject: Revert to a some what default sig-kill handler. (needs some adjustments just to clean up things, but I think it will do for now) --- examples/guided.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/guided.py b/examples/guided.py index b3df07d7..7cec99a3 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -183,6 +183,8 @@ for i in range(5, 0, -1): SIG_TRIGGER = False signal.signal(signal.SIGINT, sig_handler) print() +signal.signal(signal.SIGINT, kill_handler) + """ Setup the blockdevice, filesystem (and optionally encryption). Once that's done, we'll hand over to perform_installation() -- cgit v1.2.3-70-g09d2 From 06ee896c6c58e877325896b33a2a3dd3eb0a1727 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sun, 18 Oct 2020 21:23:01 +0200 Subject: Changed my mind, reverting to the original sig-handler is better. --- examples/guided.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/guided.py b/examples/guided.py index 7cec99a3..cc660b90 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -13,6 +13,7 @@ def sig_handler(sig, frame): global SIG_TRIGGER SIG_TRIGGER = True signal.signal(signal.SIGINT, kill_handler) +original_sigint_handler = signal.getsignal(signal.SIGINT) signal.signal(signal.SIGINT, sig_handler) def perform_installation(device, boot_partition, language, mirrors): @@ -183,7 +184,7 @@ for i in range(5, 0, -1): SIG_TRIGGER = False signal.signal(signal.SIGINT, sig_handler) print() -signal.signal(signal.SIGINT, kill_handler) +signal.signal(signal.SIGINT, original_sigint_handler) """ Setup the blockdevice, filesystem (and optionally encryption). -- cgit v1.2.3-70-g09d2