Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

332 linhas
9.1 KiB

  1. #!/usr/bin/env python3
  2. import argparse
  3. import logging
  4. import os
  5. import platform
  6. import subprocess
  7. import sys
  8. import time
  9. import urllib.request
  10. from shutil import move, unpack_archive, which
  11. from typing import Dict
  12. logging.basicConfig(
  13. filename="easy-install.log",
  14. filemode="w",
  15. format="%(asctime)s - %(levelname)s - %(message)s",
  16. level=logging.INFO,
  17. )
  18. def cprint(*args, level: int = 1):
  19. """
  20. logs colorful messages
  21. level = 1 : RED
  22. level = 2 : GREEN
  23. level = 3 : YELLOW
  24. default level = 1
  25. """
  26. CRED = "\033[31m"
  27. CGRN = "\33[92m"
  28. CYLW = "\33[93m"
  29. reset = "\033[0m"
  30. message = " ".join(map(str, args))
  31. if level == 1:
  32. print(CRED, message, reset)
  33. if level == 2:
  34. print(CGRN, message, reset)
  35. if level == 3:
  36. print(CYLW, message, reset)
  37. def clone_xhiveframework_docker_repo() -> None:
  38. try:
  39. urllib.request.urlretrieve(
  40. "https://github.com/xhiveframework/xhiveframework_docker/archive/refs/heads/main.zip",
  41. "xhiveframework_docker.zip",
  42. )
  43. logging.info("Downloaded xhiveframework_docker zip file from GitHub")
  44. unpack_archive(
  45. "xhiveframework_docker.zip", "."
  46. ) # Unzipping the xhiveframework_docker.zip creates a folder "xhiveframework_docker-main"
  47. move("xhiveframework_docker-main", "xhiveframework_docker")
  48. logging.info("Unzipped and Renamed xhiveframework_docker")
  49. os.remove("xhiveframework_docker.zip")
  50. logging.info("Removed the downloaded zip file")
  51. except Exception as e:
  52. logging.error("Download and unzip failed", exc_info=True)
  53. cprint("\nCloning xhiveframework_docker Failed\n\n", "[ERROR]: ", e, level=1)
  54. def get_from_env(dir, file) -> Dict:
  55. env_vars = {}
  56. with open(os.path.join(dir, file)) as f:
  57. for line in f:
  58. if line.startswith("#") or not line.strip():
  59. continue
  60. key, value = line.strip().split("=", 1)
  61. env_vars[key] = value
  62. return env_vars
  63. def write_to_env(
  64. wd: str,
  65. site: str,
  66. db_pass: str,
  67. admin_pass: str,
  68. email: str,
  69. xhiveerp_version: str = None,
  70. ) -> None:
  71. site_name = site or ""
  72. example_env = get_from_env(wd, "example.env")
  73. xhiveerp_version = xhiveerp_version or example_env["XHIVEERP_VERSION"]
  74. with open(os.path.join(wd, ".env"), "w") as f:
  75. f.writelines(
  76. [
  77. f"XHIVEERP_VERSION={xhiveerp_version}\n", # defaults to latest version of XhiveERP
  78. f"DB_PASSWORD={db_pass}\n",
  79. "DB_HOST=db\n",
  80. "DB_PORT=3306\n",
  81. "REDIS_CACHE=redis-cache:6379\n",
  82. "REDIS_QUEUE=redis-queue:6379\n",
  83. "REDIS_SOCKETIO=redis-socketio:6379\n",
  84. f"LETSENCRYPT_EMAIL={email}\n",
  85. f"XHIVEFRAMEWORK_SITE_NAME_HEADER={site_name}\n",
  86. f"SITE_ADMIN_PASS={admin_pass}",
  87. ]
  88. )
  89. def generate_pass(length: int = 12) -> str:
  90. """Generate random hash using best available randomness source."""
  91. import math
  92. import secrets
  93. if not length:
  94. length = 56
  95. return secrets.token_hex(math.ceil(length / 2))[:length]
  96. def check_repo_exists() -> bool:
  97. return os.path.exists(os.path.join(os.getcwd(), "xhiveframework_docker"))
  98. def setup_prod(project: str, sitename: str, email: str, version: str = None) -> None:
  99. if check_repo_exists():
  100. compose_file_name = os.path.join(os.path.expanduser("~"), f"{project}-compose.yml")
  101. docker_repo_path = os.path.join(os.getcwd(), "xhiveframework_docker")
  102. cprint(
  103. "\nPlease refer to .example.env file in the xhiveframework_docker folder to know which keys to set\n\n",
  104. level=3,
  105. )
  106. admin_pass = ""
  107. db_pass = ""
  108. with open(compose_file_name, "w") as f:
  109. # Writing to compose file
  110. if not os.path.exists(os.path.join(docker_repo_path, ".env")):
  111. admin_pass = generate_pass()
  112. db_pass = generate_pass(9)
  113. write_to_env(docker_repo_path, sitename, db_pass, admin_pass, email, version)
  114. cprint(
  115. "\nA .env file is generated with basic configs. Please edit it to fit to your needs \n",
  116. level=3,
  117. )
  118. with open(os.path.join(os.path.expanduser("~"), "passwords.txt"), "w") as en:
  119. en.writelines(f"ADMINISTRATOR_PASSWORD={admin_pass}\n")
  120. en.writelines(f"MARIADB_ROOT_PASSWORD={db_pass}\n")
  121. else:
  122. env = get_from_env(docker_repo_path, ".env")
  123. admin_pass = env["SITE_ADMIN_PASS"]
  124. db_pass = env["DB_PASSWORD"]
  125. try:
  126. # TODO: Include flags for non-https and non-xhiveerp installation
  127. subprocess.run(
  128. [
  129. which("docker"),
  130. "compose",
  131. "--project-name",
  132. project,
  133. "-f",
  134. "compose.yaml",
  135. "-f",
  136. "overrides/compose.mariadb.yaml",
  137. "-f",
  138. "overrides/compose.redis.yaml",
  139. # "-f", "overrides/compose.noproxy.yaml", TODO: Add support for local proxying without HTTPs
  140. "-f",
  141. "overrides/compose.https.yaml",
  142. "--env-file",
  143. ".env",
  144. "config",
  145. ],
  146. cwd=docker_repo_path,
  147. stdout=f,
  148. check=True,
  149. )
  150. except Exception:
  151. logging.error("Docker Compose generation failed", exc_info=True)
  152. cprint("\nGenerating Compose File failed\n")
  153. sys.exit(1)
  154. try:
  155. # Starting with generated compose file
  156. subprocess.run(
  157. [
  158. which("docker"),
  159. "compose",
  160. "-p",
  161. project,
  162. "-f",
  163. compose_file_name,
  164. "up",
  165. "-d",
  166. ],
  167. check=True,
  168. )
  169. logging.info(f"Docker Compose file generated at ~/{project}-compose.yml")
  170. except Exception as e:
  171. logging.error("Prod docker-compose failed", exc_info=True)
  172. cprint(" Docker Compose failed, please check the container logs\n", e)
  173. sys.exit(1)
  174. cprint(f"\nCreating site: {sitename} \n", level=3)
  175. try:
  176. subprocess.run(
  177. [
  178. which("docker"),
  179. "compose",
  180. "-p",
  181. project,
  182. "exec",
  183. "backend",
  184. "bench",
  185. "new-site",
  186. sitename,
  187. "--no-mariadb-socket",
  188. "--db-root-password",
  189. db_pass,
  190. "--admin-password",
  191. admin_pass,
  192. "--install-app",
  193. "xhiveerp",
  194. "--set-default",
  195. ],
  196. check=True,
  197. )
  198. logging.info("New site creation completed")
  199. except Exception as e:
  200. logging.error("Bench site creation failed", exc_info=True)
  201. cprint("Bench Site creation failed\n", e)
  202. sys.exit(1)
  203. else:
  204. install_docker()
  205. clone_xhiveframework_docker_repo()
  206. setup_prod(project, sitename, email, version) # Recursive
  207. def setup_dev_instance(project: str):
  208. if check_repo_exists():
  209. try:
  210. subprocess.run(
  211. [
  212. "docker",
  213. "compose",
  214. "-f",
  215. "devcontainer-example/docker-compose.yml",
  216. "--project-name",
  217. project,
  218. "up",
  219. "-d",
  220. ],
  221. cwd=os.path.join(os.getcwd(), "xhiveframework_docker"),
  222. check=True,
  223. )
  224. cprint(
  225. "Please go through the Development Documentation: https://github.com/xhiveframework/xhiveframework_docker/tree/main/development to fully complete the setup.",
  226. level=2,
  227. )
  228. logging.info("Development Setup completed")
  229. except Exception as e:
  230. logging.error("Dev Environment setup failed", exc_info=True)
  231. cprint("Setting Up Development Environment Failed\n", e)
  232. else:
  233. install_docker()
  234. clone_xhiveframework_docker_repo()
  235. setup_dev_instance(project) # Recursion on goes brrrr
  236. def install_docker():
  237. if which("docker") is not None:
  238. return
  239. cprint("Docker is not installed, Installing Docker...", level=3)
  240. logging.info("Docker not found, installing Docker")
  241. if platform.system() == "Darwin" or platform.system() == "Windows":
  242. print(
  243. f"""
  244. This script doesn't install Docker on {"Mac" if platform.system()=="Darwin" else "Windows"}.
  245. Please go through the Docker Installation docs for your system and run this script again"""
  246. )
  247. logging.debug("Docker setup failed due to platform is not Linux")
  248. sys.exit(1)
  249. try:
  250. ps = subprocess.run(
  251. ["curl", "-fsSL", "https://get.docker.com"],
  252. capture_output=True,
  253. check=True,
  254. )
  255. subprocess.run(["/bin/bash"], input=ps.stdout, capture_output=True)
  256. subprocess.run(
  257. ["sudo", "usermod", "-aG", "docker", str(os.getenv("USER"))], check=True
  258. )
  259. cprint("Waiting Docker to start", level=3)
  260. time.sleep(10)
  261. subprocess.run(["sudo", "systemctl", "restart", "docker.service"], check=True)
  262. except Exception as e:
  263. logging.error("Installing Docker failed", exc_info=True)
  264. cprint("Failed to Install Docker\n", e)
  265. cprint("\n Try Installing Docker Manually and re-run this script again\n")
  266. sys.exit(1)
  267. if __name__ == "__main__":
  268. parser = argparse.ArgumentParser(description="Install Xhive with Docker")
  269. parser.add_argument(
  270. "-p", "--prod", help="Setup Production System", action="store_true"
  271. )
  272. parser.add_argument(
  273. "-d", "--dev", help="Setup Development System", action="store_true"
  274. )
  275. parser.add_argument(
  276. "-s",
  277. "--sitename",
  278. help="The Site Name for your production site",
  279. default="site1.local",
  280. )
  281. parser.add_argument("-n", "--project", help="Project Name", default="xhiveframework")
  282. parser.add_argument(
  283. "--email", help="Add email for the SSL.", required="--prod" in sys.argv
  284. )
  285. parser.add_argument(
  286. "-v", "--version", help="XhiveERP version to install, defaults to latest stable"
  287. )
  288. args = parser.parse_args()
  289. if args.dev:
  290. cprint("\nSetting Up Development Instance\n", level=2)
  291. logging.info("Running Development Setup")
  292. setup_dev_instance(args.project)
  293. elif args.prod:
  294. cprint("\nSetting Up Production Instance\n", level=2)
  295. logging.info("Running Production Setup")
  296. if "example.com" in args.email:
  297. cprint("Emails with example.com not acceptable", level=1)
  298. sys.exit(1)
  299. setup_prod(args.project, args.sitename, args.email, args.version)
  300. else:
  301. parser.print_help()