From 727df5c13f770ec43b6132d9fc7d7a1009a06deb Mon Sep 17 00:00:00 2001 From: Softer Date: Fri, 1 May 2026 14:35:58 +0300 Subject: [PATCH 1/8] Add --share-log flag to upload install.log to paste.rs --- .github/ISSUE_TEMPLATE/01_bug.yml | 4 +- README.md | 4 +- archinstall/lib/args.py | 7 +++ archinstall/lib/share_log.py | 74 +++++++++++++++++++++++++++++++ archinstall/main.py | 8 +++- docs/help/report_bug.rst | 2 +- 6 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 archinstall/lib/share_log.py diff --git a/.github/ISSUE_TEMPLATE/01_bug.yml b/.github/ISSUE_TEMPLATE/01_bug.yml index ab40754fc9..92d8ee3fa5 100644 --- a/.github/ISSUE_TEMPLATE/01_bug.yml +++ b/.github/ISSUE_TEMPLATE/01_bug.yml @@ -41,8 +41,8 @@ body: attributes: value: > **Note**: Assuming you have network connectivity, - you can easily post the installation log using the following command: - `curl -F'file=@/var/log/archinstall/install.log' https://0x0.st` + you can easily upload the installation log and get a shareable URL by running: + `archinstall --share-log` - type: textarea id: freeform diff --git a/README.md b/README.md index 95d1570252..720e07efe6 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,9 @@ If you come across any issues, kindly submit your issue here on GitHub or post y When submitting an issue, please: * Provide the stacktrace of the output if applicable * Attach the `/var/log/archinstall/install.log` to the issue ticket. This helps us help you! - * To extract the log from the ISO image, one way is to use
+ * To upload the log from the ISO image and get a shareable URL, run
```shell - curl -F'file=@/var/log/archinstall/install.log' https://0x0.st + archinstall --share-log ``` diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index 77545736e0..a09f805d3f 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -55,6 +55,7 @@ class Arguments: skip_wifi_check: bool = False advanced: bool = False verbose: bool = False + share_log: bool = False @dataclass @@ -431,6 +432,12 @@ def _define_arguments(self) -> ArgumentParser: default=False, help='Enabled verbose options', ) + parser.add_argument( + '--share-log', + action='store_true', + default=False, + help='Upload /var/log/archinstall/install.log to paste.rs and print the URL, then exit', + ) return parser diff --git a/archinstall/lib/share_log.py b/archinstall/lib/share_log.py new file mode 100644 index 0000000000..16416feef5 --- /dev/null +++ b/archinstall/lib/share_log.py @@ -0,0 +1,74 @@ +import sys + +from archinstall.lib.command import SysCommand +from archinstall.lib.exceptions import SysCallError +from archinstall.lib.output import logger + +# paste.rs is a minimal text pastebin with syntax highlighting by extension. +# 10 MiB is its documented upload limit. +_PASTE_URL = 'https://paste.rs' +_PASTE_MAX_SIZE = 10 * 1024 * 1024 + + +def share_install_log() -> int: + """Upload /var/log/archinstall/install.log to paste.rs and print the URL. + + Intended for users to paste the URL into a GitHub issue when reporting a + bug. Always asks for explicit confirmation - the log may contain hostname, + mirror URLs, package list, partition layout and other system details which + become public on upload. + + All diagnostic output goes to stderr instead of the standard log helpers, + so the file we are about to upload is not modified by this command. + """ + log_path = logger.path + + if not log_path.exists(): + print(f'Log file not found: {log_path}', file=sys.stderr) + return 1 + + size = log_path.stat().st_size + if size == 0: + print(f'Log file is empty: {log_path}', file=sys.stderr) + return 1 + + if size > _PASTE_MAX_SIZE: + print( + f'Log file is too large to share: {size} bytes ' + f'(limit: {_PASTE_MAX_SIZE} bytes). ' + f'Trim it or upload manually.', + file=sys.stderr, + ) + return 1 + + print(f'About to upload {log_path} ({size} bytes) to {_PASTE_URL}', file=sys.stderr) + print( + 'The log may contain hostname, mirror URLs, package list and ' + 'partition layout. The uploaded paste is public.', + file=sys.stderr, + ) + + try: + answer = input('Continue? [y/N]: ').strip().lower() + except (EOFError, KeyboardInterrupt): + print(file=sys.stderr) + return 1 + + if answer not in ('y', 'yes'): + print('Cancelled.', file=sys.stderr) + return 1 + + try: + result = SysCommand(f'curl -sS --data-binary @{log_path} {_PASTE_URL}') + except SysCallError as e: + print(f'Upload failed: {e}', file=sys.stderr) + return 1 + + url = result.decode().strip() + + if not url.startswith('http'): + print(f'Unexpected response from {_PASTE_URL}: {url[:200]!r}', file=sys.stderr) + return 1 + + print(url) + return 0 diff --git a/archinstall/main.py b/archinstall/main.py index cf0e42f67b..f7a6e4a57a 100644 --- a/archinstall/main.py +++ b/archinstall/main.py @@ -16,6 +16,7 @@ from archinstall.lib.output import debug, error, info, warn from archinstall.lib.packages.util import check_version_upgrade from archinstall.lib.pacman.pacman import Pacman +from archinstall.lib.share_log import share_install_log from archinstall.lib.translationhandler import tr, translation_handler from archinstall.lib.utils.util import running_from_iso from archinstall.tui.ui.components import tui @@ -95,6 +96,9 @@ def run() -> int: print(tr('Archinstall requires root privileges to run. See --help for more.')) return 1 + if arch_config_handler.args.share_log: + return share_install_log() + translation_handler.save_console_font() _log_sys_info() @@ -141,8 +145,8 @@ def _error_message(exc: Exception) -> None: Archinstall experienced the above error. If you think this is a bug, please report it to https://github.com/archlinux/archinstall and include the log file "/var/log/archinstall/install.log". - Hint: To extract the log from a live ISO - curl -F 'file=@/var/log/archinstall/install.log' https://0x0.st + Hint: To upload the log and get a shareable URL, run + archinstall --share-log """ ) warn(text) diff --git a/docs/help/report_bug.rst b/docs/help/report_bug.rst index bacaeb4cc2..522d51a571 100644 --- a/docs/help/report_bug.rst +++ b/docs/help/report_bug.rst @@ -15,7 +15,7 @@ When submitting a help ticket, please include the :code:`/var/log/archinstall/in It can be found both on the live ISO but also in the installed filesystem if the base packages were strapped in. .. tip:: - | An easy way to submit logs is ``curl -F 'file=@/var/log/archinstall/install.log' https://0x0.st``. + | An easy way to submit logs is ``archinstall --share-log``, which uploads ``install.log`` to paste.rs and prints a shareable URL. | Use caution when submitting other log files, but ``archinstall`` pledges to keep ``install.log`` safe for posting publicly! There are additional log files under ``/var/log/archinstall/`` that can be useful: From 995ee8a84a8cc66144ea3e14cd9341350091d57d Mon Sep 17 00:00:00 2001 From: Softer Date: Fri, 1 May 2026 15:19:18 +0300 Subject: [PATCH 2/8] Apply ruff-format to share_log.py --- archinstall/lib/share_log.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/archinstall/lib/share_log.py b/archinstall/lib/share_log.py index 16416feef5..8af659b1f6 100644 --- a/archinstall/lib/share_log.py +++ b/archinstall/lib/share_log.py @@ -34,23 +34,20 @@ def share_install_log() -> int: if size > _PASTE_MAX_SIZE: print( - f'Log file is too large to share: {size} bytes ' - f'(limit: {_PASTE_MAX_SIZE} bytes). ' - f'Trim it or upload manually.', + f'Log file is too large to share: {size} bytes (limit: {_PASTE_MAX_SIZE} bytes). Trim it or upload manually.', file=sys.stderr, ) return 1 print(f'About to upload {log_path} ({size} bytes) to {_PASTE_URL}', file=sys.stderr) print( - 'The log may contain hostname, mirror URLs, package list and ' - 'partition layout. The uploaded paste is public.', + 'The log may contain hostname, mirror URLs, package list and partition layout. The uploaded paste is public.', file=sys.stderr, ) try: answer = input('Continue? [y/N]: ').strip().lower() - except (EOFError, KeyboardInterrupt): + except EOFError, KeyboardInterrupt: print(file=sys.stderr) return 1 From cd6565dc088b56efc6ad5bf76554960f1b1d4da0 Mon Sep 17 00:00:00 2001 From: Softer Date: Sun, 3 May 2026 05:11:09 +0300 Subject: [PATCH 3/8] Rework share-log per review: subcommand, TUI confirmation, truncate large logs --- archinstall/lib/args.py | 8 ---- archinstall/lib/output.py | 90 ++++++++++++++++++++++++++++++++++++ archinstall/lib/share_log.py | 71 ---------------------------- archinstall/main.py | 11 ++--- 4 files changed, 95 insertions(+), 85 deletions(-) delete mode 100644 archinstall/lib/share_log.py diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index a09f805d3f..8353ce9edb 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -55,7 +55,6 @@ class Arguments: skip_wifi_check: bool = False advanced: bool = False verbose: bool = False - share_log: bool = False @dataclass @@ -432,13 +431,6 @@ def _define_arguments(self) -> ArgumentParser: default=False, help='Enabled verbose options', ) - parser.add_argument( - '--share-log', - action='store_true', - default=False, - help='Upload /var/log/archinstall/install.log to paste.rs and print the URL, then exit', - ) - return parser def _parse_args(self) -> Arguments: diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index e01da3eb54..8f6018ec41 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -333,3 +333,93 @@ def log( if level != logging.DEBUG: print(text) + + +_PASTE_URL = 'https://paste.rs' +_PASTE_MAX_SIZE = 10 * 1024 * 1024 + + +def share_install_log() -> int: + from archinstall.lib.command import SysCommand + from archinstall.lib.exceptions import SysCallError + + log_path = logger.path + + if not log_path.exists(): + info(f'Log file not found: {log_path}') + return 1 + + size = log_path.stat().st_size + if size == 0: + info(f'Log file is empty: {log_path}') + return 1 + + if size > _PASTE_MAX_SIZE: + info(f'Log file exceeds {_PASTE_MAX_SIZE} bytes, uploading last {_PASTE_MAX_SIZE} bytes') + content = log_path.read_bytes()[-_PASTE_MAX_SIZE:] + else: + content = log_path.read_bytes() + + header = f'About to upload {log_path} ({len(content)} bytes) to {_PASTE_URL}\n\n' + header += 'The log may contain hostname, mirror URLs, package list and partition layout.\n' + header += 'The uploaded paste is public.\n\n' + header += 'Continue?' + + try: + from archinstall.tui.ui.components import tui + + confirmed: bool = tui.run(lambda: _confirm_share(header)) + except Exception: + confirmed = False + + if not confirmed: + info('Cancelled.') + return 1 + + import tempfile + + if size > _PASTE_MAX_SIZE: + fd, tmp_path_str = tempfile.mkstemp(suffix='.log') + try: + with os.fdopen(fd, 'wb') as f: + f.write(content) + upload_path = tmp_path_str + except Exception: + os.close(fd) + raise + else: + upload_path = str(log_path) + tmp_path_str = None + + try: + result = SysCommand(f'curl -sS --data-binary @{upload_path} {_PASTE_URL}') + except SysCallError as e: + info(f'Upload failed: {e}') + return 1 + finally: + if tmp_path_str: + Path(tmp_path_str).unlink(missing_ok=True) + + url = result.decode().strip() + + if not url.startswith('http'): + info(f'Unexpected response from {_PASTE_URL}: {url[:200]!r}') + return 1 + + # raw print so the URL is pipe-friendly (no ANSI colors, no log prefix) + print(url) + return 0 + + +async def _confirm_share(header: str) -> bool: + from archinstall.lib.menu.helpers import Confirmation + from archinstall.tui.ui.menu_item import MenuItemGroup + + result = await Confirmation( + group=MenuItemGroup.yes_no(), + header=header, + allow_skip=False, + preset=False, + ).show() + + return result.get_value() diff --git a/archinstall/lib/share_log.py b/archinstall/lib/share_log.py deleted file mode 100644 index 8af659b1f6..0000000000 --- a/archinstall/lib/share_log.py +++ /dev/null @@ -1,71 +0,0 @@ -import sys - -from archinstall.lib.command import SysCommand -from archinstall.lib.exceptions import SysCallError -from archinstall.lib.output import logger - -# paste.rs is a minimal text pastebin with syntax highlighting by extension. -# 10 MiB is its documented upload limit. -_PASTE_URL = 'https://paste.rs' -_PASTE_MAX_SIZE = 10 * 1024 * 1024 - - -def share_install_log() -> int: - """Upload /var/log/archinstall/install.log to paste.rs and print the URL. - - Intended for users to paste the URL into a GitHub issue when reporting a - bug. Always asks for explicit confirmation - the log may contain hostname, - mirror URLs, package list, partition layout and other system details which - become public on upload. - - All diagnostic output goes to stderr instead of the standard log helpers, - so the file we are about to upload is not modified by this command. - """ - log_path = logger.path - - if not log_path.exists(): - print(f'Log file not found: {log_path}', file=sys.stderr) - return 1 - - size = log_path.stat().st_size - if size == 0: - print(f'Log file is empty: {log_path}', file=sys.stderr) - return 1 - - if size > _PASTE_MAX_SIZE: - print( - f'Log file is too large to share: {size} bytes (limit: {_PASTE_MAX_SIZE} bytes). Trim it or upload manually.', - file=sys.stderr, - ) - return 1 - - print(f'About to upload {log_path} ({size} bytes) to {_PASTE_URL}', file=sys.stderr) - print( - 'The log may contain hostname, mirror URLs, package list and partition layout. The uploaded paste is public.', - file=sys.stderr, - ) - - try: - answer = input('Continue? [y/N]: ').strip().lower() - except EOFError, KeyboardInterrupt: - print(file=sys.stderr) - return 1 - - if answer not in ('y', 'yes'): - print('Cancelled.', file=sys.stderr) - return 1 - - try: - result = SysCommand(f'curl -sS --data-binary @{log_path} {_PASTE_URL}') - except SysCallError as e: - print(f'Upload failed: {e}', file=sys.stderr) - return 1 - - url = result.decode().strip() - - if not url.startswith('http'): - print(f'Unexpected response from {_PASTE_URL}: {url[:200]!r}', file=sys.stderr) - return 1 - - print(url) - return 0 diff --git a/archinstall/main.py b/archinstall/main.py index f7a6e4a57a..9c30e63c6c 100644 --- a/archinstall/main.py +++ b/archinstall/main.py @@ -13,10 +13,9 @@ from archinstall.lib.hardware import SysInfo from archinstall.lib.network.wifi_handler import WifiHandler from archinstall.lib.networking import ping -from archinstall.lib.output import debug, error, info, warn +from archinstall.lib.output import debug, error, info, share_install_log, warn from archinstall.lib.packages.util import check_version_upgrade from archinstall.lib.pacman.pacman import Pacman -from archinstall.lib.share_log import share_install_log from archinstall.lib.translationhandler import tr, translation_handler from archinstall.lib.utils.util import running_from_iso from archinstall.tui.ui.components import tui @@ -80,6 +79,9 @@ def run() -> int: OR straight as a module: python -m archinstall In any case we will be attempting to load the provided script to be run from the scripts/ folder """ + if 'share-log' in sys.argv: + return share_install_log() + arch_config_handler = ArchConfigHandler() if '--help' in sys.argv or '-h' in sys.argv: @@ -96,9 +98,6 @@ def run() -> int: print(tr('Archinstall requires root privileges to run. See --help for more.')) return 1 - if arch_config_handler.args.share_log: - return share_install_log() - translation_handler.save_console_font() _log_sys_info() @@ -146,7 +145,7 @@ def _error_message(exc: Exception) -> None: https://github.com/archlinux/archinstall and include the log file "/var/log/archinstall/install.log". Hint: To upload the log and get a shareable URL, run - archinstall --share-log + archinstall share-log """ ) warn(text) From 38b8d11b7c4971aa2d7e9b86559d21eefce23817 Mon Sep 17 00:00:00 2001 From: Softer Date: Mon, 4 May 2026 03:06:04 +0300 Subject: [PATCH 4/8] Replace curl/SysCommand with urllib.request, remove defensive try/except Per review: use stdlib urllib instead of shelling out to curl, drop unnecessary try/except around TUI confirmation, remove tempfile (content is passed directly as bytes). --- archinstall/lib/output.py | 38 ++++++++------------------------------ 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index 8f6018ec41..5536c0deac 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -1,6 +1,8 @@ import logging import os import sys +import urllib.error +import urllib.request from collections.abc import Callable from dataclasses import asdict, is_dataclass from datetime import UTC, datetime @@ -340,9 +342,6 @@ def log( def share_install_log() -> int: - from archinstall.lib.command import SysCommand - from archinstall.lib.exceptions import SysCallError - log_path = logger.path if not log_path.exists(): @@ -365,42 +364,21 @@ def share_install_log() -> int: header += 'The uploaded paste is public.\n\n' header += 'Continue?' - try: - from archinstall.tui.ui.components import tui + from archinstall.tui.ui.components import tui - confirmed: bool = tui.run(lambda: _confirm_share(header)) - except Exception: - confirmed = False + confirmed: bool = tui.run(lambda: _confirm_share(header)) if not confirmed: info('Cancelled.') return 1 - import tempfile - - if size > _PASTE_MAX_SIZE: - fd, tmp_path_str = tempfile.mkstemp(suffix='.log') - try: - with os.fdopen(fd, 'wb') as f: - f.write(content) - upload_path = tmp_path_str - except Exception: - os.close(fd) - raise - else: - upload_path = str(log_path) - tmp_path_str = None - try: - result = SysCommand(f'curl -sS --data-binary @{upload_path} {_PASTE_URL}') - except SysCallError as e: + req = urllib.request.Request(_PASTE_URL, data=content) + with urllib.request.urlopen(req) as response: + url = response.read().decode().strip() + except urllib.error.URLError as e: info(f'Upload failed: {e}') return 1 - finally: - if tmp_path_str: - Path(tmp_path_str).unlink(missing_ok=True) - - url = result.decode().strip() if not url.startswith('http'): info(f'Unexpected response from {_PASTE_URL}: {url[:200]!r}') From c0f023495d9738f71851b31ca046c80b6869f44f Mon Sep 17 00:00:00 2001 From: Softer Date: Mon, 4 May 2026 03:39:06 +0300 Subject: [PATCH 5/8] Fix TUI imports after ui module was pulled one level up (#4515) --- archinstall/lib/output.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index 66a0487070..dfaaf73420 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -364,7 +364,7 @@ def share_install_log() -> int: header += 'The uploaded paste is public.\n\n' header += 'Continue?' - from archinstall.tui.ui.components import tui + from archinstall.tui.components import tui confirmed: bool = tui.run(lambda: _confirm_share(header)) @@ -391,7 +391,7 @@ def share_install_log() -> int: async def _confirm_share(header: str) -> bool: from archinstall.lib.menu.helpers import Confirmation - from archinstall.tui.ui.menu_item import MenuItemGroup + from archinstall.tui.menu_item import MenuItemGroup result = await Confirmation( group=MenuItemGroup.yes_no(), From 6f84eca51c8d8ddebede88483bbd7eee56876ae5 Mon Sep 17 00:00:00 2001 From: Softer Date: Mon, 4 May 2026 14:25:57 +0300 Subject: [PATCH 6/8] Decouple share_install_log from TUI module --- archinstall/lib/output.py | 42 +++++++++++---------------------------- archinstall/main.py | 17 +++++++++++++++- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index dfaaf73420..3a972dfb08 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -337,11 +337,11 @@ def log( print(text) -_PASTE_URL = 'https://paste.rs' -_PASTE_MAX_SIZE = 10 * 1024 * 1024 - - -def share_install_log() -> int: +def share_install_log( + paste_url: str = 'https://paste.rs', + max_size: int = 10 * 1024 * 1024, + confirm: Callable[[str], bool] = lambda _: True, +) -> int: log_path = logger.path if not log_path.exists(): @@ -353,27 +353,23 @@ def share_install_log() -> int: info(f'Log file is empty: {log_path}') return 1 - if size > _PASTE_MAX_SIZE: - info(f'Log file exceeds {_PASTE_MAX_SIZE} bytes, uploading last {_PASTE_MAX_SIZE} bytes') - content = log_path.read_bytes()[-_PASTE_MAX_SIZE:] + if size > max_size: + info(f'Log file exceeds {max_size} bytes, uploading last {max_size} bytes') + content = log_path.read_bytes()[-max_size:] else: content = log_path.read_bytes() - header = f'About to upload {log_path} ({len(content)} bytes) to {_PASTE_URL}\n\n' + header = f'About to upload {log_path} ({len(content)} bytes) to {paste_url}\n\n' header += 'The log may contain hostname, mirror URLs, package list and partition layout.\n' header += 'The uploaded paste is public.\n\n' header += 'Continue?' - from archinstall.tui.components import tui - - confirmed: bool = tui.run(lambda: _confirm_share(header)) - - if not confirmed: + if not confirm(header): info('Cancelled.') return 1 try: - req = urllib.request.Request(_PASTE_URL, data=content) + req = urllib.request.Request(paste_url, data=content) with urllib.request.urlopen(req) as response: url = response.read().decode().strip() except urllib.error.URLError as e: @@ -381,23 +377,9 @@ def share_install_log() -> int: return 1 if not url.startswith('http'): - info(f'Unexpected response from {_PASTE_URL}: {url[:200]!r}') + info(f'Unexpected response from {paste_url}: {url[:200]!r}') return 1 # raw print so the URL is pipe-friendly (no ANSI colors, no log prefix) print(url) return 0 - - -async def _confirm_share(header: str) -> bool: - from archinstall.lib.menu.helpers import Confirmation - from archinstall.tui.menu_item import MenuItemGroup - - result = await Confirmation( - group=MenuItemGroup.yes_no(), - header=header, - allow_skip=False, - preset=False, - ).show() - - return result.get_value() diff --git a/archinstall/main.py b/archinstall/main.py index 6b75a0a5d4..9b91681b00 100644 --- a/archinstall/main.py +++ b/archinstall/main.py @@ -11,6 +11,7 @@ from archinstall.lib.args import ArchConfigHandler from archinstall.lib.disk.utils import disk_layouts from archinstall.lib.hardware import SysInfo +from archinstall.lib.menu.helpers import Confirmation from archinstall.lib.network.wifi_handler import WifiHandler from archinstall.lib.networking import ping from archinstall.lib.output import debug, error, info, share_install_log, warn @@ -19,6 +20,7 @@ from archinstall.lib.translationhandler import tr, translation_handler from archinstall.lib.utils.util import running_from_iso from archinstall.tui.components import tui +from archinstall.tui.menu_item import MenuItemGroup def _log_sys_info() -> None: @@ -73,6 +75,19 @@ def _list_scripts() -> str: return '\n'.join(lines) +def _tui_confirm(header: str) -> bool: + async def _ask() -> bool: + result = await Confirmation( + group=MenuItemGroup.yes_no(), + header=header, + allow_skip=False, + preset=False, + ).show() + return result.get_value() + + return tui.run(_ask) + + def run() -> int: """ This can either be run as the compiled and installed application: python setup.py install @@ -80,7 +95,7 @@ def run() -> int: In any case we will be attempting to load the provided script to be run from the scripts/ folder """ if 'share-log' in sys.argv: - return share_install_log() + return share_install_log(confirm=_tui_confirm) arch_config_handler = ArchConfigHandler() From e8e24b9dbca20f61ed54d038372131e02668369c Mon Sep 17 00:00:00 2001 From: Softer Date: Mon, 4 May 2026 19:03:26 +0300 Subject: [PATCH 7/8] Add unit tests for share_install_log function --- tests/test_share_log.py | 94 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tests/test_share_log.py diff --git a/tests/test_share_log.py b/tests/test_share_log.py new file mode 100644 index 0000000000..c1c3ca84d7 --- /dev/null +++ b/tests/test_share_log.py @@ -0,0 +1,94 @@ +# pylint: disable=redefined-outer-name +import urllib.error +from io import BytesIO +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from archinstall.lib.output import share_install_log + + +@pytest.fixture() +def log_file(tmp_path: Path) -> Path: + log_dir = tmp_path / 'archinstall' + log_dir.mkdir() + return log_dir / 'install.log' + + +def _fake_logger(log_file: Path) -> MagicMock: + mock = MagicMock() + mock.path = log_file + return mock + + +def test_file_not_found(tmp_path: Path) -> None: + missing = tmp_path / 'no-such' / 'install.log' + with patch('archinstall.lib.output.logger', _fake_logger(missing)): + assert share_install_log() == 1 + + +def test_empty_file(log_file: Path) -> None: + log_file.write_bytes(b'') + with patch('archinstall.lib.output.logger', _fake_logger(log_file)): + assert share_install_log() == 1 + + +def test_user_cancels(log_file: Path) -> None: + log_file.write_text('some log content') + with patch('archinstall.lib.output.logger', _fake_logger(log_file)): + assert share_install_log(confirm=lambda _: False) == 1 + + +def test_successful_upload(log_file: Path) -> None: + log_file.write_text('some log content') + fake_response = BytesIO(b'https://paste.rs/abc.def') + + with ( + patch('archinstall.lib.output.logger', _fake_logger(log_file)), + patch('urllib.request.urlopen', return_value=fake_response) as mock_open, + ): + result = share_install_log() + + assert result == 0 + req = mock_open.call_args[0][0] + assert req.data == b'some log content' + + +def test_truncation(log_file: Path) -> None: + max_size = 100 + content = b'A' * 50 + b'B' * 80 + log_file.write_bytes(content) + fake_response = BytesIO(b'https://paste.rs/abc.def') + + with ( + patch('archinstall.lib.output.logger', _fake_logger(log_file)), + patch('urllib.request.urlopen', return_value=fake_response) as mock_open, + ): + result = share_install_log(max_size=max_size) + + assert result == 0 + req = mock_open.call_args[0][0] + assert len(req.data) == max_size + assert req.data == content[-max_size:] + + +def test_network_error(log_file: Path) -> None: + log_file.write_text('some log content') + + with ( + patch('archinstall.lib.output.logger', _fake_logger(log_file)), + patch('urllib.request.urlopen', side_effect=urllib.error.URLError('no network')), + ): + assert share_install_log() == 1 + + +def test_unexpected_response(log_file: Path) -> None: + log_file.write_text('some log content') + fake_response = BytesIO(b'ERROR: something went wrong') + + with ( + patch('archinstall.lib.output.logger', _fake_logger(log_file)), + patch('urllib.request.urlopen', return_value=fake_response), + ): + assert share_install_log() == 1 From d7c80998f84995a6618b3217e6a75fa11f60b233 Mon Sep 17 00:00:00 2001 From: Softer Date: Wed, 6 May 2026 13:43:21 +0300 Subject: [PATCH 8/8] Update docs to use share-log subcommand syntax --- .github/ISSUE_TEMPLATE/01_bug.yml | 2 +- README.md | 2 +- docs/help/report_bug.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/01_bug.yml b/.github/ISSUE_TEMPLATE/01_bug.yml index 92d8ee3fa5..92061f6418 100644 --- a/.github/ISSUE_TEMPLATE/01_bug.yml +++ b/.github/ISSUE_TEMPLATE/01_bug.yml @@ -42,7 +42,7 @@ body: value: > **Note**: Assuming you have network connectivity, you can easily upload the installation log and get a shareable URL by running: - `archinstall --share-log` + `archinstall share-log` - type: textarea id: freeform diff --git a/README.md b/README.md index 720e07efe6..4e6847f406 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ When submitting an issue, please: * Attach the `/var/log/archinstall/install.log` to the issue ticket. This helps us help you! * To upload the log from the ISO image and get a shareable URL, run
```shell - archinstall --share-log + archinstall share-log ``` diff --git a/docs/help/report_bug.rst b/docs/help/report_bug.rst index 522d51a571..b11027ab3e 100644 --- a/docs/help/report_bug.rst +++ b/docs/help/report_bug.rst @@ -15,7 +15,7 @@ When submitting a help ticket, please include the :code:`/var/log/archinstall/in It can be found both on the live ISO but also in the installed filesystem if the base packages were strapped in. .. tip:: - | An easy way to submit logs is ``archinstall --share-log``, which uploads ``install.log`` to paste.rs and prints a shareable URL. + | An easy way to submit logs is ``archinstall share-log``, which uploads ``install.log`` to paste.rs and prints a shareable URL. | Use caution when submitting other log files, but ``archinstall`` pledges to keep ``install.log`` safe for posting publicly! There are additional log files under ``/var/log/archinstall/`` that can be useful: