From 044e01befde722c20eb6adeb6a6f01b353291da1 Mon Sep 17 00:00:00 2001 From: Athul Cyriac Ajay Date: Thu, 1 Dec 2022 16:24:47 +0530 Subject: [PATCH] feat: New easy-install.py Supports docker container setup with production and dev instances --- easy-install.py | 239 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 easy-install.py diff --git a/easy-install.py b/easy-install.py new file mode 100644 index 00000000..8a92a9d8 --- /dev/null +++ b/easy-install.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +import argparse +import subprocess +import os +import sys +import time +from shutil import which +import platform +from secrets import token_bytes +from base64 import b64encode + +CRED = "\033[31m" +CEND = "\033[0m" +CGRN = "\33[92m" +CYLW = "\33[93m" + + +def clone_frappe_docker_repo() -> None: + try: + subprocess.run( + ["git", "clone", "https://github.com/frappe/frappe_docker"], check=True + ) + except Exception as e: + print(f"\n{CRED}Cloning frappe_docker Failed{CEND}\n\n", e) + + +def write_to_env(wd, site,db_pass,email): + site_name = site if site != "" else "" + with open(os.path.join(wd, ".env"), "w") as f: + f.writelines( + [ + "FRAPPE_VERSION=v14.17.1\n", + "ERPNEXT_VERSION=v14.9.0\n", + f"DB_PASSWORD={db_pass}\n", + "DB_HOST=db\n", + "DB_PORT=3306\n", + "REDIS_CACHE=redis-cache:6379\n", + "REDIS_QUEUE=redis-queue:6379\n", + "REDIS_SOCKETIO=redis-socketio:6379\n", + f"LETSENCRYPT_EMAIL={email}\n" + f"FRAPPE_SITE_NAME_HEADER={site_name}", + ] + ) + +def generate_pass(length:int=9) -> str: + return b64encode(token_bytes(length)).decode() + + +def check_repo_exists() -> bool: + return os.path.exists(os.path.join(os.getcwd(), "frappe_docker")) + + +def setup_prod(project: str, sitename: str,email:str): + if check_repo_exists(): + compose_file_name = os.path.join(os.path.expanduser("~"), f"{project}-compose.yml") + docker_repo_path = os.path.join(os.getcwd(), "frappe_docker") + admin_pass = generate_pass() + db_pass = generate_pass(6) + print( + f"\n{CYLW}Please refer to .example.env file in the frappe_docker folder to know which keys to set{CEND}\n\n" + ) + with open(compose_file_name, "w") as f: + if not os.path.exists(os.path.join(docker_repo_path, "/.env")): + write_to_env(docker_repo_path, sitename,db_pass,email) + print( + f"\n{CYLW}A .env file is generated with basic configs. Please edit it to fit your needs {CEND}\n" + ) + try: + # TODO: Include flags for non-https and non-erpnext installation + subprocess.run( + [ + which("docker"), + "compose", + "--project-name", + project, + "-f", + "compose.yaml", + "-f", + "overrides/compose.mariadb.yaml", + "-f", + "overrides/compose.redis.yaml", + # "-f", "overrides/compose.noproxy.yaml", TODO: Add support for local proxying without HTTPs + "-f", + "overrides/compose.erpnext.yaml", + "-f", + "overrides/compose.https.yaml", + "--env-file", + ".env", + "config", + ], + cwd=docker_repo_path, + stdout=f, + check=True, + ) + + except Exception as e: + print(f"\n{CRED}Generating Compose File failed{CEND}\n") + try: + subprocess.run( + [ + which("docker"), + "compose", + "-p", + project, + "-f", + compose_file_name, + "up", + "-d", + ], + check=True, + ) + + except Exception as e: + print( + f"{CRED} Docker Compose failed, please check the container logs{CEND}\n", + e, + ) + print(f"\n{CGRN}Creating site: {sitename} {CEND}\n") + with open(os.path.join(os.path.expanduser("~"),"passwords.txt"),"w") as f: + f.writelines(f"Administrator:{admin_pass}\n") + f.writelines(f"MariaDB Root Password:{db_pass}\n") + try: + subprocess.run([ + which("docker"),"compose", + "-p",project,"exec","backend", + "bench","new-site",sitename, + "--db-root-password",db_pass, + "--admin-password",admin_pass, + "--install-app","erpnext" + ],check=True) + except Exception as e: + print( + f"{CRED} Bench Site creation failed{CEND}\n", + e, + ) + else: + install_docker() + clone_frappe_docker_repo() + setup_prod(project, sitename,email) # Recursive + + +def setup_dev_instance(project: str): + if check_repo_exists(): + try: + subprocess.run( + [ + "docker", + "compose", + "-f", + "devcontainer-example/docker-compose.yml", + "--project-name", + project, + "up", + "-d", + ], + cwd=os.path.join(os.getcwd(), "frappe_docker"), + check=True, + ) + print( + f"{CYLW}Please go through the Development Documentation: https://github.com/frappe/frappe_docker/tree/main/development to fully complete the setup.{CEND}" + ) + except Exception as e: + print(f"{CRED}Setting Up Development Environment Failed\n{CEND}", e) + else: + install_docker() + clone_frappe_docker_repo() + setup_dev_instance(project) # Recursion on goes brrrr + + +def install_docker(): + if which("docker") is None: + print(f"{CGRN}Docker is not installed, Installing Docker...{CEND}") + if platform.system() == "Darwin" or platform.system() == "Windows": + print( + f"""{CRED} + This script doesn't install Docker on {"Mac" if platform.system()=="Darwin" else "Windows"}. + + Please go through the Docker Installation docs for your system and run this script again{CEND}""" + ) + exit(1) + try: + ps = subprocess.run( + ["curl", "-fsSL", "https://get.docker.com"], + capture_output=True, + check=True, + ) + subprocess.run(["/bin/bash"], input=ps.stdout, capture_output=True) + subprocess.run( + ["sudo", "usermod", "-aG", "docker", str(os.getenv("USER"))], check=True + ) + print(f"{CYLW}Waiting Docker to start{CEND}") + time.sleep(10) + subprocess.run( + ["sudo", "systemctl", "restart", "docker.service"], check=True + ) + except Exception as e: + print(f"{CRED}Failed to Install Docker{CYLW}\n", e) + print( + f"\n\n {CYLW}Try Installing Docker Manually and re-run this script again{CEND}\n\n" + ) + exit(1) + else: + return + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Install Frappe with Docker") + parser.add_argument( + "-p", "--prod", help="Setup Production System", action="store_true" + ) + parser.add_argument( + "-d", "--dev", help="Setup Development System", action="store_true" + ) + parser.add_argument( + "-s", + "--sitename", + help="The Site Name for your production site", + default="site1.local", + ) + parser.add_argument( + "-n", + "--project", + help="Project Name", + default="frappe", + ) + parser.add_argument( + "--email", + help="Add email for the SSL.", + required="--prod" in sys.argv + ) + args = parser.parse_args() + if args.dev: + print(f"\n{CGRN}Setting Up Development Instance{CEND}\n") + setup_dev_instance(args.project) + elif args.prod: + print(f"\n{CGRN}Setting Up Production Instance{CEND}\n") + if "example.com" in args.email: + print(f"{CRED} Emails with example.com not acceptable{CEND}") + setup_prod(args.project, args.sitename, args.email) \ No newline at end of file