archivebox_install.py 8.1 KB

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