From 044e01befde722c20eb6adeb6a6f01b353291da1 Mon Sep 17 00:00:00 2001 From: Athul Cyriac Ajay Date: Thu, 1 Dec 2022 16:24:47 +0530 Subject: [PATCH 01/13] 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 From 7c8ce31a8da880f0d8f60818d98adc3ce09ca0e2 Mon Sep 17 00:00:00 2001 From: Athul Cyriac Ajay Date: Mon, 5 Dec 2022 00:37:39 +0530 Subject: [PATCH 02/13] chore: Added logging and removed git dependency Also added - New methods for printing - Better env file management - Better password generation - Added zip repository downloads - default container version set to v14 --- easy-install.py | 252 +++++++++++++++++++++++++++++------------------- 1 file changed, 153 insertions(+), 99 deletions(-) diff --git a/easy-install.py b/easy-install.py index 8a92a9d8..9911ee6a 100644 --- a/easy-install.py +++ b/easy-install.py @@ -4,67 +4,113 @@ import subprocess import os import sys import time -from shutil import which +import urllib.request +from shutil import which, unpack_archive, move import platform -from secrets import token_bytes -from base64 import b64encode +from hashlib import sha224 +import logging -CRED = "\033[31m" -CEND = "\033[0m" -CGRN = "\33[92m" -CYLW = "\33[93m" +logging.basicConfig( + filename="easy-install.log", + filemode="w", + format="%(levelname)s - %(message)s", + level=logging.INFO, +) + + +def cprint(*args, level: int = 1): + """ + logs colorful messages + level = 1 : RED + level = 2 : GREEN + level = 3 : YELLOW + + default level = 1 + """ + CRED = "\033[31m" + CGRN = "\33[92m" + CYLW = "\33[93m" + reset = "\033[0m" + message = " ".join(map(str, args)) + if level == 1: + print(CRED, message, reset) + if level == 2: + print(CGRN, message, reset) + if level == 3: + print(CYLW, message, reset) def clone_frappe_docker_repo() -> None: try: - subprocess.run( - ["git", "clone", "https://github.com/frappe/frappe_docker"], check=True + urllib.request.urlretrieve( + "https://github.com/frappe/frappe_docker/archive/refs/heads/main.zip", + "frappe_docker.zip", ) + logging.info("Downloaded frappe_docker zip file from GitHub") + unpack_archive( + "frappe_docker.zip", "." + ) # Unzipping the frappe_docker.zip creates a folder "frappe_docker-main" + move("frappe_docker-main", "frappe_docker") + logging.info("Unzipped and Renamed frappe_docker") + os.remove("frappe_docker.zip") + logging.info("Removed the downloaded zip file") except Exception as e: - print(f"\n{CRED}Cloning frappe_docker Failed{CEND}\n\n", e) + logging.error("Download and unzip failed", exc_info=True) + cprint("\nCloning frappe_docker Failed\n\n", "[ERROR]: ", e, level=1) -def write_to_env(wd, site,db_pass,email): - site_name = site if site != "" else "" +def write_to_env(wd: str, site: str, db_pass: str, admin_pass: str, email: str) -> None: + site_name = site or "" with open(os.path.join(wd, ".env"), "w") as f: f.writelines( [ - "FRAPPE_VERSION=v14.17.1\n", - "ERPNEXT_VERSION=v14.9.0\n", + "FRAPPE_VERSION=v14\n", # Defaults to latest version of Frappe + "ERPNEXT_VERSION=v14\n", # defaults to latest version of ERPNext 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}", + f"LETSENCRYPT_EMAIL={email}\n", + f"FRAPPE_SITE_NAME_HEADER={site_name}\n", + f"SITE_ADMIN_PASS={admin_pass}", ] ) -def generate_pass(length:int=9) -> str: - return b64encode(token_bytes(length)).decode() - + +def generate_pass(length: int = 12) -> str: + return sha224(repr(time.time()).encode()).hexdigest()[:length] + 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): +def setup_prod(project: str, sitename: str, email: str) -> None: if check_repo_exists(): - compose_file_name = os.path.join(os.path.expanduser("~"), f"{project}-compose.yml") + 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" + cprint( + "\nPlease refer to .example.env file in the frappe_docker folder to know which keys to set\n\n", + level=3, ) 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" + admin_pass = generate_pass() + db_pass = generate_pass(9) + if not os.path.exists(os.path.join(docker_repo_path, ".env")): + write_to_env(docker_repo_path, sitename, db_pass, admin_pass, email) + cprint( + "\nA .env file is generated with basic configs. Please edit it to fit to your needs \n", + level=3, ) + with open( + os.path.join(os.path.expanduser("~"), "passwords.txt"), "w" + ) as en: + en.writelines(f"Administrator:{admin_pass}\n") + en.writelines(f"MariaDB Root Password:{db_pass}\n") try: # TODO: Include flags for non-https and non-erpnext installation subprocess.run( @@ -94,7 +140,9 @@ def setup_prod(project: str, sitename: str,email:str): ) except Exception as e: - print(f"\n{CRED}Generating Compose File failed{CEND}\n") + logging.error("Docker Compose generation failed", exc_info=True) + cprint("\nGenerating Compose File failed\n") + sys.exit(1) try: subprocess.run( [ @@ -109,34 +157,43 @@ def setup_prod(project: str, sitename: str,email:str): ], check=True, ) - + logging.info(f"Docker Compose file generated at ~/{project}-compose.yml") + 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") + logging.error("Prod docker-compose failed", exc_info=True) + cprint(" Docker Compose failed, please check the container logs\n", e) + + cprint(f"\nCreating site: {sitename} \n", level=3) + 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, + 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, ) + logging.info("New site creation completed") + except Exception as e: + logging.error("Bench site creation failed", exc_info=True) + cprint("Bench Site creation failed\n", e) else: install_docker() clone_frappe_docker_repo() - setup_prod(project, sitename,email) # Recursive + setup_prod(project, sitename, email) # Recursive def setup_dev_instance(project: str): @@ -156,11 +213,14 @@ def setup_dev_instance(project: str): 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}" + cprint( + "Please go through the Development Documentation: https://github.com/frappe/frappe_docker/tree/main/development to fully complete the setup.", + level=2, ) + logging.info("Development Setup completed") except Exception as e: - print(f"{CRED}Setting Up Development Environment Failed\n{CEND}", e) + logging.error("Dev Environment setup failed", exc_info=True) + cprint("Setting Up Development Environment Failed\n", e) else: install_docker() clone_frappe_docker_repo() @@ -168,39 +228,37 @@ def setup_dev_instance(project: str): 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: + if which("docker") is not None: return + cprint("Docker is not installed, Installing Docker...", level=3) + logging.info("Docker not found, installing Docker") + if platform.system() == "Darwin" or platform.system() == "Windows": + print( + f""" + 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""" + ) + logging.debug("Docker setup failed due to platform is not Linux") + sys.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 + ) + cprint("Waiting Docker to start", level=3) + time.sleep(10) + subprocess.run(["sudo", "systemctl", "restart", "docker.service"], check=True) + except Exception as e: + logging.error("Installing Docker failed", exc_info=True) + cprint("Failed to Install Docker\n", e) + cprint("\n Try Installing Docker Manually and re-run this script again\n") + sys.exit(1) if __name__ == "__main__": @@ -217,23 +275,19 @@ if __name__ == "__main__": help="The Site Name for your production site", default="site1.local", ) + parser.add_argument("-n", "--project", help="Project Name", default="frappe") 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 + "--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") + cprint("\nSetting Up Development Instance\n", level=2) + logging.info("Running Development Setup") setup_dev_instance(args.project) elif args.prod: - print(f"\n{CGRN}Setting Up Production Instance{CEND}\n") + cprint("\nSetting Up Production Instance\n", level=2) + logging.info("Running Production Setup") if "example.com" in args.email: - print(f"{CRED} Emails with example.com not acceptable{CEND}") + cprint("Emails with example.com not acceptable", level=1) + sys.exit(1) setup_prod(args.project, args.sitename, args.email) \ No newline at end of file From 3ff7bfb35f17bd8d8c3f50e16c41280449f63569 Mon Sep 17 00:00:00 2001 From: Athul Cyriac Ajay Date: Tue, 6 Dec 2022 19:33:47 +0530 Subject: [PATCH 03/13] chore: get exact versions from example.env file --- easy-install.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/easy-install.py b/easy-install.py index 9911ee6a..fb742462 100644 --- a/easy-install.py +++ b/easy-install.py @@ -5,10 +5,11 @@ import os import sys import time import urllib.request -from shutil import which, unpack_archive, move -import platform -from hashlib import sha224 import logging +import platform +from shutil import which, unpack_archive, move +from hashlib import sha224 +from typing import Dict logging.basicConfig( filename="easy-install.log", @@ -58,14 +59,24 @@ def clone_frappe_docker_repo() -> None: logging.error("Download and unzip failed", exc_info=True) cprint("\nCloning frappe_docker Failed\n\n", "[ERROR]: ", e, level=1) +def get_latest_version(dir) -> Dict: + env_vars ={} + with open(os.path.join(dir,"example.env")) as f: + for line in f: + if line.startswith('#') or not line.strip(): + continue + key, value = line.strip().split('=', 1) + env_vars[key] = value + return env_vars def write_to_env(wd: str, site: str, db_pass: str, admin_pass: str, email: str) -> None: site_name = site or "" + example_env = get_latest_version(wd) with open(os.path.join(wd, ".env"), "w") as f: f.writelines( [ - "FRAPPE_VERSION=v14\n", # Defaults to latest version of Frappe - "ERPNEXT_VERSION=v14\n", # defaults to latest version of ERPNext + f"FRAPPE_VERSION={example_env['FRAPPE_VERSION']}\n", # Defaults to latest version of Frappe + f"ERPNEXT_VERSION={example_env['ERPNEXT_VERSION']}\n", # defaults to latest version of ERPNext f"DB_PASSWORD={db_pass}\n", "DB_HOST=db\n", "DB_PORT=3306\n", @@ -98,9 +109,10 @@ def setup_prod(project: str, sitename: str, email: str) -> None: level=3, ) with open(compose_file_name, "w") as f: - admin_pass = generate_pass() - db_pass = generate_pass(9) + # Writing to compose file if not os.path.exists(os.path.join(docker_repo_path, ".env")): + admin_pass = generate_pass() + db_pass = generate_pass(9) write_to_env(docker_repo_path, sitename, db_pass, admin_pass, email) cprint( "\nA .env file is generated with basic configs. Please edit it to fit to your needs \n", @@ -144,6 +156,7 @@ def setup_prod(project: str, sitename: str, email: str) -> None: cprint("\nGenerating Compose File failed\n") sys.exit(1) try: + # Starting with generated compose file subprocess.run( [ which("docker"), From 0234c080c83f0fa4e69e7bdadfafb1576104d243 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 12 Dec 2022 21:48:38 +0530 Subject: [PATCH 04/13] ci: run tests against current hotfix branches (#1402) - If this isn't dont then we have no way of knowing until frappe/erpnext are actually released. - Skip asset building where it doesn't matter --- bench/tests/test_base.py | 4 +--- bench/tests/test_init.py | 12 ++++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/bench/tests/test_base.py b/bench/tests/test_base.py index 9ca23de4..efc1e81f 100644 --- a/bench/tests/test_base.py +++ b/bench/tests/test_base.py @@ -15,12 +15,10 @@ from bench.bench import Bench PYTHON_VER = sys.version_info -FRAPPE_BRANCH = "version-12" +FRAPPE_BRANCH = "version-13-hotfix" if PYTHON_VER.major == 3: if PYTHON_VER.minor >= 10: FRAPPE_BRANCH = "develop" - if 7 >= PYTHON_VER.minor >= 9: - FRAPPE_BRANCH = "version-13" class TestBenchBase(unittest.TestCase): diff --git a/bench/tests/test_init.py b/bench/tests/test_init.py index 622ffc87..b0a871a4 100755 --- a/bench/tests/test_init.py +++ b/bench/tests/test_init.py @@ -46,7 +46,7 @@ class TestBenchInit(TestBenchBase): def test_multiple_benches(self): for bench_name in ("test-bench-1", "test-bench-2"): - self.init_bench(bench_name) + self.init_bench(bench_name, skip_assets=True) self.assert_common_site_config( "test-bench-1", @@ -96,7 +96,7 @@ class TestBenchInit(TestBenchBase): self.assertTrue(site_config[key]) def test_get_app(self): - self.init_bench("test-bench") + self.init_bench("test-bench", skip_assets=True) bench_path = os.path.join(self.benches_path, "test-bench") exec_cmd(f"bench get-app {TEST_FRAPPE_APP} --skip-assets", cwd=bench_path) self.assertTrue(os.path.exists(os.path.join(bench_path, "apps", TEST_FRAPPE_APP))) @@ -108,7 +108,7 @@ class TestBenchInit(TestBenchBase): @unittest.skipIf(FRAPPE_BRANCH != "develop", "only for develop branch") def test_get_app_resolve_deps(self): FRAPPE_APP = "healthcare" - self.init_bench("test-bench") + self.init_bench("test-bench", skip_assets=True) bench_path = os.path.join(self.benches_path, "test-bench") exec_cmd(f"bench get-app {FRAPPE_APP} --resolve-deps --skip-assets", cwd=bench_path) self.assertTrue(os.path.exists(os.path.join(bench_path, "apps", FRAPPE_APP))) @@ -126,7 +126,7 @@ class TestBenchInit(TestBenchBase): site_name = "install-app.test" bench_path = os.path.join(self.benches_path, "test-bench") - self.init_bench(bench_name) + self.init_bench(bench_name, skip_assets=True) exec_cmd( f"bench get-app {TEST_FRAPPE_APP} --branch master --skip-assets", cwd=bench_path ) @@ -154,7 +154,7 @@ class TestBenchInit(TestBenchBase): self.assertTrue(TEST_FRAPPE_APP in app_installed_on_site) def test_remove_app(self): - self.init_bench("test-bench") + self.init_bench("test-bench", skip_assets=True) bench_path = os.path.join(self.benches_path, "test-bench") exec_cmd( @@ -172,7 +172,7 @@ class TestBenchInit(TestBenchBase): self.assertFalse(os.path.exists(os.path.join(bench_path, "apps", TEST_FRAPPE_APP))) def test_switch_to_branch(self): - self.init_bench("test-bench") + self.init_bench("test-bench", skip_assets=True) bench_path = os.path.join(self.benches_path, "test-bench") app_path = os.path.join(bench_path, "apps", "frappe") From 931377727651da00143b062c1ce20af5adae951c Mon Sep 17 00:00:00 2001 From: Athul Cyriac Ajay Date: Tue, 13 Dec 2022 13:18:09 +0530 Subject: [PATCH 05/13] chore: Update Readme and changed password generated method Co-authored-by: @ankush --- README.md | 69 ++++++++++++++++++++++++++++++++++++++++++------- easy-install.py | 19 +++++++++----- 2 files changed, 73 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 16f61c88..04c87349 100755 --- a/README.md +++ b/README.md @@ -31,17 +31,21 @@ Bench is a command-line utility that helps you to install, update, and manage mu ## Table of Contents - - [Installation](#installation) +- [Table of Contents](#table-of-contents) +- [Installation](#installation) - [Containerized Installation](#containerized-installation) + - [Easy Install Script](#easy-install-script) + - [Setup](#setup) + - [Arguments](#arguments) + - [Troubleshooting](#troubleshooting) - [Manual Installation](#manual-installation) - - [Usage](#basic-usage) - - [Custom Bench commands](#custom-bench-commands) - - [Bench Manager](#bench-manager) - - [Guides](#guides) - - [Resources](#resources) - - [Development](#development) - - [Releases](#releases) - - [License](#license) +- [Basic Usage](#basic-usage) +- [Custom Bench Commands](#custom-bench-commands) +- [Guides](#guides) +- [Resources](#resources) +- [Development](#development) +- [Releases](#releases) +- [License](#license) ## Installation @@ -71,6 +75,53 @@ $ cd frappe_docker A quick setup guide for both the environments can be found below. For more details, check out the [Frappe/ERPNext Docker Repository](https://github.com/frappe/frappe_docker). +### Easy Install Script + +The Easy Install script should get you going with a Frappe/ERPNext setup with minimal manual intervention and effort. + +This script uses Docker with the [Frappe/ERPNext Docker Repository](https://github.com/frappe/frappe_docker) and can be used for both Development setup and Production setup. + +#### Setup + +Download the Easy Install script and execute it: + +```sh +$ wget https://raw.githubusercontent.com/frappe/bench/develop/easy-install.py +$ python3 install.py --prod +``` + +This script will install docker on your system and will fetch the required containers, setup bench and a default ERPNext instance. + +The script will generate MySQL root password and an Administrator password for the Frappe/ERPNext instance, which will then be saved under `$HOME/passwords.txt` of the user used to setup the instance. +It will also generate a new compose file under `$HOME/-compose.yml`. + +When the setup is complete, you will be able to access the system at `http://`, wherein you can use the Administrator password to login. + +#### Arguments + +Here are the arguments for the easy-install script + +```txt +usage: easy-install.py [-h] [-p] [-d] [-s SITENAME] [-n PROJECT] [--email EMAIL] + +Install Frappe with Docker + +options: + -h, --help show this help message and exit + -p, --prod Setup Production System + -d, --dev Setup Development System + -s SITENAME, --sitename SITENAME + The Site Name for your production site + -n PROJECT, --project PROJECT + Project Name + --email EMAIL Add email for the SSL. +``` + +#### Troubleshooting + +In case the setup fails, the log file is saved under `$HOME/easy-install.log`. You may then + +- Create an Issue in this repository with the log file attached. ### Manual Installation diff --git a/easy-install.py b/easy-install.py index fb742462..8334792d 100644 --- a/easy-install.py +++ b/easy-install.py @@ -14,7 +14,7 @@ from typing import Dict logging.basicConfig( filename="easy-install.log", filemode="w", - format="%(levelname)s - %(message)s", + format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO, ) @@ -91,7 +91,14 @@ def write_to_env(wd: str, site: str, db_pass: str, admin_pass: str, email: str) def generate_pass(length: int = 12) -> str: - return sha224(repr(time.time()).encode()).hexdigest()[:length] + """Generate random hash using best available randomness source.""" + import math + import secrets + + if not length: + length = 56 + + return secrets.token_hex(math.ceil(length / 2))[:length] def check_repo_exists() -> bool: @@ -108,11 +115,11 @@ def setup_prod(project: str, sitename: str, email: str) -> None: "\nPlease refer to .example.env file in the frappe_docker folder to know which keys to set\n\n", level=3, ) + admin_pass = generate_pass() + db_pass = generate_pass(9) with open(compose_file_name, "w") as f: # Writing to compose file if not os.path.exists(os.path.join(docker_repo_path, ".env")): - admin_pass = generate_pass() - db_pass = generate_pass(9) write_to_env(docker_repo_path, sitename, db_pass, admin_pass, email) cprint( "\nA .env file is generated with basic configs. Please edit it to fit to your needs \n", @@ -121,8 +128,8 @@ def setup_prod(project: str, sitename: str, email: str) -> None: with open( os.path.join(os.path.expanduser("~"), "passwords.txt"), "w" ) as en: - en.writelines(f"Administrator:{admin_pass}\n") - en.writelines(f"MariaDB Root Password:{db_pass}\n") + en.writelines(f"ADMINISTRATOR_PASSWORD={admin_pass}\n") + en.writelines(f"MARIADB_ROOT_PASSWORD={db_pass}\n") try: # TODO: Include flags for non-https and non-erpnext installation subprocess.run( From e76c7dccf57c122064dafc574b1dfa6f3d13493e Mon Sep 17 00:00:00 2001 From: Athul Cyriac Ajay Date: Wed, 14 Dec 2022 17:42:24 +0530 Subject: [PATCH 06/13] feat: add GHA tests chore: added reading from env when rerunning script - Makes current site as default --- .github/workflows/easy-install.yml | 27 +++++++++++++++++++++++++++ easy-install.py | 20 +++++++++++++++----- 2 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/easy-install.yml diff --git a/.github/workflows/easy-install.yml b/.github/workflows/easy-install.yml new file mode 100644 index 00000000..9456a364 --- /dev/null +++ b/.github/workflows/easy-install.yml @@ -0,0 +1,27 @@ +name: 'Easy Install Test' + +on: + pull_request: + workflow_dispatch: + push: + branches: [ develop ] + +permissions: + contents: read + +jobs: + easy-install-setup: + runs-on: ubuntu-latest + timeout-minutes: 60 + + name: Easy Install Test + steps: + - uses: actions/checkout@v3 + - run: | + python3 ${GITHUB_WORKSPACE}/easy-install.py -p -n actions_test --email test@frappe.io + docker compose -p actions_test exec backend bench version --format json + docker compose -p actions_test exec backend bench --site site1.local list-apps --format json + result=$(curl -sk https://127.0.0.1/api/method/ping | jq -r ."message") + if [[ "$result" == "pong" ]]; then echo "New instance works fine"; else exit 1; fi + docker compose -p actions_test down + docker volume prune -f \ No newline at end of file diff --git a/easy-install.py b/easy-install.py index 8334792d..6faf5c13 100644 --- a/easy-install.py +++ b/easy-install.py @@ -59,9 +59,9 @@ def clone_frappe_docker_repo() -> None: logging.error("Download and unzip failed", exc_info=True) cprint("\nCloning frappe_docker Failed\n\n", "[ERROR]: ", e, level=1) -def get_latest_version(dir) -> Dict: +def get_from_env(dir,file) -> Dict: env_vars ={} - with open(os.path.join(dir,"example.env")) as f: + with open(os.path.join(dir,file)) as f: for line in f: if line.startswith('#') or not line.strip(): continue @@ -69,9 +69,10 @@ def get_latest_version(dir) -> Dict: env_vars[key] = value return env_vars + def write_to_env(wd: str, site: str, db_pass: str, admin_pass: str, email: str) -> None: site_name = site or "" - example_env = get_latest_version(wd) + example_env = get_from_env(wd,"example.env") with open(os.path.join(wd, ".env"), "w") as f: f.writelines( [ @@ -115,11 +116,13 @@ def setup_prod(project: str, sitename: str, email: str) -> None: "\nPlease refer to .example.env file in the frappe_docker folder to know which keys to set\n\n", level=3, ) - admin_pass = generate_pass() - db_pass = generate_pass(9) + admin_pass = "" + db_pass = "" with open(compose_file_name, "w") as f: # Writing to compose file if not os.path.exists(os.path.join(docker_repo_path, ".env")): + admin_pass = generate_pass() + db_pass = generate_pass(9) write_to_env(docker_repo_path, sitename, db_pass, admin_pass, email) cprint( "\nA .env file is generated with basic configs. Please edit it to fit to your needs \n", @@ -130,6 +133,10 @@ def setup_prod(project: str, sitename: str, email: str) -> None: ) as en: en.writelines(f"ADMINISTRATOR_PASSWORD={admin_pass}\n") en.writelines(f"MARIADB_ROOT_PASSWORD={db_pass}\n") + else: + env = get_from_env(docker_repo_path,".env") + admin_pass = env['SITE_ADMIN_PASS'] + db_pass = env['DB_PASSWORD'] try: # TODO: Include flags for non-https and non-erpnext installation subprocess.run( @@ -182,6 +189,7 @@ def setup_prod(project: str, sitename: str, email: str) -> None: except Exception as e: logging.error("Prod docker-compose failed", exc_info=True) cprint(" Docker Compose failed, please check the container logs\n", e) + sys.exit(1) cprint(f"\nCreating site: {sitename} \n", level=3) @@ -203,6 +211,7 @@ def setup_prod(project: str, sitename: str, email: str) -> None: admin_pass, "--install-app", "erpnext", + "--set-default" ], check=True, ) @@ -210,6 +219,7 @@ def setup_prod(project: str, sitename: str, email: str) -> None: except Exception as e: logging.error("Bench site creation failed", exc_info=True) cprint("Bench Site creation failed\n", e) + sys.exit(1) else: install_docker() clone_frappe_docker_repo() From 687044f1238f71528d6f8630148314cc6da7032e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 15 Dec 2022 16:50:19 +0530 Subject: [PATCH 07/13] ci: add concurrency group --- .github/workflows/ci.yml | 4 ++++ .github/workflows/easy-install.yml | 13 +++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1040df3a..91e3dffd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,10 @@ on: push: branches: [ develop ] +concurrency: + group: ci-develop-${{ github.event_name }}-${{ github.event.number }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/easy-install.yml b/.github/workflows/easy-install.yml index 9456a364..85c095c8 100644 --- a/.github/workflows/easy-install.yml +++ b/.github/workflows/easy-install.yml @@ -1,10 +1,14 @@ -name: 'Easy Install Test' +name: "Easy Install Test" on: pull_request: workflow_dispatch: push: - branches: [ develop ] + branches: [develop] + +concurrency: + group: easy-install-develop-${{ github.event_name }}-${{ github.event.number }} + cancel-in-progress: true permissions: contents: read @@ -17,11 +21,12 @@ jobs: name: Easy Install Test steps: - uses: actions/checkout@v3 - - run: | + - name: Perform production easy install + run: | python3 ${GITHUB_WORKSPACE}/easy-install.py -p -n actions_test --email test@frappe.io docker compose -p actions_test exec backend bench version --format json docker compose -p actions_test exec backend bench --site site1.local list-apps --format json result=$(curl -sk https://127.0.0.1/api/method/ping | jq -r ."message") if [[ "$result" == "pong" ]]; then echo "New instance works fine"; else exit 1; fi docker compose -p actions_test down - docker volume prune -f \ No newline at end of file + docker volume prune -f From 80b58d9999bfc8b94781a646231d1e4ebfd322b5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 15 Dec 2022 17:00:04 +0530 Subject: [PATCH 08/13] chore: typo Co-Authored-By: Athul Cyriac Ajay --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 04c87349..840bb9db 100755 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Download the Easy Install script and execute it: ```sh $ wget https://raw.githubusercontent.com/frappe/bench/develop/easy-install.py -$ python3 install.py --prod +$ python3 easy-install.py --prod ``` This script will install docker on your system and will fetch the required containers, setup bench and a default ERPNext instance. From a987c1e9aed4b14e0b12100f35408ebababd7dee Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 16 Dec 2022 17:32:45 +0530 Subject: [PATCH 09/13] style: chmod+x, format, space -> tabs --- easy-install.py | 535 ++++++++++++++++++++++++------------------------ 1 file changed, 266 insertions(+), 269 deletions(-) mode change 100644 => 100755 easy-install.py diff --git a/easy-install.py b/easy-install.py old mode 100644 new mode 100755 index 6faf5c13..82fc1738 --- a/easy-install.py +++ b/easy-install.py @@ -1,94 +1,95 @@ #!/usr/bin/env python3 + import argparse -import subprocess +import logging import os +import platform +import subprocess import sys import time import urllib.request -import logging -import platform -from shutil import which, unpack_archive, move -from hashlib import sha224 +from shutil import move, unpack_archive, which from typing import Dict logging.basicConfig( - filename="easy-install.log", - filemode="w", - format="%(asctime)s - %(levelname)s - %(message)s", - level=logging.INFO, + filename="easy-install.log", + filemode="w", + format="%(asctime)s - %(levelname)s - %(message)s", + level=logging.INFO, ) def cprint(*args, level: int = 1): - """ - logs colorful messages - level = 1 : RED - level = 2 : GREEN - level = 3 : YELLOW + """ + logs colorful messages + level = 1 : RED + level = 2 : GREEN + level = 3 : YELLOW - default level = 1 - """ - CRED = "\033[31m" - CGRN = "\33[92m" - CYLW = "\33[93m" - reset = "\033[0m" - message = " ".join(map(str, args)) - if level == 1: - print(CRED, message, reset) - if level == 2: - print(CGRN, message, reset) - if level == 3: - print(CYLW, message, reset) + default level = 1 + """ + CRED = "\033[31m" + CGRN = "\33[92m" + CYLW = "\33[93m" + reset = "\033[0m" + message = " ".join(map(str, args)) + if level == 1: + print(CRED, message, reset) + if level == 2: + print(CGRN, message, reset) + if level == 3: + print(CYLW, message, reset) def clone_frappe_docker_repo() -> None: - try: - urllib.request.urlretrieve( - "https://github.com/frappe/frappe_docker/archive/refs/heads/main.zip", - "frappe_docker.zip", - ) - logging.info("Downloaded frappe_docker zip file from GitHub") - unpack_archive( - "frappe_docker.zip", "." - ) # Unzipping the frappe_docker.zip creates a folder "frappe_docker-main" - move("frappe_docker-main", "frappe_docker") - logging.info("Unzipped and Renamed frappe_docker") - os.remove("frappe_docker.zip") - logging.info("Removed the downloaded zip file") - except Exception as e: - logging.error("Download and unzip failed", exc_info=True) - cprint("\nCloning frappe_docker Failed\n\n", "[ERROR]: ", e, level=1) + try: + urllib.request.urlretrieve( + "https://github.com/frappe/frappe_docker/archive/refs/heads/main.zip", + "frappe_docker.zip", + ) + logging.info("Downloaded frappe_docker zip file from GitHub") + unpack_archive( + "frappe_docker.zip", "." + ) # Unzipping the frappe_docker.zip creates a folder "frappe_docker-main" + move("frappe_docker-main", "frappe_docker") + logging.info("Unzipped and Renamed frappe_docker") + os.remove("frappe_docker.zip") + logging.info("Removed the downloaded zip file") + except Exception as e: + logging.error("Download and unzip failed", exc_info=True) + cprint("\nCloning frappe_docker Failed\n\n", "[ERROR]: ", e, level=1) -def get_from_env(dir,file) -> Dict: - env_vars ={} - with open(os.path.join(dir,file)) as f: - for line in f: - if line.startswith('#') or not line.strip(): - continue - key, value = line.strip().split('=', 1) - env_vars[key] = value - return env_vars + +def get_from_env(dir, file) -> Dict: + env_vars = {} + with open(os.path.join(dir, file)) as f: + for line in f: + if line.startswith("#") or not line.strip(): + continue + key, value = line.strip().split("=", 1) + env_vars[key] = value + return env_vars def write_to_env(wd: str, site: str, db_pass: str, admin_pass: str, email: str) -> None: - site_name = site or "" - example_env = get_from_env(wd,"example.env") - with open(os.path.join(wd, ".env"), "w") as f: - f.writelines( - [ - f"FRAPPE_VERSION={example_env['FRAPPE_VERSION']}\n", # Defaults to latest version of Frappe - f"ERPNEXT_VERSION={example_env['ERPNEXT_VERSION']}\n", # defaults to latest version of ERPNext - 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}\n", - f"SITE_ADMIN_PASS={admin_pass}", - ] - ) + site_name = site or "" + example_env = get_from_env(wd, "example.env") + with open(os.path.join(wd, ".env"), "w") as f: + f.writelines( + [ + f"FRAPPE_VERSION={example_env['FRAPPE_VERSION']}\n", # Defaults to latest version of Frappe + f"ERPNEXT_VERSION={example_env['ERPNEXT_VERSION']}\n", # defaults to latest version of ERPNext + 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}\n", + f"SITE_ADMIN_PASS={admin_pass}", + ] + ) def generate_pass(length: int = 12) -> str: @@ -103,221 +104,217 @@ def generate_pass(length: int = 12) -> str: def check_repo_exists() -> bool: - return os.path.exists(os.path.join(os.getcwd(), "frappe_docker")) + return os.path.exists(os.path.join(os.getcwd(), "frappe_docker")) def setup_prod(project: str, sitename: str, email: str) -> None: - 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") - cprint( - "\nPlease refer to .example.env file in the frappe_docker folder to know which keys to set\n\n", - level=3, - ) - admin_pass = "" - db_pass = "" - with open(compose_file_name, "w") as f: - # Writing to compose file - if not os.path.exists(os.path.join(docker_repo_path, ".env")): - admin_pass = generate_pass() - db_pass = generate_pass(9) - write_to_env(docker_repo_path, sitename, db_pass, admin_pass, email) - cprint( - "\nA .env file is generated with basic configs. Please edit it to fit to your needs \n", - level=3, - ) - with open( - os.path.join(os.path.expanduser("~"), "passwords.txt"), "w" - ) as en: - en.writelines(f"ADMINISTRATOR_PASSWORD={admin_pass}\n") - en.writelines(f"MARIADB_ROOT_PASSWORD={db_pass}\n") - else: - env = get_from_env(docker_repo_path,".env") - admin_pass = env['SITE_ADMIN_PASS'] - db_pass = env['DB_PASSWORD'] - 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, - ) + 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") + cprint( + "\nPlease refer to .example.env file in the frappe_docker folder to know which keys to set\n\n", + level=3, + ) + admin_pass = "" + db_pass = "" + with open(compose_file_name, "w") as f: + # Writing to compose file + if not os.path.exists(os.path.join(docker_repo_path, ".env")): + admin_pass = generate_pass() + db_pass = generate_pass(9) + write_to_env(docker_repo_path, sitename, db_pass, admin_pass, email) + cprint( + "\nA .env file is generated with basic configs. Please edit it to fit to your needs \n", + level=3, + ) + with open(os.path.join(os.path.expanduser("~"), "passwords.txt"), "w") as en: + en.writelines(f"ADMINISTRATOR_PASSWORD={admin_pass}\n") + en.writelines(f"MARIADB_ROOT_PASSWORD={db_pass}\n") + else: + env = get_from_env(docker_repo_path, ".env") + admin_pass = env["SITE_ADMIN_PASS"] + db_pass = env["DB_PASSWORD"] + 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: - logging.error("Docker Compose generation failed", exc_info=True) - cprint("\nGenerating Compose File failed\n") - sys.exit(1) - try: - # Starting with generated compose file - subprocess.run( - [ - which("docker"), - "compose", - "-p", - project, - "-f", - compose_file_name, - "up", - "-d", - ], - check=True, - ) - logging.info(f"Docker Compose file generated at ~/{project}-compose.yml") + except Exception: + logging.error("Docker Compose generation failed", exc_info=True) + cprint("\nGenerating Compose File failed\n") + sys.exit(1) + try: + # Starting with generated compose file + subprocess.run( + [ + which("docker"), + "compose", + "-p", + project, + "-f", + compose_file_name, + "up", + "-d", + ], + check=True, + ) + logging.info(f"Docker Compose file generated at ~/{project}-compose.yml") - except Exception as e: - logging.error("Prod docker-compose failed", exc_info=True) - cprint(" Docker Compose failed, please check the container logs\n", e) - sys.exit(1) + except Exception as e: + logging.error("Prod docker-compose failed", exc_info=True) + cprint(" Docker Compose failed, please check the container logs\n", e) + sys.exit(1) - cprint(f"\nCreating site: {sitename} \n", level=3) + cprint(f"\nCreating site: {sitename} \n", level=3) - 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", - "--set-default" - ], - check=True, - ) - logging.info("New site creation completed") - except Exception as e: - logging.error("Bench site creation failed", exc_info=True) - cprint("Bench Site creation failed\n", e) - sys.exit(1) - else: - install_docker() - clone_frappe_docker_repo() - setup_prod(project, sitename, email) # Recursive + 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", + "--set-default", + ], + check=True, + ) + logging.info("New site creation completed") + except Exception as e: + logging.error("Bench site creation failed", exc_info=True) + cprint("Bench Site creation failed\n", e) + sys.exit(1) + 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, - ) - cprint( - "Please go through the Development Documentation: https://github.com/frappe/frappe_docker/tree/main/development to fully complete the setup.", - level=2, - ) - logging.info("Development Setup completed") - except Exception as e: - logging.error("Dev Environment setup failed", exc_info=True) - cprint("Setting Up Development Environment Failed\n", e) - else: - install_docker() - clone_frappe_docker_repo() - setup_dev_instance(project) # Recursion on goes brrrr + 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, + ) + cprint( + "Please go through the Development Documentation: https://github.com/frappe/frappe_docker/tree/main/development to fully complete the setup.", + level=2, + ) + logging.info("Development Setup completed") + except Exception as e: + logging.error("Dev Environment setup failed", exc_info=True) + cprint("Setting Up Development Environment Failed\n", e) + else: + install_docker() + clone_frappe_docker_repo() + setup_dev_instance(project) # Recursion on goes brrrr def install_docker(): - if which("docker") is not None: - return - cprint("Docker is not installed, Installing Docker...", level=3) - logging.info("Docker not found, installing Docker") - if platform.system() == "Darwin" or platform.system() == "Windows": - print( - f""" + if which("docker") is not None: + return + cprint("Docker is not installed, Installing Docker...", level=3) + logging.info("Docker not found, installing Docker") + if platform.system() == "Darwin" or platform.system() == "Windows": + print( + f""" 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""" - ) - logging.debug("Docker setup failed due to platform is not Linux") - sys.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 - ) - cprint("Waiting Docker to start", level=3) - time.sleep(10) - subprocess.run(["sudo", "systemctl", "restart", "docker.service"], check=True) - except Exception as e: - logging.error("Installing Docker failed", exc_info=True) - cprint("Failed to Install Docker\n", e) - cprint("\n Try Installing Docker Manually and re-run this script again\n") - sys.exit(1) + ) + logging.debug("Docker setup failed due to platform is not Linux") + sys.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 + ) + cprint("Waiting Docker to start", level=3) + time.sleep(10) + subprocess.run(["sudo", "systemctl", "restart", "docker.service"], check=True) + except Exception as e: + logging.error("Installing Docker failed", exc_info=True) + cprint("Failed to Install Docker\n", e) + cprint("\n Try Installing Docker Manually and re-run this script again\n") + sys.exit(1) 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: - cprint("\nSetting Up Development Instance\n", level=2) - logging.info("Running Development Setup") - setup_dev_instance(args.project) - elif args.prod: - cprint("\nSetting Up Production Instance\n", level=2) - logging.info("Running Production Setup") - if "example.com" in args.email: - cprint("Emails with example.com not acceptable", level=1) - sys.exit(1) - setup_prod(args.project, args.sitename, args.email) \ No newline at end of file + 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: + cprint("\nSetting Up Development Instance\n", level=2) + logging.info("Running Development Setup") + setup_dev_instance(args.project) + elif args.prod: + cprint("\nSetting Up Production Instance\n", level=2) + logging.info("Running Production Setup") + if "example.com" in args.email: + cprint("Emails with example.com not acceptable", level=1) + sys.exit(1) + setup_prod(args.project, args.sitename, args.email) From 228aeaf2fdf0205294825a40c6752b2c8abf6d7a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 16 Dec 2022 17:34:26 +0530 Subject: [PATCH 10/13] fix: print help when no args passed [skip ci] --- easy-install.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/easy-install.py b/easy-install.py index 82fc1738..26d5ab72 100755 --- a/easy-install.py +++ b/easy-install.py @@ -318,3 +318,5 @@ if __name__ == "__main__": cprint("Emails with example.com not acceptable", level=1) sys.exit(1) setup_prod(args.project, args.sitename, args.email) + else: + parser.print_help() From 8903649159676882bd9e970ecefd44c969856f05 Mon Sep 17 00:00:00 2001 From: jiangying Date: Sat, 31 Dec 2022 16:20:21 +0800 Subject: [PATCH 11/13] chore: typo in readme (#1407) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 840bb9db..5e4edcdd 100755 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ The setup for each of these installations can be achieved in multiple ways: - [Containerized Installation](#containerized-installation) - [Manual Installation](#manual-installation) -We recommend using either the Docker Installation to setup a Production Environment. For Development, you may choose either of the two methods to setup an instance. +We recommend using Docker Installation to setup a Production Environment. For Development, you may choose either of the two methods to setup an instance. Otherwise, if you are looking to evaluate Frappe apps without hassle of hosting, you can try them [on frappecloud.com](https://frappecloud.com/). From ba853c943b375e5ec32c917aada918a13e0fef03 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 2 Jan 2023 15:47:05 +0530 Subject: [PATCH 12/13] chore: incorrect license identifiers --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cab5236a..2d535b1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ authors = [ classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", - "License :: OSI Approved :: GNU Affero General Public License v3", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Natural Language :: English", "Operating System :: MacOS", "Operating System :: OS Independent", From 23eede5fd33501f58770d32ac2f4ba1793339b82 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 2 Jan 2023 16:03:33 +0530 Subject: [PATCH 13/13] fix: version check backward compatibility (#1409) This code wasn't triggering because VersionNotFound exception gets thrown before it ever reaches to this point. --- bench/utils/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/utils/app.py b/bench/utils/app.py index 5541b548..75891d5b 100644 --- a/bench/utils/app.py +++ b/bench/utils/app.py @@ -284,7 +284,7 @@ def get_current_version(app, bench_path="."): with open(init_path) as f: current_version = get_version_from_string(f.read()) - except AttributeError: + except (AttributeError, VersionNotFound): # backward compatibility with open(setup_path) as f: current_version = get_version_from_string(f.read(), field="version")