Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 

357 рядки
9.7 KiB

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