apps.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import os
  2. import sys
  3. import inspect
  4. from pathlib import Path
  5. from typing import List, Dict, Optional
  6. from pydantic import InstanceOf, Field
  7. import django
  8. from django.db.backends.sqlite3.base import Database as django_sqlite3 # type: ignore[import-type]
  9. from django.core.checks import Error, Tags
  10. from django.conf import settings
  11. from pydantic_pkgr import BinProvider, PipProvider, BinName, BinProviderName, ProviderLookupDict, SemVer
  12. from plugantic.base_plugin import BasePlugin
  13. from plugantic.base_configset import BaseConfigSet, ConfigSectionName
  14. from plugantic.base_check import BaseCheck
  15. from plugantic.base_binary import BaseBinary, BaseBinProvider, env, apt, brew
  16. from plugantic.base_hook import BaseHook
  17. ###################### Config ##########################
  18. class PipDependencyConfigs(BaseConfigSet):
  19. section: ConfigSectionName = 'DEPENDENCY_CONFIG'
  20. USE_PIP: bool = True
  21. PIP_BINARY: str = Field(default='pip')
  22. PIP_ARGS: Optional[List[str]] = Field(default=None)
  23. PIP_EXTRA_ARGS: List[str] = []
  24. PIP_DEFAULT_ARGS: List[str] = []
  25. DEFAULT_GLOBAL_CONFIG = {
  26. }
  27. PIP_CONFIG = PipDependencyConfigs(**DEFAULT_GLOBAL_CONFIG)
  28. class SystemPipBinProvider(PipProvider, BaseBinProvider):
  29. name: BinProviderName = "pip"
  30. INSTALLER_BIN: BinName = "pip"
  31. pip_venv: Optional[Path] = None # global pip scope
  32. class SystemPipxBinProvider(PipProvider, BaseBinProvider):
  33. name: BinProviderName = "pipx"
  34. INSTALLER_BIN: BinName = "pipx"
  35. class LibPipBinProvider(PipProvider, BaseBinProvider):
  36. name: BinProviderName = "lib_pip"
  37. INSTALLER_BIN: BinName = "pip"
  38. pip_venv: Optional[Path] = settings.CONFIG.OUTPUT_DIR / 'lib' / 'pip' / 'venv'
  39. SYS_PIP_BINPROVIDER = SystemPipBinProvider()
  40. SYS_PIPX_BINPROVIDER = SystemPipxBinProvider()
  41. LIB_PIP_BINPROVIDER = LibPipBinProvider()
  42. pip = LIB_PIP_BINPROVIDER
  43. class PythonBinary(BaseBinary):
  44. name: BinName = 'python'
  45. binproviders_supported: List[InstanceOf[BinProvider]] = [SYS_PIP_BINPROVIDER, apt, brew, env]
  46. provider_overrides: Dict[BinProviderName, ProviderLookupDict] = {
  47. SYS_PIP_BINPROVIDER.name: {
  48. 'abspath': lambda:
  49. sys.executable,
  50. 'version': lambda:
  51. '{}.{}.{}'.format(*sys.version_info[:3]),
  52. },
  53. }
  54. PYTHON_BINARY = PythonBinary()
  55. class SqliteBinary(BaseBinary):
  56. name: BinName = 'sqlite'
  57. binproviders_supported: List[InstanceOf[BaseBinProvider]] = Field(default=[SYS_PIP_BINPROVIDER])
  58. provider_overrides: Dict[BinProviderName, ProviderLookupDict] = {
  59. SYS_PIP_BINPROVIDER.name: {
  60. 'abspath': lambda:
  61. Path(inspect.getfile(django_sqlite3)),
  62. 'version': lambda:
  63. SemVer(django_sqlite3.version),
  64. },
  65. }
  66. SQLITE_BINARY = SqliteBinary()
  67. class DjangoBinary(BaseBinary):
  68. name: BinName = 'django'
  69. binproviders_supported: List[InstanceOf[BaseBinProvider]] = Field(default=[SYS_PIP_BINPROVIDER])
  70. provider_overrides: Dict[BinProviderName, ProviderLookupDict] = {
  71. SYS_PIP_BINPROVIDER.name: {
  72. 'abspath': lambda:
  73. inspect.getfile(django),
  74. 'version': lambda:
  75. django.VERSION[:3],
  76. },
  77. }
  78. DJANGO_BINARY = DjangoBinary()
  79. class PipBinary(BaseBinary):
  80. name: BinName = "pip"
  81. binproviders_supported: List[InstanceOf[BinProvider]] = [LIB_PIP_BINPROVIDER, SYS_PIP_BINPROVIDER, apt, brew, env]
  82. PIP_BINARY = PipBinary()
  83. class CheckUserIsNotRoot(BaseCheck):
  84. label: str = 'CheckUserIsNotRoot'
  85. tag: str = Tags.database
  86. @staticmethod
  87. def check(settings, logger) -> List[Warning]:
  88. errors = []
  89. if getattr(settings, "USER", None) == 'root' or getattr(settings, "PUID", None) == 0:
  90. errors.append(
  91. Error(
  92. "Cannot run as root!",
  93. id="core.S001",
  94. hint=f'Run ArchiveBox as a non-root user with a UID greater than 500. (currently running as UID {os.getuid()}).',
  95. )
  96. )
  97. logger.debug('[√] UID is not root')
  98. return errors
  99. class CheckPipEnvironment(BaseCheck):
  100. label: str = "CheckPipEnvironment"
  101. tag: str = Tags.database
  102. @staticmethod
  103. def check(settings, logger) -> List[Warning]:
  104. errors = []
  105. LIB_PIP_BINPROVIDER.setup()
  106. if not LIB_PIP_BINPROVIDER.INSTALLER_BIN_ABSPATH:
  107. errors.append(
  108. Error(
  109. "Failed to setup data/lib/pip virtualenv for runtime dependencies!",
  110. id="pip.P001",
  111. hint="Make sure the data dir is writable and make sure python3-pip and python3-venv are installed & available on the host.",
  112. )
  113. )
  114. logger.debug("[√] CheckPipEnvironment: data/lib/pip virtualenv is setup properly")
  115. return errors
  116. USER_IS_NOT_ROOT_CHECK = CheckUserIsNotRoot()
  117. PIP_ENVIRONMENT_CHECK = CheckPipEnvironment()
  118. class PipPlugin(BasePlugin):
  119. app_label: str = 'pip'
  120. verbose_name: str = 'PIP'
  121. hooks: List[InstanceOf[BaseHook]] = [
  122. PIP_CONFIG,
  123. SYS_PIP_BINPROVIDER,
  124. SYS_PIPX_BINPROVIDER,
  125. LIB_PIP_BINPROVIDER,
  126. PIP_BINARY,
  127. PYTHON_BINARY,
  128. SQLITE_BINARY,
  129. DJANGO_BINARY,
  130. USER_IS_NOT_ROOT_CHECK,
  131. PIP_ENVIRONMENT_CHECK,
  132. ]
  133. PLUGIN = PipPlugin()
  134. PLUGIN.register(settings)
  135. DJANGO_APP = PLUGIN.AppConfig