2
0
mirror of https://github.com/frappe/bench.git synced 2025-01-09 00:21:23 +00:00

feat: sub commands for easy-install

This commit is contained in:
Revant Nandgaonkar 2024-11-07 16:15:58 +05:30
parent 7931b39334
commit df24b714cc
3 changed files with 847 additions and 440 deletions

View File

@ -28,8 +28,7 @@ jobs:
- name: Perform production easy install - name: Perform production easy install
run: | run: |
python3 ${GITHUB_WORKSPACE}/easy-install.py build >/dev/null python3 ${GITHUB_WORKSPACE}/easy-install.py build --deploy --tag=custom-apps:latest --project=actions_test --email=test@frappe.io --image=custom-apps --version=latest --app=erpnext
python3 ${GITHUB_WORKSPACE}/easy-install.py -p -n actions_test --email test@frappe.io --image custom-apps --version latest --app erpnext
docker compose -p actions_test exec backend bench version --format json docker compose -p actions_test exec backend bench version --format json
docker compose -p actions_test exec backend bench --site site1.localhost list-apps --format json docker compose -p actions_test exec backend bench --site site1.localhost list-apps --format json
result=$(curl -H "Host: site1.localhost" -sk https://127.0.0.1/api/method/ping | jq -r ."message") result=$(curl -H "Host: site1.localhost" -sk https://127.0.0.1/api/method/ping | jq -r ."message")

148
README.md
View File

@ -87,7 +87,7 @@ Download the Easy Install script and execute it:
```sh ```sh
$ wget https://raw.githubusercontent.com/frappe/bench/develop/easy-install.py $ wget https://raw.githubusercontent.com/frappe/bench/develop/easy-install.py
$ python3 easy-install.py --prod --email your@email.tld $ python3 easy-install.py deploy --email=user@domain.tld --sitename=subdomain.domain.tld --app=erpnext
``` ```
This script will install docker on your system and will fetch the required containers, setup bench and a default ERPNext instance. This script will install docker on your system and will fetch the required containers, setup bench and a default ERPNext instance.
@ -101,20 +101,150 @@ When the setup is complete, you will be able to access the system at `http://<yo
Here are the arguments for the easy-install script Here are the arguments for the easy-install script
```txt **Build custom images**
usage: easy-install.py [-h] [-p] [-d] [-s SITENAME] [-n PROJECT] [--email EMAIL]
Install Frappe with Docker ```txt
usage: easy-install.py build [-h] [-n PROJECT] [-i IMAGE] [-q] [-m HTTP_PORT] [-v VERSION] [-a APPS] [-s SITES] [-e EMAIL]
[-p] [-r FRAPPE_PATH] [-b FRAPPE_BRANCH] [-j APPS_JSON] [-t TAGS] [-c CONTAINERFILE]
[-y PYTHON_VERSION] [-d NODE_VERSION] [-x] [-u]
options: options:
-h, --help show this help message and exit -h, --help show this help message and exit
-p, --prod Setup Production System -n PROJECT, --project PROJECT
-d, --dev Setup Development System Project Name
-s SITENAME, --sitename SITENAME The Site Name for your production site -i IMAGE, --image IMAGE
-n PROJECT, --project PROJECT Project Name Full Image Name
--email EMAIL Add email for the SSL. -q, --no-ssl No https
-m HTTP_PORT, --http-port HTTP_PORT
Http port in case of no-ssl
-v VERSION, --version VERSION
ERPNext version to install, defaults to latest stable
-a APPS, --app APPS list of app(s) to be installed
-s SITES, --sitename SITES
Site Name(s) for your production bench
-e EMAIL, --email EMAIL
Add email for the SSL.
-p, --push Push the built image to registry
-r FRAPPE_PATH, --frappe-path FRAPPE_PATH
Frappe Repository to use, default: https://github.com/frappe/frappe
-b FRAPPE_BRANCH, --frappe-branch FRAPPE_BRANCH
Frappe branch to use, default: version-15
-j APPS_JSON, --apps-json APPS_JSON
Path to apps json, default: frappe_docker/development/apps-example.json
-t TAGS, --tag TAGS Full Image Name(s), default: custom-apps:latest
-c CONTAINERFILE, --containerfile CONTAINERFILE
Path to Containerfile: images/layered/Containerfile
-y PYTHON_VERSION, --python-version PYTHON_VERSION
Python Version, default: 3.11.6
-d NODE_VERSION, --node-version NODE_VERSION
NodeJS Version, default: 18.18.2
-x, --deploy Deploy after build
-u, --upgrade Upgrade after build
``` ```
**Deploy using compose**
```txt
usage: easy-install.py deploy [-h] [-n PROJECT] [-i IMAGE] [-q] [-m HTTP_PORT] [-v VERSION] [-a APPS] [-s SITES] [-e EMAIL]
options:
-h, --help show this help message and exit
-n PROJECT, --project PROJECT
Project Name
-i IMAGE, --image IMAGE
Full Image Name
-q, --no-ssl No https
-m HTTP_PORT, --http-port HTTP_PORT
Http port in case of no-ssl
-v VERSION, --version VERSION
ERPNext version to install, defaults to latest stable
-a APPS, --app APPS list of app(s) to be installed
-s SITES, --sitename SITES
Site Name(s) for your production bench
-e EMAIL, --email EMAIL
Add email for the SSL.
```
**Upgrade existing project**
```txt
usage: easy-install.py upgrade [-h] [-n PROJECT] [-i IMAGE] [-q] [-m HTTP_PORT] [-v VERSION]
options:
-h, --help show this help message and exit
-n PROJECT, --project PROJECT
Project Name
-i IMAGE, --image IMAGE
Full Image Name
-q, --no-ssl No https
-m HTTP_PORT, --http-port HTTP_PORT
Http port in case of no-ssl
-v VERSION, --version VERSION
ERPNext or image version to install, defaults to latest stable
```
**Development setup using compose**
```txt
usage: easy-install.py develop [-h] [-n PROJECT]
options:
-h, --help show this help message and exit
-n PROJECT, --project PROJECT
Compose project name
```
**Exec into existing project**
```txt
usage: easy-install.py exec [-h] [-n PROJECT]
options:
-h, --help show this help message and exit
-n PROJECT, --project PROJECT
Project Name
```
To use custom apps, you need to create a json file with list of apps and pass it to build command.
Example apps.json
```json
[
{
"url": "https://github.com/frappe/wiki.git",
"branch": "master"
}
]
```
Execute following command to build and deploy above apps:
```sh
$ python3 easy-install.py build \
--tag=ghcr.io/org/repo/custom-apps:latest \
--push \
--image=ghcr.io/org/repo/custom-apps \
--version=latest \
--deploy \
--project=actions_test \
--email=test@frappe.io \
--apps-json=apps.json \
--app=wiki
```
Note:
- `--tag`, tag to set for built image, can be multiple.
- `--push`, push the built image.
- `--image`, the image to use when starting docker compose project.
- `--version`, the version to use when starting docker compose project.
- `--app`, app to install on site creation, can be multiple.
- `--deploy`, flag to deploy after build/push is complete
- `--project=actions_test`, name of the project, compose file with project name will be stored in user home directory.
- `--email=test@frappe.io`, valid email for letsencrypt certificate expiry notification.
- `--apps-json`, path to json file with list of apps to be added to bench.
#### Troubleshooting #### Troubleshooting
In case the setup fails, the log file is saved under `$HOME/easy-install.log`. You may then In case the setup fails, the log file is saved under `$HOME/easy-install.log`. You may then

View File

@ -50,9 +50,8 @@ def clone_frappe_docker_repo() -> None:
"frappe_docker.zip", "frappe_docker.zip",
) )
logging.info("Downloaded frappe_docker zip file from GitHub") logging.info("Downloaded frappe_docker zip file from GitHub")
unpack_archive( unpack_archive("frappe_docker.zip", ".")
"frappe_docker.zip", "." # Unzipping the frappe_docker.zip creates a folder "frappe_docker-main"
) # Unzipping the frappe_docker.zip creates a folder "frappe_docker-main"
move("frappe_docker-main", "frappe_docker") move("frappe_docker-main", "frappe_docker")
logging.info("Unzipped and Renamed frappe_docker") logging.info("Unzipped and Renamed frappe_docker")
os.remove("frappe_docker.zip") os.remove("frappe_docker.zip")
@ -75,19 +74,19 @@ def get_from_env(dir, file) -> Dict:
def write_to_env( def write_to_env(
wd: str, wd: str,
sites, sites: List[str],
db_pass: str, db_pass: str,
admin_pass: str, admin_pass: str,
email: str, email: str,
erpnext_version: str = None, erpnext_version: str = None,
http_port: str = None,
) -> None: ) -> None:
quoted_sites = ",".join([f"`{site}`" for site in sites]).strip(",") quoted_sites = ",".join([f"`{site}`" for site in sites]).strip(",")
example_env = get_from_env(wd, "example.env") example_env = get_from_env(wd, "example.env")
erpnext_version = erpnext_version or example_env["ERPNEXT_VERSION"] erpnext_version = erpnext_version or example_env["ERPNEXT_VERSION"]
with open(os.path.join(wd, ".env"), "w") as f: env_file_lines = [
f.writelines( # defaults to latest version of ERPNext
[ f"ERPNEXT_VERSION={erpnext_version}\n",
f"ERPNEXT_VERSION={erpnext_version}\n", # defaults to latest version of ERPNext
f"DB_PASSWORD={db_pass}\n", f"DB_PASSWORD={db_pass}\n",
"DB_HOST=db\n", "DB_HOST=db\n",
"DB_PORT=3306\n", "DB_PORT=3306\n",
@ -99,7 +98,12 @@ def write_to_env(
f"SITES={quoted_sites}\n", f"SITES={quoted_sites}\n",
"PULL_POLICY=missing\n", "PULL_POLICY=missing\n",
] ]
)
if http_port:
env_file_lines.append(f"HTTP_PUBLISH_PORT={http_port}\n")
with open(os.path.join(wd, ".env"), "w") as f:
f.writelines(env_file_lines)
def generate_pass(length: int = 12) -> str: def generate_pass(length: int = 12) -> str:
@ -117,12 +121,23 @@ 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, sites, email: str, version: str = None, image = None, apps = []) -> None: def start_prod(
if len(sites) == 0: project: str,
sites = ["site1.localhost"] sites: List[str] = [],
email: str = None,
version: str = None,
image: str = None,
is_https: bool = True,
http_port: str = None,
):
if not check_repo_exists():
clone_frappe_docker_repo()
install_container_runtime()
if check_repo_exists(): compose_file_name = os.path.join(
compose_file_name = os.path.join(os.path.expanduser("~"), f"{project}-compose.yml") os.path.expanduser("~"),
f"{project}-compose.yml",
)
docker_repo_path = os.path.join(os.getcwd(), "frappe_docker") docker_repo_path = os.path.join(os.getcwd(), "frappe_docker")
cprint( cprint(
"\nPlease refer to .example.env file in the frappe_docker folder to know which keys to set\n\n", "\nPlease refer to .example.env file in the frappe_docker folder to know which keys to set\n\n",
@ -135,23 +150,43 @@ def setup_prod(project: str, sites, email: str, version: str = None, image = Non
if not os.path.exists(os.path.join(docker_repo_path, ".env")): if not os.path.exists(os.path.join(docker_repo_path, ".env")):
admin_pass = generate_pass() admin_pass = generate_pass()
db_pass = generate_pass(9) db_pass = generate_pass(9)
write_to_env(docker_repo_path, sites, db_pass, admin_pass, email, version) write_to_env(
wd=docker_repo_path,
sites=sites,
db_pass=db_pass,
admin_pass=admin_pass,
email=email,
erpnext_version=version,
http_port=http_port if not is_https and http_port else None,
)
cprint( cprint(
"\nA .env file is generated with basic configs. Please edit it to fit to your needs \n", "\nA .env file is generated with basic configs. Please edit it to fit to your needs \n",
level=3, level=3,
) )
with open(os.path.join(os.path.expanduser("~"), "passwords.txt"), "w") as en: with open(
os.path.join(os.path.expanduser("~"), "passwords.txt"), "w"
) as en:
en.writelines(f"ADMINISTRATOR_PASSWORD={admin_pass}\n") en.writelines(f"ADMINISTRATOR_PASSWORD={admin_pass}\n")
en.writelines(f"MARIADB_ROOT_PASSWORD={db_pass}\n") en.writelines(f"MARIADB_ROOT_PASSWORD={db_pass}\n")
else: else:
env = get_from_env(docker_repo_path, ".env") env = get_from_env(docker_repo_path, ".env")
admin_pass = env["SITE_ADMIN_PASS"] sites = env["SITES"].replace("`", "").split(",") if env["SITES"] else []
db_pass = env["DB_PASSWORD"] db_pass = env["DB_PASSWORD"]
admin_pass = env["SITE_ADMIN_PASS"]
email = env["LETSENCRYPT_EMAIL"]
write_to_env(
wd=docker_repo_path,
sites=sites,
db_pass=db_pass,
admin_pass=admin_pass,
email=email,
erpnext_version=version,
http_port=http_port if not is_https and http_port else None,
)
try: try:
# TODO: Include flags for non-https and non-erpnext installation command = [
subprocess.run( "docker",
[
which("docker"),
"compose", "compose",
"--project-name", "--project-name",
project, project,
@ -161,13 +196,19 @@ def setup_prod(project: str, sites, email: str, version: str = None, image = Non
"overrides/compose.mariadb.yaml", "overrides/compose.mariadb.yaml",
"-f", "-f",
"overrides/compose.redis.yaml", "overrides/compose.redis.yaml",
# "-f", "overrides/compose.noproxy.yaml", TODO: Add support for local proxying without HTTPs
"-f", "-f",
"overrides/compose.https.yaml", (
"overrides/compose.https.yaml"
if is_https
else "overrides/compose.noproxy.yaml"
),
"--env-file", "--env-file",
".env", ".env",
"config", "config",
], ]
subprocess.run(
command,
cwd=docker_repo_path, cwd=docker_repo_path,
stdout=f, stdout=f,
check=True, check=True,
@ -187,17 +228,20 @@ def setup_prod(project: str, sites, email: str, version: str = None, image = Non
try: try:
# Starting with generated compose file # Starting with generated compose file
subprocess.run( command = [
[ "docker",
which("docker"),
"compose", "compose",
"-p", "-p",
project, project,
"-f", "-f",
compose_file_name, compose_file_name,
"up", "up",
"--force-recreate",
"--remove-orphans",
"-d", "-d",
], ]
subprocess.run(
command,
check=True, check=True,
) )
logging.info(f"Docker Compose file generated at ~/{project}-compose.yml") logging.info(f"Docker Compose file generated at ~/{project}-compose.yml")
@ -207,20 +251,74 @@ def setup_prod(project: str, sites, email: str, version: str = None, image = Non
cprint(" Docker Compose failed, please check the container logs\n", e) cprint(" Docker Compose failed, please check the container logs\n", e)
sys.exit(1) sys.exit(1)
return db_pass, admin_pass
def setup_prod(
project: str,
sites: List[str],
email: str,
version: str = None,
image: str = None,
apps: List[str] = [],
is_https: bool = False,
http_port: str = None,
) -> None:
if len(sites) == 0:
sites = ["site1.localhost"]
db_pass, admin_pass = start_prod(
project=project,
sites=sites,
email=email,
version=version,
image=image,
is_https=is_https,
http_port=http_port,
)
for sitename in sites: for sitename in sites:
create_site(sitename, project, db_pass, admin_pass, apps) create_site(sitename, project, db_pass, admin_pass, apps)
else: cprint(
install_docker() f"MariaDB root password is {db_pass}",
clone_frappe_docker_repo() level=2,
setup_prod(project, sites, email, version, image, apps) # Recursive )
cprint(
f"Site administrator password is {admin_pass}",
level=2,
)
passwords_file_path = os.path.join(
os.path.expanduser("~"),
"passwords.txt",
)
cprint(f"Passwords are stored in {passwords_file_path}", level=3)
def update_prod(
project: str,
version: str = None,
image=None,
is_https: bool = False,
http_port: str = None,
) -> None:
start_prod(
project=project,
version=version,
image=image,
is_https=is_https,
http_port=http_port,
)
migrate_site(project=project)
def setup_dev_instance(project: str): def setup_dev_instance(project: str):
if check_repo_exists(): if not check_repo_exists():
clone_frappe_docker_repo()
install_container_runtime()
try: try:
subprocess.run( command = [
[
"docker", "docker",
"compose", "compose",
"-f", "-f",
@ -229,7 +327,9 @@ def setup_dev_instance(project: str):
project, project,
"up", "up",
"-d", "-d",
], ]
subprocess.run(
command,
cwd=os.path.join(os.getcwd(), "frappe_docker"), cwd=os.path.join(os.getcwd(), "frappe_docker"),
check=True, check=True,
) )
@ -241,19 +341,13 @@ def setup_dev_instance(project: str):
except Exception as e: except Exception as e:
logging.error("Dev Environment setup failed", exc_info=True) logging.error("Dev Environment setup failed", exc_info=True)
cprint("Setting Up Development Environment Failed\n", e) 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(): def install_docker():
if which("docker") is not None:
return
cprint("Docker is not installed, Installing Docker...", level=3) cprint("Docker is not installed, Installing Docker...", level=3)
logging.info("Docker not found, installing Docker") logging.info("Docker not found, installing Docker")
if platform.system() == "Darwin" or platform.system() == "Windows": if platform.system() == "Darwin" or platform.system() == "Windows":
print( cprint(
f""" f"""
This script doesn't install Docker on {"Mac" if platform.system()=="Darwin" else "Windows"}. This script doesn't install Docker on {"Mac" if platform.system()=="Darwin" else "Windows"}.
@ -269,11 +363,26 @@ def install_docker():
) )
subprocess.run(["/bin/bash"], input=ps.stdout, capture_output=True) subprocess.run(["/bin/bash"], input=ps.stdout, capture_output=True)
subprocess.run( subprocess.run(
["sudo", "usermod", "-aG", "docker", str(os.getenv("USER"))], check=True [
"sudo",
"usermod",
"-aG",
"docker",
str(os.getenv("USER")),
],
check=True,
) )
cprint("Waiting Docker to start", level=3) cprint("Waiting Docker to start", level=3)
time.sleep(10) time.sleep(10)
subprocess.run(["sudo", "systemctl", "restart", "docker.service"], check=True) subprocess.run(
[
"sudo",
"systemctl",
"restart",
"docker.service",
],
check=True,
)
except Exception as e: except Exception as e:
logging.error("Installing Docker failed", exc_info=True) logging.error("Installing Docker failed", exc_info=True)
cprint("Failed to Install Docker\n", e) cprint("Failed to Install Docker\n", e)
@ -281,6 +390,14 @@ def install_docker():
sys.exit(1) sys.exit(1)
def install_container_runtime(runtime="docker"):
if which(runtime) is not None:
cprint(runtime.title() + " is already installed", level=2)
return
if runtime == "docker":
install_docker()
def create_site( def create_site(
sitename: str, sitename: str,
project: str, project: str,
@ -288,9 +405,10 @@ def create_site(
admin_pass: str, admin_pass: str,
apps: List[str] = [], apps: List[str] = [],
): ):
apps = apps or []
cprint(f"\nCreating site: {sitename} \n", level=3) cprint(f"\nCreating site: {sitename} \n", level=3)
command = [ command = [
which("docker"), "docker",
"compose", "compose",
"-p", "-p",
project, project,
@ -298,19 +416,17 @@ def create_site(
"backend", "backend",
"bench", "bench",
"new-site", "new-site",
sitename,
"--no-mariadb-socket", "--no-mariadb-socket",
"--db-root-password", f"--db-root-password={db_pass}",
db_pass, f"--admin-password={admin_pass}",
"--admin-password",
admin_pass,
"--set-default",
] ]
for app in apps: for app in apps:
command.append("--install-app") command.append("--install-app")
command.append(app) command.append(app)
command.append(sitename)
try: try:
subprocess.run( subprocess.run(
command, command,
@ -322,58 +438,186 @@ def create_site(
cprint(f"Bench Site creation failed for {sitename}\n", e) cprint(f"Bench Site creation failed for {sitename}\n", e)
def add_build_parser(argparser: argparse.ArgumentParser): def migrate_site(project: str):
subparsers = argparser.add_subparsers(dest='subcommand') cprint(f"\nMigrating sites for {project}", level=3)
build = subparsers.add_parser('build', help='Build Custom Images')
build.add_argument( exec_command(
project=project,
command=[
"bench",
"--site",
"all",
"migrate",
],
)
def exec_command(project: str, command: List[str] = [], interactive_terminal=False):
if not command:
command = ["echo", '"Please execute a command"']
cprint(f"\nExecuting Command:\n{' '.join(command)}", level=3)
exec_command = [
"docker",
"compose",
"-p",
project,
"exec",
]
if interactive_terminal:
exec_command.append("-it")
exec_command.append("backend")
exec_command += command
try:
subprocess.run(
exec_command,
check=True,
)
logging.info("New site creation completed")
except Exception as e:
logging.error(f"Exec command failed for {project}", exc_info=True)
cprint(f"Exec command failed for {project}\n", e)
def add_project_option(parser: argparse.ArgumentParser):
parser.add_argument(
"-n",
"--project",
help="Project Name",
default="frappe",
)
return parser
def add_setup_options(parser: argparse.ArgumentParser):
parser.add_argument(
"-a",
"--app",
dest="apps",
default=[],
help="list of app(s) to be installed",
action="append",
)
parser.add_argument(
"-s",
"--sitename",
help="Site Name(s) for your production bench",
default=[],
action="append",
dest="sites",
)
parser.add_argument("-e", "--email", help="Add email for the SSL.")
return parser
def add_common_parser(parser: argparse.ArgumentParser):
parser = add_project_option(parser)
parser.add_argument("-i", "--image", help="Full Image Name")
parser.add_argument("-q", "--no-ssl", action="store_true", help="No https")
parser.add_argument(
"-m", "--http-port", help="Http port in case of no-ssl", default="8080"
)
parser.add_argument(
"-v",
"--version",
help="ERPNext or image version to install, defaults to latest stable",
)
return parser
def add_build_parser(subparsers: argparse.ArgumentParser):
parser = subparsers.add_parser("build", help="Build custom images")
parser = add_common_parser(parser)
parser = add_setup_options(parser)
parser.add_argument(
"-p", "-p",
"--push", "--push",
help="Push the built image to registry", help="Push the built image to registry",
action="store_true", action="store_true",
) )
build.add_argument( parser.add_argument(
"-r", "-r",
"--frappe-path", "--frappe-path",
help="Frappe Repository to use, default: https://github.com/frappe/frappe", help="Frappe Repository to use, default: https://github.com/frappe/frappe",
default="https://github.com/frappe/frappe", default="https://github.com/frappe/frappe",
) )
build.add_argument( parser.add_argument(
"-b", "-b",
"--frappe-branch", "--frappe-branch",
help="Frappe branch to use, default: version-15", help="Frappe branch to use, default: version-15",
default="version-15", default="version-15",
) )
build.add_argument( parser.add_argument(
"-j", "-j",
"--apps-json", "--apps-json",
help="Path to apps json, default: frappe_docker/development/apps-example.json", help="Path to apps json, default: frappe_docker/development/apps-example.json",
default="frappe_docker/development/apps-example.json", default="frappe_docker/development/apps-example.json",
) )
build.add_argument( parser.add_argument(
"-t", "-t",
"--tag", "--tag",
dest="tags", dest="tags",
help="Full Image Name(s), default: custom-apps:latest", help="Full Image Name(s), default: custom-apps:latest",
action="append", action="append",
) )
build.add_argument( parser.add_argument(
"-c", "-c",
"--containerfile", "--containerfile",
help="Path to Containerfile: images/layered/Containerfile", help="Path to Containerfile: images/layered/Containerfile",
default="images/layered/Containerfile", default="images/layered/Containerfile",
) )
build.add_argument( parser.add_argument(
"-y", "-y",
"--python-version", "--python-version",
help="Python Version, default: 3.11.6", help="Python Version, default: 3.11.6",
default="3.11.6", default="3.11.6",
) )
build.add_argument( parser.add_argument(
"-n", "-d",
"--node-version", "--node-version",
help="NodeJS Version, default: 18.18.2", help="NodeJS Version, default: 18.18.2",
default="18.18.2", default="18.18.2",
) )
parser.add_argument(
"-x",
"--deploy",
help="Deploy after build",
action="store_true",
)
parser.add_argument(
"-u",
"--upgrade",
help="Upgrade after build",
action="store_true",
)
def add_deploy_parser(subparsers: argparse.ArgumentParser):
parser = subparsers.add_parser("deploy", help="Deploy using compose")
parser = add_common_parser(parser)
parser = add_setup_options(parser)
def add_develop_parser(subparsers: argparse.ArgumentParser):
parser = subparsers.add_parser("develop", help="Development setup using compose")
parser.add_argument(
"-n", "--project", default="frappe", help="Compose project name"
)
def add_upgrade_parser(subparsers: argparse.ArgumentParser):
parser = subparsers.add_parser("upgrade", help="Upgrade existing project")
parser = add_common_parser(parser)
def add_exec_parser(subparsers: argparse.ArgumentParser):
parser = subparsers.add_parser("exec", help="Exec into existing project")
parser = add_project_option(parser)
def build_image( def build_image(
push: bool, push: bool,
@ -387,7 +631,7 @@ def build_image(
): ):
if not check_repo_exists(): if not check_repo_exists():
clone_frappe_docker_repo() clone_frappe_docker_repo()
install_docker() install_container_runtime()
if not tags: if not tags:
tags = ["custom-apps:latest"] tags = ["custom-apps:latest"]
@ -426,7 +670,7 @@ def build_image(
subprocess.run( subprocess.run(
command, command,
check=True, check=True,
cwd='frappe_docker', cwd="frappe_docker",
) )
except Exception as e: except Exception as e:
logging.error("Image build failed", exc_info=True) logging.error("Image build failed", exc_info=True)
@ -444,44 +688,35 @@ def build_image(
cprint("\nImage push failed\n\n", "[ERROR]: ", e, level=1) cprint("\nImage push failed\n\n", "[ERROR]: ", e, level=1)
if __name__ == "__main__": def get_args_parser():
parser = argparse.ArgumentParser(description="Install Frappe with Docker") parser = argparse.ArgumentParser(
description="Easy install script for Frappe Framework"
)
# Setup sub-commands
subparsers = parser.add_subparsers(dest="subcommand")
# Build command # Build command
add_build_parser(parser) add_build_parser(subparsers)
# Deploy command
add_deploy_parser(subparsers)
# Upgrade command
add_upgrade_parser(subparsers)
# Develop command
add_develop_parser(subparsers)
# Exec command
add_exec_parser(subparsers)
return parser
if __name__ == "__main__":
parser = get_args_parser()
if len(sys.argv) == 1:
parser.print_help(sys.stderr)
sys.exit(1)
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="Site Name(s) for your production bench",
default=[],
action="append",
dest="sites",
)
parser.add_argument("-n", "--project", help="Project Name", default="frappe")
parser.add_argument("-i", "--image", help="Full Image Name")
parser.add_argument(
"--email", help="Add email for the SSL.", required="--prod" in sys.argv
)
parser.add_argument(
"-v", "--version", help="ERPNext version to install, defaults to latest stable"
)
parser.add_argument(
"-a",
"--app",
dest="apps",
help="list of app(s) to be installed",
action="append",
)
args = parser.parse_args() args = parser.parse_args()
if args.subcommand == 'build': if args.subcommand == "build":
build_image( build_image(
push=args.push, push=args.push,
frappe_path=args.frappe_path, frappe_path=args.frappe_path,
@ -492,18 +727,61 @@ if __name__ == "__main__":
python_version=args.python_version, python_version=args.python_version,
node_version=args.node_version, node_version=args.node_version,
) )
sys.exit(0) if args.deploy:
setup_prod(
project=args.project,
sites=args.sites,
email=args.email,
version=args.version,
image=args.image,
apps=args.apps,
is_https=not args.no_ssl,
http_port=args.http_port,
)
elif args.upgrade:
update_prod(
project=args.project,
version=args.version,
image=args.image,
is_https=not args.no_ssl,
http_port=args.http_port,
)
if args.dev: elif args.subcommand == "deploy":
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) cprint("\nSetting Up Production Instance\n", level=2)
logging.info("Running Production Setup") logging.info("Running Production Setup")
if "example.com" in args.email: if "example.com" in args.email:
cprint("Emails with example.com not acceptable", level=1) cprint("Emails with example.com not acceptable", level=1)
sys.exit(1) sys.exit(1)
setup_prod(args.project, args.sites, args.email, args.version, args.image, args.apps) setup_prod(
else: project=args.project,
parser.print_help() sites=args.sites,
email=args.email,
version=args.version,
image=args.image,
apps=args.apps,
is_https=not args.no_ssl,
http_port=args.http_port,
)
elif args.subcommand == "develop":
cprint("\nSetting Up Development Instance\n", level=2)
logging.info("Running Development Setup")
setup_dev_instance(args.project)
elif args.subcommand == "upgrade":
cprint("\nSetting Up Development Instance\n", level=2)
logging.info("Running Development Setup")
update_prod(
project=args.project,
version=args.version,
image=args.image,
is_https=not args.no_ssl,
http_port=args.http_port,
)
elif args.subcommand == "exec":
cprint(f"\nExec into {args.project} backend\n", level=2)
logging.info(f"Exec into {args.project} backend")
exec_command(
project=args.project,
command=["bash"],
interactive_terminal=True,
)