diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index 19e6050c25..ee1a571466 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -22,9 +22,8 @@ from archinstall.lib.models.packages import Repository from archinstall.lib.models.profile_model import ProfileConfiguration from archinstall.lib.models.users import Password, User -from archinstall.lib.output import debug, error, warn +from archinstall.lib.output import debug, error, logger, warn from archinstall.lib.plugins import load_plugin -from archinstall.lib.storage import storage from archinstall.lib.translationhandler import Language, tr, translation_handler from archinstall.lib.utils.util import get_password from archinstall.tui.curses_menu import Tui @@ -389,7 +388,7 @@ def _parse_args(self) -> Arguments: args.silent = False if args.debug: - warn(f'Warning: --debug mode will write certain credentials to {storage["LOG_PATH"]}/{storage["LOG_FILE"]}!') + warn(f'Warning: --debug mode will write certain credentials to {logger.path}!') if args.plugin: plugin_path = Path(args.plugin) diff --git a/archinstall/lib/configuration.py b/archinstall/lib/configuration.py index 678d54f377..63b68b9ce4 100644 --- a/archinstall/lib/configuration.py +++ b/archinstall/lib/configuration.py @@ -12,8 +12,7 @@ from .args import ArchConfig from .crypt import encrypt from .general import JSON, UNSAFE_JSON -from .output import debug, warn -from .storage import storage +from .output import debug, logger, warn from .utils.util import get_password, prompt_dir @@ -29,7 +28,7 @@ def __init__(self, config: ArchConfig): """ self._config = config - self._default_save_path = storage.get('LOG_PATH', Path('.')) + self._default_save_path = logger.directory self._user_config_file = Path('user_configuration.json') self._user_creds_file = Path('user_credentials.json') diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index 006bee3bac..056e0133e2 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -19,8 +19,7 @@ from typing import Any, override from .exceptions import RequirementError, SysCallError -from .output import debug, error -from .storage import storage +from .output import debug, error, logger # https://stackoverflow.com/a/43627833/929999 _VT100_ESCAPE_REGEX = r'\x1B\[[?0-9;]*[a-zA-Z]' @@ -415,7 +414,7 @@ def trace_log(self) -> bytes | None: def _append_log(file: str, content: str) -> None: - path = Path(f'{storage["LOG_PATH"]}/{file}') + path = logger.directory / file change_perm = not path.exists() diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 7d918511fd..7c0f98af97 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -44,7 +44,7 @@ from .models.mirrors import MirrorConfiguration from .models.network_configuration import Nic from .models.users import User -from .output import debug, error, info, log, warn +from .output import debug, error, info, log, logger, warn from .pacman import Pacman from .pacman.config import PacmanConfig from .plugins import plugins @@ -132,13 +132,12 @@ def __exit__(self, exc_type: type[BaseException] | None, exc_val, exc_tb: Traceb # We avoid printing /mnt/ because that might confuse people if they note it down # and then reboot, and a identical log file will be found in the ISO medium anyway. - log_file = os.path.join(storage['LOG_PATH'], storage['LOG_FILE']) - Tui.print(str(tr('[!] A log file has been created here: {}').format(log_file))) + Tui.print(str(tr('[!] A log file has been created here: {}').format(logger.path))) Tui.print(tr('Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues')) raise exc_val if not (missing_steps := self.post_install_check()): - msg = f'Installation completed without any errors.\nLog files temporarily available at {storage["LOG_PATH"]}.\nYou may reboot when ready.\n' + msg = f'Installation completed without any errors.\nLog files temporarily available at {logger.directory}.\nYou may reboot when ready.\n' log(msg, fg='green') self.sync_log_to_install_medium() return True @@ -148,7 +147,7 @@ def __exit__(self, exc_type: type[BaseException] | None, exc_val, exc_tb: Traceb for step in missing_steps: warn(f' - {step}') - warn(f'Detailed error logs can be found at: {storage["LOG_PATH"]}') + warn(f'Detailed error logs can be found at: {logger.directory}') warn('Submit this zip file as an issue to https://github.com/archlinux/archinstall/issues') self.sync_log_to_install_medium() @@ -456,13 +455,12 @@ def sync_log_to_install_medium(self) -> bool: # Copy over the install log (if there is one) to the install medium if # at least the base has been strapped in, otherwise we won't have a filesystem/structure to copy to. if self._helper_flags.get('base-strapped', False) is True: - if filename := storage.get('LOG_FILE', None): - absolute_logfile = os.path.join(storage.get('LOG_PATH', './'), filename) + absolute_logfile = logger.path - if not os.path.isdir(f'{self.target}/{os.path.dirname(absolute_logfile)}'): - os.makedirs(f'{self.target}/{os.path.dirname(absolute_logfile)}') + if not os.path.isdir(f'{self.target}/{os.path.dirname(absolute_logfile)}'): + os.makedirs(f'{self.target}/{os.path.dirname(absolute_logfile)}') - shutil.copy2(absolute_logfile, f'{self.target}/{absolute_logfile}') + shutil.copy2(absolute_logfile, f'{self.target}/{absolute_logfile}') return True diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index c81b11a611..3bf9902718 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -8,7 +8,6 @@ from pathlib import Path from typing import TYPE_CHECKING, Any -from .storage import storage from .utils.unicode import unicode_ljust, unicode_rjust if TYPE_CHECKING: @@ -147,30 +146,46 @@ def log(message: str, level: int = logging.DEBUG) -> None: log_adapter.log(level, message) -def _check_log_permissions() -> None: - filename = storage.get('LOG_FILE', None) - log_dir = storage.get('LOG_PATH', Path('./')) +class Logger: + def __init__(self, path: Path = Path('/var/log/archinstall')) -> None: + self._path = path - if not filename: - raise ValueError('No log file name defined') + @property + def path(self) -> Path: + return self._path / 'install.log' - log_file = log_dir / filename + @property + def directory(self) -> Path: + return self._path - try: - log_dir.mkdir(exist_ok=True, parents=True) - log_file.touch(exist_ok=True) + def _check_permissions(self) -> None: + log_file = self.path - with log_file.open('a') as fp: - fp.write('') - except PermissionError: - # Fallback to creating the log file in the current folder - fallback_dir = Path('./').absolute() - fallback_log_file = fallback_dir / filename + try: + self._path.mkdir(exist_ok=True, parents=True) + log_file.touch(exist_ok=True) + + with log_file.open('a') as f: + f.write('') + except PermissionError: + # Fallback to creating the log file in the current folder + logger._path = Path('./').absolute() + + warn( + f'Not enough permission to place log file at {log_file},', + 'creating it in {logger.path} instead' + ) + + def log(self, level: int, content: str) -> None: + self._check_permissions() - fallback_log_file.touch(exist_ok=True) + with self.path.open('a') as f: + ts = _timestamp() + level_name = logging.getLevelName(level) + f.write(f'[{ts}] - {level_name} - {content}\n') - storage['LOG_PATH'] = fallback_dir - warn(f'Not enough permission to place log file at {log_file}, creating it in {fallback_log_file} instead') + +logger = Logger() def _supports_color() -> bool: @@ -309,25 +324,15 @@ def log( reset: bool = False, font: list[Font] = [], ) -> None: - # leave this check here as we need to setup the logging - # right from the beginning when the modules are loaded - _check_log_permissions() + text = ' '.join([str(x) for x in msgs]) - text = orig_string = ' '.join([str(x) for x in msgs]) + logger.log(level, text) # Attempt to colorize the output if supported # Insert default colors and override with **kwargs if _supports_color(): text = _stylize_output(text, fg, bg, reset, font) - log_file = storage['LOG_PATH'] / storage['LOG_FILE'] - - with log_file.open('a') as fp: - ts = _timestamp() - level_name = logging.getLevelName(level) - out = f'[{ts}] - {level_name} - {orig_string}\n' - fp.write(out) - Journald.log(text, level=level) if level != logging.DEBUG: diff --git a/archinstall/lib/storage.py b/archinstall/lib/storage.py index 7c89609a8d..68861cac64 100644 --- a/archinstall/lib/storage.py +++ b/archinstall/lib/storage.py @@ -5,7 +5,6 @@ # (4. Added the ~/.config directory as an additional option for future reasons) # # And Keeping this in dict ensures that variables are shared across imports. -from pathlib import Path from typing import TYPE_CHECKING, NotRequired, TypedDict if TYPE_CHECKING: @@ -14,13 +13,8 @@ class _StorageDict(TypedDict): - LOG_FILE: Path - LOG_PATH: Path active_boot: NotRequired['Boot | None'] installation_session: NotRequired['Installer'] -storage: _StorageDict = { - 'LOG_FILE': Path('install.log'), - 'LOG_PATH': Path('/var/log/archinstall'), -} +storage: _StorageDict = {}