archivebox_install.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. #!/usr/bin/env python3
  2. __package__ = 'archivebox.cli'
  3. __command__ = 'archivebox install'
  4. import os
  5. import sys
  6. from typing import Optional, List
  7. import rich_click as click
  8. from rich import print
  9. from archivebox.misc.util import docstring, enforce_types
  10. @enforce_types
  11. def install(binproviders: Optional[List[str]]=None, binaries: Optional[List[str]]=None, dry_run: bool=False) -> None:
  12. """Automatically install all ArchiveBox dependencies and extras"""
  13. # if running as root:
  14. # - run init to create index + lib dir
  15. # - chown -R 911 DATA_DIR
  16. # - install all binaries as root
  17. # - chown -R 911 LIB_DIR
  18. # else:
  19. # - run init to create index + lib dir as current user
  20. # - install all binaries as current user
  21. # - recommend user re-run with sudo if any deps need to be installed as root
  22. import abx
  23. import archivebox
  24. from archivebox.config.permissions import IS_ROOT, ARCHIVEBOX_USER, ARCHIVEBOX_GROUP, SudoPermission
  25. from archivebox.config.paths import DATA_DIR, ARCHIVE_DIR, get_or_create_working_lib_dir
  26. from archivebox.misc.logging import stderr
  27. from archivebox.cli.archivebox_init import init
  28. from archivebox.misc.system import run as run_shell
  29. if not (os.access(ARCHIVE_DIR, os.R_OK) and ARCHIVE_DIR.is_dir()):
  30. init() # must init full index because we need a db to store InstalledBinary entries in
  31. print('\n[green][+] Installing ArchiveBox dependencies automatically...[/green]')
  32. # we never want the data dir to be owned by root, detect owner of existing owner of DATA_DIR to try and guess desired non-root UID
  33. if IS_ROOT:
  34. EUID = os.geteuid()
  35. # if we have sudo/root permissions, take advantage of them just while installing dependencies
  36. print()
  37. print(f'[yellow]:warning: Running as UID=[blue]{EUID}[/blue] with [red]sudo[/red] only for dependencies that need it.[/yellow]')
  38. print(f' DATA_DIR, LIB_DIR, and TMP_DIR will be owned by [blue]{ARCHIVEBOX_USER}:{ARCHIVEBOX_GROUP}[/blue].')
  39. print()
  40. LIB_DIR = get_or_create_working_lib_dir()
  41. package_manager_names = ', '.join(
  42. f'[yellow]{binprovider.name}[/yellow]'
  43. for binprovider in reversed(list(abx.as_dict(abx.pm.hook.get_BINPROVIDERS()).values()))
  44. if not binproviders or (binproviders and binprovider.name in binproviders)
  45. )
  46. print(f'[+] Setting up package managers {package_manager_names}...')
  47. for binprovider in reversed(list(abx.as_dict(abx.pm.hook.get_BINPROVIDERS()).values())):
  48. if binproviders and binprovider.name not in binproviders:
  49. continue
  50. try:
  51. binprovider.setup()
  52. except Exception:
  53. # it's ok, installing binaries below will automatically set up package managers as needed
  54. # e.g. if user does not have npm available we cannot set it up here yet, but once npm Binary is installed
  55. # the next package that depends on npm will automatically call binprovider.setup() during its own install
  56. pass
  57. print()
  58. for binary in reversed(list(abx.as_dict(abx.pm.hook.get_BINARIES()).values())):
  59. if binary.name in ('archivebox', 'django', 'sqlite', 'python'):
  60. # obviously must already be installed if we are running
  61. continue
  62. if binaries and binary.name not in binaries:
  63. continue
  64. providers = ' [grey53]or[/grey53] '.join(
  65. provider.name for provider in binary.binproviders_supported
  66. if not binproviders or (binproviders and provider.name in binproviders)
  67. )
  68. if not providers:
  69. continue
  70. print(f'[+] Detecting / Installing [yellow]{binary.name.ljust(22)}[/yellow] using [red]{providers}[/red]...')
  71. try:
  72. with SudoPermission(uid=0, fallback=True):
  73. # print(binary.load_or_install(fresh=True).model_dump(exclude={'overrides', 'bin_dir', 'hook_type'}))
  74. if binproviders:
  75. providers_supported_by_binary = [provider.name for provider in binary.binproviders_supported]
  76. for binprovider_name in binproviders:
  77. if binprovider_name not in providers_supported_by_binary:
  78. continue
  79. try:
  80. if dry_run:
  81. # always show install commands when doing a dry run
  82. sys.stderr.write("\033[2;49;90m") # grey53
  83. result = binary.install(binproviders=[binprovider_name], dry_run=dry_run).model_dump(exclude={'overrides', 'bin_dir', 'hook_type'})
  84. sys.stderr.write("\033[00m\n") # reset
  85. else:
  86. loaded_binary = archivebox.pm.hook.binary_load_or_install(binary=binary, binproviders=[binprovider_name], fresh=True, dry_run=dry_run, quiet=False)
  87. result = loaded_binary.model_dump(exclude={'overrides', 'bin_dir', 'hook_type'})
  88. if result and result['loaded_version']:
  89. break
  90. except Exception as e:
  91. print(f'[red]:cross_mark: Failed to install {binary.name} as using {binprovider_name} as user {ARCHIVEBOX_USER}: {e}[/red]')
  92. else:
  93. if dry_run:
  94. sys.stderr.write("\033[2;49;90m") # grey53
  95. binary.install(dry_run=dry_run).model_dump(exclude={'overrides', 'bin_dir', 'hook_type'})
  96. sys.stderr.write("\033[00m\n") # reset
  97. else:
  98. loaded_binary = archivebox.pm.hook.binary_load_or_install(binary=binary, fresh=True, dry_run=dry_run)
  99. result = loaded_binary.model_dump(exclude={'overrides', 'bin_dir', 'hook_type'})
  100. if IS_ROOT and LIB_DIR:
  101. with SudoPermission(uid=0):
  102. if ARCHIVEBOX_USER == 0:
  103. os.system(f'chmod -R 777 "{LIB_DIR.resolve()}"')
  104. else:
  105. os.system(f'chown -R {ARCHIVEBOX_USER} "{LIB_DIR.resolve()}"')
  106. except Exception as e:
  107. print(f'[red]:cross_mark: Failed to install {binary.name} as user {ARCHIVEBOX_USER}: {e}[/red]')
  108. if binaries and len(binaries) == 1:
  109. # if we are only installing a single binary, raise the exception so the user can see what went wrong
  110. raise
  111. from django.contrib.auth import get_user_model
  112. User = get_user_model()
  113. if not User.objects.filter(is_superuser=True).exclude(username='system').exists():
  114. stderr('\n[+] Don\'t forget to create a new admin user for the Web UI...', color='green')
  115. stderr(' archivebox manage createsuperuser')
  116. # run_subcommand('manage', subcommand_args=['createsuperuser'], pwd=out_dir)
  117. print('\n[green][√] Set up ArchiveBox and its dependencies successfully.[/green]\n', file=sys.stderr)
  118. from abx_plugin_pip.binaries import ARCHIVEBOX_BINARY
  119. extra_args = []
  120. if binproviders:
  121. extra_args.append(f'--binproviders={",".join(binproviders)}')
  122. if binaries:
  123. extra_args.append(f'--binaries={",".join(binaries)}')
  124. proc = run_shell([ARCHIVEBOX_BINARY.load().abspath, 'version', *extra_args], capture_output=False, cwd=DATA_DIR)
  125. raise SystemExit(proc.returncode)
  126. @click.command()
  127. @click.option('--binproviders', '-p', type=str, help='Select binproviders to use DEFAULT=env,apt,brew,sys_pip,venv_pip,lib_pip,pipx,sys_npm,lib_npm,puppeteer,playwright (all)', default=None)
  128. @click.option('--binaries', '-b', type=str, help='Select binaries to install DEFAULT=curl,wget,git,yt-dlp,chrome,single-file,readability-extractor,postlight-parser,... (all)', default=None)
  129. @click.option('--dry-run', '-d', is_flag=True, help='Show what would be installed without actually installing anything', default=False)
  130. @docstring(install.__doc__)
  131. def main(**kwargs) -> None:
  132. install(**kwargs)
  133. if __name__ == '__main__':
  134. main()