mirror of
https://github.com/frappe/bench.git
synced 2025-01-06 23:44:03 +00:00
Merge pull request #1397 from athul/new-easy-install
feat: New™️ easy-install.py
This commit is contained in:
commit
e1ec20f723
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -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
|
||||
|
||||
|
32
.github/workflows/easy-install.yml
vendored
Normal file
32
.github/workflows/easy-install.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
name: "Easy Install Test"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [develop]
|
||||
|
||||
concurrency:
|
||||
group: easy-install-develop-${{ github.event_name }}-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
easy-install-setup:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
name: Easy Install Test
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- 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
|
69
README.md
69
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/<project-name>-compose.yml`.
|
||||
|
||||
When the setup is complete, you will be able to access the system at `http://<your-server-ip>`, 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
|
||||
|
||||
|
323
easy-install.py
Normal file
323
easy-install.py
Normal file
@ -0,0 +1,323 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
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",
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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}",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def generate_pass(length: int = 12) -> str:
|
||||
"""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:
|
||||
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,
|
||||
)
|
||||
|
||||
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 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)
|
||||
|
||||
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
|
||||
|
||||
|
||||
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"""
|
||||
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__":
|
||||
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)
|
Loading…
Reference in New Issue
Block a user