From dd196e1bb41fce0671c5a43aeaa6d000bda9e0b9 Mon Sep 17 00:00:00 2001 From: priyanshu-serpentcs Date: Mon, 10 Nov 2025 15:10:10 +0530 Subject: [PATCH] [ADD]Devcontainer template --- devcontainer/devcontainer.json | 32 ++++ devcontainer/docker-compose.yml | 90 +++++++++++ development/.vscode/launch.json | 77 ++++++++++ development/.vscode/settings.json | 3 + development/.vscode/tasks.json | 22 +++ development/apps-example.json | 6 + development/installer.py | 245 ++++++++++++++++++++++++++++++ 7 files changed, 475 insertions(+) create mode 100644 devcontainer/devcontainer.json create mode 100644 devcontainer/docker-compose.yml create mode 100644 development/.vscode/launch.json create mode 100644 development/.vscode/settings.json create mode 100644 development/.vscode/tasks.json create mode 100644 development/apps-example.json create mode 100755 development/installer.py diff --git a/devcontainer/devcontainer.json b/devcontainer/devcontainer.json new file mode 100644 index 0000000..2e60f65 --- /dev/null +++ b/devcontainer/devcontainer.json @@ -0,0 +1,32 @@ +{ + "name": "Frappe Bench", + "forwardPorts": [8000, 9000, 6787], + "remoteUser": "frappe", + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-vscode.live-server", + "grapecity.gc-excelviewer", + "mtxr.sqltools", + "visualstudioexptteam.vscodeintellicode" + ], + "settings": { + "terminal.integrated.profiles.linux": { + "frappe bash": { + "path": "/bin/bash" + } + }, + "terminal.integrated.defaultProfile.linux": "frappe bash", + "debug.node.autoAttach": "disabled" + } + } + }, + "dockerComposeFile": "./docker-compose.yml", + "service": "frappe", + "workspaceFolder": "/workspace/development", + "shutdownAction": "stopCompose", + "mounts": [ + "source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/frappe/.ssh,type=bind,consistency=cached" + ] +} diff --git a/devcontainer/docker-compose.yml b/devcontainer/docker-compose.yml new file mode 100644 index 0000000..0d6d9d8 --- /dev/null +++ b/devcontainer/docker-compose.yml @@ -0,0 +1,90 @@ +version: "3.7" +services: + mariadb: + image: docker.io/mariadb:11.8 + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --skip-character-set-client-handshake + - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 + environment: + MYSQL_ROOT_PASSWORD: 123 + MARIADB_AUTO_UPGRADE: 1 + volumes: + - mariadb-data:/var/lib/mysql + + # Enable PostgreSQL only if you use it, see development/README.md for more information. + # postgresql: + # image: postgres:14 + # environment: + # POSTGRES_PASSWORD: 123 + # volumes: + # - postgresql-data:/var/lib/postgresql/data + + # Enable Mailpit if you need to test outgoing mail services + # See https://mailpit.axllent.org/ + # mailpit: + # image: axllent/mailpit + # volumes: + # - mailpit-data:/data + # ports: + # - 8025:8025 + # - 1025:1025 + # environment: + # MP_MAX_MESSAGES: 5000 + # MP_DATA_FILE: /data/mailpit.db + # MP_SMTP_AUTH_ACCEPT_ANY: 1 + # MP_SMTP_AUTH_ALLOW_INSECURE: 1 + + redis-cache: + image: docker.io/redis:alpine + + redis-queue: + image: docker.io/redis:alpine + + frappe: + image: docker.io/frappe/bench:latest + # If you want to build the current bench image the Containerfile is in this Repo. + # build: ../images/bench + command: sleep infinity + environment: + - SHELL=/bin/bash + volumes: + - ..:/workspace:cached + # Enable if you require git cloning + # - ${HOME}/.ssh:/home/frappe/.ssh + working_dir: /workspace/development + ports: + - 8000-8005:8000-8005 + - 9000-9005:9000-9005 + # enable the below service if you need Cypress UI Tests to be executed + # Before enabling ensure install_x11_deps.sh has been executed and display variable is exported. + # Run install_x11_deps.sh again if DISPLAY is not set + # ui-tester: + # # pass custom command to start Cypress otherwise it will use the entrypoint + # # specified in the Cypress Docker image. + # # also pass "--project " so that when Cypress opens + # # it can find file "cypress.json" and show integration specs + # # https://on.cypress.io/command-line#cypress-open + # entrypoint: 'sleep infinity' + # image: "docker.io/cypress/included:latest" + # environment: + # - SHELL=/bin/bash + # # get the IP address of the host machine and allow X11 to accept + # # incoming connections from that IP address + # # IP=$(ipconfig getifaddr en0) or mac or \ + # # IP=$($(hostname -I | awk '{print $1}') ) for Ubuntu + # # /usr/X11/bin/xhost + $IP + # # then pass the environment variable DISPLAY to show Cypress GUI on the host system + # # DISPLAY=$IP:0 + # - DISPLAY + # volumes: + # # for Cypress to communicate with the X11 server pass this socket file + # # in addition to any other mapped volumes + # - /tmp/.X11-unix:/tmp/.X11-unix + # - ..:/workspace:z,cached + # network_mode: "host" +volumes: + mariadb-data: + #postgresql-data: + #mailpit-data: diff --git a/development/.vscode/launch.json b/development/.vscode/launch.json new file mode 100644 index 0000000..7267fcf --- /dev/null +++ b/development/.vscode/launch.json @@ -0,0 +1,77 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Bench Web", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", + "args": [ + "frappe", + "serve", + "--port", + "8000", + "--noreload", + "--nothreading" + ], + "cwd": "${workspaceFolder}/frappe-bench/sites", + "env": { + "DEV_SERVER": "1" + } + }, + { + "name": "Bench Short Worker", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", + "args": ["frappe", "worker", "--queue", "short"], + "cwd": "${workspaceFolder}/frappe-bench/sites", + "env": { + "DEV_SERVER": "1" + } + }, + { + "name": "Bench Default Worker", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", + "args": ["frappe", "worker", "--queue", "default"], + "cwd": "${workspaceFolder}/frappe-bench/sites", + "env": { + "DEV_SERVER": "1" + } + }, + { + "name": "Bench Long Worker", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", + "args": ["frappe", "worker", "--queue", "long"], + "cwd": "${workspaceFolder}/frappe-bench/sites", + "env": { + "DEV_SERVER": "1" + } + }, + { + "name": "Honcho SocketIO Watch Schedule Worker", + "type": "debugpy", + "request": "launch", + "python": "/home/frappe/.pyenv/shims/python", + "program": "/home/frappe/.local/bin/honcho", + "cwd": "${workspaceFolder}/frappe-bench", + "console": "internalConsole", + "args": ["start", "socketio", "watch", "schedule", "worker"], + "postDebugTask": "Clean Honcho SocketIO Watch Schedule Worker" + } + ], + "compounds": [ + { + "name": "Honcho + Web debug", + "configurations": ["Bench Web", "Honcho SocketIO Watch Schedule Worker"], + "stopAll": true + } + ] +} diff --git a/development/.vscode/settings.json b/development/.vscode/settings.json new file mode 100644 index 0000000..1490b72 --- /dev/null +++ b/development/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.defaultInterpreterPath": "${workspaceFolder}/frappe-bench/env/bin/python" +} diff --git a/development/.vscode/tasks.json b/development/.vscode/tasks.json new file mode 100644 index 0000000..7c0e673 --- /dev/null +++ b/development/.vscode/tasks.json @@ -0,0 +1,22 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Clean Honcho SocketIO Watch Schedule Worker", + "detail": "When stopping the debug process from vscode window, the honcho won't receive the SIGINT signal. This task will send the SIGINT signal to the honcho processes.", + "type": "shell", + "command": "pkill -SIGINT -f bench; pkill -SIGINT -f socketio", + "isBackground": false, + "presentation": { + "echo": true, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "close": true + } + } + ] +} diff --git a/development/apps-example.json b/development/apps-example.json new file mode 100644 index 0000000..513d3d1 --- /dev/null +++ b/development/apps-example.json @@ -0,0 +1,6 @@ +[ + { + "url": "https://github.com/frappe/erpnext.git", + "branch": "version-15" + } +] diff --git a/development/installer.py b/development/installer.py new file mode 100755 index 0000000..edd6214 --- /dev/null +++ b/development/installer.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +import argparse +import os +import subprocess + + +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) # noqa: T001, T201 + if level == 2: + print(CGRN, message, reset) # noqa: T001, T201 + if level == 3: + print(CYLW, message, reset) # noqa: T001, T201 + + +def main(): + parser = get_args_parser() + args = parser.parse_args() + init_bench_if_not_exist(args) + create_site_in_bench(args) + + +def get_args_parser(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-j", + "--apps-json", + action="store", + type=str, + help="Path to apps.json, default: apps-example.json", + default="apps-example.json", + ) # noqa: E501 + parser.add_argument( + "-b", + "--bench-name", + action="store", + type=str, + help="Bench directory name, default: frappe-bench", + default="frappe-bench", + ) # noqa: E501 + parser.add_argument( + "-s", + "--site-name", + action="store", + type=str, + help="Site name, should end with .localhost, default: development.localhost", # noqa: E501 + default="development.localhost", + ) + parser.add_argument( + "-r", + "--frappe-repo", + action="store", + type=str, + help="frappe repo to use, default: https://github.com/frappe/frappe", # noqa: E501 + default="https://github.com/frappe/frappe", + ) + parser.add_argument( + "-t", + "--frappe-branch", + action="store", + type=str, + help="frappe repo to use, default: version-15", # noqa: E501 + default="version-15", + ) + parser.add_argument( + "-p", + "--py-version", + action="store", + type=str, + help="python version, default: Not Set", # noqa: E501 + default=None, + ) + parser.add_argument( + "-n", + "--node-version", + action="store", + type=str, + help="node version, default: Not Set", # noqa: E501 + default=None, + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="verbose output", # noqa: E501 + ) + parser.add_argument( + "-a", + "--admin-password", + action="store", + type=str, + help="admin password for site, default: admin", # noqa: E501 + default="admin", + ) + parser.add_argument( + "-d", + "--db-type", + action="store", + type=str, + help="Database type to use (e.g., mariadb or postgres)", + default="mariadb", # Set your default database type here + ) + return parser + + +def init_bench_if_not_exist(args): + if os.path.exists(args.bench_name): + cprint("Bench already exists. Only site will be created", level=3) + return + try: + env = os.environ.copy() + if args.py_version: + env["PYENV_VERSION"] = args.py_version + init_command = "" + if args.node_version: + init_command = f"nvm use {args.node_version};" + if args.py_version: + init_command += f"PYENV_VERSION={args.py_version} " + init_command += "bench init " + init_command += "--skip-redis-config-generation " + init_command += "--verbose " if args.verbose else " " + init_command += f"--frappe-path={args.frappe_repo} " + init_command += f"--frappe-branch={args.frappe_branch} " + init_command += f"--apps_path={args.apps_json} " + init_command += args.bench_name + command = [ + "/bin/bash", + "-i", + "-c", + init_command, + ] + subprocess.call(command, env=env, cwd=os.getcwd()) + cprint("Configuring Bench ...", level=2) + cprint("Set db_host", level=3) + if args.db_type: + cprint(f"Setting db_type to {args.db_type}", level=3) + subprocess.call( + ["bench", "set-config", "-g", "db_type", args.db_type], + cwd=os.path.join(os.getcwd(), args.bench_name), + ) + + cprint("Set redis_cache to redis://redis-cache:6379", level=3) + subprocess.call( + [ + "bench", + "set-config", + "-g", + "redis_cache", + "redis://redis-cache:6379", + ], + cwd=os.getcwd() + "/" + args.bench_name, + ) + cprint("Set redis_queue to redis://redis-queue:6379", level=3) + subprocess.call( + [ + "bench", + "set-config", + "-g", + "redis_queue", + "redis://redis-queue:6379", + ], + cwd=os.getcwd() + "/" + args.bench_name, + ) + cprint( + "Set redis_socketio to redis://redis-queue:6379 for backward compatibility", # noqa: E501 + level=3, + ) + subprocess.call( + [ + "bench", + "set-config", + "-g", + "redis_socketio", + "redis://redis-queue:6379", + ], + cwd=os.getcwd() + "/" + args.bench_name, + ) + cprint("Set developer_mode", level=3) + subprocess.call( + ["bench", "set-config", "-gp", "developer_mode", "1"], + cwd=os.getcwd() + "/" + args.bench_name, + ) + except subprocess.CalledProcessError as e: + cprint(e.output, level=1) + + +def create_site_in_bench(args): + if "mariadb" == args.db_type: + cprint("Set db_host", level=3) + subprocess.call( + ["bench", "set-config", "-g", "db_host", "mariadb"], + cwd=os.getcwd() + "/" + args.bench_name, + ) + new_site_cmd = [ + "bench", + "new-site", + f"--db-root-username=root", + f"--db-host=mariadb", # Should match the compose service name + f"--db-type={args.db_type}", # Add the selected database type + f"--mariadb-user-host-login-scope=%", + f"--db-root-password=123", # Replace with your MariaDB password + f"--admin-password={args.admin_password}", + ] + else: + cprint("Set db_host", level=3) + subprocess.call( + ["bench", "set-config", "-g", "db_host", "postgresql"], + cwd=os.getcwd() + "/" + args.bench_name, + ) + new_site_cmd = [ + "bench", + "new-site", + f"--db-root-username=root", + f"--db-host=postgresql", # Should match the compose service name + f"--db-type={args.db_type}", # Add the selected database type + f"--db-root-password=123", # Replace with your PostgreSQL password + f"--admin-password={args.admin_password}", + ] + apps = os.listdir(f"{os.getcwd()}/{args.bench_name}/apps") + apps.remove("frappe") + for app in apps: + new_site_cmd.append(f"--install-app={app}") + new_site_cmd.append(args.site_name) + cprint(f"Creating Site {args.site_name} ...", level=2) + subprocess.call( + new_site_cmd, + cwd=os.getcwd() + "/" + args.bench_name, + ) + + +if __name__ == "__main__": + main()