archivebox_version.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. #!/usr/bin/env python3
  2. __package__ = 'archivebox.cli'
  3. import sys
  4. from typing import Iterable
  5. import rich_click as click
  6. from archivebox.misc.util import docstring, enforce_types
  7. @enforce_types
  8. def version(quiet: bool=False,
  9. binproviders: Iterable[str]=(),
  10. binaries: Iterable[str]=()) -> list[str]:
  11. """Print the ArchiveBox version, debug metadata, and installed dependency versions"""
  12. # fast path for just getting the version and exiting, dont do any slower imports
  13. from archivebox.config.version import VERSION
  14. print(VERSION)
  15. if quiet or '--version' in sys.argv:
  16. return []
  17. # Only do slower imports when getting full version info
  18. import os
  19. import platform
  20. from pathlib import Path
  21. from rich.panel import Panel
  22. from rich.console import Console
  23. from abx_pkg import Binary
  24. import abx
  25. import archivebox
  26. from archivebox.config import CONSTANTS, DATA_DIR
  27. from archivebox.config.version import get_COMMIT_HASH, get_BUILD_TIME
  28. from archivebox.config.permissions import ARCHIVEBOX_USER, ARCHIVEBOX_GROUP, RUNNING_AS_UID, RUNNING_AS_GID, IN_DOCKER
  29. from archivebox.config.paths import get_data_locations, get_code_locations
  30. from archivebox.config.common import SHELL_CONFIG, STORAGE_CONFIG, SEARCH_BACKEND_CONFIG
  31. from archivebox.misc.logging_util import printable_folder_status
  32. from abx_plugin_default_binproviders import apt, brew, env
  33. console = Console()
  34. prnt = console.print
  35. LDAP_ENABLED = archivebox.pm.hook.get_SCOPE_CONFIG().LDAP_ENABLED
  36. # 0.7.1
  37. # ArchiveBox v0.7.1+editable COMMIT_HASH=951bba5 BUILD_TIME=2023-12-17 16:46:05 1702860365
  38. # IN_DOCKER=False IN_QEMU=False ARCH=arm64 OS=Darwin PLATFORM=macOS-14.2-arm64-arm-64bit PYTHON=Cpython
  39. # FS_ATOMIC=True FS_REMOTE=False FS_USER=501:20 FS_PERMS=644
  40. # DEBUG=False IS_TTY=True TZ=UTC SEARCH_BACKEND=ripgrep LDAP=False
  41. p = platform.uname()
  42. COMMIT_HASH = get_COMMIT_HASH()
  43. prnt(
  44. '[dark_green]ArchiveBox[/dark_green] [dark_goldenrod]v{}[/dark_goldenrod]'.format(CONSTANTS.VERSION),
  45. f'COMMIT_HASH={COMMIT_HASH[:7] if COMMIT_HASH else "unknown"}',
  46. f'BUILD_TIME={get_BUILD_TIME()}',
  47. )
  48. prnt(
  49. f'IN_DOCKER={IN_DOCKER}',
  50. f'IN_QEMU={SHELL_CONFIG.IN_QEMU}',
  51. f'ARCH={p.machine}',
  52. f'OS={p.system}',
  53. f'PLATFORM={platform.platform()}',
  54. f'PYTHON={sys.implementation.name.title()}' + (' (venv)' if CONSTANTS.IS_INSIDE_VENV else ''),
  55. )
  56. OUTPUT_IS_REMOTE_FS = get_data_locations().DATA_DIR.is_mount or get_data_locations().ARCHIVE_DIR.is_mount
  57. DATA_DIR_STAT = CONSTANTS.DATA_DIR.stat()
  58. prnt(
  59. f'EUID={os.geteuid()}:{os.getegid()} UID={RUNNING_AS_UID}:{RUNNING_AS_GID} PUID={ARCHIVEBOX_USER}:{ARCHIVEBOX_GROUP}',
  60. f'FS_UID={DATA_DIR_STAT.st_uid}:{DATA_DIR_STAT.st_gid}',
  61. f'FS_PERMS={STORAGE_CONFIG.OUTPUT_PERMISSIONS}',
  62. f'FS_ATOMIC={STORAGE_CONFIG.ENFORCE_ATOMIC_WRITES}',
  63. f'FS_REMOTE={OUTPUT_IS_REMOTE_FS}',
  64. )
  65. prnt(
  66. f'DEBUG={SHELL_CONFIG.DEBUG}',
  67. f'IS_TTY={SHELL_CONFIG.IS_TTY}',
  68. f'SUDO={CONSTANTS.IS_ROOT}',
  69. f'ID={CONSTANTS.MACHINE_ID}:{CONSTANTS.COLLECTION_ID}',
  70. f'SEARCH_BACKEND={SEARCH_BACKEND_CONFIG.SEARCH_BACKEND_ENGINE}',
  71. f'LDAP={LDAP_ENABLED}',
  72. #f'DB=django.db.backends.sqlite3 (({CONFIG["SQLITE_JOURNAL_MODE"]})', # add this if we have more useful info to show eventually
  73. )
  74. prnt()
  75. if not (os.access(CONSTANTS.ARCHIVE_DIR, os.R_OK) and os.access(CONSTANTS.CONFIG_FILE, os.R_OK)):
  76. PANEL_TEXT = '\n'.join((
  77. # '',
  78. # f'[yellow]CURRENT DIR =[/yellow] [red]{os.getcwd()}[/red]',
  79. '',
  80. '[violet]Hint:[/violet] [green]cd[/green] into a collection [blue]DATA_DIR[/blue] and run [green]archivebox version[/green] again...',
  81. ' [grey53]OR[/grey53] run [green]archivebox init[/green] to create a new collection in the current dir.',
  82. '',
  83. ' [i][grey53](this is [red]REQUIRED[/red] if you are opening a Github Issue to get help)[/grey53][/i]',
  84. '',
  85. ))
  86. prnt(Panel(PANEL_TEXT, expand=False, border_style='grey53', title='[red]:exclamation: No collection [blue]DATA_DIR[/blue] is currently active[/red]', subtitle='Full version info is only available when inside a collection [light_slate_blue]DATA DIR[/light_slate_blue]'))
  87. prnt()
  88. return []
  89. prnt('[pale_green1][i] Binary Dependencies:[/pale_green1]')
  90. failures = []
  91. BINARIES = abx.as_dict(archivebox.pm.hook.get_BINARIES())
  92. for name, binary in list(BINARIES.items()):
  93. if binary.name == 'archivebox':
  94. continue
  95. # skip if the binary is not in the requested list of binaries
  96. if binaries and binary.name not in binaries:
  97. continue
  98. # skip if the binary is not supported by any of the requested binproviders
  99. if binproviders and binary.binproviders_supported and not any(provider.name in binproviders for provider in binary.binproviders_supported):
  100. continue
  101. err = None
  102. try:
  103. loaded_bin = binary.load()
  104. except Exception as e:
  105. err = e
  106. loaded_bin = binary
  107. provider_summary = f'[dark_sea_green3]{loaded_bin.binprovider.name.ljust(10)}[/dark_sea_green3]' if loaded_bin.binprovider else '[grey23]not found[/grey23] '
  108. if loaded_bin.abspath:
  109. abspath = str(loaded_bin.abspath).replace(str(DATA_DIR), '[light_slate_blue].[/light_slate_blue]').replace(str(Path('~').expanduser()), '~')
  110. if ' ' in abspath:
  111. abspath = abspath.replace(' ', r'\ ')
  112. else:
  113. abspath = f'[red]{err}[/red]'
  114. prnt('', '[green]√[/green]' if loaded_bin.is_valid else '[red]X[/red]', '', loaded_bin.name.ljust(21), str(loaded_bin.version).ljust(12), provider_summary, abspath, overflow='ignore', crop=False)
  115. if not loaded_bin.is_valid:
  116. failures.append(loaded_bin.name)
  117. prnt()
  118. prnt('[gold3][i] Package Managers:[/gold3]')
  119. BINPROVIDERS = abx.as_dict(archivebox.pm.hook.get_BINPROVIDERS())
  120. for name, binprovider in list(BINPROVIDERS.items()):
  121. err = None
  122. if binproviders and binprovider.name not in binproviders:
  123. continue
  124. # TODO: implement a BinProvider.BINARY() method that gets the loaded binary for a binprovider's INSTALLER_BIN
  125. loaded_bin = binprovider.INSTALLER_BINARY or Binary(name=binprovider.INSTALLER_BIN, binproviders=[env, apt, brew])
  126. abspath = str(loaded_bin.abspath).replace(str(DATA_DIR), '[light_slate_blue].[/light_slate_blue]').replace(str(Path('~').expanduser()), '~')
  127. abspath = None
  128. if loaded_bin.abspath:
  129. abspath = str(loaded_bin.abspath).replace(str(DATA_DIR), '.').replace(str(Path('~').expanduser()), '~')
  130. if ' ' in abspath:
  131. abspath = abspath.replace(' ', r'\ ')
  132. PATH = str(binprovider.PATH).replace(str(DATA_DIR), '[light_slate_blue].[/light_slate_blue]').replace(str(Path('~').expanduser()), '~')
  133. ownership_summary = f'UID=[blue]{str(binprovider.EUID).ljust(4)}[/blue]'
  134. provider_summary = f'[dark_sea_green3]{str(abspath).ljust(52)}[/dark_sea_green3]' if abspath else f'[grey23]{"not available".ljust(52)}[/grey23]'
  135. prnt('', '[green]√[/green]' if binprovider.is_valid else '[grey53]-[/grey53]', '', binprovider.name.ljust(11), provider_summary, ownership_summary, f'PATH={PATH}', overflow='ellipsis', soft_wrap=True)
  136. if not (binaries or binproviders):
  137. # dont show source code / data dir info if we just want to get version info for a binary or binprovider
  138. prnt()
  139. prnt('[deep_sky_blue3][i] Code locations:[/deep_sky_blue3]')
  140. for name, path in get_code_locations().items():
  141. prnt(printable_folder_status(name, path), overflow='ignore', crop=False)
  142. prnt()
  143. if os.access(CONSTANTS.ARCHIVE_DIR, os.R_OK) or os.access(CONSTANTS.CONFIG_FILE, os.R_OK):
  144. prnt('[bright_yellow][i] Data locations:[/bright_yellow]')
  145. for name, path in get_data_locations().items():
  146. prnt(printable_folder_status(name, path), overflow='ignore', crop=False)
  147. from archivebox.misc.checks import check_data_dir_permissions
  148. check_data_dir_permissions()
  149. else:
  150. prnt()
  151. prnt('[red][i] Data locations:[/red] (not in a data directory)')
  152. prnt()
  153. if failures:
  154. prnt('[red]Error:[/red] [yellow]Failed to detect the following binaries:[/yellow]')
  155. prnt(f' [red]{", ".join(failures)}[/red]')
  156. prnt()
  157. prnt('[violet]Hint:[/violet] To install missing binaries automatically, run:')
  158. prnt(' [green]archivebox install[/green]')
  159. prnt()
  160. return failures
  161. @click.command()
  162. @click.option('--quiet', '-q', is_flag=True, help='Only print ArchiveBox version number and nothing else. (equivalent to archivebox --version)')
  163. @click.option('--binproviders', '-p', help='Select binproviders to detect DEFAULT=env,apt,brew,sys_pip,venv_pip,lib_pip,pipx,sys_npm,lib_npm,puppeteer,playwright (all)')
  164. @click.option('--binaries', '-b', help='Select binaries to detect DEFAULT=curl,wget,git,yt-dlp,chrome,single-file,readability-extractor,postlight-parser,... (all)')
  165. @docstring(version.__doc__)
  166. def main(**kwargs):
  167. failures = version(**kwargs)
  168. if failures:
  169. raise SystemExit(1)
  170. if __name__ == '__main__':
  171. main()