Introducing log data and support files
Logs are stored under `~/.cache/archinstall` and documentation has been updated.
This commit is contained in:
commit
16c98e8102
|
|
@ -42,3 +42,4 @@ At present the core developers are (alphabetically):
|
|||
* Anton Hvornum (@Torxed)
|
||||
* Jerker Bengtsson (@jaybent)
|
||||
* Varun Madiath (@vamega)
|
||||
* demostanis (@demostanis)
|
||||
|
|
@ -70,6 +70,11 @@ This installer will perform the following:
|
|||
|
||||
> **Creating your own ISO with this script on it:** Follow [ArchISO](https://wiki.archlinux.org/index.php/archiso)'s guide on how to create your own ISO or use a pre-built [guided ISO](https://hvornum.se/archiso/) to skip the python installation step, or to create auto-installing ISO templates. Further down are examples and cheat sheets on how to create different live ISO's.
|
||||
|
||||
# Help
|
||||
|
||||
Submit an issue on Github, or submit a post in the discord help channel.<br>
|
||||
When doing so, attach any `install-session_*.log` to the issue ticket which can be found under `~/.cache/archinstall/`.
|
||||
|
||||
# Testing
|
||||
|
||||
To test this without a live ISO, the simplest approach is to use a local image and create a loop device.<br>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import glob, re, os, json
|
|||
from collections import OrderedDict
|
||||
from .exceptions import DiskError
|
||||
from .general import *
|
||||
from .output import log, LOG_LEVELS
|
||||
from .storage import storage
|
||||
|
||||
ROOT_DIR_PATTERN = re.compile('^.*?/devices')
|
||||
GPT = 0b00000001
|
||||
|
|
@ -115,7 +117,7 @@ class Partition():
|
|||
return f'Partition(path={self.path}, fs={self.filesystem}, mounted={self.mountpoint})'
|
||||
|
||||
def format(self, filesystem):
|
||||
log(f'Formatting {self} -> {filesystem}')
|
||||
log(f'Formatting {self} -> {filesystem}', level=LOG_LEVELS.Info, file=storage.get('logfile', None))
|
||||
if filesystem == 'btrfs':
|
||||
o = b''.join(sys_command(f'/usr/bin/mkfs.btrfs -f {self.path}'))
|
||||
if b'UUID' not in o:
|
||||
|
|
@ -154,7 +156,7 @@ class Partition():
|
|||
|
||||
def mount(self, target, fs=None, options=''):
|
||||
if not self.mountpoint:
|
||||
log(f'Mounting {self} to {target}')
|
||||
log(f'Mounting {self} to {target}', level=LOG_LEVELS.Info, file=storage.get('logfile', None))
|
||||
if not fs:
|
||||
if not self.filesystem: raise DiskError(f'Need to format (or define) the filesystem on {self} before mounting.')
|
||||
fs = self.filesystem
|
||||
|
|
@ -216,7 +218,7 @@ class Filesystem():
|
|||
self.add_partition('primary', start='513MiB', end='100%', format='ext4')
|
||||
|
||||
def add_partition(self, type, start, end, format=None):
|
||||
log(f'Adding partition to {self.blockdevice}')
|
||||
log(f'Adding partition to {self.blockdevice}', level=LOG_LEVELS.Info, file=storage.get('logfile', None))
|
||||
if format:
|
||||
return self.parted(f'{self.blockdevice.device} mkpart {type} {format} {start} {end}') == 0
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from datetime import datetime, date
|
|||
from subprocess import Popen, STDOUT, PIPE, check_output
|
||||
from select import epoll, EPOLLIN, EPOLLHUP
|
||||
from .exceptions import *
|
||||
from .output import *
|
||||
from .output import log, LOG_LEVELS
|
||||
|
||||
def gen_uid(entropy_length=256):
|
||||
return hashlib.sha512(os.urandom(entropy_length)).hexdigest()
|
||||
|
|
@ -77,8 +77,11 @@ class sys_command():#Thread):
|
|||
kwargs.setdefault("emulate", False)
|
||||
kwargs.setdefault("suppress_errors", False)
|
||||
|
||||
self.log = kwargs.get('log', log)
|
||||
|
||||
if kwargs['emulate']:
|
||||
log(f"Starting command '{cmd}' in emulation mode.")
|
||||
self.log(f"Starting command '{cmd}' in emulation mode.", level=LOG_LEVELS.Debug)
|
||||
|
||||
self.raw_cmd = cmd
|
||||
try:
|
||||
self.cmd = shlex.split(cmd)
|
||||
|
|
@ -105,8 +108,8 @@ class sys_command():#Thread):
|
|||
# "which" doesn't work as it's a builtin to bash.
|
||||
# It used to work, but for whatever reason it doesn't anymore. So back to square one..
|
||||
|
||||
#log('Worker command is not executed with absolute path, trying to find: {}'.format(self.cmd[0]), origin='spawn', level=5)
|
||||
#log('This is the binary {} for {}'.format(o.decode('UTF-8'), self.cmd[0]), origin='spawn', level=5)
|
||||
#self.log('Worker command is not executed with absolute path, trying to find: {}'.format(self.cmd[0]), origin='spawn', level=5)
|
||||
#self.log('This is the binary {} for {}'.format(o.decode('UTF-8'), self.cmd[0]), origin='spawn', level=5)
|
||||
self.cmd[0] = locate_binary(self.cmd[0])
|
||||
|
||||
if not os.path.isdir(self.exec_dir):
|
||||
|
|
@ -150,7 +153,7 @@ class sys_command():#Thread):
|
|||
os.execv(self.cmd[0], self.cmd)
|
||||
except FileNotFoundError:
|
||||
self.status = 'done'
|
||||
log(f"{self.cmd[0]} does not exist.", origin='spawn', level=2)
|
||||
self.log(f"{self.cmd[0]} does not exist.", level=LOG_LEVELS.Debug)
|
||||
self.exit_code = 1
|
||||
return False
|
||||
|
||||
|
|
@ -160,8 +163,8 @@ class sys_command():#Thread):
|
|||
poller.register(child_fd, EPOLLIN | EPOLLHUP)
|
||||
|
||||
if 'events' in self.kwargs and 'debug' in self.kwargs:
|
||||
log(f'[D] Using triggers for command: {self.cmd}')
|
||||
log(json.dumps(self.kwargs['events']))
|
||||
self.log(f'[D] Using triggers for command: {self.cmd}', level=LOG_LEVELS.Debug)
|
||||
self.log(json.dumps(self.kwargs['events']), level=LOG_LEVELS.Debug)
|
||||
|
||||
alive = True
|
||||
last_trigger_pos = 0
|
||||
|
|
@ -175,7 +178,7 @@ class sys_command():#Thread):
|
|||
break
|
||||
|
||||
if 'debug' in self.kwargs and self.kwargs['debug'] and len(output):
|
||||
log(self.cmd, 'gave:', output.decode('UTF-8'))
|
||||
self.log(self.cmd, 'gave:', output.decode('UTF-8'), level=LOG_LEVELS.Debug)
|
||||
|
||||
if 'on_output' in self.kwargs:
|
||||
self.kwargs['on_output'](self.kwargs['worker'], output)
|
||||
|
|
@ -196,8 +199,8 @@ class sys_command():#Thread):
|
|||
trigger_pos = self.trace_log[last_trigger_pos:].lower().find(trigger.lower())
|
||||
|
||||
if 'debug' in self.kwargs and self.kwargs['debug']:
|
||||
log(f"Writing to subprocess {self.cmd[0]}: {self.kwargs['events'][trigger].decode('UTF-8')}")
|
||||
log(f"Writing to subprocess {self.cmd[0]}: {self.kwargs['events'][trigger].decode('UTF-8')}", origin='spawn', level=5)
|
||||
self.log(f"Writing to subprocess {self.cmd[0]}: {self.kwargs['events'][trigger].decode('UTF-8')}", level=LOG_LEVELS.Debug)
|
||||
self.log(f"Writing to subprocess {self.cmd[0]}: {self.kwargs['events'][trigger].decode('UTF-8')}", level=LOG_LEVELS.Debug)
|
||||
|
||||
last_trigger_pos = trigger_pos
|
||||
os.write(child_fd, self.kwargs['events'][trigger])
|
||||
|
|
@ -211,18 +214,18 @@ class sys_command():#Thread):
|
|||
## Adding a exit trigger:
|
||||
if len(self.kwargs['events']) == 0:
|
||||
if 'debug' in self.kwargs and self.kwargs['debug']:
|
||||
log(f"Waiting for last command {self.cmd[0]} to finish.", origin='spawn', level=4)
|
||||
self.log(f"Waiting for last command {self.cmd[0]} to finish.", level=LOG_LEVELS.Debug)
|
||||
|
||||
if bytes(f']$'.lower(), 'UTF-8') in self.trace_log[0-len(f']$')-5:].lower():
|
||||
if 'debug' in self.kwargs and self.kwargs['debug']:
|
||||
log(f"{self.cmd[0]} has finished.")
|
||||
self.log(f"{self.cmd[0]} has finished.", level=LOG_LEVELS.Debug)
|
||||
alive = False
|
||||
break
|
||||
|
||||
self.status = 'done'
|
||||
|
||||
if 'debug' in self.kwargs and self.kwargs['debug']:
|
||||
log(f"{self.cmd[0]} waiting for exit code.")
|
||||
self.log(f"{self.cmd[0]} waiting for exit code.", level=LOG_LEVELS.Debug)
|
||||
|
||||
if not self.kwargs['emulate']:
|
||||
try:
|
||||
|
|
@ -236,14 +239,14 @@ class sys_command():#Thread):
|
|||
self.exit_code = 0
|
||||
|
||||
if 'debug' in self.kwargs and self.kwargs['debug']:
|
||||
log(f"{self.cmd[0]} got exit code: {self.exit_code}")
|
||||
self.log(f"{self.cmd[0]} got exit code: {self.exit_code}", level=LOG_LEVELS.Debug)
|
||||
|
||||
if 'ignore_errors' in self.kwargs:
|
||||
self.exit_code = 0
|
||||
|
||||
if self.exit_code != 0 and not self.kwargs['suppress_errors']:
|
||||
log(f"'{self.raw_cmd}' did not exit gracefully, exit code {self.exit_code}.")
|
||||
log(self.trace_log.decode('UTF-8'))
|
||||
self.log(f"'{self.raw_cmd}' did not exit gracefully, exit code {self.exit_code}.", level=LOG_LEVELS.Error)
|
||||
self.log(self.trace_log.decode('UTF-8'), level=LOG_LEVELS.Debug)
|
||||
raise SysCallError(f"'{self.raw_cmd}' did not exit gracefully, exit code {self.exit_code}.\n{self.trace_log.decode('UTF-8')}")
|
||||
|
||||
self.ended = time.time()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import os, stat
|
||||
import os, stat, time
|
||||
|
||||
from .exceptions import *
|
||||
from .disk import *
|
||||
|
|
@ -6,6 +6,8 @@ from .general import *
|
|||
from .user_interaction import *
|
||||
from .profiles import Profile
|
||||
from .mirrors import *
|
||||
from .output import log, LOG_LEVELS
|
||||
from .storage import storage
|
||||
|
||||
class Installer():
|
||||
"""
|
||||
|
|
@ -35,6 +37,8 @@ class Installer():
|
|||
self.profile = profile
|
||||
self.hostname = hostname
|
||||
self.mountpoint = mountpoint
|
||||
self.init_time = time.strftime('%Y-%m-%d_%H-%M-%S')
|
||||
self.milliseconds = int(str(time.time()).split('.')[1])
|
||||
|
||||
self.helper_flags = {
|
||||
'bootloader' : False,
|
||||
|
|
@ -43,10 +47,24 @@ class Installer():
|
|||
}
|
||||
|
||||
self.base_packages = base_packages.split(' ')
|
||||
storage['session'] = self
|
||||
|
||||
self.partition = partition
|
||||
self.boot_partition = boot_partition
|
||||
|
||||
def log(self, *args, level=LOG_LEVELS.Debug, file=None, **kwargs):
|
||||
if not file:
|
||||
if 'logfile' not in storage:
|
||||
log_root = os.path.join(os.path.expanduser('~/'), '.cache/archinstall')
|
||||
if not os.path.isdir(log_root):
|
||||
os.makedirs(log_root)
|
||||
|
||||
storage['logfile'] = f"{log_root}/install-session_{self.init_time}.{self.milliseconds}.log"
|
||||
|
||||
file = storage['logfile']
|
||||
|
||||
log(*args, level=level, file=file, **kwargs)
|
||||
|
||||
def __enter__(self, *args, **kwargs):
|
||||
self.partition.mount(self.mountpoint)
|
||||
os.makedirs(f'{self.mountpoint}/boot', exist_ok=True)
|
||||
|
|
@ -60,12 +78,14 @@ class Installer():
|
|||
raise args[1]
|
||||
|
||||
if not (missing_steps := self.post_install_check()):
|
||||
log('Installation completed without any errors. You may now reboot.', bg='black', fg='green')
|
||||
self.log('Installation completed without any errors. You may now reboot.', bg='black', fg='green', level=LOG_LEVELS.Info)
|
||||
return True
|
||||
else:
|
||||
log('Some required steps were not successfully installed/configured before leaving the installer:', bg='black', fg='red')
|
||||
self.log('Some required steps were not successfully installed/configured before leaving the installer:', bg='black', fg='red', level=LOG_LEVELS.Warning)
|
||||
for step in missing_steps:
|
||||
log(f' - {step}', bg='black', fg='red')
|
||||
self.log(f' - {step}', bg='black', fg='red', level=LOG_LEVELS.Warning)
|
||||
self.log(f"Detailed error logs can be found at: {log_path}", level=LOG_LEVELS.Warning)
|
||||
self.log(f"Submit this zip file as an issue to https://github.com/Torxed/archinstall/issues", level=LOG_LEVELS.Warning)
|
||||
return False
|
||||
|
||||
def post_install_check(self, *args, **kwargs):
|
||||
|
|
@ -73,15 +93,15 @@ class Installer():
|
|||
|
||||
def pacstrap(self, *packages, **kwargs):
|
||||
if type(packages[0]) in (list, tuple): packages = packages[0]
|
||||
log(f'Installing packages: {packages}')
|
||||
self.log(f'Installing packages: {packages}', level=LOG_LEVELS.Info)
|
||||
|
||||
if (sync_mirrors := sys_command('/usr/bin/pacman -Syy')).exit_code == 0:
|
||||
if (pacstrap := sys_command(f'/usr/bin/pacstrap {self.mountpoint} {" ".join(packages)}', **kwargs)).exit_code == 0:
|
||||
return True
|
||||
else:
|
||||
log(f'Could not strap in packages: {pacstrap.exit_code}')
|
||||
self.log(f'Could not strap in packages: {pacstrap.exit_code}', level=LOG_LEVELS.Info)
|
||||
else:
|
||||
log(f'Could not sync mirrors: {sync_mirrors.exit_code}')
|
||||
self.log(f'Could not sync mirrors: {sync_mirrors.exit_code}', level=LOG_LEVELS.Info)
|
||||
|
||||
def set_mirrors(self, mirrors):
|
||||
return use_mirrors(mirrors, destination=f'{self.mountpoint}/etc/pacman.d/mirrorlist')
|
||||
|
|
@ -114,13 +134,13 @@ class Installer():
|
|||
return True
|
||||
|
||||
def activate_ntp(self):
|
||||
log(f'Installing and activating NTP.')
|
||||
self.log(f'Installing and activating NTP.', level=LOG_LEVELS.Info)
|
||||
if self.pacstrap('ntp'):
|
||||
if self.enable_service('ntpd'):
|
||||
return True
|
||||
|
||||
def enable_service(self, service):
|
||||
log(f'Enabling service {service}')
|
||||
self.log(f'Enabling service {service}', level=LOG_LEVELS.Info)
|
||||
return self.arch_chroot(f'systemctl enable {service}').exit_code == 0
|
||||
|
||||
def run_command(self, cmd, *args, **kwargs):
|
||||
|
|
@ -168,47 +188,51 @@ class Installer():
|
|||
self.helper_flags['base'] = True
|
||||
return True
|
||||
|
||||
def add_bootloader(self):
|
||||
log(f'Adding bootloader to {self.boot_partition}')
|
||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} bootctl --no-variables --path=/boot install'))
|
||||
with open(f'{self.mountpoint}/boot/loader/loader.conf', 'w') as loader:
|
||||
loader.write('default arch\n')
|
||||
loader.write('timeout 5\n')
|
||||
def add_bootloader(self, bootloader='systemd-bootctl'):
|
||||
self.log(f'Adding bootloader {bootloader} to {self.boot_partition}', level=LOG_LEVELS.Info)
|
||||
|
||||
## For some reason, blkid and /dev/disk/by-uuid are not getting along well.
|
||||
## And blkid is wrong in terms of LUKS.
|
||||
#UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip()
|
||||
with open(f'{self.mountpoint}/boot/loader/entries/arch.conf', 'w') as entry:
|
||||
entry.write('title Arch Linux\n')
|
||||
entry.write('linux /vmlinuz-linux\n')
|
||||
entry.write('initrd /initramfs-linux.img\n')
|
||||
## blkid doesn't trigger on loopback devices really well,
|
||||
## so we'll use the old manual method until we get that sorted out.
|
||||
if bootloader == 'systemd-bootctl':
|
||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} bootctl --no-variables --path=/boot install'))
|
||||
with open(f'{self.mountpoint}/boot/loader/loader.conf', 'w') as loader:
|
||||
loader.write('default arch\n')
|
||||
loader.write('timeout 5\n')
|
||||
|
||||
## For some reason, blkid and /dev/disk/by-uuid are not getting along well.
|
||||
## And blkid is wrong in terms of LUKS.
|
||||
#UUID = sys_command('blkid -s PARTUUID -o value {drive}{partition_2}'.format(**args)).decode('UTF-8').strip()
|
||||
with open(f'{self.mountpoint}/boot/loader/entries/arch.conf', 'w') as entry:
|
||||
entry.write('title Arch Linux\n')
|
||||
entry.write('linux /vmlinuz-linux\n')
|
||||
entry.write('initrd /initramfs-linux.img\n')
|
||||
## blkid doesn't trigger on loopback devices really well,
|
||||
## so we'll use the old manual method until we get that sorted out.
|
||||
|
||||
|
||||
if self.partition.encrypted:
|
||||
for root, folders, uids in os.walk('/dev/disk/by-uuid'):
|
||||
for uid in uids:
|
||||
real_path = os.path.realpath(os.path.join(root, uid))
|
||||
if not os.path.basename(real_path) == os.path.basename(self.partition.real_device): continue
|
||||
if self.partition.encrypted:
|
||||
for root, folders, uids in os.walk('/dev/disk/by-uuid'):
|
||||
for uid in uids:
|
||||
real_path = os.path.realpath(os.path.join(root, uid))
|
||||
if not os.path.basename(real_path) == os.path.basename(self.partition.real_device): continue
|
||||
|
||||
entry.write(f'options cryptdevice=UUID={uid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n')
|
||||
entry.write(f'options cryptdevice=UUID={uid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp\n')
|
||||
|
||||
self.helper_flags['bootloader'] = True
|
||||
return True
|
||||
break
|
||||
else:
|
||||
for root, folders, uids in os.walk('/dev/disk/by-partuuid'):
|
||||
for uid in uids:
|
||||
real_path = os.path.realpath(os.path.join(root, uid))
|
||||
if not os.path.basename(real_path) == os.path.basename(self.partition.path): continue
|
||||
self.helper_flags['bootloader'] = bootloader
|
||||
return True
|
||||
break
|
||||
else:
|
||||
for root, folders, uids in os.walk('/dev/disk/by-partuuid'):
|
||||
for uid in uids:
|
||||
real_path = os.path.realpath(os.path.join(root, uid))
|
||||
if not os.path.basename(real_path) == os.path.basename(self.partition.path): continue
|
||||
|
||||
entry.write(f'options root=PARTUUID={uid} rw intel_pstate=no_hwp\n')
|
||||
entry.write(f'options root=PARTUUID={uid} rw intel_pstate=no_hwp\n')
|
||||
|
||||
self.helper_flags['bootloader'] = True
|
||||
return True
|
||||
break
|
||||
raise RequirementError(f'Could not identify the UUID of {self.partition}, there for {self.mountpoint}/boot/loader/entries/arch.conf will be broken until fixed.')
|
||||
self.helper_flags['bootloader'] = bootloader
|
||||
return True
|
||||
break
|
||||
raise RequirementError(f"Could not identify the UUID of {self.partition}, there for {self.mountpoint}/boot/loader/entries/arch.conf will be broken until fixed.")
|
||||
else:
|
||||
raise RequirementError(f"Unknown (or not yet implemented) bootloader added to add_bootloader(): {bootloader}")
|
||||
|
||||
def add_additional_packages(self, *packages):
|
||||
return self.pacstrap(*packages)
|
||||
|
|
@ -216,17 +240,17 @@ class Installer():
|
|||
def install_profile(self, profile):
|
||||
profile = Profile(self, profile)
|
||||
|
||||
log(f'Installing network profile {profile}')
|
||||
self.log(f'Installing network profile {profile}', level=LOG_LEVELS.Info)
|
||||
return profile.install()
|
||||
|
||||
def enable_sudo(self, entity :str, group=False):
|
||||
log(f'Enabling sudo permissions for {entity}.')
|
||||
self.log(f'Enabling sudo permissions for {entity}.', level=LOG_LEVELS.Info)
|
||||
with open(f'{self.mountpoint}/etc/sudoers', 'a') as sudoers:
|
||||
sudoers.write(f'{"%" if group else ""}{entity} ALL=(ALL) ALL\n')
|
||||
return True
|
||||
|
||||
def user_create(self, user :str, password=None, groups=[], sudo=False):
|
||||
log(f'Creating user {user}')
|
||||
self.log(f'Creating user {user}', level=LOG_LEVELS.Info)
|
||||
o = b''.join(sys_command(f'/usr/bin/arch-chroot {self.mountpoint} useradd -m -G wheel {user}'))
|
||||
if password:
|
||||
self.user_set_pw(user, password)
|
||||
|
|
@ -239,7 +263,7 @@ class Installer():
|
|||
self.helper_flags['user'] = True
|
||||
|
||||
def user_set_pw(self, user, password):
|
||||
log(f'Setting password for {user}')
|
||||
self.log(f'Setting password for {user}', level=LOG_LEVELS.Info)
|
||||
|
||||
if user == 'root':
|
||||
# This means the root account isn't locked/disabled with * in /etc/passwd
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import os
|
|||
from .exceptions import *
|
||||
from .general import *
|
||||
from .disk import Partition
|
||||
from .output import log, LOG_LEVELS
|
||||
from .storage import storage
|
||||
|
||||
class luks2():
|
||||
def __init__(self, partition, mountpoint, password, *args, **kwargs):
|
||||
|
|
@ -22,7 +24,10 @@ class luks2():
|
|||
return True
|
||||
|
||||
def encrypt(self, partition, password, key_size=512, hash_type='sha512', iter_time=10000, key_file=None):
|
||||
log(f'Encrypting {partition}')
|
||||
# TODO: We should be able to integrate this into the main log some how.
|
||||
# Perhaps post-mortem?
|
||||
log(f'Encrypting {partition}', level=LOG_LEVELS.Info, file=storage.get('logfile', None))
|
||||
|
||||
if not key_file:
|
||||
key_file = f"/tmp/{os.path.basename(self.partition.path)}.disk_pw" # TODO: Make disk-pw-file randomly unique?
|
||||
if type(password) != bytes: password = bytes(password, 'UTF-8')
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import urllib.request
|
|||
|
||||
from .exceptions import *
|
||||
from .general import *
|
||||
from .output import log
|
||||
from .storage import storage
|
||||
|
||||
def filter_mirrors_by_region(regions, destination='/etc/pacman.d/mirrorlist', tmp_dir='/root', *args, **kwargs):
|
||||
"""
|
||||
|
|
@ -57,7 +59,7 @@ def insert_mirrors(mirrors, *args, **kwargs):
|
|||
return True
|
||||
|
||||
def use_mirrors(regions :dict, destination='/etc/pacman.d/mirrorlist'):
|
||||
log(f'A new package mirror-list has been created: {destination}')
|
||||
log(f'A new package mirror-list has been created: {destination}', level=LOG_LEVELS.Info, file=storage.get('logfile', None))
|
||||
for region, mirrors in regions.items():
|
||||
with open(destination, 'w') as mirrorlist:
|
||||
for mirror in mirrors:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,40 @@
|
|||
import abc
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from .storage import storage
|
||||
|
||||
class LOG_LEVELS:
|
||||
Critical = 0b001
|
||||
Error = 0b010
|
||||
Warning = 0b011
|
||||
Info = 0b101
|
||||
Debug = 0b111
|
||||
|
||||
class journald(dict):
|
||||
@abc.abstractmethod
|
||||
def log(message, level=LOG_LEVELS.Debug):
|
||||
import systemd.journal
|
||||
log_adapter = logging.getLogger('archinstall')
|
||||
log_fmt = logging.Formatter("[%(levelname)s]: %(message)s")
|
||||
log_ch = systemd.journal.JournalHandler()
|
||||
log_ch.setFormatter(log_fmt)
|
||||
log_adapter.addHandler(log_ch)
|
||||
log_adapter.setLevel(logging.DEBUG)
|
||||
|
||||
if level == LOG_LEVELS.Critical:
|
||||
log_adapter.critical(message)
|
||||
elif level == LOG_LEVELS.Error:
|
||||
log_adapter.error(message)
|
||||
elif level == LOG_LEVELS.Warning:
|
||||
log_adapter.warning(message)
|
||||
elif level == LOG_LEVELS.Info:
|
||||
log_adapter.info(message)
|
||||
elif level == LOG_LEVELS.Debug:
|
||||
log_adapter.debug(message)
|
||||
else:
|
||||
# Fallback logger
|
||||
log_adapter.debug(message)
|
||||
|
||||
# Found first reference here: https://stackoverflow.com/questions/7445658/how-to-detect-if-the-console-does-support-ansi-escape-codes-in-python
|
||||
# And re-used this: https://github.com/django/django/blob/master/django/core/management/color.py#L12
|
||||
|
|
@ -38,9 +74,41 @@ def stylize_output(text :str, *opts, **kwargs):
|
|||
return '%s%s' % (('\x1b[%sm' % ';'.join(code_list)), text or '')
|
||||
|
||||
def log(*args, **kwargs):
|
||||
string = ' '.join([str(x) for x in args])
|
||||
string = orig_string = ' '.join([str(x) for x in args])
|
||||
|
||||
if supports_color():
|
||||
kwargs = {'bg' : 'black', 'fg': 'white', **kwargs}
|
||||
string = stylize_output(string, **kwargs)
|
||||
|
||||
if (logfile := storage.get('logfile', None)) and 'file' not in kwargs:
|
||||
kwargs['file'] = logfile
|
||||
|
||||
# Log to a file output unless specifically told to suppress this feature.
|
||||
# (level has no effect on the log file, everything will be written there)
|
||||
if 'file' in kwargs and ('suppress' not in kwargs or kwargs['suppress'] == False):
|
||||
if type(kwargs['file']) is str:
|
||||
with open(kwargs['file'], 'a') as log_file:
|
||||
log_file.write(f"{orig_string}\n")
|
||||
else:
|
||||
kwargs['file'].write(f"{orig_string}\n")
|
||||
|
||||
# If we assigned a level, try to log it to systemd's journald.
|
||||
# Unless the level is higher than we've decided to output interactively.
|
||||
# (Remember, log files still get *ALL* the output despite level restrictions)
|
||||
if 'level' in kwargs:
|
||||
if 'LOG_LEVEL' not in storage:
|
||||
storage['LOG_LEVEL'] = LOG_LEVELS.Info
|
||||
|
||||
if kwargs['level'] > storage['LOG_LEVEL']:
|
||||
# Level on log message was Debug, but output level is set to Info.
|
||||
# In that case, we'll drop it.
|
||||
return None
|
||||
|
||||
try:
|
||||
journald.log(string, level=kwargs['level'])
|
||||
except ModuleNotFoundError:
|
||||
pass # Ignore writing to journald
|
||||
|
||||
# Finally, print the log unless we skipped it based on level.
|
||||
# And we print the string which may or may not contain color formatting.
|
||||
print(string)
|
||||
|
|
@ -4,6 +4,8 @@ from collections import OrderedDict
|
|||
from .general import multisplit, sys_command, log
|
||||
from .exceptions import *
|
||||
from .networking import *
|
||||
from .output import log, LOG_LEVELS
|
||||
from .storage import storage
|
||||
|
||||
UPSTREAM_URL = 'https://raw.githubusercontent.com/Torxed/archinstall/master/profiles'
|
||||
|
||||
|
|
@ -117,7 +119,7 @@ class Profile():
|
|||
# TODO: Remove
|
||||
__builtins__['installation'] = self.installer
|
||||
with instructions as runtime:
|
||||
log(f'{self} finished successfully.', bg='black', fg='green')
|
||||
log(f'{self} finished successfully.', bg='black', fg='green', level=LOG_LEVELS.Info, file=storage.get('logfile', None))
|
||||
|
||||
return True
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
.. _help.discord:
|
||||
|
||||
Discord
|
||||
=======
|
||||
|
||||
There's a discord channel which is frequent by some `contributors <https://github.com/Torxed/archinstall/graphs/contributors>`_.
|
||||
|
||||
To join the server, head over to `discord.com/archinstall <https://discord.gg/cqXU88y>`_'s server and join in.
|
||||
There's not many rules other than common sense and treat others with respect.
|
||||
|
||||
There's the `@Party Animals` role if you want notifications of new releases which is posted in the `#Release Party` channel.
|
||||
Another thing is the `@Contributors` role which you can get by writing `!verify` and verify that you're a contributor.
|
||||
|
||||
Hop in, I hope to see you there! : )
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
.. _help.issues:
|
||||
|
||||
Issue tracker
|
||||
=============
|
||||
|
||||
Issues should be reported over at `GitHub/issues <https://github.com/Torxed/archinstall/issues>`_.
|
||||
|
||||
General questions, enhancements and security issues can be reported over there too.
|
||||
For quick issues or if you need help, head over the to the Discord server which has a help channel.
|
||||
|
||||
Submitting a help ticket
|
||||
========================
|
||||
|
||||
When submitting a help ticket, please include the *install-session_\*.log* found under *~/.cache/archinstall/* on the installation medium.
|
||||
|
||||
.. code::bash
|
||||
|
||||
cd ~/.cache/archinstall
|
||||
.
|
||||
├── install-session_2020-11-08_10-43-50.665316.log
|
||||
└── workers
|
||||
├── 1edc2abd08261603fb78a1f6083dc74654ea6625d167744221f6bd3dec4bcd5b
|
||||
├── a7c8c2ceea27df2b483c493995556c86bc3e4a1befd0f6709ef6a56ff91d23f4
|
||||
└── fadaf96c1164684cc16b374f703f7d3b959545e1ec1fb5471ace9835bf105752
|
||||
|
||||
| You can submit the *install-session_2020-11-08_10-43-50.665316.log* in this example to the support personel.
|
||||
| They might ask you for individual worker files as well, they contain the raw output from the individual commands executed such *pacman -S ...* etc.
|
||||
|
||||
.. warning::
|
||||
|
||||
Worker log-files *may* contain sensitive information such as **passwords** and **private information**. Never submit these logs without going through them manually making sure they're good for submission. Or submit parts of it that's relevant to the issue itself.
|
||||
|
|
@ -1,5 +1,15 @@
|
|||
import getpass, time, json, sys, signal, os
|
||||
import archinstall
|
||||
import getpass, time, json, sys, signal
|
||||
|
||||
# Setup a global log file.
|
||||
# Archinstall will honor storage['logfile'] in most of it's functions log handle.
|
||||
log_root = os.path.join(os.path.expanduser('~/'), '.cache/archinstall')
|
||||
if not os.path.isdir(log_root):
|
||||
os.makedirs(log_root)
|
||||
|
||||
init_time = time.strftime('%Y-%m-%d_%H-%M-%S')
|
||||
milliseconds = int(str(time.time()).split('.')[1])
|
||||
archinstall.storage['logfile'] = f"{log_root}/install-session_{init_time}.{milliseconds}.log"
|
||||
|
||||
"""
|
||||
This signal-handler chain (and global variable)
|
||||
|
|
@ -29,7 +39,7 @@ def perform_installation(device, boot_partition, language, mirrors):
|
|||
# Certain services might be running that affects the system during installation.
|
||||
# Currently, only one such service is "reflector.service" which updates /etc/pacman.d/mirrorlist
|
||||
# We need to wait for it before we continue since we opted in to use a custom mirror/region.
|
||||
archinstall.log(f'Waiting for automatic mirror selection has completed before using custom mirrors.')
|
||||
installation.log(f'Waiting for automatic mirror selection has completed before using custom mirrors.')
|
||||
while 'dead' not in (status := archinstall.service_state('reflector')):
|
||||
time.sleep(1)
|
||||
|
||||
|
|
@ -136,6 +146,9 @@ while 1:
|
|||
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():
|
||||
# TODO: See how we can incorporate this into
|
||||
# the general log flow. As this is pre-installation
|
||||
# session setup. Which creates the installation.log file.
|
||||
archinstall.log(
|
||||
' * Profile\'s preparation requirements was not fulfilled.',
|
||||
bg='black',
|
||||
|
|
@ -162,9 +175,11 @@ 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))
|
||||
archinstall.log("-- Guided template chosen (with below config) --", level=archinstall.LOG_LEVELS.Debug)
|
||||
archinstall.log(json.dumps(archinstall.storage['_guided'], indent=4, sort_keys=True, cls=archinstall.JSON), level=archinstall.LOG_LEVELS.Info)
|
||||
print()
|
||||
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue