Compare commits
3 Commits
main
...
compat-202
Author | SHA1 | Date | |
---|---|---|---|
|
6bc8b4041b | ||
|
3c77d58e59 | ||
|
aab7c0cee6 |
14
.github/dependabot.yml
vendored
14
.github/dependabot.yml
vendored
@ -11,12 +11,22 @@ updates:
|
||||
interval: daily
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: images/production
|
||||
directory: images/nginx
|
||||
schedule:
|
||||
interval: daily
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: images/custom
|
||||
directory: images/worker
|
||||
schedule:
|
||||
interval: daily
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: images/socketio
|
||||
schedule:
|
||||
interval: daily
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: images/socketio
|
||||
schedule:
|
||||
interval: daily
|
||||
|
||||
|
4
.github/scripts/get_latest_tags.py
vendored
4
.github/scripts/get_latest_tags.py
vendored
@ -9,7 +9,7 @@ import sys
|
||||
from typing import Literal
|
||||
|
||||
Repo = Literal["frappe", "erpnext"]
|
||||
MajorVersion = Literal["12", "13", "14", "15", "develop"]
|
||||
MajorVersion = Literal["12", "13", "14", "develop"]
|
||||
|
||||
|
||||
def get_latest_tag(repo: Repo, version: MajorVersion) -> str:
|
||||
@ -57,7 +57,7 @@ def main(_args: list[str]) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--repo", choices=["frappe", "erpnext"], required=True)
|
||||
parser.add_argument(
|
||||
"--version", choices=["12", "13", "14", "15", "develop"], required=True
|
||||
"--version", choices=["12", "13", "14", "develop"], required=True
|
||||
)
|
||||
args = parser.parse_args(_args)
|
||||
|
||||
|
18
.github/scripts/update_example_env.py
vendored
18
.github/scripts/update_example_env.py
vendored
@ -2,25 +2,29 @@ import os
|
||||
import re
|
||||
|
||||
|
||||
def get_erpnext_version():
|
||||
def get_versions():
|
||||
frappe_version = os.getenv("FRAPPE_VERSION")
|
||||
erpnext_version = os.getenv("ERPNEXT_VERSION")
|
||||
assert frappe_version, "No Frappe version set"
|
||||
assert erpnext_version, "No ERPNext version set"
|
||||
return erpnext_version
|
||||
return frappe_version, erpnext_version
|
||||
|
||||
|
||||
def update_env(erpnext_version: str):
|
||||
def update_env(frappe_version: str, erpnext_version: str):
|
||||
with open("example.env", "r+") as f:
|
||||
content = f.read()
|
||||
content = re.sub(
|
||||
rf"ERPNEXT_VERSION=.*", f"ERPNEXT_VERSION={erpnext_version}", content
|
||||
)
|
||||
for env, var in (
|
||||
("FRAPPE_VERSION", frappe_version),
|
||||
("ERPNEXT_VERSION", erpnext_version),
|
||||
):
|
||||
content = re.sub(rf"{env}=.*", f"{env}={var}", content)
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(content)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
update_env(get_erpnext_version())
|
||||
update_env(*get_versions())
|
||||
return 0
|
||||
|
||||
|
||||
|
9
.github/scripts/update_pwd.py
vendored
9
.github/scripts/update_pwd.py
vendored
@ -13,9 +13,12 @@ def get_versions():
|
||||
def update_pwd(frappe_version: str, erpnext_version: str):
|
||||
with open("pwd.yml", "r+") as f:
|
||||
content = f.read()
|
||||
content = re.sub(
|
||||
rf"frappe/erpnext:.*", f"frappe/erpnext:{erpnext_version}", content
|
||||
)
|
||||
for image, version in (
|
||||
("frappe/frappe-socketio", frappe_version),
|
||||
("frappe/erpnext-worker", erpnext_version),
|
||||
("frappe/erpnext-nginx", erpnext_version),
|
||||
):
|
||||
content = re.sub(rf"{image}:.*", f"{image}:{version}", content)
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(content)
|
||||
|
18
.github/workflows/build_bench.yml
vendored
18
.github/workflows/build_bench.yml
vendored
@ -20,38 +20,32 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
image: tonistiigi/binfmt:latest
|
||||
platforms: all
|
||||
|
||||
- name: Setup Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set Environment Variables
|
||||
run: cat example.env | grep -o '^[^#]*' >> "$GITHUB_ENV"
|
||||
|
||||
- name: Get Bench Latest Version
|
||||
run: echo "LATEST_BENCH_RELEASE=$(curl -s 'https://api.github.com/repos/frappe/bench/releases/latest' | jq -r '.tag_name')" >> "$GITHUB_ENV"
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Build and test
|
||||
uses: docker/bake-action@v4.1.0
|
||||
uses: docker/bake-action@v2.3.0
|
||||
with:
|
||||
targets: bench-test
|
||||
|
||||
- name: Login
|
||||
if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Push
|
||||
if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
|
||||
uses: docker/bake-action@v4.1.0
|
||||
uses: docker/bake-action@v2.3.0
|
||||
with:
|
||||
targets: bench
|
||||
push: true
|
||||
|
6
.github/workflows/build_develop.yml
vendored
6
.github/workflows/build_develop.yml
vendored
@ -5,7 +5,9 @@ on:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- images/production/**
|
||||
- images/nginx/**
|
||||
- images/socketio/**
|
||||
- images/worker/**
|
||||
- overrides/**
|
||||
- tests/**
|
||||
- compose.yaml
|
||||
@ -26,8 +28,6 @@ jobs:
|
||||
repo: erpnext
|
||||
version: develop
|
||||
push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
|
||||
python_version: 3.11.6
|
||||
node_version: 18.18.2
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
46
.github/workflows/build_stable.yml
vendored
46
.github/workflows/build_stable.yml
vendored
@ -5,7 +5,9 @@ on:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- images/production/**
|
||||
- images/nginx/**
|
||||
- images/socketio/**
|
||||
- images/worker/**
|
||||
- overrides/**
|
||||
- tests/**
|
||||
- compose.yaml
|
||||
@ -17,7 +19,9 @@ on:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- images/production/**
|
||||
- images/nginx/**
|
||||
- images/socketio/**
|
||||
- images/worker/**
|
||||
- overrides/**
|
||||
- tests/**
|
||||
- compose.yaml
|
||||
@ -30,26 +34,22 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
v13:
|
||||
uses: ./.github/workflows/docker-build-push.yml
|
||||
with:
|
||||
repo: erpnext
|
||||
version: "13"
|
||||
push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
v14:
|
||||
uses: ./.github/workflows/docker-build-push.yml
|
||||
with:
|
||||
repo: erpnext
|
||||
version: "14"
|
||||
push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
|
||||
python_version: 3.10.13
|
||||
node_version: 16.20.2
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
v15:
|
||||
uses: ./.github/workflows/docker-build-push.yml
|
||||
with:
|
||||
repo: erpnext
|
||||
version: "15"
|
||||
push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
|
||||
python_version: 3.11.6
|
||||
node_version: 18.18.2
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@ -58,19 +58,19 @@ jobs:
|
||||
name: Update example.env and pwd.yml
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
|
||||
needs: v15
|
||||
needs: v14
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Get latest versions
|
||||
run: python3 ./.github/scripts/get_latest_tags.py --repo erpnext --version 15
|
||||
run: python3 ./.github/scripts/get_latest_tags.py --repo erpnext --version 14
|
||||
|
||||
- name: Update
|
||||
run: |
|
||||
@ -96,11 +96,11 @@ jobs:
|
||||
name: Release Helm
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
|
||||
needs: v15
|
||||
needs: v14
|
||||
|
||||
steps:
|
||||
- name: Setup deploy key
|
||||
uses: webfactory/ssh-agent@v0.8.0
|
||||
uses: webfactory/ssh-agent@v0.7.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.HELM_DEPLOY_KEY }}
|
||||
|
||||
@ -113,4 +113,4 @@ jobs:
|
||||
run: |
|
||||
git clone git@github.com:frappe/helm.git && cd helm
|
||||
pip install -r release_wizard/requirements.txt
|
||||
./release_wizard/wizard 15 patch --remote origin --ci
|
||||
./release_wizard/wizard 14 patch --remote origin --ci
|
||||
|
28
.github/workflows/docker-build-push.yml
vendored
28
.github/workflows/docker-build-push.yml
vendored
@ -14,14 +14,6 @@ on:
|
||||
push:
|
||||
required: true
|
||||
type: boolean
|
||||
python_version:
|
||||
required: true
|
||||
type: string
|
||||
description: Python Version
|
||||
node_version:
|
||||
required: true
|
||||
type: string
|
||||
description: NodeJS Version
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: true
|
||||
@ -40,33 +32,31 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Get latest versions
|
||||
run: python3 ./.github/scripts/get_latest_tags.py --repo ${{ inputs.repo }} --version ${{ inputs.version }}
|
||||
|
||||
- name: Set build args
|
||||
run: |
|
||||
echo "PYTHON_VERSION=${{ inputs.python_version }}" >> "$GITHUB_ENV"
|
||||
echo "NODE_VERSION=${{ inputs.node_version }}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build
|
||||
uses: docker/bake-action@v4.1.0
|
||||
uses: docker/bake-action@v2.3.0
|
||||
with:
|
||||
push: true
|
||||
env:
|
||||
REGISTRY_USER: localhost:5000/frappe
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install Docker Compose v2
|
||||
uses: ndeloof/install-compose-action@4a33bc31f327b8231c4f343f6fba704fedc0fa23
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m venv venv
|
||||
@ -77,13 +67,13 @@ jobs:
|
||||
|
||||
- name: Login
|
||||
if: ${{ inputs.push }}
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Push
|
||||
if: ${{ inputs.push }}
|
||||
uses: docker/bake-action@v4.1.0
|
||||
uses: docker/bake-action@v2.3.0
|
||||
with:
|
||||
push: true
|
||||
|
16
.github/workflows/lint.yml
vendored
16
.github/workflows/lint.yml
vendored
@ -13,19 +13,27 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10.6"
|
||||
python-version: "3.10"
|
||||
|
||||
# For shfmt pre-commit hook
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "^1.14"
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pre-commit
|
||||
~/.cache/pip
|
||||
key: lint-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
|
||||
- name: Install pre-commit
|
||||
run: pip install -U pre-commit
|
||||
|
||||
|
4
.github/workflows/pre-commit-autoupdate.yml
vendored
4
.github/workflows/pre-commit-autoupdate.yml
vendored
@ -10,13 +10,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Update pre-commit hooks
|
||||
uses: vrslev/pre-commit-autoupdate@v1.0.0
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
branch: pre-commit-autoupdate
|
||||
title: "chore(deps): Update pre-commit hooks"
|
||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
- uses: actions/stale@v7
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: This issue has been automatically marked as stale. You have a week to explain why you believe this is an error.
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -6,7 +6,7 @@ sites
|
||||
|
||||
development/*
|
||||
!development/README.md
|
||||
!development/installer.py
|
||||
!development/installer.sh
|
||||
!development/apps-example.json
|
||||
!development/vscode-example/
|
||||
|
||||
@ -25,6 +25,3 @@ development/*
|
||||
*.pyc
|
||||
__pycache__
|
||||
venv
|
||||
|
||||
# NodeJS
|
||||
node_modules
|
||||
|
@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-shebang-scripts-are-executable
|
||||
@ -8,28 +8,28 @@ repos:
|
||||
- id: end-of-file-fixer
|
||||
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.15.1
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py37-plus]
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 24.2.0
|
||||
rev: 22.12.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.13.2
|
||||
rev: 5.11.4
|
||||
hooks:
|
||||
- id: isort
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v4.0.0-alpha.8
|
||||
rev: v3.0.0-alpha.4
|
||||
hooks:
|
||||
- id: prettier
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.6
|
||||
rev: v2.2.2
|
||||
hooks:
|
||||
- id: codespell
|
||||
args:
|
||||
@ -47,7 +47,7 @@ repos:
|
||||
types: [shell]
|
||||
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.9.0.6
|
||||
rev: v0.9.0.2
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
args: [-x]
|
||||
|
@ -61,21 +61,16 @@ Run pytest:
|
||||
pytest
|
||||
```
|
||||
|
||||
> We also have `requirements-dev.txt` file that contains development requirements for backend image (you can find it in `images/worker/` directory).
|
||||
|
||||
# Documentation
|
||||
|
||||
Place relevant markdown files in the `docs` directory and index them in README.md located at the root of repo.
|
||||
|
||||
# Wiki
|
||||
|
||||
Add alternatives that can be used optionally along with frappe_docker. Add articles to list on home page as well.
|
||||
|
||||
# Frappe and ERPNext updates
|
||||
|
||||
Each Frappe/ERPNext release triggers new stable images builds as well as bump to helm chart.
|
||||
|
||||
# Maintenance
|
||||
|
||||
In case of new release of Debian. e.g. bullseye to bookworm. Change following files:
|
||||
|
||||
- `images/erpnext/Containerfile` and `images/custom/Containerfile`: Change the files to use new debian release, make sure new python version tag that is available on new debian release image. e.g. 3.9.9 (bullseye) to 3.9.17 (bookworm) or 3.10.5 (bullseye) to 3.10.12 (bookworm). Make sure apt-get packages and wkhtmltopdf version are also upgraded accordingly.
|
||||
- `images/bench/Dockerfile`: Change the files to use new debian release. Make sure apt-get packages and wkhtmltopdf version are also upgraded accordingly.
|
||||
|
||||
Change following files on release of ERPNext
|
||||
|
||||
- `.github/workflows/build_stable.yml`: Add the new release step under `jobs` and remove the unmaintained one. e.g. In case v12, v13 available, v14 will be added and v12 will be removed on release of v14. Also change the `needs:` for later steps to `v14` from `v13`.
|
||||
|
144
README.md
144
README.md
@ -5,7 +5,7 @@ Everything about [Frappe](https://github.com/frappe/frappe) and [ERPNext](https:
|
||||
|
||||
# Getting Started
|
||||
|
||||
To get started you need [Docker](https://docs.docker.com/get-docker/), [docker-compose](https://docs.docker.com/compose/), and [git](https://docs.github.com/en/get-started/getting-started-with-git/set-up-git) setup on your machine. For Docker basics and best practices refer to Docker's [documentation](http://docs.docker.com).
|
||||
To get started, you need Docker, docker-compose and git setup on your machine. For Docker basics and best practices. Refer Docker [documentation](http://docs.docker.com).
|
||||
After that, clone this repo:
|
||||
|
||||
```sh
|
||||
@ -21,138 +21,40 @@ cd frappe_docker
|
||||
|
||||
Wait for 5 minutes for ERPNext site to be created or check `create-site` container logs before opening browser on port 8080. (username: `Administrator`, password: `admin`)
|
||||
|
||||
# Documentation
|
||||
# Development
|
||||
|
||||
### [Production](#production)
|
||||
We have baseline for developing in VSCode devcontainer with [frappe/bench](https://github.com/frappe/bench). [Start development](development).
|
||||
|
||||
- [List of containers](docs/list-of-containers.md)
|
||||
- [Single Compose Setup](docs/single-compose-setup.md)
|
||||
- [Environment Variables](docs/environment-variables.md)
|
||||
- [Single Server Example](docs/single-server-example.md)
|
||||
- [Setup Options](docs/setup-options.md)
|
||||
- [Site Operations](docs/site-operations.md)
|
||||
- [Backup and Push Cron Job](docs/backup-and-push-cronjob.md)
|
||||
- [Port Based Multi Tenancy](docs/port-based-multi-tenancy.md)
|
||||
- [Migrate from multi-image setup](docs/migrate-from-multi-image-setup.md)
|
||||
- [running on linux/mac](docs/setup_for_linux_mac.md)
|
||||
# Production
|
||||
|
||||
### [Custom Images](#custom-images)
|
||||
We provide simple and intuitive production setup with prebuilt Frappe and ERPNext images and compose files. To learn more about those, [read the docs](docs/images-and-compose-files.md).
|
||||
|
||||
- [Custom Apps](docs/custom-apps.md)
|
||||
- [Build Version 10 Images](docs/build-version-10-images.md)
|
||||
Also, there's docs to help with deployment:
|
||||
|
||||
### [Development](#development)
|
||||
- Examples:
|
||||
- [Single Server](docs/single-server-example.md)
|
||||
- [Setup options](docs/setup-options.md)
|
||||
- [Kubernetes (frappe/helm)](https://helm.erpnext.com)
|
||||
- [Site operations](docs/site-operations.md).
|
||||
- Other
|
||||
- [backup and push cron jobs](docs/backup-and-push-cronjob.md)
|
||||
- [bench console and vscode debugger](docs/bench-console-and-vscode-debugger.md)
|
||||
- [build version 10](docs/build-version-10-images.md)
|
||||
- [connect to localhost services from containers for local app development](docs/connect-to-localhost-services-from-containers-for-local-app-development.md)
|
||||
- [patch code from images](docs/patch-code-from-images.md)
|
||||
- [port based multi tenancy](docs/port-based-multi-tenancy.md)
|
||||
- [Troubleshoot](docs/troubleshoot.md)
|
||||
|
||||
- [Development using containers](docs/development.md)
|
||||
- [Bench Console and VSCode Debugger](docs/bench-console-and-vscode-debugger.md)
|
||||
- [Connect to localhost services](docs/connect-to-localhost-services-from-containers-for-local-app-development.md)
|
||||
# Custom app
|
||||
|
||||
### [Troubleshoot](docs/troubleshoot.md)
|
||||
|
||||
### Shiloh Setup
|
||||
|
||||
based on: https://raw.githubusercontent.com/frappe/bench/develop/easy-install.py
|
||||
|
||||
`git clone ssh://git@githaven.org:2222/Shiloh/frappe_docker.git`
|
||||
|
||||
sites = [erp.sprinklersnorthwest.com]
|
||||
email = support@lasthourhosting.org
|
||||
|
||||
`cd frappe_docker`
|
||||
`cp example.env .env`
|
||||
|
||||
Write inside of env:
|
||||
|
||||
<!-- Commented items because the script doesn't read them. -->
|
||||
<!-- f"ERPNEXT_VERSION={erpnext_version}\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"SITE_ADMIN_PASS={admin_pass}\n",
|
||||
|
||||
<!-- f"SITES={quoted_sites}\n", -->
|
||||
|
||||
<!-- from inside the frappe-docker repo: -->
|
||||
|
||||
<!-- Setup reference to our own erpnext repo -->
|
||||
|
||||
`APPS_JSON_BASE64=$(base64 -w 0 ./apps.json)`
|
||||
|
||||
<!-- build the docker image -->
|
||||
|
||||
`sudo docker build \
|
||||
--build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \
|
||||
--build-arg=FRAPPE_BRANCH=version-15 \
|
||||
--build-arg=PYTHON_VERSION=3.11.6 \
|
||||
--build-arg=NODE_VERSION=18.18.2 \
|
||||
--build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \
|
||||
--tag=githaven.org/shiloh/frappe_docker:production \
|
||||
--file=images/custom/Containerfile \
|
||||
--rm \
|
||||
--no-cache \
|
||||
.`
|
||||
|
||||
<!-- Push to githaven -->
|
||||
|
||||
<!-- sudo docker image tag NAME:TAG githaven.org/shiloh/frappe_docker:production -->
|
||||
|
||||
`sudo docker login githaven.org`
|
||||
setup credential service to hide password
|
||||
`sudo docker push githaven.org/shiloh/frappe_docker:production`
|
||||
https://docs.gitea.com/packages/packages/container
|
||||
|
||||
<!-- Install erpnext for first time -->
|
||||
|
||||
`sudo python3 easy-installer.py --prod --email support@lasthourhosting.org --site erp.sprinklersnorthwest.com`
|
||||
|
||||
`bench use` should already be done by the installer.
|
||||
|
||||
<!-- `sudo docker compose -p frappe exec backend bench use erp.sprinklersnorthwest.com` -->
|
||||
|
||||
Create a project through the dashboard with the sidebar, then login with the admin account and view the projects page through the other dashboard, you should see edits.
|
||||
|
||||
<!-- to enter the container: -->
|
||||
|
||||
`sudo docker compose -p frappe exec -it backend bash`
|
||||
|
||||
To update erpnext, add any commits you want to use to the "production" branch of the erpnext repo on githaven. Then ensure the "backend" container has git repositories in the frappe and erpnext apps:
|
||||
`cd ~/frappe-bench/apps/erpnext`
|
||||
`git init`
|
||||
`git remote add upstream <repo>`
|
||||
`git fetch upstream production`
|
||||
`git checkout production --force`
|
||||
`git clean -fd`
|
||||
|
||||
Then pull the latest erpnext, and it will be updated.
|
||||
|
||||
<!-- Bench update hangs on node esbuild, so we're just manually pulling the latest changes rather than doing it through this command. -->
|
||||
<!-- Then from ./frappe-bench run:
|
||||
`bench update` -->
|
||||
|
||||
<!-- `sudo docker compose --project-name brotherton -f compose.yaml -f overrides/compose.mariadb.yaml -f overrides/compose.redis.yaml -f overrides/compose.https.yaml --env-file .env config`
|
||||
|
||||
Make images/production/Containerfile available as a package on githaven? need to replace the erpnext repo argument inside it.
|
||||
inside of compose.yaml, replace "image: frappe/erpnext" with the above package.
|
||||
|
||||
`. .env`
|
||||
`sudo docker compose -p brotherton -f compose.yaml up -d`
|
||||
|
||||
The "erpnext" in this command is probably fine, I think the bench get-app inside the docker compose gets the erpnext repository and puts it into an erpnext "app", regardless of what the erpnext repo env variable is. This command only works if SITES contains a single site.
|
||||
`sudo docker compose -p brotherton exec backend bench new-site "$SITES" --no-mariadb-socket --db-root-password "$DB_PASSWORD" --admin-password "$SITE_ADMIN_PASS" --install-app erpnext --set-default` -->
|
||||
Learn how to containerize your custom Frappe app(s) in [this guide](custom_app/README.md).
|
||||
|
||||
# Contributing
|
||||
|
||||
If you want to contribute to this repo refer to [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
|
||||
This repository is only for container related stuff. You also might want to contribute to:
|
||||
This repository is only for Docker related stuff. You also might want to contribute to:
|
||||
|
||||
- [Frappe framework](https://github.com/frappe/frappe#contributing),
|
||||
- [ERPNext](https://github.com/frappe/erpnext#contributing),
|
||||
- [Frappe Bench](https://github.com/frappe/bench).
|
||||
- or [Frappe Bench](https://github.com/frappe/bench).
|
||||
|
147
Shilohimage
147
Shilohimage
@ -1,147 +0,0 @@
|
||||
ARG PYTHON_VERSION=3.11.6
|
||||
ARG DEBIAN_BASE=bookworm
|
||||
FROM python:${PYTHON_VERSION}-slim-${DEBIAN_BASE} AS base
|
||||
|
||||
ARG WKHTMLTOPDF_VERSION=0.12.6.1-3
|
||||
ARG WKHTMLTOPDF_DISTRO=bookworm
|
||||
ARG NODE_VERSION=18.18.2
|
||||
ENV NVM_DIR=/home/frappe/.nvm
|
||||
ENV PATH ${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}
|
||||
|
||||
RUN useradd -ms /bin/bash frappe \
|
||||
&& apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
curl \
|
||||
git \
|
||||
vim \
|
||||
nginx \
|
||||
gettext-base \
|
||||
# weasyprint dependencies
|
||||
libpango-1.0-0 \
|
||||
libharfbuzz0b \
|
||||
libpangoft2-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
# For backups
|
||||
restic \
|
||||
gpg \
|
||||
# MariaDB
|
||||
mariadb-client \
|
||||
less \
|
||||
# Postgres
|
||||
libpq-dev \
|
||||
postgresql-client \
|
||||
# For healthcheck
|
||||
wait-for-it \
|
||||
jq \
|
||||
# NodeJS
|
||||
&& mkdir -p ${NVM_DIR} \
|
||||
&& curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \
|
||||
&& . ${NVM_DIR}/nvm.sh \
|
||||
&& nvm install ${NODE_VERSION} \
|
||||
&& nvm use v${NODE_VERSION} \
|
||||
&& npm install -g yarn \
|
||||
&& nvm alias default v${NODE_VERSION} \
|
||||
&& rm -rf ${NVM_DIR}/.cache \
|
||||
&& echo 'export NVM_DIR="/home/frappe/.nvm"' >>/home/frappe/.bashrc \
|
||||
&& echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >>/home/frappe/.bashrc \
|
||||
&& echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >>/home/frappe/.bashrc \
|
||||
# Install wkhtmltopdf with patched qt
|
||||
&& if [ "$(uname -m)" = "aarch64" ]; then export ARCH=arm64; fi \
|
||||
&& if [ "$(uname -m)" = "x86_64" ]; then export ARCH=amd64; fi \
|
||||
&& downloaded_file=wkhtmltox_${WKHTMLTOPDF_VERSION}.${WKHTMLTOPDF_DISTRO}_${ARCH}.deb \
|
||||
&& curl -sLO https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \
|
||||
&& apt-get install -y ./$downloaded_file \
|
||||
&& rm $downloaded_file \
|
||||
# Clean up
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rm -fr /etc/nginx/sites-enabled/default \
|
||||
&& pip3 install frappe-bench \
|
||||
# Fixes for non-root nginx and logs to stdout
|
||||
&& sed -i '/user www-data/d' /etc/nginx/nginx.conf \
|
||||
&& ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log \
|
||||
&& touch /run/nginx.pid \
|
||||
&& chown -R frappe:frappe /etc/nginx/conf.d \
|
||||
&& chown -R frappe:frappe /etc/nginx/nginx.conf \
|
||||
&& chown -R frappe:frappe /var/log/nginx \
|
||||
&& chown -R frappe:frappe /var/lib/nginx \
|
||||
&& chown -R frappe:frappe /run/nginx.pid
|
||||
|
||||
COPY resources/nginx-template.conf /templates/nginx/frappe.conf.template
|
||||
COPY resources/nginx-entrypoint.sh /usr/local/bin/nginx-entrypoint.sh
|
||||
|
||||
FROM base AS builder
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
|
||||
# For frappe framework
|
||||
wget \
|
||||
# For psycopg2
|
||||
libpq-dev \
|
||||
# Other
|
||||
libffi-dev \
|
||||
liblcms2-dev \
|
||||
libldap2-dev \
|
||||
libmariadb-dev \
|
||||
libsasl2-dev \
|
||||
libtiff5-dev \
|
||||
libwebp-dev \
|
||||
redis-tools \
|
||||
rlwrap \
|
||||
tk8.6-dev \
|
||||
cron \
|
||||
# For pandas
|
||||
gcc \
|
||||
build-essential \
|
||||
libbz2-dev \
|
||||
# for erpnext repo
|
||||
openssh-client \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
USER frappe
|
||||
|
||||
RUN mkdir -p /home/frappe/.ssh
|
||||
RUN echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /home/frappe/.ssh/config
|
||||
|
||||
ARG FRAPPE_BRANCH=version-15
|
||||
ARG FRAPPE_PATH=https://github.com/frappe/frappe
|
||||
ARG ERPNEXT_REPO=https://githaven.org/Shiloh/brotherton-erpnext.git
|
||||
ARG ERPNEXT_BRANCH=production
|
||||
# RUN ssh -T ${ERPNEXT_REPO}
|
||||
RUN bench init \
|
||||
--frappe-branch=${FRAPPE_BRANCH} \
|
||||
--frappe-path=${FRAPPE_PATH} \
|
||||
--no-procfile \
|
||||
--no-backups \
|
||||
--skip-redis-config-generation \
|
||||
--verbose \
|
||||
/home/frappe/frappe-bench && \
|
||||
cd /home/frappe/frappe-bench && \
|
||||
bench get-app --branch=${ERPNEXT_BRANCH} --resolve-deps erpnext ${ERPNEXT_REPO} && \
|
||||
echo "{}" > sites/common_site_config.json
|
||||
|
||||
FROM base as erpnext
|
||||
|
||||
USER frappe
|
||||
|
||||
COPY --from=builder --chown=frappe:frappe /home/frappe/frappe-bench /home/frappe/frappe-bench
|
||||
|
||||
WORKDIR /home/frappe/frappe-bench
|
||||
|
||||
VOLUME [ \
|
||||
"/home/frappe/frappe-bench/sites", \
|
||||
"/home/frappe/frappe-bench/sites/assets", \
|
||||
"/home/frappe/frappe-bench/logs" \
|
||||
]
|
||||
|
||||
CMD [ \
|
||||
"/home/frappe/frappe-bench/env/bin/gunicorn", \
|
||||
"--chdir=/home/frappe/frappe-bench/sites", \
|
||||
"--bind=0.0.0.0:8000", \
|
||||
"--threads=4", \
|
||||
"--workers=2", \
|
||||
"--worker-class=gthread", \
|
||||
"--worker-tmp-dir=/dev/shm", \
|
||||
"--timeout=120", \
|
||||
"--preload", \
|
||||
"frappe.app:application" \
|
||||
]
|
@ -1,6 +0,0 @@
|
||||
[
|
||||
{
|
||||
"url": "https://githaven.org/Shiloh/brotherton-erpnext.git",
|
||||
"branch": "production"
|
||||
}
|
||||
]
|
23
build.sh
23
build.sh
@ -1,23 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
export APPS_JSON='[
|
||||
{
|
||||
"url": "https://githaven.org/Shiloh/brotherton-erpnext",
|
||||
"branch": "production"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/frappe/hrms",
|
||||
"branch": "v15.15.0"
|
||||
}
|
||||
]'
|
||||
export APPS_JSON_BASE64=$(echo ${APPS_JSON} | base64 -w 0)
|
||||
export TAG=1.0.1 # Change this
|
||||
docker build --platform=linux/amd64 \
|
||||
--build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \
|
||||
--build-arg=FRAPPE_BRANCH=v15.15.0 \
|
||||
--build-arg=PYTHON_VERSION=3.11.6 \
|
||||
--build-arg=NODE_VERSION=18.18.2 \
|
||||
--build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \
|
||||
--tag=githaven.org/shiloh/frappe_docker:$TAG \
|
||||
--file=images/custom/Containerfile .
|
||||
docker push githaven.org/shiloh/frappe_docker:$TAG
|
54
compose.yaml
54
compose.yaml
@ -1,45 +1,25 @@
|
||||
x-customizable-image: &customizable_image
|
||||
# By default the image used only contains the `frappe` and `erpnext` apps.
|
||||
# See https://github.com/frappe/frappe_docker/blob/main/docs/custom-apps.md
|
||||
# about using custom images.
|
||||
image: githaven.org/shiloh/frappe_docker:production
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: ./Shilohimage
|
||||
|
||||
x-depends-on-configurator: &depends_on_configurator
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
|
||||
x-backend-defaults: &backend_defaults
|
||||
<<: [*depends_on_configurator, *customizable_image]
|
||||
<<: *depends_on_configurator
|
||||
image: frappe/frappe-worker:${FRAPPE_VERSION:?No Frappe version set}
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- assets:/home/frappe/frappe-bench/sites/assets:ro
|
||||
|
||||
services:
|
||||
configurator:
|
||||
<<: *backend_defaults
|
||||
entrypoint:
|
||||
- bash
|
||||
- -c
|
||||
# add redis_socketio for backward compatibility
|
||||
command:
|
||||
- >
|
||||
ls -1 apps > sites/apps.txt;
|
||||
rm -rf sites/assets || true;
|
||||
ln -s /home/frappe/frappe-bench/assets sites/assets || true;
|
||||
bench set-config -g db_host $$DB_HOST;
|
||||
bench set-config -gp db_port $$DB_PORT;
|
||||
bench set-config -g redis_cache "redis://$$REDIS_CACHE";
|
||||
bench set-config -g redis_queue "redis://$$REDIS_QUEUE";
|
||||
bench set-config -g redis_socketio "redis://$$REDIS_QUEUE";
|
||||
bench set-config -gp socketio_port $$SOCKETIO_PORT;
|
||||
command: configure.py
|
||||
environment:
|
||||
DB_HOST: ${DB_HOST}
|
||||
DB_PORT: ${DB_PORT}
|
||||
REDIS_CACHE: ${REDIS_CACHE}
|
||||
REDIS_QUEUE: ${REDIS_QUEUE}
|
||||
REDIS_SOCKETIO: ${REDIS_SOCKETIO}
|
||||
SOCKETIO_PORT: 9000
|
||||
depends_on: {}
|
||||
|
||||
@ -47,9 +27,7 @@ services:
|
||||
<<: *backend_defaults
|
||||
|
||||
frontend:
|
||||
<<: *customizable_image
|
||||
command:
|
||||
- nginx-entrypoint.sh
|
||||
image: frappe/frappe-nginx:${FRAPPE_VERSION}
|
||||
environment:
|
||||
BACKEND: backend:8000
|
||||
SOCKETIO: websocket:9000
|
||||
@ -57,29 +35,32 @@ services:
|
||||
UPSTREAM_REAL_IP_ADDRESS: ${UPSTREAM_REAL_IP_ADDRESS:-127.0.0.1}
|
||||
UPSTREAM_REAL_IP_HEADER: ${UPSTREAM_REAL_IP_HEADER:-X-Forwarded-For}
|
||||
UPSTREAM_REAL_IP_RECURSIVE: ${UPSTREAM_REAL_IP_RECURSIVE:-off}
|
||||
PROXY_READ_TIMEOUT: ${PROXY_READ_TIMEOUT:-120}
|
||||
PROXY_READ_TIMOUT: ${PROXY_READ_TIMOUT:-120}
|
||||
CLIENT_MAX_BODY_SIZE: ${CLIENT_MAX_BODY_SIZE:-50m}
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- sites:/usr/share/nginx/html/sites
|
||||
- assets:/usr/share/nginx/html/assets
|
||||
depends_on:
|
||||
- backend
|
||||
- websocket
|
||||
|
||||
websocket:
|
||||
<<: [*depends_on_configurator, *customizable_image]
|
||||
command:
|
||||
- node
|
||||
- /home/frappe/frappe-bench/apps/frappe/socketio.js
|
||||
<<: *depends_on_configurator
|
||||
image: frappe/frappe-socketio:${FRAPPE_VERSION}
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
|
||||
queue-short:
|
||||
<<: *backend_defaults
|
||||
command: bench worker --queue short,default
|
||||
command: bench worker --queue short
|
||||
|
||||
queue-default:
|
||||
<<: *backend_defaults
|
||||
command: bench worker --queue default
|
||||
|
||||
queue-long:
|
||||
<<: *backend_defaults
|
||||
command: bench worker --queue long,default,short
|
||||
command: bench worker --queue long
|
||||
|
||||
scheduler:
|
||||
<<: *backend_defaults
|
||||
@ -88,3 +69,4 @@ services:
|
||||
# ERPNext requires local assets access (Frappe does not)
|
||||
volumes:
|
||||
sites:
|
||||
assets:
|
||||
|
46
custom_app/README.md
Normal file
46
custom_app/README.md
Normal file
@ -0,0 +1,46 @@
|
||||
This is basic configuration for building images and testing custom apps that use Frappe.
|
||||
|
||||
You can see that there's four files in this folder:
|
||||
|
||||
- `backend.Dockerfile`,
|
||||
- `frontend.Dockerfile`,
|
||||
- `docker-bake.hcl`,
|
||||
- `compose.override.yaml`.
|
||||
|
||||
Python code will be built in `backend.Dockerfile`. JS and CSS (and other fancy frontend stuff) files will be built in `frontend.Dockerfile`.
|
||||
|
||||
`docker-bake.hcl` is reference file for [Buildx Bake](https://github.com/docker/buildx/blob/master/docs/reference/buildx_bake.md). It helps to build images without having to remember all build arguments.
|
||||
|
||||
`compose.override.yaml` is [Compose](https://docs.docker.com/compose/compose-file/) override that replaces images from [main compose file](https://github.com/frappe/frappe_docker/blob/main/compose.yaml) so it would use your own images.
|
||||
|
||||
To get started, install Docker and [Buildx](https://github.com/docker/buildx#installing). Then copy all content of this folder (except this README) to your app's root directory. Also copy `compose.yaml` in the root of this repository.
|
||||
|
||||
Before the next step—to build images—replace "custom_app" with your app's name in `docker-bake.hcl`. After that, let's try to build:
|
||||
|
||||
```bash
|
||||
FRAPPE_VERSION=... ERPNEXT_VERSION=... docker buildx bake
|
||||
```
|
||||
|
||||
> 💡 We assume that majority of our users use ERPNext, that's why images in this tutorial are based on ERPNext images. If don't want ERPNext, change base image in Dockerfile and remove ERPNEXT_VERSION from bake file. To know more about steps used to build frontend image read comments in `frontend.Dockerfile`.
|
||||
|
||||
If something goes wrong feel free to leave an issue.
|
||||
|
||||
To test if site works, setup `.env` file (check [example](<(https://github.com/frappe/frappe_docker/blob/main/example.env)>)) and run:
|
||||
|
||||
```bash
|
||||
docker-compose -f compose.yaml -f overrides/compose.noproxy.yaml -f overrides/compose.mariadb.yaml -f overrides/compose.redis.yaml -f custom_app/compose.override.yaml up -d
|
||||
docker-compose exec backend \
|
||||
bench new-site 127.0.0.1 \
|
||||
--mariadb-root-password 123 \
|
||||
--admin-password admin \
|
||||
--install-app <Name of your app>
|
||||
docker-compose restart backend
|
||||
```
|
||||
|
||||
Cool! You just containerized your app!
|
||||
|
||||
## Installing multiple apps
|
||||
|
||||
Backend builds contain `install-app` script that places app where it should be. Each call to script installs given app. Usage: `install-app [APP_NAME]`.
|
||||
|
||||
If you want to install an app from git, clone it locally, COPY in Dockerfile.
|
14
custom_app/backend.Dockerfile
Normal file
14
custom_app/backend.Dockerfile
Normal file
@ -0,0 +1,14 @@
|
||||
# syntax=docker/dockerfile:1.3
|
||||
|
||||
ARG ERPNEXT_VERSION
|
||||
FROM frappe/erpnext-worker:${ERPNEXT_VERSION}
|
||||
|
||||
USER root
|
||||
|
||||
ARG APP_NAME
|
||||
COPY . ../apps/${APP_NAME}
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
install-app ${APP_NAME}
|
||||
|
||||
USER frappe
|
21
custom_app/compose.override.yaml
Normal file
21
custom_app/compose.override.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
services:
|
||||
configurator:
|
||||
image: custom_app/worker:${VERSION}
|
||||
|
||||
backend:
|
||||
image: custom_app/worker:${VERSION}
|
||||
|
||||
frontend:
|
||||
image: custom_app/nginx:${VERSION}
|
||||
|
||||
queue-short:
|
||||
image: custom_app/worker:${VERSION}
|
||||
|
||||
queue-default:
|
||||
image: custom_app/worker:${VERSION}
|
||||
|
||||
queue-long:
|
||||
image: custom_app/worker:${VERSION}
|
||||
|
||||
scheduler:
|
||||
image: custom_app/worker:${VERSION}
|
27
custom_app/docker-bake.hcl
Normal file
27
custom_app/docker-bake.hcl
Normal file
@ -0,0 +1,27 @@
|
||||
APP_NAME="custom_app"
|
||||
|
||||
variable "FRAPPE_VERSION" {}
|
||||
variable "ERPNEXT_VERSION" {}
|
||||
|
||||
group "default" {
|
||||
targets = ["backend", "frontend"]
|
||||
}
|
||||
|
||||
target "backend" {
|
||||
dockerfile = "backend.Dockerfile"
|
||||
tags = ["custom_app/worker:latest"]
|
||||
args = {
|
||||
"ERPNEXT_VERSION" = ERPNEXT_VERSION
|
||||
"APP_NAME" = APP_NAME
|
||||
}
|
||||
}
|
||||
|
||||
target "frontend" {
|
||||
dockerfile = "frontend.Dockerfile"
|
||||
tags = ["custom_app/nginx:latest"]
|
||||
args = {
|
||||
"FRAPPE_VERSION" = FRAPPE_VERSION
|
||||
"ERPNEXT_VERSION" = ERPNEXT_VERSION
|
||||
"APP_NAME" = APP_NAME
|
||||
}
|
||||
}
|
40
custom_app/frontend.Dockerfile
Normal file
40
custom_app/frontend.Dockerfile
Normal file
@ -0,0 +1,40 @@
|
||||
ARG FRAPPE_VERSION=version-14
|
||||
# Prepare builder image
|
||||
FROM frappe/bench:latest as assets
|
||||
|
||||
ARG FRAPPE_VERSION=version-14
|
||||
ARG ERPNEXT_VERSION=version-14
|
||||
ARG APP_NAME
|
||||
|
||||
# Setup frappe-bench using FRAPPE_VERSION
|
||||
RUN bench init --version=${FRAPPE_VERSION} --skip-redis-config-generation --verbose --skip-assets /home/frappe/frappe-bench
|
||||
WORKDIR /home/frappe/frappe-bench
|
||||
|
||||
# Comment following if ERPNext is not required
|
||||
RUN bench get-app --branch=${ERPNEXT_VERSION} --skip-assets --resolve-deps erpnext
|
||||
|
||||
# Copy custom app(s)
|
||||
COPY --chown=frappe:frappe . apps/${APP_NAME}
|
||||
|
||||
# Setup dependencies
|
||||
RUN bench setup requirements
|
||||
|
||||
# Build static assets, copy files instead of symlink
|
||||
RUN if [ -z "${ERPNEXT_VERSION##*v14*}" ] || [ "$ERPNEXT_VERSION" = "develop" ]; then \
|
||||
export BUILD_OPTS="--production"; \
|
||||
fi \
|
||||
&& FRAPPE_ENV=production bench build --verbose --hard-link ${BUILD_OPTS}
|
||||
|
||||
|
||||
# Use frappe-nginx image with nginx template and env vars
|
||||
FROM frappe/frappe-nginx:${FRAPPE_VERSION}
|
||||
|
||||
# Remove existing assets
|
||||
USER root
|
||||
RUN rm -fr /usr/share/nginx/html/assets
|
||||
|
||||
# Copy built assets
|
||||
COPY --from=assets /home/frappe/frappe-bench/sites/assets /usr/share/nginx/html/assets
|
||||
|
||||
# Use non-root user
|
||||
USER 1000
|
@ -2,31 +2,24 @@
|
||||
"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"
|
||||
"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"
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-vscode.live-server",
|
||||
"grapecity.gc-excelviewer",
|
||||
"mtxr.sqltools",
|
||||
"visualstudioexptteam.vscodeintellicode"
|
||||
]
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
version: "3.7"
|
||||
services:
|
||||
mariadb:
|
||||
image: docker.io/mariadb:10.6
|
||||
image: mariadb:10.6
|
||||
command:
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
@ -20,29 +20,17 @@ services:
|
||||
# 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
|
||||
image: redis:alpine
|
||||
|
||||
redis-queue:
|
||||
image: docker.io/redis:alpine
|
||||
image: redis:alpine
|
||||
|
||||
redis-socketio:
|
||||
image: redis:alpine
|
||||
|
||||
frappe:
|
||||
image: docker.io/frappe/bench:latest
|
||||
image: frappe/bench:latest
|
||||
command: sleep infinity
|
||||
environment:
|
||||
- SHELL=/bin/bash
|
||||
@ -54,34 +42,7 @@ services:
|
||||
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 <folder>" 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:
|
||||
postgresql-data:
|
||||
|
@ -14,9 +14,9 @@ It is recommended you allocate at least 4GB of RAM to docker:
|
||||
- [Instructions for macOS](https://docs.docker.com/desktop/settings/mac/#advanced)
|
||||
|
||||
Here is a screenshot showing the relevant setting in the Help Manual
|
||||

|
||||

|
||||
Here is a screenshot showing the settings in Docker Desktop on Mac
|
||||

|
||||

|
||||
|
||||
## Bootstrap Containers for development
|
||||
|
||||
@ -70,8 +70,6 @@ Notes:
|
||||
|
||||
Run the following commands in the terminal inside the container. You might need to create a new terminal in VSCode.
|
||||
|
||||
NOTE: Prior to doing the following, make sure the user is **frappe**.
|
||||
|
||||
```shell
|
||||
bench init --skip-redis-config-generation frappe-bench
|
||||
cd frappe-bench
|
||||
@ -84,16 +82,16 @@ To setup frappe framework version 14 bench set `PYENV_VERSION` environment varia
|
||||
bench init --skip-redis-config-generation --frappe-branch version-14 frappe-bench
|
||||
# Or set environment versions explicitly
|
||||
nvm use v16
|
||||
PYENV_VERSION=3.10.13 bench init --skip-redis-config-generation --frappe-branch version-14 frappe-bench
|
||||
PYENV_VERSION=3.10.5 bench init --skip-redis-config-generation --frappe-branch version-14 frappe-bench
|
||||
# Switch directory
|
||||
cd frappe-bench
|
||||
```
|
||||
|
||||
To setup frappe framework version 13 bench set `PYENV_VERSION` environment variable to `3.9.17` and use NodeJS version 14,
|
||||
To setup frappe framework version 13 bench set `PYENV_VERSION` environment variable to `3.9.9` and use NodeJS version 14,
|
||||
|
||||
```shell
|
||||
nvm use v14
|
||||
PYENV_VERSION=3.9.17 bench init --skip-redis-config-generation --frappe-branch version-13 frappe-bench
|
||||
PYENV_VERSION=3.9.9 bench init --skip-redis-config-generation --frappe-branch version-13 frappe-bench
|
||||
cd frappe-bench
|
||||
```
|
||||
|
||||
@ -105,7 +103,7 @@ We need to tell bench to use the right containers instead of localhost. Run the
|
||||
bench set-config -g db_host mariadb
|
||||
bench set-config -g redis_cache redis://redis-cache:6379
|
||||
bench set-config -g redis_queue redis://redis-queue:6379
|
||||
bench set-config -g redis_socketio redis://redis-queue:6379
|
||||
bench set-config -g redis_socketio redis://redis-socketio:6379
|
||||
```
|
||||
|
||||
For any reason the above commands fail, set the values in `common_site_config.json` manually.
|
||||
@ -115,7 +113,7 @@ For any reason the above commands fail, set the values in `common_site_config.js
|
||||
"db_host": "mariadb",
|
||||
"redis_cache": "redis://redis-cache:6379",
|
||||
"redis_queue": "redis://redis-queue:6379",
|
||||
"redis_socketio": "redis://redis-queue:6379"
|
||||
"redis_socketio": "redis://redis-socketio:6379"
|
||||
}
|
||||
```
|
||||
|
||||
@ -144,7 +142,7 @@ sed -i '/redis/d' ./Procfile
|
||||
You can create a new site with the following command:
|
||||
|
||||
```shell
|
||||
bench new-site --no-mariadb-socket sitename
|
||||
bench new-site sitename --no-mariadb-socket
|
||||
```
|
||||
|
||||
sitename MUST end with .localhost for trying deployments locally.
|
||||
@ -152,17 +150,17 @@ sitename MUST end with .localhost for trying deployments locally.
|
||||
for example:
|
||||
|
||||
```shell
|
||||
bench new-site --no-mariadb-socket development.localhost
|
||||
bench new-site mysite.localhost --no-mariadb-socket
|
||||
```
|
||||
|
||||
The same command can be run non-interactively as well:
|
||||
|
||||
```shell
|
||||
bench new-site --mariadb-root-password 123 --admin-password admin --no-mariadb-socket development.localhost
|
||||
bench new-site mysite.localhost --mariadb-root-password 123 --admin-password admin --no-mariadb-socket
|
||||
```
|
||||
|
||||
The command will ask the MariaDB root password. The default root password is `123`.
|
||||
This will create a new site and a `development.localhost` directory under `frappe-bench/sites`.
|
||||
This will create a new site and a `mysite.localhost` directory under `frappe-bench/sites`.
|
||||
The option `--no-mariadb-socket` will configure site's database credentials to work with docker.
|
||||
You may need to configure your system /etc/hosts if you're on Linux, Mac, or its Windows equivalent.
|
||||
|
||||
@ -171,7 +169,7 @@ To setup site with PostgreSQL as database use option `--db-type postgres` and `-
|
||||
Example:
|
||||
|
||||
```shell
|
||||
bench new-site --db-type postgres --db-host postgresql mypgsql.localhost
|
||||
bench new-site mypgsql.localhost --db-type postgres --db-host postgresql
|
||||
```
|
||||
|
||||
To avoid entering postgresql username and root password, set it in `common_site_config.json`,
|
||||
@ -188,8 +186,8 @@ Note: If PostgreSQL is not required, the postgresql service / container can be s
|
||||
To develop a new app, the last step will be setting the site into developer mode. Documentation is available at [this link](https://frappe.io/docs/user/en/guides/app-development/how-enable-developer-mode-in-frappe).
|
||||
|
||||
```shell
|
||||
bench --site development.localhost set-config developer_mode 1
|
||||
bench --site development.localhost clear-cache
|
||||
bench --site mysite.localhost set-config developer_mode 1
|
||||
bench --site mysite.localhost clear-cache
|
||||
```
|
||||
|
||||
### Install an app
|
||||
@ -203,21 +201,21 @@ To install custom app
|
||||
```shell
|
||||
# --branch is optional, use it to point to branch on custom app repository
|
||||
bench get-app --branch version-12 https://github.com/myusername/myapp
|
||||
bench --site development.localhost install-app myapp
|
||||
bench --site mysite.localhost install-app myapp
|
||||
```
|
||||
|
||||
At the time of this writing, the Payments app has been factored out of the Version 14 ERPNext app and is now a separate app. ERPNext will not install it.
|
||||
At the time of this writing, the Payments app has been factored out of the Version 14 ERPNext app and is now a separate app. ERPNext will not install without it, however, so we need to specify `--resolve-deps` command line switch to install it.
|
||||
|
||||
```shell
|
||||
bench get-app --branch version-14 --resolve-deps erpnext
|
||||
bench --site development.localhost install-app erpnext
|
||||
bench --site mysite.localhost install-app erpnext
|
||||
```
|
||||
|
||||
To install ERPNext (from the version-13 branch):
|
||||
|
||||
```shell
|
||||
bench get-app --branch version-13 erpnext
|
||||
bench --site development.localhost install-app erpnext
|
||||
bench --site mysite.localhost install-app erpnext
|
||||
```
|
||||
|
||||
Note: Both frappe and erpnext must be on branch with same name. e.g. version-14
|
||||
@ -231,59 +229,42 @@ bench start
|
||||
```
|
||||
|
||||
You can now login with user `Administrator` and the password you choose when creating the site.
|
||||
Your website will now be accessible at location [development.localhost:8000](http://development.localhost:8000)
|
||||
Your website will now be accessible at location [mysite.localhost:8000](http://mysite.localhost:8000)
|
||||
Note: To start bench with debugger refer section for debugging.
|
||||
|
||||
### Setup bench / new site using script
|
||||
|
||||
Most developers work with numerous clients and versions. Moreover, apps may be required to be installed by everyone on the team working for a client.
|
||||
|
||||
This is simplified using a script to automate the process of creating a new bench / site and installing the required apps. `Administrator` password is for created sites is `admin`.
|
||||
This is simplified using a script to automate the process of creating a new bench / site and installing the required apps.
|
||||
|
||||
Sample `apps-example.json` is used by default, it installs erpnext on current stable release. To install custom apps, copy the `apps-example.json` to custom json file and make changes to list of apps. Pass this file to the `installer.py` script.
|
||||
Create a copy of apps-example.json and name it apps.json
|
||||
|
||||
```shell
|
||||
cp apps-example.json apps.json
|
||||
```
|
||||
|
||||
Maintain a directory of all client apps in apps.json. Note that Maintaining a fork is optional in apps.json. Also `name` should be app name in apps.json (could be different from repo name).
|
||||
|
||||
> You may have apps in private repos which may require ssh access. You may use SSH from your home directory on linux (configurable in docker-compose.yml).
|
||||
|
||||
```shell
|
||||
python installer.py #pass --db-type postgres for postgresdb
|
||||
```
|
||||
|
||||
For command help
|
||||
After you have created apps.json, run the following command:
|
||||
|
||||
```shell
|
||||
python installer.py --help
|
||||
usage: installer.py [-h] [-j APPS_JSON] [-b BENCH_NAME] [-s SITE_NAME] [-r FRAPPE_REPO] [-t FRAPPE_BRANCH] [-p PY_VERSION] [-n NODE_VERSION] [-v] [-a ADMIN_PASSWORD] [-d DB_TYPE]
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-j APPS_JSON, --apps-json APPS_JSON
|
||||
Path to apps.json, default: apps-example.json
|
||||
-b BENCH_NAME, --bench-name BENCH_NAME
|
||||
Bench directory name, default: frappe-bench
|
||||
-s SITE_NAME, --site-name SITE_NAME
|
||||
Site name, should end with .localhost, default: development.localhost
|
||||
-r FRAPPE_REPO, --frappe-repo FRAPPE_REPO
|
||||
frappe repo to use, default: https://github.com/frappe/frappe
|
||||
-t FRAPPE_BRANCH, --frappe-branch FRAPPE_BRANCH
|
||||
frappe repo to use, default: version-15
|
||||
-p PY_VERSION, --py-version PY_VERSION
|
||||
python version, default: Not Set
|
||||
-n NODE_VERSION, --node-version NODE_VERSION
|
||||
node version, default: Not Set
|
||||
-v, --verbose verbose output
|
||||
-a ADMIN_PASSWORD, --admin-password ADMIN_PASSWORD
|
||||
admin password for site, default: admin
|
||||
-d DB_TYPE, --db-type DB_TYPE
|
||||
Database type to use (e.g., mariadb or postgres)
|
||||
bash installer.sh
|
||||
```
|
||||
|
||||
The script will ask for the following information:
|
||||
|
||||
- Client name (from apps.json).
|
||||
- Bench directory name. If you enter existing bench directory name, it will create a new site in that bench. Else it will create a new bench and site.
|
||||
- Site name (should end with `.localhost`).
|
||||
|
||||
A new bench and / or site is created for the client with following defaults.
|
||||
|
||||
- MariaDB root password: `123`
|
||||
- Admin password: `admin`
|
||||
|
||||
> To use Postegres DB, comment the mariabdb service and uncomment postegres service.
|
||||
|
||||
### Start Frappe with Visual Studio Code Python Debugging
|
||||
|
||||
To enable Python debugging inside Visual Studio Code, you must first install the `ms-python.python` extension inside the container. This should have already happened automatically, but depending on your VSCode config, you can force it by:
|
||||
@ -301,7 +282,8 @@ honcho start \
|
||||
watch \
|
||||
schedule \
|
||||
worker_short \
|
||||
worker_long
|
||||
worker_long \
|
||||
worker_default
|
||||
```
|
||||
|
||||
Alternatively you can use the VSCode launch configuration "Honcho SocketIO Watch Schedule Worker" which launches the same command as above.
|
||||
@ -319,7 +301,7 @@ For advance vscode configuration in the devcontainer, change the config files in
|
||||
You can launch a simple interactive shell console in the terminal with:
|
||||
|
||||
```shell
|
||||
bench --site development.localhost console
|
||||
bench --site mysite.localhost console
|
||||
```
|
||||
|
||||
More likely, you may want to launch VSCode interactive console based on Jupyter kernel.
|
||||
@ -334,12 +316,12 @@ The first step is installing and updating the required software. Usually the fra
|
||||
|
||||
Then, run the command `Python: Show Python interactive window` from the VSCode command palette.
|
||||
|
||||
Replace `development.localhost` with your site and run the following code in a Jupyter cell:
|
||||
Replace `mysite.localhost` with your site and run the following code in a Jupyter cell:
|
||||
|
||||
```python
|
||||
import frappe
|
||||
|
||||
frappe.init(site='development.localhost', sites_path='/workspace/development/frappe-bench/sites')
|
||||
frappe.init(site='mysite.localhost', sites_path='/workspace/development/frappe-bench/sites')
|
||||
frappe.connect()
|
||||
frappe.local.lang = frappe.db.get_default('lang')
|
||||
frappe.db.connect()
|
||||
@ -388,30 +370,3 @@ volumes:
|
||||
```
|
||||
|
||||
Access the service by service name from the `frappe` development container. The above service will be accessible via hostname `postgresql`. If ports are published on to host, access it via `localhost:5432`.
|
||||
|
||||
## Using Cypress UI tests
|
||||
|
||||
To run cypress based UI tests in a docker environment, follow the below steps:
|
||||
|
||||
1. Install and setup X11 tooling on VM using the script `install_x11_deps.sh`
|
||||
|
||||
```shell
|
||||
sudo bash ./install_x11_deps.sh
|
||||
```
|
||||
|
||||
This script will install required deps, enable X11Forwarding and restart SSH daemon and export `DISPLAY` variable.
|
||||
|
||||
2. Run X11 service `startx` or `xquartz`
|
||||
3. Start docker compose services.
|
||||
4. SSH into ui-tester service using `docker exec..` command
|
||||
5. Export CYPRESS_baseUrl and other required env variables
|
||||
6. Start Cypress UI console by issuing `cypress run command`
|
||||
|
||||
> More references : [Cypress Official Documentation](https://www.cypress.io/blog/2019/05/02/run-cypress-with-a-single-docker-command)
|
||||
|
||||
> Ensure DISPLAY environment is always exported.
|
||||
|
||||
## Using Mailpit to test mail services
|
||||
|
||||
To use Mailpit just uncomment the service in the docker-compose.yml file.
|
||||
The Interface is then available under port 8025 and the smtp service can be used as mailpit:1025.
|
@ -1,6 +1,15 @@
|
||||
[
|
||||
{
|
||||
"url": "https://github.com/frappe/erpnext.git",
|
||||
"branch": "version-15"
|
||||
}
|
||||
]
|
||||
{
|
||||
"client_name": [
|
||||
{
|
||||
"name": "frappe",
|
||||
"branch": "develop",
|
||||
"upstream": "git@github.com:frappe/frappe.git",
|
||||
"fork": "[your fork]"
|
||||
},
|
||||
{
|
||||
"name": "erpnext",
|
||||
"branch": "develop",
|
||||
"upstream": "git@github.com:frappe/erpnext.git"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,243 +0,0 @@
|
||||
#!/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-host=mariadb", # Should match the compose service name
|
||||
f"--db-type={args.db_type}", # Add the selected database type
|
||||
f"--no-mariadb-socket",
|
||||
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-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()
|
160
development/installer.sh
Executable file
160
development/installer.sh
Executable file
@ -0,0 +1,160 @@
|
||||
#!/bin/bash
|
||||
# Developer Note: Run this script in the /workspace/development directory
|
||||
|
||||
export NVM_DIR=~/.nvm
|
||||
# shellcheck disable=SC1091
|
||||
source $NVM_DIR/nvm.sh
|
||||
|
||||
sudo apt -qq update && sudo apt -qq install jq -y
|
||||
|
||||
get_client_apps() {
|
||||
apps=$(jq ".\"$client\"" apps.json)
|
||||
|
||||
if [ "$apps" == "null" ]; then
|
||||
echo "No apps found for $client"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
validate_bench_exists() {
|
||||
dir="$(pwd)/$bench_name"
|
||||
if [ -d "$dir" ]; then
|
||||
echo "Bench already exists. Only site will be created"
|
||||
is_existing_bench=true
|
||||
else
|
||||
is_existing_bench=false
|
||||
fi
|
||||
}
|
||||
|
||||
validate_branch() {
|
||||
if [ "$app" == "frappe" ] || [ "$app" == "erpnext" ]; then
|
||||
if [ "$branch" != "develop" ] && [ "$branch" != "version-14" ] && [ "$branch" != "version-13" ]; then
|
||||
echo "Branch should be one of develop or version-14 or version-13"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
validate_site() {
|
||||
if [[ ! "$site_name" =~ ^.*\.localhost$ ]]; then
|
||||
echo "Site name should end with .localhost"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$is_existing_bench" = true ]; then
|
||||
validate_site_exists
|
||||
fi
|
||||
}
|
||||
|
||||
validate_site_exists() {
|
||||
dir="$(pwd)/$bench_name/sites/$site_name"
|
||||
if [ -d "$dir" ]; then
|
||||
echo "Site already exists. Exiting"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
validate_app_exists() {
|
||||
dir="$(pwd)/apps/$app"
|
||||
if [ -d "$dir" ]; then
|
||||
echo "App $app already exists."
|
||||
is_app_installed=true
|
||||
else
|
||||
is_app_installed=false
|
||||
fi
|
||||
}
|
||||
|
||||
add_fork() {
|
||||
dir="$(pwd)/apps/$app"
|
||||
if [ "$fork" != "null" ]; then
|
||||
git -C "$dir" remote add fork "$fork"
|
||||
fi
|
||||
}
|
||||
|
||||
install_apps() {
|
||||
initialize_bench=$1
|
||||
|
||||
for row in $(echo "$apps" | jq -r '.[] | @base64'); do
|
||||
# helper function to retrieve values from dict
|
||||
_jq() {
|
||||
echo "${row}" | base64 --decode | jq -r "${1}"
|
||||
}
|
||||
|
||||
app=$(_jq '.name')
|
||||
branch=$(_jq '.branch')
|
||||
upstream=$(_jq '.upstream')
|
||||
fork=$(_jq '.fork')
|
||||
|
||||
if [ "$initialize_bench" = true ] && [ "$app" == "frappe" ]; then
|
||||
init_bench
|
||||
fi
|
||||
if [ "$initialize_bench" = false ]; then
|
||||
get_apps_from_upstream
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
init_bench() {
|
||||
echo "Creating bench $bench_name"
|
||||
|
||||
if [ "$branch" == "develop" ] || [ "$branch" == "version-14" ]; then
|
||||
python_version=python3.10
|
||||
PYENV_VERSION=3.10.5
|
||||
NODE_VERSION=v16
|
||||
elif [ "$branch" == "version-13" ]; then
|
||||
python_version=python3.9
|
||||
PYENV_VERSION=3.9.9
|
||||
NODE_VERSION=v14
|
||||
fi
|
||||
|
||||
nvm use "$NODE_VERSION"
|
||||
PYENV_VERSION="$PYENV_VERSION" bench init --skip-redis-config-generation --frappe-branch "$branch" --python "$python_version" "$bench_name"
|
||||
cd "$bench_name" || exit
|
||||
|
||||
echo "Setting up config"
|
||||
|
||||
bench set-config -g db_host mariadb
|
||||
bench set-config -g redis_cache redis://redis-cache:6379
|
||||
bench set-config -g redis_queue redis://redis-queue:6379
|
||||
bench set-config -g redis_socketio redis://redis-socketio:6379
|
||||
|
||||
./env/bin/pip install honcho
|
||||
}
|
||||
|
||||
get_apps_from_upstream() {
|
||||
validate_app_exists
|
||||
if [ "$is_app_installed" = false ]; then
|
||||
bench get-app --branch "$branch" --resolve-deps "$app" "$upstream" && add_fork
|
||||
fi
|
||||
|
||||
if [ "$app" != "frappe" ]; then
|
||||
all_apps+=("$app")
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Client Name (from apps.json file)?"
|
||||
read -r client && client=${client:-develop_client} && get_client_apps
|
||||
|
||||
echo "Bench Directory Name? (give name of existing bench to just create a new site) (default: frape-bench)"
|
||||
read -r bench_name && bench_name=${bench_name:-frappe-bench} && validate_bench_exists
|
||||
|
||||
echo "Site Name? (should end with .localhost) (default: site1.localhost)"
|
||||
read -r site_name && site_name=${site_name:-site1.localhost} && validate_site
|
||||
|
||||
if [ "$is_existing_bench" = true ]; then
|
||||
cd "$bench_name" || exit
|
||||
else
|
||||
install_apps true
|
||||
fi
|
||||
|
||||
echo "Getting apps from upstream for $client"
|
||||
all_apps=() && install_apps false
|
||||
|
||||
echo "Creating site $site_name"
|
||||
bench new-site "$site_name" --mariadb-root-password 123 --admin-password admin --no-mariadb-socket
|
||||
|
||||
echo "Installing apps to $site_name"
|
||||
bench --site "$site_name" install-app "${all_apps[@]}"
|
||||
|
||||
bench --site "$site_name" set-config developer_mode 1
|
||||
bench --site "$site_name" clear-cache
|
@ -23,6 +23,18 @@
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Bench Default Worker",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"args": ["frappe", "worker", "--queue", "default"],
|
||||
"pythonPath": "${workspaceFolder}/frappe-bench/env/bin/python",
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"env": {
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Bench Short Worker",
|
||||
"type": "python",
|
||||
|
@ -5,13 +5,6 @@ variable "REGISTRY_USER" {
|
||||
default = "frappe"
|
||||
}
|
||||
|
||||
variable PYTHON_VERSION {
|
||||
default = "3.11.6"
|
||||
}
|
||||
variable NODE_VERSION {
|
||||
default = "18.18.2"
|
||||
}
|
||||
|
||||
variable "FRAPPE_VERSION" {
|
||||
default = "develop"
|
||||
}
|
||||
@ -32,10 +25,6 @@ variable "BENCH_REPO" {
|
||||
default = "https://github.com/frappe/bench"
|
||||
}
|
||||
|
||||
variable "LATEST_BENCH_RELEASE" {
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
# Bench image
|
||||
|
||||
target "bench" {
|
||||
@ -44,10 +33,7 @@ target "bench" {
|
||||
}
|
||||
context = "images/bench"
|
||||
target = "bench"
|
||||
tags = [
|
||||
"frappe/bench:${LATEST_BENCH_RELEASE}",
|
||||
"frappe/bench:latest",
|
||||
]
|
||||
tags = ["frappe/bench:latest"]
|
||||
}
|
||||
|
||||
target "bench-test" {
|
||||
@ -58,8 +44,16 @@ target "bench-test" {
|
||||
# Main images
|
||||
# Base for all other targets
|
||||
|
||||
group "frappe" {
|
||||
targets = ["frappe-worker", "frappe-nginx", "frappe-socketio"]
|
||||
}
|
||||
|
||||
group "erpnext" {
|
||||
targets = ["erpnext-worker", "erpnext-nginx"]
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["erpnext"]
|
||||
targets = ["frappe", "erpnext"]
|
||||
}
|
||||
|
||||
function "tag" {
|
||||
@ -74,20 +68,46 @@ function "tag" {
|
||||
|
||||
target "default-args" {
|
||||
args = {
|
||||
FRAPPE_PATH = "${FRAPPE_REPO}"
|
||||
ERPNEXT_PATH = "${ERPNEXT_REPO}"
|
||||
FRAPPE_REPO = "${FRAPPE_REPO}"
|
||||
ERPNEXT_REPO = "${ERPNEXT_REPO}"
|
||||
BENCH_REPO = "${BENCH_REPO}"
|
||||
FRAPPE_BRANCH = "${FRAPPE_VERSION}"
|
||||
ERPNEXT_BRANCH = "${ERPNEXT_VERSION}"
|
||||
PYTHON_VERSION = "${PYTHON_VERSION}"
|
||||
NODE_VERSION = "${NODE_VERSION}"
|
||||
FRAPPE_VERSION = "${FRAPPE_VERSION}"
|
||||
ERPNEXT_VERSION = "${ERPNEXT_VERSION}"
|
||||
PYTHON_VERSION = can(regex("v13", "${ERPNEXT_VERSION}")) ? "3.9.17" : "3.10.12"
|
||||
NODE_VERSION = can(regex("v13", "${FRAPPE_VERSION}")) ? "14.21.3" : "16.20.1"
|
||||
}
|
||||
}
|
||||
|
||||
target "erpnext" {
|
||||
target "frappe-worker" {
|
||||
inherits = ["default-args"]
|
||||
context = "."
|
||||
dockerfile = "images/production/Containerfile"
|
||||
target = "erpnext"
|
||||
tags = tag("erpnext", "${ERPNEXT_VERSION}")
|
||||
context = "images/worker"
|
||||
target = "frappe"
|
||||
tags = tag("frappe-worker", "${FRAPPE_VERSION}")
|
||||
}
|
||||
|
||||
target "erpnext-worker" {
|
||||
inherits = ["default-args"]
|
||||
context = "images/worker"
|
||||
target = "erpnext"
|
||||
tags = tag("erpnext-worker", "${ERPNEXT_VERSION}")
|
||||
}
|
||||
|
||||
target "frappe-nginx" {
|
||||
inherits = ["default-args"]
|
||||
context = "images/nginx"
|
||||
target = "frappe"
|
||||
tags = tag("frappe-nginx", "${FRAPPE_VERSION}")
|
||||
}
|
||||
|
||||
target "erpnext-nginx" {
|
||||
inherits = ["default-args"]
|
||||
context = "images/nginx"
|
||||
target = "erpnext"
|
||||
tags = tag("erpnext-nginx", "${ERPNEXT_VERSION}")
|
||||
}
|
||||
|
||||
target "frappe-socketio" {
|
||||
inherits = ["default-args"]
|
||||
context = "images/socketio"
|
||||
tags = tag("frappe-socketio", "${FRAPPE_VERSION}")
|
||||
}
|
||||
|
@ -5,36 +5,40 @@ Create backup service or stack.
|
||||
version: "3.7"
|
||||
services:
|
||||
backup:
|
||||
image: frappe/erpnext:${VERSION}
|
||||
image: frappe/erpnext-worker:v14
|
||||
entrypoint: ["bash", "-c"]
|
||||
command:
|
||||
- |
|
||||
bench --site all backup
|
||||
## Uncomment for restic snapshots.
|
||||
# restic snapshots || restic init
|
||||
# restic backup sites
|
||||
## Uncomment to keep only last n=30 snapshots.
|
||||
# restic forget --group-by=paths --keep-last=30 --prune
|
||||
command: |
|
||||
for SITE in $(/home/frappe/frappe-bench/env/bin/python -c "import frappe;print(' '.join(frappe.utils.get_sites()))")
|
||||
do
|
||||
bench --site $SITE backup --with-files
|
||||
push-backup \
|
||||
--site $SITE \
|
||||
--bucket $BUCKET_NAME \
|
||||
--region-name $REGION \
|
||||
--endpoint-url $ENDPOINT_URL \
|
||||
--aws-access-key-id $ACCESS_KEY_ID \
|
||||
--aws-secret-access-key $SECRET_ACCESS_KEY
|
||||
done
|
||||
environment:
|
||||
# Set correct environment variables for restic
|
||||
- RESTIC_REPOSITORY=s3:https://s3.endpoint.com/restic
|
||||
- AWS_ACCESS_KEY_ID=access_key
|
||||
- AWS_SECRET_ACCESS_KEY=secret_access_key
|
||||
- RESTIC_PASSWORD=restic_password
|
||||
- BUCKET_NAME=erpnext
|
||||
- REGION=us-east-1
|
||||
- ACCESS_KEY_ID=RANDOMACCESSKEY
|
||||
- SECRET_ACCESS_KEY=RANDOMSECRETKEY
|
||||
- ENDPOINT_URL=https://endpoint.url
|
||||
volumes:
|
||||
- "sites:/home/frappe/frappe-bench/sites"
|
||||
- "sites-vol:/home/frappe/frappe-bench/sites"
|
||||
networks:
|
||||
- erpnext-network
|
||||
|
||||
networks:
|
||||
erpnext-network:
|
||||
external: true
|
||||
name: ${PROJECT_NAME:-erpnext}_default
|
||||
name: <your_frappe_docker_project_name>_default
|
||||
|
||||
volumes:
|
||||
sites:
|
||||
sites-vol:
|
||||
external: true
|
||||
name: ${PROJECT_NAME:-erpnext}_sites
|
||||
name: <your_frappe_docker_project_name>_sites-vol
|
||||
```
|
||||
|
||||
In case of single docker host setup, add crontab entry for backup every 6 hours.
|
||||
@ -43,16 +47,6 @@ In case of single docker host setup, add crontab entry for backup every 6 hours.
|
||||
0 */6 * * * /usr/local/bin/docker-compose -f /path/to/backup-job.yml up -d > /dev/null
|
||||
```
|
||||
|
||||
Or
|
||||
|
||||
```
|
||||
0 */6 * * * docker compose -p erpnext exec backend bench --site all backup --with-files > /dev/null
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Make sure `docker-compose` or `docker compose` is available in path during execution.
|
||||
- Change the cron string as per need.
|
||||
- Set the correct project name in place of `erpnext`.
|
||||
- For Docker Swarm add it as a [swarm-cronjob](https://github.com/crazy-max/swarm-cronjob)
|
||||
- Add it as a `CronJob` in case of Kubernetes cluster.
|
||||
|
@ -1,4 +1,4 @@
|
||||
Add the following configuration to `launch.json` `configurations` array to start bench console and use debugger. Replace `development.localhost` with appropriate site. Also replace `frappe-bench` with name of the bench directory.
|
||||
Add the following configuration to `launch.json` `configurations` array to start bench console and use debugger. Replace `mysite.localhost` with appropriate site. Also replace `frappe-bench` with name of the bench directory.
|
||||
|
||||
```json
|
||||
{
|
||||
@ -6,7 +6,7 @@ Add the following configuration to `launch.json` `configurations` array to start
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"args": ["frappe", "--site", "development.localhost", "console"],
|
||||
"args": ["frappe", "--site", "mysite.localhost", "console"],
|
||||
"pythonPath": "${workspaceFolder}/frappe-bench/env/bin/python",
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"env": {
|
||||
|
@ -17,7 +17,7 @@ services:
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-changeit}
|
||||
volumes:
|
||||
- db-data:/var/lib/mysql
|
||||
- ${HOME}/data/mariadb:/var/lib/mysql
|
||||
networks:
|
||||
- mariadb-network
|
||||
|
||||
@ -25,6 +25,3 @@ networks:
|
||||
mariadb-network:
|
||||
name: mariadb-network
|
||||
external: false
|
||||
|
||||
volumes:
|
||||
db-data:
|
@ -26,6 +26,10 @@ services:
|
||||
networks:
|
||||
- bench-network
|
||||
- mariadb-network
|
||||
queue-default:
|
||||
networks:
|
||||
- bench-network
|
||||
- mariadb-network
|
||||
queue-short:
|
||||
networks:
|
||||
- bench-network
|
||||
@ -34,12 +38,7 @@ services:
|
||||
networks:
|
||||
- bench-network
|
||||
- mariadb-network
|
||||
redis-cache:
|
||||
networks:
|
||||
- bench-network
|
||||
- mariadb-network
|
||||
|
||||
redis-queue:
|
||||
redis:
|
||||
networks:
|
||||
- bench-network
|
||||
- mariadb-network
|
@ -42,7 +42,4 @@ services:
|
||||
ports:
|
||||
- 443:443
|
||||
volumes:
|
||||
- cert-data:/certificates
|
||||
|
||||
volumes:
|
||||
cert-data:
|
||||
- ${HOME}/data/traefik/certificates:/certificates
|
@ -3,7 +3,6 @@ version: "3.3"
|
||||
services:
|
||||
traefik:
|
||||
image: "traefik:v2.6"
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
# Enable Traefik for this service, to make it available in the public network
|
||||
- traefik.enable=true
|
@ -1,114 +0,0 @@
|
||||
### Clone frappe_docker and switch directory
|
||||
|
||||
```shell
|
||||
git clone https://github.com/frappe/frappe_docker
|
||||
cd frappe_docker
|
||||
```
|
||||
|
||||
### Load custom apps through json
|
||||
|
||||
`apps.json` needs to be passed in as build arg environment variable.
|
||||
|
||||
```shell
|
||||
export APPS_JSON='[
|
||||
{
|
||||
"url": "https://github.com/frappe/erpnext",
|
||||
"branch": "version-15"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/frappe/payments",
|
||||
"branch": "version-15"
|
||||
},
|
||||
{
|
||||
"url": "https://{{ PAT }}@git.example.com/project/repository.git",
|
||||
"branch": "main"
|
||||
}
|
||||
]'
|
||||
|
||||
export APPS_JSON_BASE64=$(echo ${APPS_JSON} | base64 -w 0)
|
||||
```
|
||||
|
||||
You can also generate base64 string from json file:
|
||||
|
||||
```shell
|
||||
export APPS_JSON_BASE64=$(base64 -w 0 /path/to/apps.json)
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
- `url` needs to be http(s) git url with personal access tokens without username eg:- http://{{PAT}}@github.com/project/repository.git in case of private repo.
|
||||
- add dependencies manually in `apps.json` e.g. add `payments` if you are installing `erpnext`
|
||||
- use fork repo or branch for ERPNext in case you need to use your fork or test a PR.
|
||||
|
||||
### Build Image
|
||||
|
||||
```shell
|
||||
buildah build \
|
||||
--build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \
|
||||
--build-arg=FRAPPE_BRANCH=version-15 \
|
||||
--build-arg=PYTHON_VERSION=3.11.6 \
|
||||
--build-arg=NODE_VERSION=18.18.2 \
|
||||
--build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \
|
||||
--tag=ghcr.io/user/repo/custom:1.0.0 \
|
||||
--file=images/custom/Containerfile .
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
- Use `docker` instead of `buildah` as per your setup.
|
||||
- `FRAPPE_PATH` and `FRAPPE_BRANCH` build args are optional and can be overridden in case of fork/branch or test a PR.
|
||||
- Make sure `APPS_JSON_BASE64` variable has correct base64 encoded JSON string. It is consumed as build arg, base64 encoding ensures it to be friendly with environment variables. Use `jq empty apps.json` to validate `apps.json` file.
|
||||
- Make sure the `--tag` is valid image name that will be pushed to registry. See section [below](#use-images) for remarks about its use.
|
||||
- Change `--build-arg` as per version of Python, NodeJS, Frappe Framework repo and branch
|
||||
- `.git` directories for all apps are removed from the image.
|
||||
|
||||
### Push image to use in yaml files
|
||||
|
||||
Login to `docker` or `buildah`
|
||||
|
||||
```shell
|
||||
buildah login
|
||||
```
|
||||
|
||||
Push image
|
||||
|
||||
```shell
|
||||
buildah push ghcr.io/user/repo/custom:1.0.0
|
||||
```
|
||||
|
||||
### Use Kaniko
|
||||
|
||||
Following executor args are required. Example runs locally in docker container.
|
||||
You can run it part of CI/CD or part of your cluster.
|
||||
|
||||
```shell
|
||||
podman run --rm -it \
|
||||
-v "$HOME"/.docker/config.json:/kaniko/.docker/config.json \
|
||||
gcr.io/kaniko-project/executor:latest \
|
||||
--dockerfile=images/custom/Containerfile \
|
||||
--context=git://github.com/frappe/frappe_docker \
|
||||
--build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \
|
||||
--build-arg=FRAPPE_BRANCH=version-14 \
|
||||
--build-arg=PYTHON_VERSION=3.10.12 \
|
||||
--build-arg=NODE_VERSION=16.20.1 \
|
||||
--build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \
|
||||
--cache=true \
|
||||
--destination=ghcr.io/user/repo/custom:1.0.0 \
|
||||
--destination=ghcr.io/user/repo/custom:latest
|
||||
```
|
||||
|
||||
More about [kaniko](https://github.com/GoogleContainerTools/kaniko)
|
||||
|
||||
### Use Images
|
||||
|
||||
On the [compose.yaml](../compose.yaml) replace the image reference to the `tag` you used when you built it. Then, if you used a tag like `custom_erpnext:staging` the `x-customizable-image` section will look like this:
|
||||
|
||||
```
|
||||
x-customizable-image: &customizable_image
|
||||
image: custom_erpnext:staging
|
||||
pull_policy: never
|
||||
```
|
||||
|
||||
The `pull_policy` above is optional and prevents `docker` to try to download the image when that one has been built locally.
|
||||
|
||||
Make sure image name is correct to be pushed to registry. After the images are pushed, you can pull them to servers to be deployed. If the registry is private, additional auth is needed.
|
@ -1,56 +0,0 @@
|
||||
## Environment Variables
|
||||
|
||||
All of the commands are directly passed to container as per type of service. Only environment variables used in image are for `nginx-entrypoint.sh` command. They are as follows:
|
||||
|
||||
- `BACKEND`: Set to `{host}:{port}`, defaults to `0.0.0.0:8000`
|
||||
- `SOCKETIO`: Set to `{host}:{port}`, defaults to `0.0.0.0:9000`
|
||||
- `UPSTREAM_REAL_IP_ADDRESS`: Set Nginx config for [ngx_http_realip_module#set_real_ip_from](http://nginx.org/en/docs/http/ngx_http_realip_module.html#set_real_ip_from), defaults to `127.0.0.1`
|
||||
- `UPSTREAM_REAL_IP_HEADER`: Set Nginx config for [ngx_http_realip_module#real_ip_header](http://nginx.org/en/docs/http/ngx_http_realip_module.html#real_ip_header), defaults to `X-Forwarded-For`
|
||||
- `UPSTREAM_REAL_IP_RECURSIVE`: Set Nginx config for [ngx_http_realip_module#real_ip_recursive](http://nginx.org/en/docs/http/ngx_http_realip_module.html#real_ip_recursive) Set defaults to `off`
|
||||
- `FRAPPE_SITE_NAME_HEADER`: Set proxy header `X-Frappe-Site-Name` and serve site named in the header, defaults to `$host`, i.e. find site name from host header. More details [below](#frappe_site_name_header)
|
||||
- `PROXY_READ_TIMEOUT`: Upstream gunicorn service timeout, defaults to `120`
|
||||
- `CLIENT_MAX_BODY_SIZE`: Max body size for uploads, defaults to `50m`
|
||||
|
||||
To bypass `nginx-entrypoint.sh`, mount desired `/etc/nginx/conf.d/default.conf` and run `nginx -g 'daemon off;'` as container command.
|
||||
|
||||
## Configuration
|
||||
|
||||
We use environment variables to configure our setup. docker-compose uses variables from `.env` file. To get started, copy `example.env` to `.env`.
|
||||
|
||||
### `FRAPPE_VERSION`
|
||||
|
||||
Frappe framework release. You can find all releases [here](https://github.com/frappe/frappe/releases).
|
||||
|
||||
### `DB_PASSWORD`
|
||||
|
||||
Password for MariaDB (or Postgres) database.
|
||||
|
||||
### `DB_HOST`
|
||||
|
||||
Hostname for MariaDB (or Postgres) database. Set only if external service for database is used.
|
||||
|
||||
### `DB_PORT`
|
||||
|
||||
Port for MariaDB (3306) or Postgres (5432) database. Set only if external service for database is used.
|
||||
|
||||
### `REDIS_CACHE`
|
||||
|
||||
Hostname for redis server to store cache. Set only if external service for redis is used.
|
||||
|
||||
### `REDIS_QUEUE`
|
||||
|
||||
Hostname for redis server to store queue data and socketio. Set only if external service for redis is used.
|
||||
|
||||
### `ERPNEXT_VERSION`
|
||||
|
||||
ERPNext [release](https://github.com/frappe/frappe/releases). This variable is required if you use ERPNext override.
|
||||
|
||||
### `LETSENCRYPT_EMAIL`
|
||||
|
||||
Email that used to register https certificate. This one is required only if you use HTTPS override.
|
||||
|
||||
### `FRAPPE_SITE_NAME_HEADER`
|
||||
|
||||
This environment variable is not required. Default value is `$$host` which resolves site by host. For example, if your host is `example.com`, site's name should be `example.com`, or if host is `127.0.0.1` (local debugging), it should be `127.0.0.1` This variable allows to override described behavior. Let's say you create site named `mysite` and do want to access it by `127.0.0.1` host. Than you would set this variable to `mysite`.
|
||||
|
||||
There is other variables not mentioned here. They're somewhat internal and you don't have to worry about them except you want to change main compose file.
|
101
docs/images-and-compose-files.md
Normal file
101
docs/images-and-compose-files.md
Normal file
@ -0,0 +1,101 @@
|
||||
# Images
|
||||
|
||||
There's 4 images that you can find in `/images` directory:
|
||||
|
||||
- `bench`. It is used for development. [Learn more how to start development](../development/README.md).
|
||||
- `nginx`. This image contains JS and CSS assets. Container using this image also routes incoming requests using [nginx](https://www.nginx.com).
|
||||
- `socketio`. Container using this image processes realtime websocket requests using [Socket.IO](https://socket.io).
|
||||
- `worker`. Multi-purpose Python backend. Runs [Werkzeug server](https://werkzeug.palletsprojects.com/en/2.0.x/) with [gunicorn](https://gunicorn.org), queues (via `bench worker`), or schedule (via `bench schedule`).
|
||||
|
||||
> `nginx`, `socketio` and `worker` images — everything we need to be able to run all processes that Frappe framework requires (take a look at [Bench Procfile reference](https://frappeframework.com/docs/v13/user/en/bench/resources/bench-procfile)). We follow [Docker best practices](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#decouple-applications) and split these processes to different containers.
|
||||
|
||||
> ERPNext images don't have their own Dockerfiles. We use [multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) and [Docker Buildx](https://docs.docker.com/engine/reference/commandline/buildx/) to reuse as much things as possible and make our builds more efficient.
|
||||
|
||||
# Compose files
|
||||
|
||||
After building the images we have to run the containers. The best and simplest way to do this is to use [compose files](https://docs.docker.com/compose/compose-file/).
|
||||
|
||||
We have one main compose file, `compose.yaml`. Services described, networking, volumes are also handled there.
|
||||
|
||||
## Services
|
||||
|
||||
All services are described in `compose.yaml`
|
||||
|
||||
- `configurator`. Updates `common_site_config.json` so Frappe knows how to access db and redis. It is executed on every `docker-compose up` (and exited immediately). Other services start after this container exits successfully.
|
||||
- `backend`. [Werkzeug server](https://werkzeug.palletsprojects.com/en/2.0.x/).
|
||||
- `db`. Optional service that runs [MariaDB](https://mariadb.com) if you also use `overrides/compose.mariadb.yaml` or [Postgres](https://www.postgresql.org) if you also use `overrides/compose.postgres.yaml`.
|
||||
- `redis`. Optional service that runs [Redis](https://redis.io) server with cache, [Socket.IO](https://socket.io) and queues data.
|
||||
- `frontend`. [nginx](https://www.nginx.com) server that serves JS/CSS assets and routes incoming requests.
|
||||
- `proxy`. [Traefik](https://traefik.io/traefik/) proxy. It is here for complicated setups or HTTPS override (with `overrides/compose.https.yaml`).
|
||||
- `websocket`. Node server that runs [Socket.IO](https://socket.io).
|
||||
- `queue-short`, `queue-default`, `queue-long`. Python servers that run job queues using [rq](https://python-rq.org).
|
||||
- `scheduler`. Python server that runs tasks on schedule using [schedule](https://schedule.readthedocs.io/en/stable/).
|
||||
|
||||
## Overrides
|
||||
|
||||
We have several [overrides](https://docs.docker.com/compose/extends/):
|
||||
|
||||
- `overrides/compose.proxy.yaml`. Adds traefik proxy to setup.
|
||||
- `overrides/compose.noproxy.yaml`. Publishes `frontend` ports directly without any proxy.
|
||||
- `overrides/compose.erpnext.yaml`. Replaces all Frappe images with ERPNext ones. ERPNext images are built on top of Frappe ones, so it is safe to replace them.
|
||||
- `overrides/compose.https.yaml`. Automatically sets up Let's Encrypt certificate and redirects all requests to directed to http, to https.
|
||||
- `overrides/compose.mariadb.yaml`. Adds `db` service and sets its image to MariaDB.
|
||||
- `overrides/compose.postgres.yaml`. Adds `db` service and sets its image to Postgres. Note that ERPNext currently doesn't support Postgres.
|
||||
- `overrides/compose.redis.yaml`. Adds `redis` service and sets its image to `redis`.
|
||||
|
||||
It is quite simple to run overrides. All we need to do is to specify compose files that should be used by docker-compose. For example, we want ERPNext:
|
||||
|
||||
```bash
|
||||
# Point to main compose file (compose.yaml) and add one more.
|
||||
docker-compose -f compose.yaml -f overrides/compose.erpnext.yaml config
|
||||
```
|
||||
|
||||
⚠ Make sure to use docker-compose v2 (run `docker-compose -v` to check). If you want to use v1 make sure the correct `$`-signs as they get duplicated by the `config` command!
|
||||
|
||||
That's it! Of course, we also have to setup `.env` before all of that, but that's not the point.
|
||||
|
||||
## Configuration
|
||||
|
||||
We use environment variables to configure our setup. docker-compose uses variables from `.env` file. To get started, copy `example.env` to `.env`.
|
||||
|
||||
### `FRAPPE_VERSION`
|
||||
|
||||
Frappe framework release. You can find all releases [here](https://github.com/frappe/frappe/releases).
|
||||
|
||||
### `DB_PASSWORD`
|
||||
|
||||
Password for MariaDB (or Postgres) database.
|
||||
|
||||
### `DB_HOST`
|
||||
|
||||
Hostname for MariaDB (or Postgres) database. Set only if external service for database is used.
|
||||
|
||||
### `DB_PORT`
|
||||
|
||||
Port for MariaDB (3306) or Postgres (5432) database. Set only if external service for database is used.
|
||||
|
||||
### `REDIS_CACHE`
|
||||
|
||||
Hostname for redis server to store cache. Set only if external service for redis is used.
|
||||
|
||||
### `REDIS_QUEUE`
|
||||
|
||||
Hostname for redis server to store queue data. Set only if external service for redis is used.
|
||||
|
||||
### `REDIS_SOCKETIO`
|
||||
|
||||
Hostname for redis server to store socketio data. Set only if external service for redis is used.
|
||||
|
||||
### `ERPNEXT_VERSION`
|
||||
|
||||
ERPNext [release](https://github.com/frappe/frappe/releases). This variable is required if you use ERPNext override.
|
||||
|
||||
### `LETSENCRYPT_EMAIL`
|
||||
|
||||
Email that used to register https certificate. This one is required only if you use HTTPS override.
|
||||
|
||||
### `FRAPPE_SITE_NAME_HEADER`
|
||||
|
||||
This environment variable is not required. Default value is `$$host` which resolves site by host. For example, if your host is `example.com`, site's name should be `example.com`, or if host is `127.0.0.1` (local debugging), it should be `127.0.0.1` This variable allows to override described behavior. Let's say you create site named `mysite` and do want to access it by `127.0.0.1` host. Than you would set this variable to `mysite`.
|
||||
|
||||
There is other variables not mentioned here. They're somewhat internal and you don't have to worry about them except you want to change main compose file.
|
@ -1,58 +0,0 @@
|
||||
# Images
|
||||
|
||||
There are 3 images that you can find in `/images` directory:
|
||||
|
||||
- `bench`. It is used for development. [Learn more how to start development](../development/README.md).
|
||||
- `production`.
|
||||
- Multi-purpose Python backend. Runs [Werkzeug server](https://werkzeug.palletsprojects.com/en/2.0.x/) with [gunicorn](https://gunicorn.org), queues (via `bench worker`), or schedule (via `bench schedule`).
|
||||
- Contains JS and CSS assets and routes incoming requests using [nginx](https://www.nginx.com).
|
||||
- Processes realtime websocket requests using [Socket.IO](https://socket.io).
|
||||
- `custom`. It is used to build bench using `apps.json` file set with `--apps_path` during bench initialization. `apps.json` is a json array. e.g. `[{"url":"{{repo_url}}","branch":"{{repo_branch}}"}]`
|
||||
|
||||
Image has everything we need to be able to run all processes that Frappe framework requires (take a look at [Bench Procfile reference](https://frappeframework.com/docs/v14/user/en/bench/resources/bench-procfile)). We follow [Docker best practices](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#decouple-applications) and split these processes to different containers.
|
||||
|
||||
> We use [multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) and [Docker Buildx](https://docs.docker.com/engine/reference/commandline/buildx/) to reuse as much things as possible and make our builds more efficient.
|
||||
|
||||
# Compose files
|
||||
|
||||
After building the images we have to run the containers. The best and simplest way to do this is to use [compose files](https://docs.docker.com/compose/compose-file/).
|
||||
|
||||
We have one main compose file, `compose.yaml`. Services described, networking, volumes are also handled there.
|
||||
|
||||
## Services
|
||||
|
||||
All services are described in `compose.yaml`
|
||||
|
||||
- `configurator`. Updates `common_site_config.json` so Frappe knows how to access db and redis. It is executed on every `docker-compose up` (and exited immediately). Other services start after this container exits successfully.
|
||||
- `backend`. [Werkzeug server](https://werkzeug.palletsprojects.com/en/2.0.x/).
|
||||
- `db`. Optional service that runs [MariaDB](https://mariadb.com) if you also use `overrides/compose.mariadb.yaml` or [Postgres](https://www.postgresql.org) if you also use `overrides/compose.postgres.yaml`.
|
||||
- `redis`. Optional service that runs [Redis](https://redis.io) server with cache, [Socket.IO](https://socket.io) and queues data.
|
||||
- `frontend`. [nginx](https://www.nginx.com) server that serves JS/CSS assets and routes incoming requests.
|
||||
- `proxy`. [Traefik](https://traefik.io/traefik/) proxy. It is here for complicated setups or HTTPS override (with `overrides/compose.https.yaml`).
|
||||
- `websocket`. Node server that runs [Socket.IO](https://socket.io).
|
||||
- `queue-short`, `queue-long`. Python servers that run job queues using [rq](https://python-rq.org).
|
||||
- `scheduler`. Python server that runs tasks on schedule using [schedule](https://schedule.readthedocs.io/en/stable/).
|
||||
|
||||
## Overrides
|
||||
|
||||
We have several [overrides](https://docs.docker.com/compose/extends/):
|
||||
|
||||
- `overrides/compose.proxy.yaml`. Adds traefik proxy to setup.
|
||||
- `overrides/compose.noproxy.yaml`. Publishes `frontend` ports directly without any proxy.
|
||||
- `overrides/compose.https.yaml`. Automatically sets up Let's Encrypt certificate and redirects all requests to directed to http, to https.
|
||||
- `overrides/compose.mariadb.yaml`. Adds `db` service and sets its image to MariaDB.
|
||||
- `overrides/compose.postgres.yaml`. Adds `db` service and sets its image to Postgres. Note that ERPNext currently doesn't support Postgres.
|
||||
- `overrides/compose.redis.yaml`. Adds `redis` service and sets its image to `redis`.
|
||||
|
||||
It is quite simple to run overrides. All we need to do is to specify compose files that should be used by docker-compose. For example, we want ERPNext:
|
||||
|
||||
```bash
|
||||
# Point to main compose file (compose.yaml) and add one more.
|
||||
docker-compose -f compose.yaml -f overrides/compose.redis.yaml config
|
||||
```
|
||||
|
||||
⚠ Make sure to use docker-compose v2 (run `docker-compose -v` to check). If you want to use v1 make sure the correct `$`-signs as they get duplicated by the `config` command!
|
||||
|
||||
That's it! Of course, we also have to setup `.env` before all of that, but that's not the point.
|
||||
|
||||
Check [environment variables](environment-variables.md) for more.
|
@ -1,112 +0,0 @@
|
||||
## Migrate from multi-image setup
|
||||
|
||||
All the containers now use same image. Use `frappe/erpnext` instead of `frappe/frappe-worker`, `frappe/frappe-nginx` , `frappe/frappe-socketio` , `frappe/erpnext-worker` and `frappe/erpnext-nginx`.
|
||||
|
||||
Now you need to specify command and environment variables for following containers:
|
||||
|
||||
### Frontend
|
||||
|
||||
For `frontend` service to act as static assets frontend and reverse proxy, you need to pass `nginx-entrypoint.sh` as container `command` and `BACKEND` and `SOCKETIO` environment variables pointing `{host}:{port}` for gunicorn and websocket services. Check [environment variables](environment-variables.md)
|
||||
|
||||
Now you only need to mount the `sites` volume at location `/home/frappe/frappe-bench/sites`. No need for `assets` volume and asset population script or steps.
|
||||
|
||||
Example change:
|
||||
|
||||
```yaml
|
||||
# ... removed for brevity
|
||||
frontend:
|
||||
image: frappe/erpnext:${ERPNEXT_VERSION:?ERPNext version not set}
|
||||
command:
|
||||
- nginx-entrypoint.sh
|
||||
environment:
|
||||
BACKEND: backend:8000
|
||||
SOCKETIO: websocket:9000
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
# ... removed for brevity
|
||||
```
|
||||
|
||||
### Websocket
|
||||
|
||||
For `websocket` service to act as socketio backend, you need to pass `["node", "/home/frappe/frappe-bench/apps/frappe/socketio.js"]` as container `command`
|
||||
|
||||
Example change:
|
||||
|
||||
```yaml
|
||||
# ... removed for brevity
|
||||
websocket:
|
||||
image: frappe/erpnext:${ERPNEXT_VERSION:?ERPNext version not set}
|
||||
command:
|
||||
- node
|
||||
- /home/frappe/frappe-bench/apps/frappe/socketio.js
|
||||
# ... removed for brevity
|
||||
```
|
||||
|
||||
### Configurator
|
||||
|
||||
For `configurator` service to act as run once configuration job, you need to pass `["bash", "-c"]` as container `entrypoint` and bash script inline to yaml. There is no `configure.py` in the container now.
|
||||
|
||||
Example change:
|
||||
|
||||
```yaml
|
||||
# ... removed for brevity
|
||||
configurator:
|
||||
image: frappe/erpnext:${ERPNEXT_VERSION:?ERPNext version not set}
|
||||
restart: "no"
|
||||
entrypoint:
|
||||
- bash
|
||||
- -c
|
||||
command:
|
||||
- >
|
||||
bench set-config -g db_host $$DB_HOST;
|
||||
bench set-config -gp db_port $$DB_PORT;
|
||||
bench set-config -g redis_cache "redis://$$REDIS_CACHE";
|
||||
bench set-config -g redis_queue "redis://$$REDIS_QUEUE";
|
||||
bench set-config -gp socketio_port $$SOCKETIO_PORT;
|
||||
environment:
|
||||
DB_HOST: db
|
||||
DB_PORT: "3306"
|
||||
REDIS_CACHE: redis-cache:6379
|
||||
REDIS_QUEUE: redis-queue:6379
|
||||
SOCKETIO_PORT: "9000"
|
||||
# ... removed for brevity
|
||||
```
|
||||
|
||||
### Site Creation
|
||||
|
||||
For `create-site` service to act as run once site creation job, you need to pass `["bash", "-c"]` as container `entrypoint` and bash script inline to yaml. Make sure to use `--no-mariadb-socket` as upstream bench is installed in container.
|
||||
|
||||
The `WORKDIR` has changed to `/home/frappe/frappe-bench` like `bench` setup we are used to. So the path to find `common_site_config.json` has changed to `sites/common_site_config.json`.
|
||||
|
||||
Example change:
|
||||
|
||||
```yaml
|
||||
# ... removed for brevity
|
||||
create-site:
|
||||
image: frappe/erpnext:${ERPNEXT_VERSION:?ERPNext version not set}
|
||||
restart: "no"
|
||||
entrypoint:
|
||||
- bash
|
||||
- -c
|
||||
command:
|
||||
- >
|
||||
wait-for-it -t 120 db:3306;
|
||||
wait-for-it -t 120 redis-cache:6379;
|
||||
wait-for-it -t 120 redis-queue:6379;
|
||||
export start=`date +%s`;
|
||||
until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
|
||||
do
|
||||
echo "Waiting for sites/common_site_config.json to be created";
|
||||
sleep 5;
|
||||
if (( `date +%s`-start > 120 )); then
|
||||
echo "could not find sites/common_site_config.json with required keys";
|
||||
exit 1
|
||||
fi
|
||||
done;
|
||||
echo "sites/common_site_config.json found";
|
||||
bench new-site --no-mariadb-socket --admin-password=admin --db-root-password=admin --install-app erpnext --set-default frontend;
|
||||
|
||||
# ... removed for brevity
|
||||
```
|
34
docs/patch-code-from-images.md
Normal file
34
docs/patch-code-from-images.md
Normal file
@ -0,0 +1,34 @@
|
||||
Example: https://discuss.erpnext.com/t/sms-two-factor-authentication-otp-msg-change/47835
|
||||
|
||||
Above example needs following Dockerfile based patch
|
||||
|
||||
```Dockerfile
|
||||
FROM frappe/erpnext-worker:v12.17.0
|
||||
|
||||
...
|
||||
USER root
|
||||
RUN sed -i -e "s/Your verification code is/আপনার লগইন কোড/g" /home/frappe/frappe-bench/apps/frappe/frappe/twofactor.py
|
||||
USER frappe
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
Example for `nginx` image,
|
||||
|
||||
```Dockerfile
|
||||
FROM frappe/erpnext-nginx:v13.27.0
|
||||
|
||||
# Hack to use Frappe/ERPNext offline.
|
||||
RUN sed -i 's/navigator.onLine/navigator.onLine||true/' \
|
||||
/usr/share/nginx/html/assets/js/desk.min.js \
|
||||
/usr/share/nginx/html/assets/js/dialog.min.js \
|
||||
/usr/share/nginx/html/assets/js/frappe-web.min.js
|
||||
```
|
||||
|
||||
Alternatively copy the modified source code file directly over `/home/frappe/frappe-bench/apps/frappe/frappe/twofactor.py`
|
||||
|
||||
```Dockerfile
|
||||
...
|
||||
COPY twofactor.py /home/frappe/frappe-bench/apps/frappe/frappe/twofactor.py
|
||||
...
|
||||
```
|
@ -6,64 +6,43 @@ Remove the traefik service from docker-compose.yml
|
||||
|
||||
### Step 2
|
||||
|
||||
Add service for each port that needs to be exposed.
|
||||
Create nginx config file `/opt/nginx/conf/serve-8001.conf`:
|
||||
|
||||
e.g. `port-site-1`, `port-site-2`, `port-site-3`.
|
||||
```
|
||||
server {
|
||||
listen 8001;
|
||||
server_name $http_host;
|
||||
|
||||
```yaml
|
||||
# ... removed for brevity
|
||||
services:
|
||||
# ... removed for brevity
|
||||
port-site-1:
|
||||
image: frappe/erpnext:v14.11.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command:
|
||||
- nginx-entrypoint.sh
|
||||
environment:
|
||||
BACKEND: backend:8000
|
||||
FRAPPE_SITE_NAME_HEADER: site1.local
|
||||
SOCKETIO: websocket:9000
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
ports:
|
||||
- "8080:8080"
|
||||
port-site-2:
|
||||
image: frappe/erpnext:v14.11.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command:
|
||||
- nginx-entrypoint.sh
|
||||
environment:
|
||||
BACKEND: backend:8000
|
||||
FRAPPE_SITE_NAME_HEADER: site2.local
|
||||
SOCKETIO: websocket:9000
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
ports:
|
||||
- "8081:8080"
|
||||
port-site-3:
|
||||
image: frappe/erpnext:v14.11.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command:
|
||||
- nginx-entrypoint.sh
|
||||
environment:
|
||||
BACKEND: backend:8000
|
||||
FRAPPE_SITE_NAME_HEADER: site3.local
|
||||
SOCKETIO: websocket:9000
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
ports:
|
||||
- "8082:8080"
|
||||
location / {
|
||||
|
||||
rewrite ^(.+)/$ $1 permanent;
|
||||
rewrite ^(.+)/index\.html$ $1 permanent;
|
||||
rewrite ^(.+)\.html$ $1 permanent;
|
||||
|
||||
proxy_set_header X-Frappe-Site-Name mysite.localhost;
|
||||
proxy_set_header Host mysite.localhost;
|
||||
proxy_pass http://frontend;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Above setup will expose `site1.local`, `site2.local`, `site3.local` on port `8080`, `8081`, `8082` respectively.
|
||||
- Change `site1.local` to site name to serve from bench.
|
||||
- Change the `BACKEND` and `SOCKETIO` environment variables as per your service names.
|
||||
- Make sure `sites:` volume is available as part of yaml.
|
||||
- Replace the port with any port of choice e.g. `listen 4200;`
|
||||
- Change `mysite.localhost` to site name
|
||||
- Repeat the server blocks for multiple ports and site names to get the effect of port based multi tenancy
|
||||
- For old images use `proxy_pass http://erpnext-nginx` instead of `proxy_pass http://frontend`
|
||||
|
||||
### Step 3
|
||||
|
||||
Run the docker container
|
||||
|
||||
```shell
|
||||
docker run --network=<project-name>_default \
|
||||
-p 8001:8001 \
|
||||
--volume=/opt/nginx/conf/serve-8001.conf:/etc/nginx/conf.d/default.conf -d nginx
|
||||
```
|
||||
|
||||
Note: Change the volumes, network and ports as needed
|
||||
|
||||
With the above example configured site will be accessible on `http://localhost:8001`
|
||||
|
@ -17,7 +17,7 @@ Copy the example docker environment file to `.env`:
|
||||
cp example.env .env
|
||||
```
|
||||
|
||||
Note: To know more about environment variable [read here](./environment-variables.md). Set the necessary variables in the `.env` file.
|
||||
Note: To know more about environment variable [read here](./images-and-compose-files.md#configuration). Set the necessary variables in the `.env` file.
|
||||
|
||||
## Generate docker-compose.yml for variety of setups
|
||||
|
||||
@ -40,7 +40,8 @@ Instead of `docker compose config`, you can directly use `docker compose up` to
|
||||
|
||||
### Setup Frappe without proxy and external MariaDB and Redis
|
||||
|
||||
In this case make sure you've set `DB_HOST`, `DB_PORT`, `REDIS_CACHE` and `REDIS_QUEUE` environment variables or the `configurator` will fail.
|
||||
In this case make sure you've set `DB_HOST`, `DB_PORT`, `REDIS_CACHE`, `REDIS_QUEUE` and `REDIS_SOCKETIO`
|
||||
environment variables or the `configurator` will fail.
|
||||
|
||||
```sh
|
||||
# Generate YAML
|
||||
@ -52,12 +53,14 @@ docker compose --project-name <project-name> -f ~/gitops/docker-compose.yml up -
|
||||
|
||||
### Setup ERPNext with proxy and external MariaDB and Redis
|
||||
|
||||
In this case make sure you've set `DB_HOST`, `DB_PORT`, `REDIS_CACHE` and `REDIS_QUEUE` environment variables or the `configurator` will fail.
|
||||
In this case make sure you've set `DB_HOST`, `DB_PORT`, `REDIS_CACHE`, `REDIS_QUEUE` and `REDIS_SOCKETIO`
|
||||
environment variables or the `configurator` will fail.
|
||||
|
||||
```sh
|
||||
# Generate YAML
|
||||
docker compose -f compose.yaml \
|
||||
-f overrides/compose.proxy.yaml \
|
||||
-f overrides/compose.erpnext.yaml \
|
||||
config > ~/gitops/docker-compose.yml
|
||||
|
||||
# Start containers
|
||||
@ -66,8 +69,6 @@ docker compose --project-name <project-name> -f ~/gitops/docker-compose.yml up -
|
||||
|
||||
### Setup Frappe using containerized MariaDB and Redis with Letsencrypt certificates.
|
||||
|
||||
In this case make sure you've set `LETSENCRYPT_EMAIL` and `SITES` environment variables are set or certificates won't work.
|
||||
|
||||
```sh
|
||||
# Generate YAML
|
||||
docker compose -f compose.yaml \
|
||||
@ -82,11 +83,10 @@ docker compose --project-name <project-name> -f ~/gitops/docker-compose.yml up -
|
||||
|
||||
### Setup ERPNext using containerized MariaDB and Redis with Letsencrypt certificates.
|
||||
|
||||
In this case make sure you've set `LETSENCRYPT_EMAIL` and `SITES` environment variables are set or certificates won't work.
|
||||
|
||||
```sh
|
||||
# Generate YAML
|
||||
docker compose -f compose.yaml \
|
||||
-f overrides/compose.erpnext.yaml \
|
||||
-f overrides/compose.mariadb.yaml \
|
||||
-f overrides/compose.redis.yaml \
|
||||
-f overrides/compose.https.yaml \
|
||||
@ -110,22 +110,20 @@ nano .env
|
||||
|
||||
# Pull new images
|
||||
docker compose -f compose.yaml \
|
||||
-f overrides/compose.erpnext.yaml \
|
||||
# ... your other overrides
|
||||
config > ~/gitops/docker-compose.yml
|
||||
|
||||
# Pull images
|
||||
docker compose --project-name <project-name> -f ~/gitops/docker-compose.yml pull
|
||||
|
||||
# Stop containers
|
||||
docker compose --project-name <project-name> -f ~/gitops/docker-compose.yml down
|
||||
|
||||
# Remove assets volume for repopulation
|
||||
docker volume rm <name of assets volume>
|
||||
|
||||
# Restart containers
|
||||
docker compose --project-name <project-name> -f ~/gitops/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
- pull and stop container commands can be skipped if immutable image tags are used
|
||||
- `docker compose up -d` will pull new immutable tags if not found.
|
||||
|
||||
To migrate sites refer [site operations](./site-operations.md#migrate-site)
|
||||
|
@ -1,225 +0,0 @@
|
||||
# How to install ERPNext on linux/mac using Frappe_docker ?
|
||||
|
||||
step1: clone the repo
|
||||
|
||||
```
|
||||
git clone https://github.com/frappe/frappe_docker
|
||||
```
|
||||
|
||||
step2: add platform: linux/amd64 to all services in the /pwd.yaml
|
||||
|
||||
here is the update pwd.yml file
|
||||
|
||||
```yml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
backend:
|
||||
image: frappe/erpnext:v14
|
||||
platform: linux/amd64
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
|
||||
configurator:
|
||||
image: frappe/erpnext:v14
|
||||
platform: linux/amd64
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: none
|
||||
entrypoint:
|
||||
- bash
|
||||
- -c
|
||||
command:
|
||||
- >
|
||||
ls -1 apps > sites/apps.txt;
|
||||
bench set-config -g db_host $$DB_HOST;
|
||||
bench set-config -gp db_port $$DB_PORT;
|
||||
bench set-config -g redis_cache "redis://$$REDIS_CACHE";
|
||||
bench set-config -g redis_queue "redis://$$REDIS_QUEUE";
|
||||
bench set-config -gp socketio_port $$SOCKETIO_PORT;
|
||||
environment:
|
||||
DB_HOST: db
|
||||
DB_PORT: "3306"
|
||||
REDIS_CACHE: redis-cache:6379
|
||||
REDIS_QUEUE: redis-queue:6379
|
||||
SOCKETIO_PORT: "9000"
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
|
||||
create-site:
|
||||
image: frappe/erpnext:v14
|
||||
platform: linux/amd64
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: none
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
entrypoint:
|
||||
- bash
|
||||
- -c
|
||||
command:
|
||||
- >
|
||||
wait-for-it -t 120 db:3306;
|
||||
wait-for-it -t 120 redis-cache:6379;
|
||||
wait-for-it -t 120 redis-queue:6379;
|
||||
export start=`date +%s`;
|
||||
until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
|
||||
do
|
||||
echo "Waiting for sites/common_site_config.json to be created";
|
||||
sleep 5;
|
||||
if (( `date +%s`-start > 120 )); then
|
||||
echo "could not find sites/common_site_config.json with required keys";
|
||||
exit 1
|
||||
fi
|
||||
done;
|
||||
echo "sites/common_site_config.json found";
|
||||
bench new-site --no-mariadb-socket --admin-password=admin --db-root-password=admin --install-app erpnext --set-default frontend;
|
||||
|
||||
db:
|
||||
image: mariadb:10.6
|
||||
platform: linux/amd64
|
||||
healthcheck:
|
||||
test: mysqladmin ping -h localhost --password=admin
|
||||
interval: 1s
|
||||
retries: 15
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
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: admin
|
||||
volumes:
|
||||
- db-data:/var/lib/mysql
|
||||
|
||||
frontend:
|
||||
image: frappe/erpnext:v14
|
||||
platform: linux/amd64
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command:
|
||||
- nginx-entrypoint.sh
|
||||
environment:
|
||||
BACKEND: backend:8000
|
||||
FRAPPE_SITE_NAME_HEADER: frontend
|
||||
SOCKETIO: websocket:9000
|
||||
UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
|
||||
UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
|
||||
UPSTREAM_REAL_IP_RECURSIVE: "off"
|
||||
PROXY_READ_TIMEOUT: 120
|
||||
CLIENT_MAX_BODY_SIZE: 50m
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
queue-long:
|
||||
image: frappe/erpnext:v14
|
||||
platform: linux/amd64
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- long
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
|
||||
queue-short:
|
||||
image: frappe/erpnext:v14
|
||||
platform: linux/amd64
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- short
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
|
||||
redis-queue:
|
||||
image: redis:6.2-alpine
|
||||
platform: linux/amd64
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
volumes:
|
||||
- redis-queue-data:/data
|
||||
|
||||
redis-cache:
|
||||
image: redis:6.2-alpine
|
||||
platform: linux/amd64
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
volumes:
|
||||
- redis-cache-data:/data
|
||||
|
||||
scheduler:
|
||||
image: frappe/erpnext:v14
|
||||
platform: linux/amd64
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command:
|
||||
- bench
|
||||
- schedule
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
|
||||
websocket:
|
||||
image: frappe/erpnext:v14
|
||||
platform: linux/amd64
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command:
|
||||
- node
|
||||
- /home/frappe/frappe-bench/apps/frappe/socketio.js
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
redis-queue-data:
|
||||
redis-cache-data:
|
||||
sites:
|
||||
logs:
|
||||
```
|
||||
|
||||
step3: run the docker
|
||||
|
||||
```
|
||||
cd frappe_docker
|
||||
```
|
||||
|
||||
```
|
||||
docker-compose -f ./pwd.yml up
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Wait for couple of minutes.
|
||||
|
||||
Open localhost:8080
|
@ -1,38 +0,0 @@
|
||||
# Single Compose Setup
|
||||
|
||||
This setup is a very simple single compose file that does everything to start required services and a frappe-bench. It is used to start play with docker instance with a site. The file is located in the root of repo and named `pwd.yml`.
|
||||
|
||||
## Services
|
||||
|
||||
### frappe-bench components
|
||||
|
||||
- backend, serves gunicorn backend
|
||||
- frontend, serves static assets through nginx frontend reverse proxies websocket and gunicorn.
|
||||
- queue-long, long default and short rq worker.
|
||||
- queue-short, default and short rq worker.
|
||||
- schedule, event scheduler.
|
||||
- websocket, socketio websocket for realtime communication.
|
||||
|
||||
### Run once configuration
|
||||
|
||||
- configurator, configures `common_site_config.json` to set db and redis hosts.
|
||||
- create-site, creates one site to serve as default site for the frappe-bench.
|
||||
|
||||
### Service dependencies
|
||||
|
||||
- db, mariadb, container with frappe specific configuration.
|
||||
- redis-cache, redis for cache data.
|
||||
- redis-queue, redis for rq data and pub/sub.
|
||||
|
||||
## Volumes
|
||||
|
||||
- sites: Volume for bench data. Common config, all sites, all site configs and site files will be stored here.
|
||||
- logs: Volume for bench logs. all process logs are dumped here. No need to mount it. Each container will create a temporary volume for logs if not specified.
|
||||
|
||||
## Adaptation
|
||||
|
||||
If you understand containers use the `pwd.yml` as a reference to build more complex setup like, single server example, Docker Swarm stack, Kubernetes Helm chart, etc.
|
||||
|
||||
This serves only site called `frontend` through the nginx. `FRAPPE_SITE_NAME_HEADER` is set to `frontend` and a default site called `frontend` is created.
|
||||
|
||||
Change the `$$host` will allow container to accept any host header and serve that site. To escape `$` in compose yaml use it like `$$`. To unset default site remove `currentsite.txt` file from `sites` directory.
|
@ -41,7 +41,7 @@ chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
|
||||
### Prepare
|
||||
|
||||
Clone `frappe_docker` repo for the needed YAMLs and change the current working directory of your shell to the cloned repo.
|
||||
Clone `frappe_docker` repo for the needed YAMLs and change the current working director of you shell to the cloned repo.
|
||||
|
||||
```shell
|
||||
git clone https://github.com/frappe/frappe_docker
|
||||
@ -82,20 +82,18 @@ EMAIL=admin@example.com
|
||||
HASHED_PASSWORD=$apr1$K.4gp7RT$tj9R2jHh0D4Gb5o5fIAzm/
|
||||
```
|
||||
|
||||
If Container does not deploy put the HASHED_PASSWORD in ''.
|
||||
|
||||
Deploy the traefik container with letsencrypt SSL
|
||||
|
||||
```shell
|
||||
docker compose --project-name traefik \
|
||||
--env-file ~/gitops/traefik.env \
|
||||
-f overrides/compose.traefik.yaml \
|
||||
-f overrides/compose.traefik-ssl.yaml up -d
|
||||
-f docs/compose/compose.traefik.yaml \
|
||||
-f docs/compose/compose.traefik-ssl.yaml up -d
|
||||
```
|
||||
|
||||
This will make the traefik dashboard available on `traefik.example.com` and all certificates will reside in the Docker volume `cert-data`.
|
||||
This will make the traefik dashboard available on `traefik.example.com` and all certificates will reside in `/data/traefik/certificates` on host filesystem.
|
||||
|
||||
For LAN setup deploy the traefik container without overriding `overrides/compose.traefik-ssl.yaml`.
|
||||
For LAN setup deploy the traefik container without overriding `docs/compose/compose.traefik-ssl.yaml`.
|
||||
|
||||
### Install MariaDB
|
||||
|
||||
@ -122,7 +120,7 @@ Note: Change the password from `changeit` to more secure one.
|
||||
Deploy the mariadb container
|
||||
|
||||
```shell
|
||||
docker compose --project-name mariadb --env-file ~/gitops/mariadb.env -f overrides/compose.mariadb-shared.yaml up -d
|
||||
docker compose --project-name mariadb --env-file ~/gitops/mariadb.env -f docs/compose/compose.mariadb-shared.yaml up -d
|
||||
```
|
||||
|
||||
This will make `mariadb-database` service available under `mariadb-network`. Data will reside in `/data/mariadb`.
|
||||
@ -140,8 +138,8 @@ cp example.env ~/gitops/erpnext-one.env
|
||||
sed -i 's/DB_PASSWORD=123/DB_PASSWORD=changeit/g' ~/gitops/erpnext-one.env
|
||||
sed -i 's/DB_HOST=/DB_HOST=mariadb-database/g' ~/gitops/erpnext-one.env
|
||||
sed -i 's/DB_PORT=/DB_PORT=3306/g' ~/gitops/erpnext-one.env
|
||||
sed -i 's/SITES=`erp.example.com`/SITES=\`one.example.com\`,\`two.example.com\`/g' ~/gitops/erpnext-one.env
|
||||
echo 'ROUTER=erpnext-one' >> ~/gitops/erpnext-one.env
|
||||
echo "SITES=\`one.example.com\`,\`two.example.com\`" >> ~/gitops/erpnext-one.env
|
||||
echo "BENCH_NETWORK=erpnext-one" >> ~/gitops/erpnext-one.env
|
||||
```
|
||||
|
||||
@ -157,9 +155,10 @@ Create a yaml file called `erpnext-one.yaml` in `~/gitops` directory:
|
||||
docker compose --project-name erpnext-one \
|
||||
--env-file ~/gitops/erpnext-one.env \
|
||||
-f compose.yaml \
|
||||
-f overrides/compose.erpnext.yaml \
|
||||
-f overrides/compose.redis.yaml \
|
||||
-f overrides/compose.multi-bench.yaml \
|
||||
-f overrides/compose.multi-bench-ssl.yaml config > ~/gitops/erpnext-one.yaml
|
||||
-f docs/compose/compose.multi-bench.yaml \
|
||||
-f docs/compose/compose.multi-bench-ssl.yaml config > ~/gitops/erpnext-one.yaml
|
||||
```
|
||||
|
||||
For LAN setup do not override `compose.multi-bench-ssl.yaml`.
|
||||
@ -177,7 +176,7 @@ Create sites `one.example.com` and `two.example.com`:
|
||||
```shell
|
||||
# one.example.com
|
||||
docker compose --project-name erpnext-one exec backend \
|
||||
bench new-site --no-mariadb-socket --mariadb-root-password changeit --install-app erpnext --admin-password changeit one.example.com
|
||||
bench new-site one.example.com --mariadb-root-password changeit --install-app erpnext --admin-password changeit
|
||||
```
|
||||
|
||||
You can stop here and have a single bench single site setup complete. Continue to add one more site to the current bench.
|
||||
@ -185,7 +184,7 @@ You can stop here and have a single bench single site setup complete. Continue t
|
||||
```shell
|
||||
# two.example.com
|
||||
docker compose --project-name erpnext-one exec backend \
|
||||
bench new-site --no-mariadb-socket --mariadb-root-password changeit --install-app erpnext --admin-password changeit two.example.com
|
||||
bench new-site two.example.com --mariadb-root-password changeit --install-app erpnext --admin-password changeit
|
||||
```
|
||||
|
||||
#### Create second bench
|
||||
@ -218,9 +217,10 @@ Create a yaml file called `erpnext-two.yaml` in `~/gitops` directory:
|
||||
docker compose --project-name erpnext-two \
|
||||
--env-file ~/gitops/erpnext-two.env \
|
||||
-f compose.yaml \
|
||||
-f overrides/compose.erpnext.yaml \
|
||||
-f overrides/compose.redis.yaml \
|
||||
-f overrides/compose.multi-bench.yaml \
|
||||
-f overrides/compose.multi-bench-ssl.yaml config > ~/gitops/erpnext-two.yaml
|
||||
-f docs/compose/compose.multi-bench.yaml \
|
||||
-f docs/compose/compose.multi-bench-ssl.yaml config > ~/gitops/erpnext-two.yaml
|
||||
```
|
||||
|
||||
Use the above command after any changes are made to `erpnext-two.env` file to regenerate `~/gitops/erpnext-two.yaml`. e.g. after changing version to migrate the bench.
|
||||
@ -236,10 +236,10 @@ Create sites `three.example.com` and `four.example.com`:
|
||||
```shell
|
||||
# three.example.com
|
||||
docker compose --project-name erpnext-two exec backend \
|
||||
bench new-site --no-mariadb-socket --mariadb-root-password changeit --install-app erpnext --admin-password changeit three.example.com
|
||||
bench new-site three.example.com --mariadb-root-password changeit --install-app erpnext --admin-password changeit
|
||||
# four.example.com
|
||||
docker compose --project-name erpnext-two exec backend \
|
||||
bench new-site --no-mariadb-socket --mariadb-root-password changeit --install-app erpnext --admin-password changeit four.example.com
|
||||
bench new-site four.example.com --mariadb-root-password changeit --install-app erpnext --admin-password changeit
|
||||
```
|
||||
|
||||
#### Create custom domain to existing site
|
||||
@ -271,8 +271,8 @@ Generate yaml to reverse proxy:
|
||||
```shell
|
||||
docker compose --project-name custom-one-example \
|
||||
--env-file ~/gitops/custom-one-example.env \
|
||||
-f overrides/compose.custom-domain.yaml \
|
||||
-f overrides/compose.custom-domain-ssl.yaml config > ~/gitops/custom-one-example.yaml
|
||||
-f docs/compose/compose.custom-domain.yaml \
|
||||
-f docs/compose/compose.custom-domain-ssl.yaml config > ~/gitops/custom-one-example.yaml
|
||||
```
|
||||
|
||||
For LAN setup do not override `compose.custom-domain-ssl.yaml`.
|
||||
|
@ -9,7 +9,7 @@ Note:
|
||||
- Wait for the `db` service to start and `configurator` to exit before trying to create a new site. Usually this takes up to 10 seconds.
|
||||
|
||||
```sh
|
||||
docker-compose exec backend bench new-site --no-mariadb-socket --mariadb-root-password <db-password> --admin-password <admin-password> <site-name>
|
||||
docker-compose exec backend bench new-site <site-name> --mariadb-root-password <db-password> --admin-password <admin-password>
|
||||
```
|
||||
|
||||
If you need to install some app, specify `--install-app`. To see all options, just run `bench new-site --help`.
|
||||
@ -24,7 +24,7 @@ docker-compose exec backend bench set-config -g root_password <root-password>
|
||||
Also command is slightly different:
|
||||
|
||||
```sh
|
||||
docker-compose exec backend bench new-site --no-mariadb-socket --db-type postgres --admin-password <admin-password> <site-name>
|
||||
docker-compose exec backend bench new-site <site-name> --db-type postgres --admin-password <admin-password>
|
||||
```
|
||||
|
||||
## Push backup to S3 storage
|
||||
|
@ -1,4 +1,5 @@
|
||||
1. [Fixing MariaDB issues after rebuilding the container](#fixing-mariadb-issues-after-rebuilding-the-container)
|
||||
1. [Letsencrypt companion not working](#letsencrypt-companion-not-working)
|
||||
1. [docker-compose does not recognize variables from `.env` file](#docker-compose-does-not-recognize-variables-from-env-file)
|
||||
1. [Windows Based Installation](#windows-based-installation)
|
||||
|
||||
@ -36,20 +37,21 @@ EXIT;
|
||||
|
||||
Note: For MariaDB 10.4 and above use `mysql.global_priv` instead of `mysql.user`.
|
||||
|
||||
### Letsencrypt companion not working
|
||||
|
||||
- Nginx Letsencrypt Companion needs to be setup before starting ERPNext services.
|
||||
- Are domain names in `SITES` variable correct?
|
||||
- Is DNS record configured? `A Name` record needs to point to Public IP of server.
|
||||
- Try Restarting containers.
|
||||
|
||||
### docker-compose does not recognize variables from `.env` file
|
||||
|
||||
If you are using old version of `docker-compose` the .env file needs to be located in directory from where the docker-compose command is executed. There may also be difference in official `docker-compose` and the one packaged by distro. Use `--env-file=.env` if available to explicitly specify the path to file.
|
||||
If you are using old version of `docker-compose` the .env file needs to be located in directory from where the docker-compose command is executed. There may also be difference in official `docker-compose` and the one packaged by distro.
|
||||
|
||||
### Windows Based Installation
|
||||
|
||||
- Set environment variable `COMPOSE_CONVERT_WINDOWS_PATHS` e.g. `set COMPOSE_CONVERT_WINDOWS_PATHS=1`
|
||||
- While using docker machine, port-forward the ports of VM to ports of host machine. (ports 8080/8000/9000)
|
||||
- Make the `frappe-mariadb.cnf` read-only for mariadb container to pick it up.
|
||||
- While using docker machine, port-forward the port 80 of VM to port 80 of host machine
|
||||
- Name all the sites ending with `.localhost`. and access it via browser locally. e.g. `http://site1.localhost`
|
||||
|
||||
### Redo installation
|
||||
|
||||
- If you have made changes and just want to start over again (abandoning all changes), remove all docker
|
||||
- containers
|
||||
- images
|
||||
- volumes
|
||||
- Install a fresh
|
||||
- related issue comment https://github.com/frappe/frappe_docker/issues/448#issuecomment-851723912
|
||||
|
@ -1,363 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import fileinput
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
sites,
|
||||
db_pass: str,
|
||||
admin_pass: str,
|
||||
email: str,
|
||||
erpnext_version: str = None,
|
||||
) -> None:
|
||||
quoted_sites = ",".join([f"`{site}`" for site in sites]).strip(",")
|
||||
example_env = get_from_env(wd, "example.env")
|
||||
erpnext_version = erpnext_version or example_env["ERPNEXT_VERSION"]
|
||||
with open(os.path.join(wd, ".env"), "w") as f:
|
||||
f.writelines(
|
||||
[
|
||||
f"ERPNEXT_VERSION={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"SITE_ADMIN_PASS={admin_pass}\n",
|
||||
f"SITES={quoted_sites}\n",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
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, sites, email: str, version: str = None, image=None
|
||||
) -> None:
|
||||
if len(sites) == 0:
|
||||
sites = ["site1.localhost"]
|
||||
|
||||
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, sites, db_pass, admin_pass, email, version
|
||||
)
|
||||
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.https.yaml",
|
||||
"--env-file",
|
||||
".env",
|
||||
"config",
|
||||
],
|
||||
cwd=docker_repo_path,
|
||||
stdout=f,
|
||||
check=True,
|
||||
)
|
||||
|
||||
except Exception:
|
||||
logging.error("Docker Compose generation failed", exc_info=True)
|
||||
cprint("\nGenerating Compose File failed\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Use custom image
|
||||
if image:
|
||||
for line in fileinput.input(compose_file_name, inplace=True):
|
||||
if "image: frappe/erpnext" in line:
|
||||
line = line.replace("image: frappe/erpnext", f"image: {image}")
|
||||
sys.stdout.write(line)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
for sitename in sites:
|
||||
create_site(sitename, project, db_pass, admin_pass)
|
||||
|
||||
else:
|
||||
install_docker()
|
||||
clone_frappe_docker_repo()
|
||||
setup_prod(project, sites, email, version, image) # 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)
|
||||
|
||||
|
||||
def create_site(
|
||||
sitename: str,
|
||||
project: str,
|
||||
db_pass: str,
|
||||
admin_pass: str,
|
||||
):
|
||||
cprint(f"\nCreating site: {sitename} \n", level=3)
|
||||
|
||||
try:
|
||||
subprocess.run(
|
||||
[
|
||||
which("docker"),
|
||||
"compose",
|
||||
"-p",
|
||||
project,
|
||||
"exec",
|
||||
"backend",
|
||||
"bench",
|
||||
"new-site",
|
||||
sitename,
|
||||
"--no-mariadb-socket",
|
||||
"--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(f"Bench site creation failed for {sitename}", exc_info=True)
|
||||
cprint(f"Bench Site creation failed for {sitename}\n", e)
|
||||
|
||||
|
||||
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="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"
|
||||
)
|
||||
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.sites, args.email, args.version, args.image)
|
||||
else:
|
||||
parser.print_help()
|
13
example.env
13
example.env
@ -1,6 +1,9 @@
|
||||
# Reference: https://github.com/frappe/frappe_docker/blob/main/docs/images-and-compose-files.md
|
||||
|
||||
ERPNEXT_VERSION=v15.16.2
|
||||
FRAPPE_VERSION=v14.22.0
|
||||
|
||||
# Only with ERPNext override
|
||||
ERPNEXT_VERSION=v14.12.1
|
||||
|
||||
DB_PASSWORD=123
|
||||
|
||||
@ -11,6 +14,7 @@ DB_PORT=
|
||||
# Only if you use external Redis
|
||||
REDIS_CACHE=
|
||||
REDIS_QUEUE=
|
||||
REDIS_SOCKETIO=
|
||||
|
||||
# Only with HTTPS override
|
||||
LETSENCRYPT_EMAIL=mail@example.com
|
||||
@ -37,13 +41,8 @@ UPSTREAM_REAL_IP_RECURSIVE=
|
||||
|
||||
# All Values Allowed by nginx proxy_read_timeout are allowed, default value is 120s
|
||||
# Useful if you have longrunning print formats or slow loading sites
|
||||
PROXY_READ_TIMEOUT=
|
||||
PROXY_READ_TIMOUT=
|
||||
|
||||
# All Values allowed by nginx client_max_body_size are allowed, default value is 50m
|
||||
# Necessary if the upload limit in the frappe application is increased
|
||||
CLIENT_MAX_BODY_SIZE=
|
||||
|
||||
# List of sites for letsencrypt certificates quoted with backtick (`) and separated by comma (,)
|
||||
# More https://doc.traefik.io/traefik/routing/routers/#rule
|
||||
# About acme https://doc.traefik.io/traefik/https/acme/#domain-definition
|
||||
SITES=`erp.example.com`
|
||||
|
@ -1,9 +1,9 @@
|
||||
FROM debian:bookworm-slim as bench
|
||||
FROM debian:bullseye-slim as bench
|
||||
|
||||
LABEL author=frappé
|
||||
|
||||
ARG GIT_REPO=https://github.com/frappe/bench.git
|
||||
ARG GIT_BRANCH=v5.x
|
||||
ARG GIT_BRANCH=develop
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
|
||||
@ -18,11 +18,6 @@ RUN apt-get update \
|
||||
fonts-cantarell \
|
||||
xfonts-75dpi \
|
||||
xfonts-base \
|
||||
# weasyprint dependencies
|
||||
libpango-1.0-0 \
|
||||
libharfbuzz0b \
|
||||
libpangoft2-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
# to work inside the container
|
||||
locales \
|
||||
build-essential \
|
||||
@ -69,18 +64,16 @@ RUN apt-get update \
|
||||
xz-utils \
|
||||
tk-dev \
|
||||
liblzma-dev \
|
||||
file \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \
|
||||
&& dpkg-reconfigure --frontend=noninteractive locales
|
||||
|
||||
# Detect arch and install wkhtmltopdf
|
||||
ARG WKHTMLTOPDF_VERSION=0.12.6.1-3
|
||||
ARG WKHTMLTOPDF_DISTRO=bookworm
|
||||
ENV WKHTMLTOPDF_VERSION 0.12.6-1
|
||||
RUN if [ "$(uname -m)" = "aarch64" ]; then export ARCH=arm64; fi \
|
||||
&& if [ "$(uname -m)" = "x86_64" ]; then export ARCH=amd64; fi \
|
||||
&& downloaded_file=wkhtmltox_${WKHTMLTOPDF_VERSION}.${WKHTMLTOPDF_DISTRO}_${ARCH}.deb \
|
||||
&& downloaded_file=wkhtmltox_$WKHTMLTOPDF_VERSION.buster_${ARCH}.deb \
|
||||
&& wget -q https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \
|
||||
&& dpkg -i $downloaded_file \
|
||||
&& rm $downloaded_file
|
||||
@ -96,18 +89,18 @@ USER frappe
|
||||
WORKDIR /home/frappe
|
||||
|
||||
# Install Python via pyenv
|
||||
ENV PYTHON_VERSION_V14=3.10.13
|
||||
ENV PYTHON_VERSION=3.11.6
|
||||
ENV PYTHON_VERSION_V13=3.9.9
|
||||
ENV PYTHON_VERSION=3.10.5
|
||||
ENV PYENV_ROOT /home/frappe/.pyenv
|
||||
ENV PATH $PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH
|
||||
|
||||
# From https://github.com/pyenv/pyenv#basic-github-checkout
|
||||
RUN git clone --depth 1 https://github.com/pyenv/pyenv.git .pyenv \
|
||||
&& pyenv install $PYTHON_VERSION_V14 \
|
||||
&& pyenv install $PYTHON_VERSION_V13 \
|
||||
&& pyenv install $PYTHON_VERSION \
|
||||
&& PYENV_VERSION=$PYTHON_VERSION_V14 pip install --no-cache-dir virtualenv \
|
||||
&& PYENV_VERSION=$PYTHON_VERSION_V13 pip install --no-cache-dir virtualenv \
|
||||
&& PYENV_VERSION=$PYTHON_VERSION pip install --no-cache-dir virtualenv \
|
||||
&& pyenv global $PYTHON_VERSION $PYTHON_VERSION_v14 \
|
||||
&& pyenv global $PYTHON_VERSION $PYTHON_VERSION_v13 \
|
||||
&& sed -Ei -e '/^([^#]|$)/ {a export PYENV_ROOT="/home/frappe/.pyenv" a export PATH="$PYENV_ROOT/bin:$PATH" a ' -e ':a' -e '$!{n;ba};}' ~/.profile \
|
||||
&& echo 'eval "$(pyenv init --path)"' >>~/.profile \
|
||||
&& echo 'eval "$(pyenv init -)"' >>~/.bashrc
|
||||
@ -123,12 +116,12 @@ RUN git clone ${GIT_REPO} --depth 1 -b ${GIT_BRANCH} .bench \
|
||||
&& echo "export BENCH_DEVELOPER=1" >>/home/frappe/.bashrc
|
||||
|
||||
# Install Node via nvm
|
||||
ENV NODE_VERSION_14=16.20.2
|
||||
ENV NODE_VERSION=18.18.2
|
||||
ENV NODE_VERSION_14=14.19.3
|
||||
ENV NODE_VERSION=16.18.0
|
||||
ENV NVM_DIR /home/frappe/.nvm
|
||||
ENV PATH ${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}
|
||||
|
||||
RUN wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \
|
||||
RUN wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash \
|
||||
&& . ${NVM_DIR}/nvm.sh \
|
||||
&& nvm install ${NODE_VERSION_14} \
|
||||
&& nvm use v${NODE_VERSION_14} \
|
||||
|
@ -1,149 +0,0 @@
|
||||
ARG PYTHON_VERSION=3.11.6
|
||||
ARG DEBIAN_BASE=bookworm
|
||||
FROM python:${PYTHON_VERSION}-slim-${DEBIAN_BASE} AS base
|
||||
|
||||
COPY resources/nginx-template.conf /templates/nginx/frappe.conf.template
|
||||
COPY resources/nginx-entrypoint.sh /usr/local/bin/nginx-entrypoint.sh
|
||||
|
||||
ARG WKHTMLTOPDF_VERSION=0.12.6.1-3
|
||||
ARG WKHTMLTOPDF_DISTRO=bookworm
|
||||
ARG NODE_VERSION=18.18.2
|
||||
ENV NVM_DIR=/home/frappe/.nvm
|
||||
ENV PATH ${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}
|
||||
|
||||
RUN useradd -ms /bin/bash frappe \
|
||||
&& apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
curl \
|
||||
git \
|
||||
vim \
|
||||
nginx \
|
||||
gettext-base \
|
||||
# weasyprint dependencies
|
||||
libpango-1.0-0 \
|
||||
libharfbuzz0b \
|
||||
libpangoft2-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
# For backups
|
||||
restic \
|
||||
gpg \
|
||||
# MariaDB
|
||||
mariadb-client \
|
||||
less \
|
||||
# Postgres
|
||||
libpq-dev \
|
||||
postgresql-client \
|
||||
# For healthcheck
|
||||
wait-for-it \
|
||||
jq \
|
||||
# NodeJS
|
||||
&& mkdir -p ${NVM_DIR} \
|
||||
&& curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \
|
||||
&& . ${NVM_DIR}/nvm.sh \
|
||||
&& nvm install ${NODE_VERSION} \
|
||||
&& nvm use v${NODE_VERSION} \
|
||||
&& npm install -g yarn \
|
||||
&& nvm alias default v${NODE_VERSION} \
|
||||
&& rm -rf ${NVM_DIR}/.cache \
|
||||
&& echo 'export NVM_DIR="/home/frappe/.nvm"' >>/home/frappe/.bashrc \
|
||||
&& echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >>/home/frappe/.bashrc \
|
||||
&& echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >>/home/frappe/.bashrc \
|
||||
# Install wkhtmltopdf with patched qt
|
||||
&& if [ "$(uname -m)" = "aarch64" ]; then export ARCH=arm64; fi \
|
||||
&& if [ "$(uname -m)" = "x86_64" ]; then export ARCH=amd64; fi \
|
||||
&& downloaded_file=wkhtmltox_${WKHTMLTOPDF_VERSION}.${WKHTMLTOPDF_DISTRO}_${ARCH}.deb \
|
||||
&& curl -sLO https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \
|
||||
&& apt-get install -y ./$downloaded_file \
|
||||
&& rm $downloaded_file \
|
||||
# Clean up
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rm -fr /etc/nginx/sites-enabled/default \
|
||||
&& pip3 install frappe-bench \
|
||||
# Fixes for non-root nginx and logs to stdout
|
||||
&& sed -i '/user www-data/d' /etc/nginx/nginx.conf \
|
||||
&& ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log \
|
||||
&& touch /run/nginx.pid \
|
||||
&& chown -R frappe:frappe /etc/nginx/conf.d \
|
||||
&& chown -R frappe:frappe /etc/nginx/nginx.conf \
|
||||
&& chown -R frappe:frappe /var/log/nginx \
|
||||
&& chown -R frappe:frappe /var/lib/nginx \
|
||||
&& chown -R frappe:frappe /run/nginx.pid \
|
||||
&& chmod 755 /usr/local/bin/nginx-entrypoint.sh \
|
||||
&& chmod 644 /templates/nginx/frappe.conf.template
|
||||
|
||||
FROM base AS builder
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
|
||||
# For frappe framework
|
||||
wget \
|
||||
# For psycopg2
|
||||
libpq-dev \
|
||||
# Other
|
||||
libffi-dev \
|
||||
liblcms2-dev \
|
||||
libldap2-dev \
|
||||
libmariadb-dev \
|
||||
libsasl2-dev \
|
||||
libtiff5-dev \
|
||||
libwebp-dev \
|
||||
redis-tools \
|
||||
rlwrap \
|
||||
tk8.6-dev \
|
||||
cron \
|
||||
# For pandas
|
||||
gcc \
|
||||
build-essential \
|
||||
libbz2-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# apps.json includes
|
||||
ARG APPS_JSON_BASE64
|
||||
RUN if [ -n "${APPS_JSON_BASE64}" ]; then \
|
||||
mkdir /opt/frappe && echo "${APPS_JSON_BASE64}" | base64 -d > /opt/frappe/apps.json; \
|
||||
fi
|
||||
|
||||
USER frappe
|
||||
|
||||
ARG FRAPPE_BRANCH=version-15
|
||||
ARG FRAPPE_PATH=https://github.com/frappe/frappe
|
||||
RUN export APP_INSTALL_ARGS="" && \
|
||||
if [ -n "${APPS_JSON_BASE64}" ]; then \
|
||||
export APP_INSTALL_ARGS="--apps_path=/opt/frappe/apps.json"; \
|
||||
fi && \
|
||||
bench init ${APP_INSTALL_ARGS}\
|
||||
--frappe-branch=${FRAPPE_BRANCH} \
|
||||
--frappe-path=${FRAPPE_PATH} \
|
||||
--no-procfile \
|
||||
--no-backups \
|
||||
--skip-redis-config-generation \
|
||||
--verbose \
|
||||
/home/frappe/frappe-bench && \
|
||||
cd /home/frappe/frappe-bench && \
|
||||
echo "{}" > sites/common_site_config.json && \
|
||||
find apps -mindepth 1 -path "*/.git" | xargs rm -fr
|
||||
|
||||
FROM base as backend
|
||||
|
||||
USER frappe
|
||||
|
||||
COPY --from=builder --chown=frappe:frappe /home/frappe/frappe-bench /home/frappe/frappe-bench
|
||||
|
||||
WORKDIR /home/frappe/frappe-bench
|
||||
|
||||
# Move the generated assets folder out of the "sites" folder, which will be attached to a volume and thus persisted
|
||||
# So that it can be referenced by symlink even after "sites" is replaced with the volume
|
||||
RUN mv /home/frappe/frappe-bench/sites/assets /home/frappe/frappe-bench/assets
|
||||
|
||||
CMD [ \
|
||||
"/home/frappe/frappe-bench/env/bin/gunicorn", \
|
||||
"--chdir=/home/frappe/frappe-bench/sites", \
|
||||
"--bind=0.0.0.0:8000", \
|
||||
"--threads=4", \
|
||||
"--workers=2", \
|
||||
"--worker-class=gthread", \
|
||||
"--worker-tmp-dir=/dev/shm", \
|
||||
"--timeout=120", \
|
||||
"--preload", \
|
||||
"frappe.app:application" \
|
||||
]
|
62
images/nginx/Dockerfile
Normal file
62
images/nginx/Dockerfile
Normal file
@ -0,0 +1,62 @@
|
||||
FROM frappe/bench:latest as assets_builder
|
||||
|
||||
ARG FRAPPE_VERSION
|
||||
ARG FRAPPE_REPO=https://github.com/frappe/frappe
|
||||
ARG PYTHON_VERSION
|
||||
ARG NODE_VERSION
|
||||
ENV NVM_DIR=/home/frappe/.nvm
|
||||
ENV PATH ${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}
|
||||
RUN PYENV_VERSION=${PYTHON_VERSION} bench init --version=${FRAPPE_VERSION} --frappe-path=${FRAPPE_REPO} --skip-redis-config-generation --verbose --skip-assets /home/frappe/frappe-bench
|
||||
|
||||
WORKDIR /home/frappe/frappe-bench
|
||||
|
||||
|
||||
FROM assets_builder as frappe_assets
|
||||
|
||||
RUN bench setup requirements \
|
||||
&& if [ -z "${FRAPPE_VERSION##*v14*}" ] || [ "$FRAPPE_VERSION" = "develop" ]; then \
|
||||
export BUILD_OPTS="--production";\
|
||||
fi \
|
||||
&& FRAPPE_ENV=production bench build --verbose --hard-link ${BUILD_OPTS}
|
||||
|
||||
|
||||
FROM assets_builder as erpnext_assets
|
||||
|
||||
ARG ERPNEXT_VERSION
|
||||
ARG ERPNEXT_REPO=https://github.com/frappe/erpnext
|
||||
RUN bench get-app --branch=${ERPNEXT_VERSION} --skip-assets --resolve-deps erpnext ${ERPNEXT_REPO}\
|
||||
&& if [ -z "${ERPNEXT_VERSION##*v14*}" ] || [ "$ERPNEXT_VERSION" = "develop" ]; then \
|
||||
export BUILD_OPTS="--production"; \
|
||||
fi \
|
||||
&& FRAPPE_ENV=production bench build --verbose --hard-link ${BUILD_OPTS}
|
||||
|
||||
|
||||
FROM alpine/git as bench
|
||||
|
||||
# Error pages
|
||||
ARG BENCH_REPO=https://github.com/frappe/bench
|
||||
RUN git clone --depth 1 ${BENCH_REPO} /tmp/bench \
|
||||
&& mkdir /out \
|
||||
&& mv /tmp/bench/bench/config/templates/502.html /out \
|
||||
&& touch /out/.build
|
||||
|
||||
FROM nginxinc/nginx-unprivileged:1.23.3-alpine as frappe
|
||||
|
||||
# Set default ENV variables for backwards compatibility
|
||||
ENV PROXY_READ_TIMOUT=120
|
||||
ENV CLIENT_MAX_BODY_SIZE=50m
|
||||
|
||||
# https://github.com/nginxinc/docker-nginx-unprivileged/blob/main/stable/alpine/20-envsubst-on-templates.sh
|
||||
COPY nginx-template.conf /etc/nginx/templates/default.conf.template
|
||||
# https://github.com/nginxinc/docker-nginx-unprivileged/blob/main/stable/alpine/docker-entrypoint.sh
|
||||
COPY entrypoint.sh /docker-entrypoint.d/frappe-entrypoint.sh
|
||||
|
||||
COPY --from=bench /out /usr/share/nginx/html/
|
||||
COPY --from=frappe_assets /home/frappe/frappe-bench/sites/assets /usr/share/nginx/html/assets
|
||||
|
||||
USER 1000
|
||||
|
||||
|
||||
FROM frappe as erpnext
|
||||
|
||||
COPY --from=erpnext_assets /home/frappe/frappe-bench/sites/assets /usr/share/nginx/html/assets
|
9
images/nginx/entrypoint.sh
Executable file
9
images/nginx/entrypoint.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# Update timestamp for ".build" file to enable caching assets:
|
||||
# https://github.com/frappe/frappe/blob/52d8e6d952130eea64a9990b9fd5b1f6877be1b7/frappe/utils/__init__.py#L799-L805
|
||||
if [ -d /usr/share/nginx/html/sites ]; then
|
||||
touch /usr/share/nginx/html/sites/.build -r /usr/share/nginx/html/.build
|
||||
fi
|
@ -6,10 +6,16 @@ upstream socketio-server {
|
||||
server ${SOCKETIO} fail_timeout=0;
|
||||
}
|
||||
|
||||
# Parse the X-Forwarded-Proto header - if set - defaulting to $scheme.
|
||||
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
|
||||
default $scheme;
|
||||
https https;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
server_name ${FRAPPE_SITE_NAME_HEADER};
|
||||
root /home/frappe/frappe-bench/sites;
|
||||
server_name $http_host;
|
||||
root /usr/share/nginx/html;
|
||||
|
||||
proxy_buffer_size 128k;
|
||||
proxy_buffers 4 256k;
|
||||
@ -31,46 +37,53 @@ server {
|
||||
|
||||
location ~ ^/protected/(.*) {
|
||||
internal;
|
||||
try_files /${FRAPPE_SITE_NAME_HEADER}/$1 =404;
|
||||
try_files /sites/${FRAPPE_SITE_NAME_HEADER}/$1 =404;
|
||||
}
|
||||
|
||||
location /socket.io {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header X-Frappe-Site-Name ${FRAPPE_SITE_NAME_HEADER};
|
||||
proxy_set_header Origin $scheme://${FRAPPE_SITE_NAME_HEADER};
|
||||
proxy_set_header Origin $scheme://$http_host;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
proxy_pass http://socketio-server;
|
||||
}
|
||||
|
||||
location / {
|
||||
rewrite ^(.+)/$ $1 permanent;
|
||||
rewrite ^(.+)/index\.html$ $1 permanent;
|
||||
rewrite ^(.+)\.html$ $1 permanent;
|
||||
rewrite ^(.+)/$ $proxy_x_forwarded_proto://$http_host$1 permanent;
|
||||
rewrite ^(.+)/index\.html$ $proxy_x_forwarded_proto://$http_host$1 permanent;
|
||||
rewrite ^(.+)\.html$ $proxy_x_forwarded_proto://$http_host$1 permanent;
|
||||
|
||||
location ~ ^/files/.*.(htm|html|svg|xml) {
|
||||
add_header Content-disposition "attachment";
|
||||
try_files /${FRAPPE_SITE_NAME_HEADER}/public/$uri @webserver;
|
||||
try_files /sites/${FRAPPE_SITE_NAME_HEADER}/public/$uri @webserver;
|
||||
}
|
||||
|
||||
try_files /${FRAPPE_SITE_NAME_HEADER}/public/$uri @webserver;
|
||||
try_files /sites/${FRAPPE_SITE_NAME_HEADER}/public/$uri @webserver;
|
||||
}
|
||||
|
||||
location @webserver {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||
proxy_set_header X-Frappe-Site-Name ${FRAPPE_SITE_NAME_HEADER};
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Use-X-Accel-Redirect True;
|
||||
proxy_read_timeout ${PROXY_READ_TIMEOUT};
|
||||
proxy_read_timeout ${PROXY_READ_TIMOUT};
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_pass http://backend-server;
|
||||
}
|
||||
|
||||
# error pages
|
||||
error_page 502 /502.html;
|
||||
location /502.html {
|
||||
internal;
|
||||
}
|
||||
|
||||
# optimizations
|
||||
sendfile on;
|
||||
keepalive_timeout 15;
|
@ -1,150 +0,0 @@
|
||||
ARG PYTHON_VERSION=3.11.6
|
||||
ARG DEBIAN_BASE=bookworm
|
||||
FROM python:${PYTHON_VERSION}-slim-${DEBIAN_BASE} AS base
|
||||
|
||||
ARG WKHTMLTOPDF_VERSION=0.12.6.1-3
|
||||
ARG WKHTMLTOPDF_DISTRO=bookworm
|
||||
ARG NODE_VERSION=18.18.2
|
||||
ENV NVM_DIR=/home/frappe/.nvm
|
||||
ENV PATH ${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}
|
||||
|
||||
RUN useradd -ms /bin/bash frappe \
|
||||
&& apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
curl \
|
||||
git \
|
||||
vim \
|
||||
nginx \
|
||||
gettext-base \
|
||||
# weasyprint dependencies
|
||||
libpango-1.0-0 \
|
||||
libharfbuzz0b \
|
||||
libpangoft2-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
# For backups
|
||||
restic \
|
||||
gpg \
|
||||
# MariaDB
|
||||
mariadb-client \
|
||||
less \
|
||||
# Postgres
|
||||
libpq-dev \
|
||||
postgresql-client \
|
||||
# For healthcheck
|
||||
wait-for-it \
|
||||
jq \
|
||||
# NodeJS
|
||||
&& mkdir -p ${NVM_DIR} \
|
||||
&& curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \
|
||||
&& . ${NVM_DIR}/nvm.sh \
|
||||
&& nvm install ${NODE_VERSION} \
|
||||
&& nvm use v${NODE_VERSION} \
|
||||
&& npm install -g yarn \
|
||||
&& nvm alias default v${NODE_VERSION} \
|
||||
&& rm -rf ${NVM_DIR}/.cache \
|
||||
&& echo 'export NVM_DIR="/home/frappe/.nvm"' >>/home/frappe/.bashrc \
|
||||
&& echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >>/home/frappe/.bashrc \
|
||||
&& echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >>/home/frappe/.bashrc \
|
||||
# Install wkhtmltopdf with patched qt
|
||||
&& if [ "$(uname -m)" = "aarch64" ]; then export ARCH=arm64; fi \
|
||||
&& if [ "$(uname -m)" = "x86_64" ]; then export ARCH=amd64; fi \
|
||||
&& downloaded_file=wkhtmltox_${WKHTMLTOPDF_VERSION}.${WKHTMLTOPDF_DISTRO}_${ARCH}.deb \
|
||||
&& curl -sLO https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \
|
||||
&& apt-get install -y ./$downloaded_file \
|
||||
&& rm $downloaded_file \
|
||||
# Clean up
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rm -fr /etc/nginx/sites-enabled/default \
|
||||
&& pip3 install frappe-bench \
|
||||
# Fixes for non-root nginx and logs to stdout
|
||||
&& sed -i '/user www-data/d' /etc/nginx/nginx.conf \
|
||||
&& ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log \
|
||||
&& touch /run/nginx.pid \
|
||||
&& chown -R frappe:frappe /etc/nginx/conf.d \
|
||||
&& chown -R frappe:frappe /etc/nginx/nginx.conf \
|
||||
&& chown -R frappe:frappe /var/log/nginx \
|
||||
&& chown -R frappe:frappe /var/lib/nginx \
|
||||
&& chown -R frappe:frappe /run/nginx.pid
|
||||
|
||||
COPY resources/nginx-template.conf /templates/nginx/frappe.conf.template
|
||||
COPY resources/nginx-entrypoint.sh /usr/local/bin/nginx-entrypoint.sh
|
||||
|
||||
FROM base AS builder
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
|
||||
# For frappe framework
|
||||
wget \
|
||||
# For psycopg2
|
||||
libpq-dev \
|
||||
# Other
|
||||
libffi-dev \
|
||||
liblcms2-dev \
|
||||
libldap2-dev \
|
||||
libmariadb-dev \
|
||||
libsasl2-dev \
|
||||
libtiff5-dev \
|
||||
libwebp-dev \
|
||||
redis-tools \
|
||||
rlwrap \
|
||||
tk8.6-dev \
|
||||
cron \
|
||||
# For pandas
|
||||
gcc \
|
||||
build-essential \
|
||||
libbz2-dev \
|
||||
# for erpnext repo
|
||||
openssh-ssh \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
USER frappe
|
||||
|
||||
ARG SSH_PRIVATE_KEY
|
||||
RUN mkdir ~/.ssh/
|
||||
RUN echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_ed25519
|
||||
|
||||
ARG FRAPPE_BRANCH=version-15
|
||||
ARG FRAPPE_PATH=https://github.com/frappe/frappe
|
||||
ARG ERPNEXT_REPO=ssh://git@githaven.org:2222/Shiloh/brotherton-erpnext.git
|
||||
ARG ERPNEXT_BRANCH=version-15
|
||||
RUN bench init \
|
||||
--frappe-branch=${FRAPPE_BRANCH} \
|
||||
--frappe-path=${FRAPPE_PATH} \
|
||||
--no-procfile \
|
||||
--no-backups \
|
||||
--skip-redis-config-generation \
|
||||
--verbose \
|
||||
/home/frappe/frappe-bench && \
|
||||
cd /home/frappe/frappe-bench && \
|
||||
bench get-app --branch=${ERPNEXT_BRANCH} --resolve-deps erpnext ${ERPNEXT_REPO} && \
|
||||
echo "{}" > sites/common_site_config.json && \
|
||||
find apps -mindepth 1 -path "*/.git" | xargs rm -fr
|
||||
|
||||
FROM base as erpnext
|
||||
|
||||
USER frappe
|
||||
|
||||
RUN mkdir testFolder
|
||||
|
||||
COPY --from=builder --chown=frappe:frappe /home/frappe/frappe-bench /home/frappe/frappe-bench
|
||||
|
||||
WORKDIR /home/frappe/frappe-bench
|
||||
|
||||
VOLUME [ \
|
||||
"/home/frappe/frappe-bench/sites", \
|
||||
"/home/frappe/frappe-bench/sites/assets", \
|
||||
"/home/frappe/frappe-bench/logs" \
|
||||
]
|
||||
|
||||
CMD [ \
|
||||
"/home/frappe/frappe-bench/env/bin/gunicorn", \
|
||||
"--chdir=/home/frappe/frappe-bench/sites", \
|
||||
"--bind=0.0.0.0:8000", \
|
||||
"--threads=4", \
|
||||
"--workers=2", \
|
||||
"--worker-class=gthread", \
|
||||
"--worker-tmp-dir=/dev/shm", \
|
||||
"--timeout=120", \
|
||||
"--preload", \
|
||||
"frappe.app:application" \
|
||||
]
|
34
images/socketio/Dockerfile
Normal file
34
images/socketio/Dockerfile
Normal file
@ -0,0 +1,34 @@
|
||||
FROM alpine/git as builder
|
||||
|
||||
ARG FRAPPE_VERSION
|
||||
ARG FRAPPE_REPO=https://github.com/frappe/frappe
|
||||
RUN apk add -U jq
|
||||
RUN git clone --depth 1 -b ${FRAPPE_VERSION} ${FRAPPE_REPO} /opt/frappe
|
||||
RUN jq --argjson dependencies "$(jq '.dependencies | INDEX( "express", "redis", "socket.io", "superagent" ) as $keep | \
|
||||
del( \
|
||||
. | objects | \
|
||||
.[ \
|
||||
keys_unsorted[] | \
|
||||
select( $keep[ . ] | not ) \
|
||||
] \
|
||||
)' /opt/frappe/package.json)" '.dependencies = $dependencies | del(.scripts.prepare)' /opt/frappe/package.json > /opt/frappe/dependencies.json && \
|
||||
mv /opt/frappe/dependencies.json /opt/frappe/package.json
|
||||
|
||||
# NodeJS LTS
|
||||
FROM node:18-alpine
|
||||
|
||||
RUN addgroup -S frappe \
|
||||
&& adduser -S frappe -G frappe
|
||||
USER frappe
|
||||
|
||||
WORKDIR /home/frappe/frappe-bench
|
||||
RUN mkdir -p sites apps/frappe
|
||||
|
||||
COPY --chown=frappe:frappe --from=builder /opt/frappe/package.json /opt/frappe/socketio.js /opt/frappe/node_utils.js apps/frappe/
|
||||
|
||||
RUN cd apps/frappe \
|
||||
&& npm install --omit=dev
|
||||
|
||||
WORKDIR /home/frappe/frappe-bench/sites
|
||||
|
||||
CMD [ "node", "/home/frappe/frappe-bench/apps/frappe/socketio.js" ]
|
146
images/worker/Dockerfile
Normal file
146
images/worker/Dockerfile
Normal file
@ -0,0 +1,146 @@
|
||||
# syntax=docker/dockerfile:1.3
|
||||
|
||||
ARG PYTHON_VERSION
|
||||
FROM python:${PYTHON_VERSION}-slim-bullseye as base
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
# Postgres
|
||||
libpq-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN useradd -ms /bin/bash frappe
|
||||
USER frappe
|
||||
RUN mkdir -p /home/frappe/frappe-bench/apps /home/frappe/frappe-bench/logs /home/frappe/frappe-bench/sites /home/frappe/frappe-bench/config
|
||||
WORKDIR /home/frappe/frappe-bench
|
||||
|
||||
USER root
|
||||
RUN pip install --no-cache-dir -U pip wheel \
|
||||
&& python -m venv env \
|
||||
&& env/bin/pip install --no-cache-dir -U pip wheel
|
||||
|
||||
COPY install-app.sh /usr/local/bin/install-app
|
||||
|
||||
|
||||
FROM base as build_deps
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
# Install git here because it is not required in production
|
||||
git \
|
||||
# gcc and g++ are required for building different packages across different versions
|
||||
# of Frappe and ERPNext and also on different platforms (for example, linux/arm64).
|
||||
# It is safe to install build deps even if they are not required
|
||||
# because they won't be included in final images.
|
||||
gcc \
|
||||
g++ \
|
||||
# Make is required to build wheels of ERPNext deps in develop branch for linux/arm64
|
||||
make \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
FROM build_deps as frappe_builder
|
||||
|
||||
ARG FRAPPE_VERSION
|
||||
ARG FRAPPE_REPO=https://github.com/frappe/frappe
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
git clone --depth 1 -b ${FRAPPE_VERSION} ${FRAPPE_REPO} apps/frappe \
|
||||
&& install-app frappe \
|
||||
&& env/bin/pip install -U gevent \
|
||||
# Link Frappe's node_modules/ to make Website Theme work
|
||||
&& mkdir -p /home/frappe/frappe-bench/sites/assets/frappe/node_modules \
|
||||
&& ln -s /home/frappe/frappe-bench/sites/assets/frappe/node_modules /home/frappe/frappe-bench/apps/frappe/node_modules
|
||||
|
||||
|
||||
FROM frappe_builder as erpnext_builder
|
||||
|
||||
ARG PAYMENTS_VERSION=develop
|
||||
ARG PAYMENTS_REPO=https://github.com/frappe/payments
|
||||
ARG ERPNEXT_VERSION
|
||||
ARG ERPNEXT_REPO=https://github.com/frappe/erpnext
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
if [ -z "${ERPNEXT_VERSION##*v14*}" ] || [ "$ERPNEXT_VERSION" = "develop" ]; then \
|
||||
git clone --depth 1 -b ${PAYMENTS_VERSION} ${PAYMENTS_REPO} apps/payments && install-app payments; \
|
||||
fi \
|
||||
&& git clone --depth 1 -b ${ERPNEXT_VERSION} ${ERPNEXT_REPO} apps/erpnext \
|
||||
&& install-app erpnext
|
||||
|
||||
FROM base as configured_base
|
||||
|
||||
ARG WKHTMLTOPDF_VERSION=0.12.6-1
|
||||
ARG NODE_VERSION
|
||||
ENV NVM_DIR=/home/frappe/.nvm
|
||||
ENV PATH ${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}
|
||||
RUN apt-get update \
|
||||
# Setup Node lists
|
||||
&& apt-get install --no-install-recommends -y curl git \
|
||||
# NodeJS with NVM
|
||||
&& mkdir -p ${NVM_DIR} \
|
||||
&& curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash \
|
||||
&& . ${NVM_DIR}/nvm.sh \
|
||||
&& nvm install ${NODE_VERSION} \
|
||||
&& nvm use v${NODE_VERSION} \
|
||||
&& npm install -g yarn \
|
||||
&& nvm alias default v${NODE_VERSION} \
|
||||
&& rm -rf ${NVM_DIR}/.cache \
|
||||
&& echo 'export NVM_DIR="/home/frappe/.nvm"' >>~/.bashrc \
|
||||
&& echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >> ~/.bashrc \
|
||||
&& echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >> ~/.bashrc \
|
||||
# Install wkhtmltopdf with patched qt
|
||||
&& if [ "$(uname -m)" = "aarch64" ]; then export ARCH=arm64; fi \
|
||||
&& if [ "$(uname -m)" = "x86_64" ]; then export ARCH=amd64; fi \
|
||||
&& downloaded_file=wkhtmltox_$WKHTMLTOPDF_VERSION.buster_${ARCH}.deb \
|
||||
&& curl -sLO https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \
|
||||
&& apt-get install -y ./$downloaded_file \
|
||||
&& rm $downloaded_file \
|
||||
# Cleanup
|
||||
&& apt-get purge -y --auto-remove curl \
|
||||
&& apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
# MariaDB
|
||||
mariadb-client \
|
||||
# Postgres
|
||||
postgresql-client \
|
||||
# For healthcheck
|
||||
wait-for-it \
|
||||
jq \
|
||||
# Clean up
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY pretend-bench.sh /usr/local/bin/bench
|
||||
COPY push_backup.py /usr/local/bin/push-backup
|
||||
COPY configure.py patched_bench_helper.py /usr/local/bin/
|
||||
COPY gevent_patch.py /opt/patches/
|
||||
|
||||
WORKDIR /home/frappe/frappe-bench/sites
|
||||
|
||||
CMD [ "/home/frappe/frappe-bench/env/bin/gunicorn", \
|
||||
"--bind=0.0.0.0:8000", \
|
||||
"--threads=4", \
|
||||
"--workers=2", \
|
||||
"--worker-class=gthread", \
|
||||
"--worker-tmp-dir=/dev/shm", \
|
||||
"--timeout=120", \
|
||||
"--preload", \
|
||||
"frappe.app:application" \
|
||||
]
|
||||
|
||||
|
||||
FROM configured_base as frappe
|
||||
|
||||
COPY --from=frappe_builder /home/frappe/frappe-bench/apps/frappe /home/frappe/frappe-bench/apps/frappe
|
||||
COPY --from=frappe_builder /home/frappe/frappe-bench/env /home/frappe/frappe-bench/env
|
||||
COPY --from=frappe_builder /home/frappe/frappe-bench/sites/apps.txt /home/frappe/frappe-bench/sites/
|
||||
|
||||
USER frappe
|
||||
|
||||
|
||||
# Split frappe and erpnext to reduce image size (because of frappe-bench/env/ directory)
|
||||
FROM configured_base as erpnext
|
||||
|
||||
COPY --from=erpnext_builder --chown=frappe:frappe /home/frappe/frappe-bench/apps /home/frappe/frappe-bench/apps
|
||||
COPY --from=erpnext_builder --chown=frappe:frappe /home/frappe/frappe-bench/env /home/frappe/frappe-bench/env
|
||||
COPY --from=erpnext_builder --chown=frappe:frappe /home/frappe/frappe-bench/sites/apps.txt /home/frappe/frappe-bench/sites/
|
||||
|
||||
|
||||
USER frappe
|
56
images/worker/configure.py
Executable file
56
images/worker/configure.py
Executable file
@ -0,0 +1,56 @@
|
||||
#!/usr/local/bin/python
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Any, TypeVar
|
||||
|
||||
|
||||
def update_config(**values: Any):
|
||||
fname = "common_site_config.json"
|
||||
if not os.path.exists(fname):
|
||||
with open(fname, "a") as f:
|
||||
json.dump({}, f)
|
||||
|
||||
with open(fname, "r+") as f:
|
||||
config: dict[str, Any] = json.load(f)
|
||||
config.update(values)
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
json.dump(config, f)
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
def env(name: str, type_: type[_T] = str) -> _T:
|
||||
value = os.getenv(name)
|
||||
if not value:
|
||||
raise RuntimeError(f'Required environment variable "{name}" not set')
|
||||
try:
|
||||
value = type_(value)
|
||||
except Exception:
|
||||
raise RuntimeError(
|
||||
f'Cannot convert environment variable "{name}" to type "{type_}"'
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def generate_redis_url(url: str):
|
||||
return f"redis://{url}"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
update_config(
|
||||
db_host=env("DB_HOST"),
|
||||
db_port=env("DB_PORT", int),
|
||||
redis_cache=generate_redis_url(env("REDIS_CACHE")),
|
||||
redis_queue=generate_redis_url(env("REDIS_QUEUE")),
|
||||
redis_socketio=generate_redis_url(env("REDIS_SOCKETIO")),
|
||||
socketio_port=env("SOCKETIO_PORT", int),
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
3
images/worker/gevent_patch.py
Normal file
3
images/worker/gevent_patch.py
Normal file
@ -0,0 +1,3 @@
|
||||
import gevent.monkey
|
||||
|
||||
gevent.monkey.patch_all()
|
11
images/worker/install-app.sh
Executable file
11
images/worker/install-app.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
set -x
|
||||
|
||||
APP=$1
|
||||
|
||||
cd /home/frappe/frappe-bench
|
||||
|
||||
env/bin/pip install -e "apps/$APP"
|
||||
|
||||
echo "$APP" >>sites/apps.txt
|
48
images/worker/patched_bench_helper.py
Normal file
48
images/worker/patched_bench_helper.py
Normal file
@ -0,0 +1,48 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import click
|
||||
import click.exceptions
|
||||
import frappe.app
|
||||
import frappe.database.db_manager
|
||||
import frappe.utils.bench_helper
|
||||
|
||||
|
||||
def patch_database_creator():
|
||||
"""
|
||||
We need to interrupt Frappe site database creation to monkeypatch
|
||||
functions that resolve host for user that owns site database.
|
||||
In frappe_docker this was implemented in "new" command:
|
||||
https://github.com/frappe/frappe_docker/blob/c808ad1767feaf793a2d14541ac0f4d9cbab45b3/build/frappe-worker/commands/new.py#L87
|
||||
"""
|
||||
|
||||
frappe.database.db_manager.DbManager.get_current_host = lambda self: "%"
|
||||
|
||||
|
||||
def patch_click_usage_error():
|
||||
bits: tuple[str, ...] = (
|
||||
click.style(
|
||||
"Only Frappe framework bench commands are available in container setup.",
|
||||
fg="yellow",
|
||||
bold=True,
|
||||
),
|
||||
"https://frappeframework.com/docs/v13/user/en/bench/frappe-commands",
|
||||
)
|
||||
notice = "\n".join(bits)
|
||||
|
||||
def format_message(self: click.exceptions.UsageError):
|
||||
if "No such command" in self.message:
|
||||
return f"{notice}\n\n{self.message}"
|
||||
return self.message
|
||||
|
||||
click.exceptions.UsageError.format_message = format_message
|
||||
|
||||
|
||||
def main() -> int:
|
||||
patch_database_creator()
|
||||
patch_click_usage_error()
|
||||
frappe.utils.bench_helper.main()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
5
images/worker/pretend-bench.sh
Executable file
5
images/worker/pretend-bench.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# shellcheck disable=SC2068
|
||||
~/frappe-bench/env/bin/python /usr/local/bin/patched_bench_helper.py frappe $@
|
117
images/worker/push_backup.py
Executable file
117
images/worker/push_backup.py
Executable file
@ -0,0 +1,117 @@
|
||||
#!/home/frappe/frappe-bench/env/bin/python
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, List, cast
|
||||
|
||||
import boto3
|
||||
import frappe
|
||||
from frappe.utils.backups import BackupGenerator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mypy_boto3_s3.service_resource import _Bucket
|
||||
|
||||
|
||||
class Arguments(argparse.Namespace):
|
||||
site: str
|
||||
bucket: str
|
||||
region_name: str
|
||||
endpoint_url: str
|
||||
aws_access_key_id: str
|
||||
aws_secret_access_key: str
|
||||
bucket_directory: str
|
||||
|
||||
|
||||
def _get_files_from_previous_backup(site_name: str) -> list[Path]:
|
||||
frappe.connect(site_name)
|
||||
|
||||
conf = cast(Any, frappe.conf)
|
||||
backup_generator = BackupGenerator(
|
||||
db_name=conf.db_name,
|
||||
user=conf.db_name,
|
||||
password=conf.db_password,
|
||||
db_host=frappe.db.host,
|
||||
db_port=frappe.db.port,
|
||||
db_type=conf.db_type,
|
||||
)
|
||||
recent_backup_files = backup_generator.get_recent_backup(24)
|
||||
|
||||
frappe.destroy()
|
||||
return [Path(f) for f in recent_backup_files if f]
|
||||
|
||||
|
||||
def get_files_from_previous_backup(site_name: str) -> list[Path]:
|
||||
files = _get_files_from_previous_backup(site_name)
|
||||
if not files:
|
||||
print("No backup found that was taken <24 hours ago.")
|
||||
return files
|
||||
|
||||
|
||||
def get_bucket(args: Arguments) -> _Bucket:
|
||||
return boto3.resource(
|
||||
service_name="s3",
|
||||
endpoint_url=args.endpoint_url,
|
||||
region_name=args.region_name,
|
||||
aws_access_key_id=args.aws_access_key_id,
|
||||
aws_secret_access_key=args.aws_secret_access_key,
|
||||
).Bucket(args.bucket)
|
||||
|
||||
|
||||
def upload_file(
|
||||
path: Path, site_name: str, bucket: _Bucket, bucket_directory: str = None
|
||||
) -> None:
|
||||
filename = str(path.absolute())
|
||||
key = str(Path(site_name) / path.name)
|
||||
if bucket_directory:
|
||||
key = bucket_directory + "/" + key
|
||||
print(f"Uploading {key}")
|
||||
bucket.upload_file(Filename=filename, Key=key)
|
||||
os.remove(path)
|
||||
|
||||
|
||||
def push_backup(args: Arguments) -> None:
|
||||
"""Get latest backup files using Frappe utils, push them to S3 and remove local copy"""
|
||||
|
||||
files = get_files_from_previous_backup(args.site)
|
||||
bucket = get_bucket(args)
|
||||
|
||||
for path in files:
|
||||
upload_file(
|
||||
path=path,
|
||||
site_name=args.site,
|
||||
bucket=bucket,
|
||||
bucket_directory=args.bucket_directory,
|
||||
)
|
||||
|
||||
print("Done!")
|
||||
|
||||
|
||||
def parse_args(args: list[str]) -> Arguments:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--site", required=True)
|
||||
parser.add_argument("--bucket", required=True)
|
||||
parser.add_argument("--region-name", required=True)
|
||||
parser.add_argument("--endpoint-url", required=True)
|
||||
# Looking for default AWS credentials variables
|
||||
parser.add_argument(
|
||||
"--aws-access-key-id", required=True, default=os.getenv("AWS_ACCESS_KEY_ID")
|
||||
)
|
||||
parser.add_argument(
|
||||
"--aws-secret-access-key",
|
||||
required=True,
|
||||
default=os.getenv("AWS_SECRET_ACCESS_KEY"),
|
||||
)
|
||||
parser.add_argument("--bucket-directory")
|
||||
return parser.parse_args(args, namespace=Arguments())
|
||||
|
||||
|
||||
def main(args: list[str]) -> int:
|
||||
push_backup(parse_args(args))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
@ -1,104 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
# This script configures X11 forwarding for Linux and macOS systems.
|
||||
# It installs X11, Openbox (on Linux), and checks for XQuartz (on macOS).
|
||||
# It also updates the sshd_config file to enable X11Forwarding and restarts the SSH service.
|
||||
|
||||
# Check if the script is running with root privileges
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Error: This script requires root privileges. Please run it as a superuser"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to restart SSH service (Linux)
|
||||
restart_ssh_linux() {
|
||||
if command -v service >/dev/null 2>&1; then
|
||||
sudo service ssh restart
|
||||
else
|
||||
sudo systemctl restart ssh
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to restart SSH service (macOS)
|
||||
restart_ssh_macos() {
|
||||
launchctl stop com.openssh.sshd
|
||||
launchctl start com.openssh.sshd
|
||||
}
|
||||
|
||||
update_x11_forwarding() {
|
||||
if grep -q "X11Forwarding yes" /etc/ssh/sshd_config; then
|
||||
echo "X11Forwarding is already set to 'yes' in ssh_config."
|
||||
else
|
||||
if [[ "$OSTYPE" == "linux-gnu" ]]; then
|
||||
# Linux: Use sed for Linux
|
||||
sudo sed -i 's/#\?X11Forwarding.*/X11Forwarding yes/' /etc/ssh/sshd_config
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS: Use sed for macOS
|
||||
sudo sed -i -E 's/#X11Forwarding.*/X11Forwarding yes/' /etc/ssh/sshd_config
|
||||
restart_ssh_macos
|
||||
fi
|
||||
if [[ "$OSTYPE" == "linux-gnu" ]]; then
|
||||
restart_ssh_linux
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Determine the operating system
|
||||
if [[ "$OSTYPE" == "linux-gnu" ]]; then
|
||||
# Linux
|
||||
if command -v startx >/dev/null 2>&1; then
|
||||
echo "X11 is already installed."
|
||||
else
|
||||
# Check which package manager is available
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
install_command="sudo apt-get update && sudo apt-get install xorg openbox"
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
install_command="sudo dnf install xorg-x11-server-Xorg openbox"
|
||||
else
|
||||
echo "Error: Unable to determine the package manager. Manual installation required."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
# Check if the installation command is defined
|
||||
if [ -n "$install_command" ]; then
|
||||
# Execute the installation command
|
||||
if $install_command; then
|
||||
echo "X11 and Openbox have been successfully installed."
|
||||
else
|
||||
echo "Error: Failed to install X11 and Openbox."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Error: Unsupported package manager."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Call the function to update X11Forwarding
|
||||
update_x11_forwarding
|
||||
|
||||
# Get the IP address of the host dynamically
|
||||
host_ip=$(hostname -I | awk '{print $1}')
|
||||
xhost + "$host_ip" && xhost + local:
|
||||
# Set the DISPLAY variable to the host IP
|
||||
export DISPLAY="$host_ip:0.0"
|
||||
echo "DISPLAY variable set to $DISPLAY"
|
||||
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS
|
||||
if command -v xquartz >/dev/null 2>&1; then
|
||||
echo "XQuartz is already installed."
|
||||
else
|
||||
echo "Error: XQuartz is required for X11 forwarding on macOS. Please install XQuartz manually."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Call the function to update X11Forwarding
|
||||
update_x11_forwarding
|
||||
|
||||
# Export the DISPLAY variable for macOS
|
||||
export DISPLAY=:0
|
||||
echo "DISPLAY variable set to $DISPLAY"
|
||||
else
|
||||
echo "Error: Unsupported operating system."
|
||||
exit 1
|
||||
fi
|
27
overrides/compose.erpnext.yaml
Normal file
27
overrides/compose.erpnext.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
x-erpnext-backend-image: &erpnext_backend_image
|
||||
image: frappe/erpnext-worker:${ERPNEXT_VERSION:?No ERPNext version set}
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- assets:/home/frappe/frappe-bench/sites/assets:ro
|
||||
|
||||
services:
|
||||
configurator:
|
||||
<<: *erpnext_backend_image
|
||||
|
||||
backend:
|
||||
<<: *erpnext_backend_image
|
||||
|
||||
frontend:
|
||||
image: frappe/erpnext-nginx:${ERPNEXT_VERSION}
|
||||
|
||||
queue-short:
|
||||
<<: *erpnext_backend_image
|
||||
|
||||
queue-default:
|
||||
<<: *erpnext_backend_image
|
||||
|
||||
queue-long:
|
||||
<<: *erpnext_backend_image
|
||||
|
||||
scheduler:
|
||||
<<: *erpnext_backend_image
|
@ -5,7 +5,7 @@ services:
|
||||
- traefik.http.services.frontend.loadbalancer.server.port=8080
|
||||
- traefik.http.routers.frontend-http.entrypoints=websecure
|
||||
- traefik.http.routers.frontend-http.tls.certresolver=main-resolver
|
||||
- traefik.http.routers.frontend-http.rule=Host(${SITES:?List of sites not set})
|
||||
- traefik.http.routers.frontend-http.rule=HostRegexp(`{any:.+}`)
|
||||
|
||||
proxy:
|
||||
image: traefik:2.5
|
||||
|
@ -12,7 +12,7 @@ services:
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD:?No db password set}
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
- db-data:/var/lib/postgresql
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
|
@ -1,22 +1,16 @@
|
||||
services:
|
||||
configurator:
|
||||
environment:
|
||||
REDIS_CACHE: redis-cache:6379
|
||||
REDIS_QUEUE: redis-queue:6379
|
||||
REDIS_CACHE: redis:6379/0
|
||||
REDIS_QUEUE: redis:6379/1
|
||||
REDIS_SOCKETIO: redis:6379/2
|
||||
depends_on:
|
||||
- redis-cache
|
||||
- redis-queue
|
||||
- redis
|
||||
|
||||
redis-cache:
|
||||
redis:
|
||||
image: redis:6.2-alpine
|
||||
volumes:
|
||||
- redis-cache-data:/data
|
||||
|
||||
redis-queue:
|
||||
image: redis:6.2-alpine
|
||||
volumes:
|
||||
- redis-queue-data:/data
|
||||
- redis-data:/data
|
||||
|
||||
volumes:
|
||||
redis-cache-data:
|
||||
redis-queue-data:
|
||||
redis-data:
|
||||
|
107
pwd.yml
107
pwd.yml
@ -2,50 +2,36 @@ version: "3"
|
||||
|
||||
services:
|
||||
backend:
|
||||
image: frappe/erpnext:v15.16.2
|
||||
image: frappe/erpnext-worker:v14.12.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
- assets:/home/frappe/frappe-bench/sites/assets
|
||||
|
||||
configurator:
|
||||
image: frappe/erpnext:v15.16.2
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: none
|
||||
entrypoint:
|
||||
- bash
|
||||
- -c
|
||||
# add redis_socketio for backward compatibility
|
||||
image: frappe/erpnext-worker:v14.12.1
|
||||
command:
|
||||
- >
|
||||
ls -1 apps > sites/apps.txt;
|
||||
bench set-config -g db_host $$DB_HOST;
|
||||
bench set-config -gp db_port $$DB_PORT;
|
||||
bench set-config -g redis_cache "redis://$$REDIS_CACHE";
|
||||
bench set-config -g redis_queue "redis://$$REDIS_QUEUE";
|
||||
bench set-config -g redis_socketio "redis://$$REDIS_QUEUE";
|
||||
bench set-config -gp socketio_port $$SOCKETIO_PORT;
|
||||
- configure.py
|
||||
environment:
|
||||
DB_HOST: db
|
||||
DB_PORT: "3306"
|
||||
REDIS_CACHE: redis-cache:6379
|
||||
REDIS_QUEUE: redis-queue:6379
|
||||
REDIS_SOCKETIO: redis-socketio:6379
|
||||
SOCKETIO_PORT: "9000"
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
|
||||
create-site:
|
||||
image: frappe/erpnext:v15.16.2
|
||||
image: frappe/erpnext-worker:v14.12.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: none
|
||||
condition: on-failure
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
- assets:/home/frappe/frappe-bench/sites/assets
|
||||
entrypoint:
|
||||
- bash
|
||||
- -c
|
||||
@ -54,20 +40,21 @@ services:
|
||||
wait-for-it -t 120 db:3306;
|
||||
wait-for-it -t 120 redis-cache:6379;
|
||||
wait-for-it -t 120 redis-queue:6379;
|
||||
wait-for-it -t 120 redis-socketio:6379;
|
||||
export start=`date +%s`;
|
||||
until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
|
||||
until [[ -n `grep -hs ^ common_site_config.json | jq -r ".db_host // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ common_site_config.json | jq -r ".redis_queue // empty"` ]];
|
||||
do
|
||||
echo "Waiting for sites/common_site_config.json to be created";
|
||||
echo "Waiting for common_site_config.json to be created";
|
||||
sleep 5;
|
||||
if (( `date +%s`-start > 120 )); then
|
||||
echo "could not find sites/common_site_config.json with required keys";
|
||||
echo "could not find common_site_config.json with required keys";
|
||||
exit 1
|
||||
fi
|
||||
done;
|
||||
echo "sites/common_site_config.json found";
|
||||
bench new-site --no-mariadb-socket --admin-password=admin --db-root-password=admin --install-app erpnext --set-default frontend;
|
||||
echo "common_site_config.json found";
|
||||
bench new-site frontend --admin-password=admin --db-root-password=admin --install-app payments --install-app erpnext --set-default;
|
||||
|
||||
db:
|
||||
image: mariadb:10.6
|
||||
@ -89,14 +76,10 @@ services:
|
||||
- db-data:/var/lib/mysql
|
||||
|
||||
frontend:
|
||||
image: frappe/erpnext:v15.16.2
|
||||
depends_on:
|
||||
- websocket
|
||||
image: frappe/erpnext-nginx:v14.12.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command:
|
||||
- nginx-entrypoint.sh
|
||||
environment:
|
||||
BACKEND: backend:8000
|
||||
FRAPPE_SITE_NAME_HEADER: frontend
|
||||
@ -104,16 +87,16 @@ services:
|
||||
UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
|
||||
UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
|
||||
UPSTREAM_REAL_IP_RECURSIVE: "off"
|
||||
PROXY_READ_TIMEOUT: 120
|
||||
PROXY_READ_TIMOUT: 120
|
||||
CLIENT_MAX_BODY_SIZE: 50m
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
- sites:/usr/share/nginx/html/sites
|
||||
- assets:/usr/share/nginx/html/assets
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
queue-long:
|
||||
image: frappe/erpnext:v15.16.2
|
||||
queue-default:
|
||||
image: frappe/erpnext-worker:v14.12.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@ -121,13 +104,27 @@ services:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- long,default,short
|
||||
- default
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
- assets:/home/frappe/frappe-bench/sites/assets
|
||||
|
||||
queue-long:
|
||||
image: frappe/erpnext-worker:v14.12.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- long
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- assets:/home/frappe/frappe-bench/sites/assets
|
||||
|
||||
queue-short:
|
||||
image: frappe/erpnext:v15.16.2
|
||||
image: frappe/erpnext-worker:v14.12.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@ -135,10 +132,10 @@ services:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- short,default
|
||||
- short
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
- assets:/home/frappe/frappe-bench/sites/assets
|
||||
|
||||
redis-queue:
|
||||
image: redis:6.2-alpine
|
||||
@ -156,8 +153,16 @@ services:
|
||||
volumes:
|
||||
- redis-cache-data:/data
|
||||
|
||||
redis-socketio:
|
||||
image: redis:6.2-alpine
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
volumes:
|
||||
- redis-socketio-data:/data
|
||||
|
||||
scheduler:
|
||||
image: frappe/erpnext:v15.16.2
|
||||
image: frappe/erpnext-worker:v14.12.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@ -166,23 +171,21 @@ services:
|
||||
- schedule
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
- assets:/home/frappe/frappe-bench/sites/assets
|
||||
|
||||
websocket:
|
||||
image: frappe/erpnext:v15.16.2
|
||||
image: frappe/frappe-socketio:v14.22.0
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command:
|
||||
- node
|
||||
- /home/frappe/frappe-bench/apps/frappe/socketio.js
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
- logs:/home/frappe/frappe-bench/logs
|
||||
- assets:/home/frappe/frappe-bench/sites/assets
|
||||
|
||||
volumes:
|
||||
assets:
|
||||
db-data:
|
||||
redis-queue-data:
|
||||
redis-cache-data:
|
||||
redis-socketio-data:
|
||||
sites:
|
||||
logs:
|
||||
|
3
requirements-dev.txt
Normal file
3
requirements-dev.txt
Normal file
@ -0,0 +1,3 @@
|
||||
frappe @ git+https://github.com/frappe/frappe.git
|
||||
boto3-stubs[s3]
|
||||
black==22.12.0
|
@ -1 +1 @@
|
||||
pytest==8.1.0
|
||||
pytest==7.2.0
|
||||
|
@ -1,52 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Set variables that do not exist
|
||||
if [[ -z "$BACKEND" ]]; then
|
||||
echo "BACKEND defaulting to 0.0.0.0:8000"
|
||||
export BACKEND=0.0.0.0:8000
|
||||
fi
|
||||
if [[ -z "$SOCKETIO" ]]; then
|
||||
echo "SOCKETIO defaulting to 0.0.0.0:9000"
|
||||
export SOCKETIO=0.0.0.0:9000
|
||||
fi
|
||||
if [[ -z "$UPSTREAM_REAL_IP_ADDRESS" ]]; then
|
||||
echo "UPSTREAM_REAL_IP_ADDRESS defaulting to 127.0.0.1"
|
||||
export UPSTREAM_REAL_IP_ADDRESS=127.0.0.1
|
||||
fi
|
||||
if [[ -z "$UPSTREAM_REAL_IP_HEADER" ]]; then
|
||||
echo "UPSTREAM_REAL_IP_HEADER defaulting to X-Forwarded-For"
|
||||
export UPSTREAM_REAL_IP_HEADER=X-Forwarded-For
|
||||
fi
|
||||
if [[ -z "$UPSTREAM_REAL_IP_RECURSIVE" ]]; then
|
||||
echo "UPSTREAM_REAL_IP_RECURSIVE defaulting to off"
|
||||
export UPSTREAM_REAL_IP_RECURSIVE=off
|
||||
fi
|
||||
if [[ -z "$FRAPPE_SITE_NAME_HEADER" ]]; then
|
||||
# shellcheck disable=SC2016
|
||||
echo 'FRAPPE_SITE_NAME_HEADER defaulting to $host'
|
||||
# shellcheck disable=SC2016
|
||||
export FRAPPE_SITE_NAME_HEADER='$host'
|
||||
fi
|
||||
|
||||
if [[ -z "$PROXY_READ_TIMEOUT" ]]; then
|
||||
echo "PROXY_READ_TIMEOUT defaulting to 120"
|
||||
export PROXY_READ_TIMEOUT=120
|
||||
fi
|
||||
|
||||
if [[ -z "$CLIENT_MAX_BODY_SIZE" ]]; then
|
||||
echo "CLIENT_MAX_BODY_SIZE defaulting to 50m"
|
||||
export CLIENT_MAX_BODY_SIZE=50m
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
envsubst '${BACKEND}
|
||||
${SOCKETIO}
|
||||
${UPSTREAM_REAL_IP_ADDRESS}
|
||||
${UPSTREAM_REAL_IP_HEADER}
|
||||
${UPSTREAM_REAL_IP_RECURSIVE}
|
||||
${FRAPPE_SITE_NAME_HEADER}
|
||||
${PROXY_READ_TIMEOUT}
|
||||
${CLIENT_MAX_BODY_SIZE}' \
|
||||
</templates/nginx/frappe.conf.template >/etc/nginx/conf.d/frappe.conf
|
||||
|
||||
nginx -g 'daemon off;'
|
69
tests/_check_backup_files.py
Normal file
69
tests/_check_backup_files.py
Normal file
@ -0,0 +1,69 @@
|
||||
import os
|
||||
import re
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import boto3
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mypy_boto3_s3.service_resource import BucketObjectsCollection, _Bucket
|
||||
|
||||
|
||||
def get_bucket() -> "_Bucket":
|
||||
return boto3.resource(
|
||||
service_name="s3",
|
||||
endpoint_url="http://minio:9000",
|
||||
region_name="us-east-1",
|
||||
aws_access_key_id=os.getenv("S3_ACCESS_KEY"),
|
||||
aws_secret_access_key=os.getenv("S3_SECRET_KEY"),
|
||||
).Bucket("frappe")
|
||||
|
||||
|
||||
def get_key_builder():
|
||||
site_name = os.getenv("SITE_NAME")
|
||||
assert site_name
|
||||
|
||||
def builder(key: str, suffix: str) -> bool:
|
||||
return bool(re.match(rf"{site_name}.*{suffix}$", key))
|
||||
|
||||
return builder
|
||||
|
||||
|
||||
def check_keys(objects: "BucketObjectsCollection"):
|
||||
check_key = get_key_builder()
|
||||
|
||||
db = False
|
||||
config = False
|
||||
private_files = False
|
||||
public_files = False
|
||||
|
||||
for obj in objects:
|
||||
if check_key(obj.key, "database.sql.gz"):
|
||||
db = True
|
||||
elif check_key(obj.key, "site_config_backup.json"):
|
||||
config = True
|
||||
elif check_key(obj.key, "private-files.tar"):
|
||||
private_files = True
|
||||
elif check_key(obj.key, "files.tar"):
|
||||
public_files = True
|
||||
|
||||
exc = lambda type_: Exception(f"Didn't push {type_} backup")
|
||||
if not db:
|
||||
raise exc("database")
|
||||
if not config:
|
||||
raise exc("site config")
|
||||
if not private_files:
|
||||
raise exc("private files")
|
||||
if not public_files:
|
||||
raise exc("public files")
|
||||
|
||||
print("All files were pushed to S3!")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
bucket = get_bucket()
|
||||
check_keys(bucket.objects.all())
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
@ -31,7 +31,7 @@ def get_redis_url(addr: str) -> Address:
|
||||
|
||||
def get_addresses(config: dict[str, Any]) -> Iterable[Address]:
|
||||
yield (config["db_host"], config["db_port"])
|
||||
for key in ("redis_cache", "redis_queue"):
|
||||
for key in ("redis_cache", "redis_queue", "redis_socketio"):
|
||||
yield get_redis_url(config[key])
|
||||
|
||||
|
||||
|
@ -16,7 +16,7 @@ def check_cache():
|
||||
|
||||
|
||||
def main() -> int:
|
||||
frappe.connect(site="tests.localhost")
|
||||
frappe.connect(site="tests")
|
||||
check_db()
|
||||
check_cache()
|
||||
return 0
|
||||
|
24
tests/compose.ci-erpnext.yaml
Normal file
24
tests/compose.ci-erpnext.yaml
Normal file
@ -0,0 +1,24 @@
|
||||
services:
|
||||
configurator:
|
||||
image: localhost:5000/frappe/erpnext-worker:${ERPNEXT_VERSION}
|
||||
|
||||
backend:
|
||||
image: localhost:5000/frappe/erpnext-worker:${ERPNEXT_VERSION}
|
||||
|
||||
frontend:
|
||||
image: localhost:5000/frappe/erpnext-nginx:${ERPNEXT_VERSION}
|
||||
|
||||
websocket:
|
||||
image: localhost:5000/frappe/frappe-socketio:${FRAPPE_VERSION}
|
||||
|
||||
queue-short:
|
||||
image: localhost:5000/frappe/erpnext-worker:${ERPNEXT_VERSION}
|
||||
|
||||
queue-default:
|
||||
image: localhost:5000/frappe/erpnext-worker:${ERPNEXT_VERSION}
|
||||
|
||||
queue-long:
|
||||
image: localhost:5000/frappe/erpnext-worker:${ERPNEXT_VERSION}
|
||||
|
||||
scheduler:
|
||||
image: localhost:5000/frappe/erpnext-worker:${ERPNEXT_VERSION}
|
@ -1,21 +1,24 @@
|
||||
services:
|
||||
configurator:
|
||||
image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION}
|
||||
image: localhost:5000/frappe/frappe-worker:${FRAPPE_VERSION}
|
||||
|
||||
backend:
|
||||
image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION}
|
||||
image: localhost:5000/frappe/frappe-worker:${FRAPPE_VERSION}
|
||||
|
||||
frontend:
|
||||
image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION}
|
||||
image: localhost:5000/frappe/frappe-nginx:${FRAPPE_VERSION}
|
||||
|
||||
websocket:
|
||||
image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION}
|
||||
image: localhost:5000/frappe/frappe-socketio:${FRAPPE_VERSION}
|
||||
|
||||
queue-short:
|
||||
image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION}
|
||||
image: localhost:5000/frappe/frappe-worker:${FRAPPE_VERSION}
|
||||
|
||||
queue-default:
|
||||
image: localhost:5000/frappe/frappe-worker:${FRAPPE_VERSION}
|
||||
|
||||
queue-long:
|
||||
image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION}
|
||||
image: localhost:5000/frappe/frappe-worker:${FRAPPE_VERSION}
|
||||
|
||||
scheduler:
|
||||
image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION}
|
||||
image: localhost:5000/frappe/frappe-worker:${FRAPPE_VERSION}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
@ -23,27 +22,12 @@ def _add_version_var(name: str, env_path: Path):
|
||||
f.write(f"\n{name}={os.environ[name]}")
|
||||
|
||||
|
||||
def _add_sites_var(env_path: Path):
|
||||
with open(env_path, "r+") as f:
|
||||
content = f.read()
|
||||
content = re.sub(
|
||||
rf"SITES=.*",
|
||||
f"SITES=`tests.localhost`,`test-erpnext-site.localhost`,`test-pg-site.localhost`",
|
||||
content,
|
||||
)
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(content)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def env_file(tmp_path_factory: pytest.TempPathFactory):
|
||||
tmp_path = tmp_path_factory.mktemp("frappe-docker")
|
||||
file_path = tmp_path / ".env"
|
||||
shutil.copy("example.env", file_path)
|
||||
|
||||
_add_sites_var(file_path)
|
||||
|
||||
for var in ("FRAPPE_VERSION", "ERPNEXT_VERSION"):
|
||||
_add_version_var(name=var, env_path=file_path)
|
||||
|
||||
@ -68,15 +52,14 @@ def frappe_setup(compose: Compose):
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def frappe_site(compose: Compose):
|
||||
site_name = "tests.localhost"
|
||||
site_name = "tests"
|
||||
compose.bench(
|
||||
"new-site",
|
||||
"--no-mariadb-socket",
|
||||
site_name,
|
||||
"--mariadb-root-password",
|
||||
"123",
|
||||
"--admin-password",
|
||||
"admin",
|
||||
site_name,
|
||||
)
|
||||
compose("restart", "backend")
|
||||
yield site_name
|
||||
@ -85,7 +68,11 @@ def frappe_site(compose: Compose):
|
||||
@pytest.fixture(scope="class")
|
||||
def erpnext_setup(compose: Compose):
|
||||
compose.stop()
|
||||
compose("up", "-d", "--quiet-pull")
|
||||
|
||||
args = ["-f", "overrides/compose.erpnext.yaml"]
|
||||
if CI:
|
||||
args += ("-f", "tests/compose.ci-erpnext.yaml")
|
||||
compose(*args, "up", "-d", "--quiet-pull")
|
||||
|
||||
yield
|
||||
compose.stop()
|
||||
@ -93,18 +80,23 @@ def erpnext_setup(compose: Compose):
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def erpnext_site(compose: Compose):
|
||||
site_name = "test-erpnext-site.localhost"
|
||||
site_name = "test_erpnext_site"
|
||||
args = [
|
||||
"new-site",
|
||||
"--no-mariadb-socket",
|
||||
site_name,
|
||||
"--mariadb-root-password",
|
||||
"123",
|
||||
"--admin-password",
|
||||
"admin",
|
||||
"--install-app",
|
||||
"erpnext",
|
||||
site_name,
|
||||
]
|
||||
erpnext_version = os.environ.get("ERPNEXT_VERSION")
|
||||
if erpnext_version in [
|
||||
"develop",
|
||||
"version-14",
|
||||
] or erpnext_version.startswith("v14"):
|
||||
args.append("--install-app=payments")
|
||||
compose.bench(*args)
|
||||
compose("restart", "backend")
|
||||
yield site_name
|
||||
|
@ -10,6 +10,7 @@ from tests.utils import Compose, check_url_content
|
||||
BACKEND_SERVICES = (
|
||||
"backend",
|
||||
"queue-short",
|
||||
"queue-default",
|
||||
"queue-long",
|
||||
"scheduler",
|
||||
)
|
||||
@ -38,7 +39,7 @@ def assets_cb(text: str):
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("url", "callback"), (("/", index_cb), ("/api/method/ping", api_cb))
|
||||
("url", "callback"), (("/", index_cb), ("/api/method/version", api_cb))
|
||||
)
|
||||
def test_endpoints(url: str, callback: Any, frappe_site: str):
|
||||
check_url_content(
|
||||
@ -88,31 +89,44 @@ def test_frappe_connections_in_backends(
|
||||
):
|
||||
filename = "_ping_frappe_connections.py"
|
||||
compose("cp", f"tests/{filename}", f"{service}:/tmp/")
|
||||
compose.exec(
|
||||
"-w",
|
||||
"/home/frappe/frappe-bench/sites",
|
||||
service,
|
||||
python_path,
|
||||
f"/tmp/{filename}",
|
||||
)
|
||||
compose.exec(service, python_path, f"/tmp/{filename}")
|
||||
|
||||
|
||||
def test_push_backup(
|
||||
python_path: str,
|
||||
frappe_site: str,
|
||||
s3_service: S3ServiceResult,
|
||||
compose: Compose,
|
||||
):
|
||||
restic_password = "secret"
|
||||
compose.bench("--site", frappe_site, "backup", "--with-files")
|
||||
restic_args = [
|
||||
"--env=RESTIC_REPOSITORY=s3:http://minio:9000/frappe",
|
||||
f"--env=AWS_ACCESS_KEY_ID={s3_service.access_key}",
|
||||
f"--env=AWS_SECRET_ACCESS_KEY={s3_service.secret_key}",
|
||||
f"--env=RESTIC_PASSWORD={restic_password}",
|
||||
]
|
||||
compose.exec(*restic_args, "backend", "restic", "init")
|
||||
compose.exec(*restic_args, "backend", "restic", "backup", "sites")
|
||||
compose.exec(*restic_args, "backend", "restic", "snapshots")
|
||||
compose.exec(
|
||||
"backend",
|
||||
"push-backup",
|
||||
"--site",
|
||||
frappe_site,
|
||||
"--bucket",
|
||||
"frappe",
|
||||
"--region-name",
|
||||
"us-east-1",
|
||||
"--endpoint-url",
|
||||
"http://minio:9000",
|
||||
"--aws-access-key-id",
|
||||
s3_service.access_key,
|
||||
"--aws-secret-access-key",
|
||||
s3_service.secret_key,
|
||||
)
|
||||
compose("cp", "tests/_check_backup_files.py", "backend:/tmp")
|
||||
compose.exec(
|
||||
"-e",
|
||||
f"S3_ACCESS_KEY={s3_service.access_key}",
|
||||
"-e",
|
||||
f"S3_SECRET_KEY={s3_service.secret_key}",
|
||||
"-e",
|
||||
f"SITE_NAME={frappe_site}",
|
||||
"backend",
|
||||
python_path,
|
||||
"/tmp/_check_backup_files.py",
|
||||
)
|
||||
|
||||
|
||||
def test_https(frappe_site: str, compose: Compose):
|
||||
@ -126,7 +140,7 @@ class TestErpnext:
|
||||
("url", "callback"),
|
||||
(
|
||||
(
|
||||
"/api/method/erpnext.templates.pages.search_help.get_help_results_sections?text=help",
|
||||
"/api/method/erpnext.templates.pages.product_search.get_product_list",
|
||||
api_cb,
|
||||
),
|
||||
("/assets/erpnext/js/setup_wizard.js", assets_cb),
|
||||
@ -143,7 +157,7 @@ class TestPostgres:
|
||||
def test_site_creation(self, compose: Compose):
|
||||
compose.bench(
|
||||
"new-site",
|
||||
"test-pg-site.localhost",
|
||||
"test_pg_site",
|
||||
"--db-type",
|
||||
"postgres",
|
||||
"--admin-password",
|
||||
|
Loading…
x
Reference in New Issue
Block a user