Compare commits
1 Commits
main
...
version-10
Author | SHA1 | Date | |
---|---|---|---|
|
c818932d72 |
@ -1,4 +1,7 @@
|
||||
# frappe_docker .dockerignore file
|
||||
|
||||
.travis.yml
|
||||
README.md
|
||||
LICENSE
|
||||
LICENSE.md
|
||||
.gitignore
|
||||
compose*.yaml
|
||||
docker-*.yml
|
||||
|
@ -1,19 +0,0 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{py, pyi}]
|
||||
indent_size = 4
|
||||
|
||||
[*Dockerfile*]
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
@ -9,4 +9,4 @@ Welcome to the frappe_docker issue tracker! Before creating an issue, please hee
|
||||
|
||||
1. Use the search function before creating a new issue. Duplicates will be closed and directed to the original discussion.
|
||||
2. Please write extensively, clearly and in detail.
|
||||
-->
|
||||
-->
|
26
.github/dependabot.yml
vendored
26
.github/dependabot.yml
vendored
@ -1,26 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: images/bench
|
||||
schedule:
|
||||
interval: daily
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: images/production
|
||||
schedule:
|
||||
interval: daily
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: images/custom
|
||||
schedule:
|
||||
interval: daily
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
78
.github/scripts/get_latest_tags.py
vendored
78
.github/scripts/get_latest_tags.py
vendored
@ -1,78 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Literal
|
||||
|
||||
Repo = Literal["frappe", "erpnext"]
|
||||
MajorVersion = Literal["12", "13", "14", "15", "develop"]
|
||||
|
||||
|
||||
def get_latest_tag(repo: Repo, version: MajorVersion) -> str:
|
||||
if version == "develop":
|
||||
return "develop"
|
||||
regex = rf"v{version}.*"
|
||||
refs = subprocess.check_output(
|
||||
(
|
||||
"git",
|
||||
"-c",
|
||||
"versionsort.suffix=-",
|
||||
"ls-remote",
|
||||
"--refs",
|
||||
"--tags",
|
||||
"--sort=v:refname",
|
||||
f"https://github.com/frappe/{repo}",
|
||||
str(regex),
|
||||
),
|
||||
encoding="UTF-8",
|
||||
).split()[1::2]
|
||||
|
||||
if not refs:
|
||||
raise RuntimeError(f'No tags found for version "{regex}"')
|
||||
ref = refs[-1]
|
||||
matches: list[str] = re.findall(regex, ref)
|
||||
if not matches:
|
||||
raise RuntimeError(f'Can\'t parse tag from ref "{ref}"')
|
||||
return matches[0]
|
||||
|
||||
|
||||
def update_env(file_name: str, frappe_tag: str, erpnext_tag: str | None = None):
|
||||
text = f"\nFRAPPE_VERSION={frappe_tag}"
|
||||
if erpnext_tag:
|
||||
text += f"\nERPNEXT_VERSION={erpnext_tag}"
|
||||
|
||||
with open(file_name, "a") as f:
|
||||
f.write(text)
|
||||
|
||||
|
||||
def _print_resp(frappe_tag: str, erpnext_tag: str | None = None):
|
||||
print(json.dumps({"frappe": frappe_tag, "erpnext": erpnext_tag}))
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
args = parser.parse_args(_args)
|
||||
|
||||
frappe_tag = get_latest_tag("frappe", args.version)
|
||||
if args.repo == "erpnext":
|
||||
erpnext_tag = get_latest_tag("erpnext", args.version)
|
||||
else:
|
||||
erpnext_tag = None
|
||||
|
||||
file_name = os.getenv("GITHUB_ENV")
|
||||
if file_name:
|
||||
update_env(file_name, frappe_tag, erpnext_tag)
|
||||
_print_resp(frappe_tag, erpnext_tag)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
28
.github/scripts/update_example_env.py
vendored
28
.github/scripts/update_example_env.py
vendored
@ -1,28 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
def get_erpnext_version():
|
||||
erpnext_version = os.getenv("ERPNEXT_VERSION")
|
||||
assert erpnext_version, "No ERPNext version set"
|
||||
return erpnext_version
|
||||
|
||||
|
||||
def update_env(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
|
||||
)
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(content)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
update_env(get_erpnext_version())
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
30
.github/scripts/update_pwd.py
vendored
30
.github/scripts/update_pwd.py
vendored
@ -1,30 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
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 frappe_version, erpnext_version
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(content)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
update_pwd(*get_versions())
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
58
.github/workflows/build_bench.yml
vendored
58
.github/workflows/build_bench.yml
vendored
@ -1,58 +0,0 @@
|
||||
name: Bench
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- images/bench/**
|
||||
- docker-bake.hcl
|
||||
- .github/workflows/build_bench.yml
|
||||
|
||||
schedule:
|
||||
# Every day at 12:00 pm
|
||||
- cron: 0 0 * * *
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
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"
|
||||
|
||||
- name: Build and test
|
||||
uses: docker/bake-action@v4.1.0
|
||||
with:
|
||||
targets: bench-test
|
||||
|
||||
- name: Login
|
||||
if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
|
||||
uses: docker/login-action@v3
|
||||
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
|
||||
with:
|
||||
targets: bench
|
||||
push: true
|
||||
set: "*.platform=linux/amd64,linux/arm64"
|
33
.github/workflows/build_develop.yml
vendored
33
.github/workflows/build_develop.yml
vendored
@ -1,33 +0,0 @@
|
||||
name: Develop build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- images/production/**
|
||||
- overrides/**
|
||||
- tests/**
|
||||
- compose.yaml
|
||||
- docker-bake.hcl
|
||||
- example.env
|
||||
- .github/workflows/build_develop.yml
|
||||
|
||||
schedule:
|
||||
# Every day at 12:00 pm
|
||||
- cron: 0 0 * * *
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: ./.github/workflows/docker-build-push.yml
|
||||
with:
|
||||
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 }}
|
116
.github/workflows/build_stable.yml
vendored
116
.github/workflows/build_stable.yml
vendored
@ -1,116 +0,0 @@
|
||||
name: Stable build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- images/production/**
|
||||
- overrides/**
|
||||
- tests/**
|
||||
- compose.yaml
|
||||
- docker-bake.hcl
|
||||
- example.env
|
||||
- .github/workflows/build_stable.yml
|
||||
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- images/production/**
|
||||
- overrides/**
|
||||
- tests/**
|
||||
- compose.yaml
|
||||
- docker-bake.hcl
|
||||
- example.env
|
||||
|
||||
# Triggered from frappe/frappe and frappe/erpnext on releases
|
||||
repository_dispatch:
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
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 }}
|
||||
|
||||
update_versions:
|
||||
name: Update example.env and pwd.yml
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
|
||||
needs: v15
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Get latest versions
|
||||
run: python3 ./.github/scripts/get_latest_tags.py --repo erpnext --version 15
|
||||
|
||||
- name: Update
|
||||
run: |
|
||||
python3 ./.github/scripts/update_example_env.py
|
||||
python3 ./.github/scripts/update_pwd.py
|
||||
|
||||
- name: Push
|
||||
run: |
|
||||
git config --global user.name github-actions
|
||||
git config --global user.email github-actions@github.com
|
||||
git add example.env pwd.yml
|
||||
if [ -z "$(git status --porcelain)" ]; then
|
||||
echo "versions did not change, exiting."
|
||||
exit 0
|
||||
else
|
||||
echo "version changed, pushing changes..."
|
||||
git commit -m "chore: Update example.env"
|
||||
git pull --rebase
|
||||
git push origin main
|
||||
fi
|
||||
|
||||
release_helm:
|
||||
name: Release Helm
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
|
||||
needs: v15
|
||||
|
||||
steps:
|
||||
- name: Setup deploy key
|
||||
uses: webfactory/ssh-agent@v0.8.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.HELM_DEPLOY_KEY }}
|
||||
|
||||
- name: Setup Git Credentials
|
||||
run: |
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
|
||||
- name: Release
|
||||
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
|
89
.github/workflows/docker-build-push.yml
vendored
89
.github/workflows/docker-build-push.yml
vendored
@ -1,89 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
repo:
|
||||
required: true
|
||||
type: string
|
||||
description: "'erpnext' or 'frappe'"
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
description: "Major version, git tags should match 'v{version}.*'; or 'develop'"
|
||||
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
|
||||
DOCKERHUB_TOKEN:
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
registry:
|
||||
image: registry:2
|
||||
ports:
|
||||
- 5000:5000
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
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
|
||||
with:
|
||||
push: true
|
||||
env:
|
||||
REGISTRY_USER: localhost:5000/frappe
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m venv venv
|
||||
venv/bin/pip install -r requirements-test.txt
|
||||
|
||||
- name: Test
|
||||
run: venv/bin/pytest --color=yes
|
||||
|
||||
- name: Login
|
||||
if: ${{ inputs.push }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Push
|
||||
if: ${{ inputs.push }}
|
||||
uses: docker/bake-action@v4.1.0
|
||||
with:
|
||||
push: true
|
35
.github/workflows/lint.yml
vendored
35
.github/workflows/lint.yml
vendored
@ -1,35 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10.6"
|
||||
|
||||
# For shfmt pre-commit hook
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1.14"
|
||||
|
||||
- name: Install pre-commit
|
||||
run: pip install -U pre-commit
|
||||
|
||||
- name: Lint
|
||||
run: pre-commit run --color=always --all-files
|
||||
env:
|
||||
GO111MODULE: on
|
26
.github/workflows/pre-commit-autoupdate.yml
vendored
26
.github/workflows/pre-commit-autoupdate.yml
vendored
@ -1,26 +0,0 @@
|
||||
name: Autoupdate pre-commit hooks
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Every day at 7 am
|
||||
- cron: 0 7 * * *
|
||||
|
||||
jobs:
|
||||
pre-commit-autoupdate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Update pre-commit hooks
|
||||
uses: vrslev/pre-commit-autoupdate@v1.0.0
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
branch: pre-commit-autoupdate
|
||||
title: "chore(deps): Update pre-commit hooks"
|
||||
commit-message: "chore(deps): Update pre-commit hooks"
|
||||
body: Update pre-commit hooks
|
||||
labels: dependencies,development
|
||||
delete-branch: True
|
19
.github/workflows/stale.yml
vendored
19
.github/workflows/stale.yml
vendored
@ -2,17 +2,18 @@ name: Mark stale issues and pull requests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Every day at 12:00 pm
|
||||
- cron: 0 0 * * *
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
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.
|
||||
stale-pr-message: This PR has been automatically marked as stale. You have a week to explain why you believe this is an error.
|
||||
stale-issue-label: no-issue-activity
|
||||
stale-pr-label: no-pr-activity
|
||||
- uses: actions/stale@v1
|
||||
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.'
|
||||
stale-pr-message: 'This PR has been automatically marked as stale. You have a week to explain why you believe this is an error.'
|
||||
stale-issue-label: 'no-issue-activity'
|
||||
stale-pr-label: 'no-pr-activity'
|
22
.gitignore
vendored
22
.gitignore
vendored
@ -1,30 +1,18 @@
|
||||
*.code-workspace
|
||||
|
||||
# Environment Variables
|
||||
.env
|
||||
|
||||
# mounted volume
|
||||
sites
|
||||
|
||||
development/*
|
||||
development
|
||||
!development/README.md
|
||||
!development/installer.py
|
||||
!development/apps-example.json
|
||||
!development/vscode-example/
|
||||
deploy_key
|
||||
deploy_key.pub
|
||||
|
||||
# Pycharm
|
||||
.idea
|
||||
|
||||
# VS Code
|
||||
.vscode/**
|
||||
!.vscode/extensions.json
|
||||
|
||||
# VS Code devcontainer
|
||||
.devcontainer
|
||||
*.code-workspace
|
||||
|
||||
# Python
|
||||
*.pyc
|
||||
__pycache__
|
||||
venv
|
||||
|
||||
# NodeJS
|
||||
node_modules
|
||||
|
@ -1,53 +0,0 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-shebang-scripts-are-executable
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.15.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py37-plus]
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 24.2.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.13.2
|
||||
hooks:
|
||||
- id: isort
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v4.0.0-alpha.8
|
||||
hooks:
|
||||
- id: prettier
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.6
|
||||
hooks:
|
||||
- id: codespell
|
||||
args:
|
||||
- -L
|
||||
- "ro"
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: shfmt
|
||||
name: shfmt
|
||||
language: golang
|
||||
additional_dependencies: [mvdan.cc/sh/v3/cmd/shfmt@latest]
|
||||
entry: shfmt
|
||||
args: [-w]
|
||||
types: [shell]
|
||||
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.9.0.6
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
args: [-x]
|
@ -1 +0,0 @@
|
||||
external-sources=true
|
187
.travis.yml
Normal file
187
.travis.yml
Normal file
@ -0,0 +1,187 @@
|
||||
sudo: required
|
||||
|
||||
dist: bionic
|
||||
|
||||
language: python
|
||||
|
||||
python:
|
||||
- '3.6'
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
before_install:
|
||||
- if [[ "$BUILD" != "development" ]]; then
|
||||
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin;
|
||||
sudo apt-get update && sudo apt-get -y install git;
|
||||
fi
|
||||
- if [[ $BUILD == "development" ]];then
|
||||
sudo apt-get update && sudo apt-get -y install docker-compose;
|
||||
fi
|
||||
- chmod u+x ./travis.py
|
||||
|
||||
after_success:
|
||||
- docker --version
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: "Build Frappe bench development environment (latest)"
|
||||
if: branch = develop AND type != pull_request
|
||||
script:
|
||||
- docker build -t frappe/bench:latest -f build/bench/Dockerfile .
|
||||
- docker push frappe/bench:latest
|
||||
- stage: "Frappe (edge)"
|
||||
if: branch = develop AND type != pull_request
|
||||
script:
|
||||
- ./travis.py frappe --worker --tag latest
|
||||
- ./travis.py frappe --worker --tag edge --tag-only
|
||||
- ./travis.py frappe --worker --tag develop --tag-only
|
||||
- stage: "Frappe (edge)"
|
||||
if: branch = develop AND type != pull_request
|
||||
script:
|
||||
- ./travis.py frappe --nginx --tag latest
|
||||
- ./travis.py frappe --nginx --tag edge --tag-only
|
||||
- ./travis.py frappe --nginx --tag develop --tag-only
|
||||
- stage: "Frappe (edge)"
|
||||
if: branch = develop AND type != pull_request
|
||||
script:
|
||||
- ./travis.py frappe --socketio --tag latest
|
||||
- ./travis.py frappe --socketio --tag edge --tag-only
|
||||
- ./travis.py frappe --socketio --tag develop --tag-only
|
||||
- stage: "ERPNext (edge)"
|
||||
if: branch = develop AND type != pull_request
|
||||
script:
|
||||
- ./travis.py erpnext --worker --tag latest
|
||||
- ./travis.py erpnext --worker --tag edge --tag-only
|
||||
- ./travis.py erpnext --worker --tag develop --tag-only
|
||||
- stage: "ERPNext (edge)"
|
||||
if: branch = develop AND type != pull_request
|
||||
script:
|
||||
- ./travis.py erpnext --nginx --tag latest
|
||||
- ./travis.py erpnext --nginx --tag edge --tag-only
|
||||
- ./travis.py erpnext --nginx --tag develop --tag-only
|
||||
- stage: "Frappe (v13)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py frappe --worker --git-version 13
|
||||
- ./travis.py frappe --worker --tag v13 --tag-only
|
||||
- ./travis.py frappe --worker --tag version-13 --tag-only
|
||||
- stage: "Frappe (v13)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py frappe --nginx --git-version 13
|
||||
- ./travis.py frappe --nginx --tag v13 --tag-only
|
||||
- ./travis.py frappe --nginx --tag version-13 --tag-only
|
||||
- stage: "Frappe (v13)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py frappe --socketio --git-version 13
|
||||
- ./travis.py frappe --socketio --tag v13 --tag-only
|
||||
- ./travis.py frappe --socketio --tag version-13 --tag-only
|
||||
- stage: "ERPNext (v13)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py erpnext --worker --git-version 13
|
||||
- ./travis.py erpnext --worker --tag v13 --tag-only
|
||||
- ./travis.py erpnext --worker --tag version-13 --tag-only
|
||||
- stage: "ERPNext (v13)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py erpnext --nginx --git-version 13
|
||||
- ./travis.py erpnext --nginx --tag v13 --tag-only
|
||||
- ./travis.py erpnext --nginx --tag version-13 --tag-only
|
||||
- stage: "Frappe (v12)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py frappe --worker --git-version 12
|
||||
- ./travis.py frappe --worker --tag v12 --tag-only
|
||||
- ./travis.py frappe --worker --tag version-12 --tag-only
|
||||
- stage: "Frappe (v12)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py frappe --nginx --git-version 12
|
||||
- ./travis.py frappe --nginx --tag v12 --tag-only
|
||||
- ./travis.py frappe --nginx --tag version-12 --tag-only
|
||||
- stage: "Frappe (v12)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py frappe --socketio --git-version 12
|
||||
- ./travis.py frappe --socketio --tag v12 --tag-only
|
||||
- ./travis.py frappe --socketio --tag version-12 --tag-only
|
||||
- stage: "ERPNext (v12)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py erpnext --worker --git-version 12
|
||||
- ./travis.py erpnext --worker --tag v12 --tag-only
|
||||
- ./travis.py erpnext --worker --tag version-12 --tag-only
|
||||
- stage: "ERPNext (v12)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py erpnext --nginx --git-version 12
|
||||
- ./travis.py erpnext --nginx --tag v12 --tag-only
|
||||
- ./travis.py erpnext --nginx --tag version-12 --tag-only
|
||||
- stage: "Frappe (v11)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py frappe --worker --git-version 11
|
||||
- ./travis.py frappe --worker --tag v11 --tag-only
|
||||
- ./travis.py frappe --worker --tag version-11 --tag-only
|
||||
- stage: "Frappe (v11)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py frappe --nginx --git-version 11
|
||||
- ./travis.py frappe --nginx --tag v11 --tag-only
|
||||
- ./travis.py frappe --nginx --tag version-11 --tag-only
|
||||
- stage: "Frappe (v11)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py frappe --socketio --git-version 11
|
||||
- ./travis.py frappe --socketio --tag v11 --tag-only
|
||||
- ./travis.py frappe --socketio --tag version-11 --tag-only
|
||||
- stage: "ERPNext (v11)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py erpnext --worker --git-version 11
|
||||
- ./travis.py erpnext --worker --tag v11 --tag-only
|
||||
- ./travis.py erpnext --worker --tag version-11 --tag-only
|
||||
- stage: "ERPNext (v11)"
|
||||
if: branch = master AND type != pull_request
|
||||
script:
|
||||
- ./travis.py erpnext --nginx --git-version 11
|
||||
- ./travis.py erpnext --nginx --tag v11 --tag-only
|
||||
- ./travis.py erpnext --nginx --tag version-11 --tag-only
|
||||
- stage: "Build and test edge images"
|
||||
if: type = pull_request
|
||||
before_install:
|
||||
- sudo curl -L "https://github.com/docker/compose/releases/download/1.26.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
- sudo chmod +x /usr/local/bin/docker-compose
|
||||
- sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
|
||||
- sudo apt-get update && sudo apt-get -y install w3m shellcheck
|
||||
script:
|
||||
- ./tests/check-format.sh
|
||||
- docker build -t frappe/frappe-socketio:edge -f build/frappe-socketio/Dockerfile .
|
||||
- docker build -t frappe/frappe-worker:develop -f build/frappe-worker/Dockerfile .
|
||||
- docker build -t frappe/erpnext-worker:edge -f build/erpnext-worker/Dockerfile .
|
||||
- docker build -t frappe/frappe-nginx:develop -f build/frappe-nginx/Dockerfile .
|
||||
- docker build -t frappe/erpnext-nginx:edge -f build/erpnext-nginx/Dockerfile .
|
||||
- ./tests/docker-test.sh
|
||||
- stage: "Pull and test edge images"
|
||||
if: branch = develop AND type != pull_request
|
||||
before_install:
|
||||
- sudo apt-get update && sudo apt-get -y install docker-compose w3m
|
||||
script:
|
||||
- ./tests/docker-test.sh
|
||||
- stage: "Helm Chart Release"
|
||||
if: branch = master AND type != pull_request
|
||||
env:
|
||||
- GIT_SSH_COMMAND="ssh -i ${TRAVIS_BUILD_DIR}/deploy_key"
|
||||
before_install:
|
||||
- openssl aes-256-cbc -K $encrypted_189e52c2c347_key -iv $encrypted_189e52c2c347_iv -in deploy_key.enc -out deploy_key -d;
|
||||
chmod 400 deploy_key;
|
||||
- ssh-keyscan github.com >> $HOME/.ssh/known_hosts 2>/dev/null;
|
||||
install:
|
||||
- pip install --upgrade pip
|
||||
script:
|
||||
- git clone git@github.com:frappe/helm.git && cd helm
|
||||
- pip install -r release_wizard/requirements.txt
|
||||
- ./release_wizard/wizard 13 patch --remote origin --ci
|
18
.vscode/extensions.json
vendored
18
.vscode/extensions.json
vendored
@ -1,9 +1,13 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": ["ms-vscode-remote.remote-containers"],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
"ms-vscode-remote.remote-containers"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": [
|
||||
|
||||
]
|
||||
}
|
@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation.
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
|
101
CONTRIBUTING.md
101
CONTRIBUTING.md
@ -1,81 +1,68 @@
|
||||
# Contribution Guidelines
|
||||
|
||||
Before publishing a PR, please test builds locally.
|
||||
## Branches
|
||||
|
||||
On each PR that contains changes relevant to Docker builds, images are being built and tested in our CI (GitHub Actions).
|
||||
* *master*: images on the master branch are built monthly and with github action triggered by ERPNext release.
|
||||
* *develop*: images on this branch are built daily and when PR is merged into develop.
|
||||
|
||||
> :evergreen_tree: Please be considerate when pushing commits and opening PR for multiple branches, as the process of building images uses energy and contributes to global warming.
|
||||
# Pull Requests
|
||||
|
||||
## Lint
|
||||
Please **send all pull request exclusively to the *develop*** branch.
|
||||
When the PR are merged, the merge will trigger the image build automatically.
|
||||
|
||||
We use `pre-commit` framework to lint the codebase before committing.
|
||||
First, you need to install pre-commit with pip:
|
||||
Please test all PR as extensively as you can, considering that the software can be run in different modes:
|
||||
|
||||
```shell
|
||||
pip install pre-commit
|
||||
```
|
||||
* with docker-compose for production
|
||||
* with or without Nginx proxy
|
||||
* with VScode for testing environments
|
||||
|
||||
Also you can use brew if you're on Mac:
|
||||
Every once in a while (or with monthly release) develop will be merged into master.
|
||||
|
||||
```shell
|
||||
brew install pre-commit
|
||||
```
|
||||
There is Github Action is configured on ERPNext repository. Whenever there is a ERPNext release it will trigger a build on master branch of frappe_docker repo to generate images for released version.
|
||||
|
||||
To setup _pre-commit_ hook, run:
|
||||
When a PR is sent, the images are built and all commands are tested.
|
||||
|
||||
```shell
|
||||
pre-commit install
|
||||
```
|
||||
If update or fixes to documentation are pushed use `[skip travis]` anywhere in commit message to skip travis.
|
||||
|
||||
To run all the files in repository, run:
|
||||
## Reducing the number of branching and builds :evergreen_tree: :evergreen_tree: :evergreen_tree:
|
||||
|
||||
```shell
|
||||
pre-commit run --all-files
|
||||
```
|
||||
Please be considerate when pushing commits and opening PR for multiple branches, as the process of building images (triggered on push and PR branch push) uses energy and contributes to global warming.
|
||||
|
||||
## Build
|
||||
|
||||
We use [Docker Buildx Bake](https://docs.docker.com/engine/reference/commandline/buildx_bake/). To build the images, run command below:
|
||||
|
||||
```shell
|
||||
FRAPPE_VERSION=... ERPNEXT_VERSION=... docker buildx bake <targets>
|
||||
```
|
||||
|
||||
Available targets can be found in `docker-bake.hcl`.
|
||||
|
||||
## Test
|
||||
|
||||
We use [pytest](https://pytest.org) for our integration tests.
|
||||
|
||||
Install Python test requirements:
|
||||
|
||||
```shell
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements-test.txt
|
||||
```
|
||||
|
||||
Run pytest:
|
||||
|
||||
```shell
|
||||
pytest
|
||||
```
|
||||
|
||||
# Documentation
|
||||
|
||||
Place relevant markdown files in the `docs` directory and index them in README.md located at the root of repo.
|
||||
Place relevant markdown file(s) in the `docs` directory and index them in README.md located at the root of repo.
|
||||
|
||||
# Frappe and ERPNext updates
|
||||
# Wiki
|
||||
|
||||
Each Frappe/ERPNext release triggers new stable images builds as well as bump to helm chart.
|
||||
Add alternatives that can be used optionally along with frappe_docker. Add articles to list on home page as well.
|
||||
|
||||
# Maintenance
|
||||
# Prerequisites to pass CI
|
||||
|
||||
In case of new release of Debian. e.g. bullseye to bookworm. Change following files:
|
||||
### Check shell script format
|
||||
|
||||
- `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.
|
||||
Use the following script
|
||||
|
||||
Change following files on release of ERPNext
|
||||
```shell
|
||||
./tests/check-format.sh
|
||||
```
|
||||
|
||||
- `.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`.
|
||||
### Build images locally
|
||||
|
||||
Use the following commands
|
||||
|
||||
```shell
|
||||
docker build -t frappe/frappe-socketio:edge -f build/frappe-socketio/Dockerfile .
|
||||
docker build -t frappe/frappe-worker:develop -f build/frappe-worker/Dockerfile .
|
||||
docker build -t frappe/erpnext-worker:edge -f build/erpnext-worker/Dockerfile .
|
||||
docker build -t frappe/frappe-nginx:develop -f build/frappe-nginx/Dockerfile .
|
||||
docker build -t frappe/erpnext-nginx:edge -f build/erpnext-nginx/Dockerfile .
|
||||
```
|
||||
|
||||
### Test running docker containers
|
||||
|
||||
Use the following script
|
||||
|
||||
```shell
|
||||
./tests/docker-test.sh
|
||||
```
|
||||
|
178
README.md
178
README.md
@ -1,158 +1,70 @@
|
||||
[](https://github.com/frappe/frappe_docker/actions/workflows/build_stable.yml)
|
||||
[](https://github.com/frappe/frappe_docker/actions/workflows/build_develop.yml)
|
||||
| Develop | [](https://travis-ci.com/frappe/frappe_docker) |
|
||||
|---------|-----------------------------------------------------------------------------------------------------------------------------|
|
||||
| Master | [](https://travis-ci.com/frappe/frappe_docker) |
|
||||
|
||||
Everything about [Frappe](https://github.com/frappe/frappe) and [ERPNext](https://github.com/frappe/erpnext) in containers.
|
||||
|
||||
# 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).
|
||||
After that, clone this repo:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/frappe/frappe_docker
|
||||
cd frappe_docker
|
||||
```
|
||||
## Getting Started
|
||||
|
||||
### Try in Play With Docker
|
||||
|
||||
<a href="https://labs.play-with-docker.com/?stack=https://raw.githubusercontent.com/frappe/frappe_docker/main/pwd.yml">
|
||||
<a href="https://labs.play-with-docker.com/?stack=https://raw.githubusercontent.com/frappe/frappe_docker/develop/tests/pwd.yml">
|
||||
<img src="https://raw.githubusercontent.com/play-with-docker/stacks/master/assets/images/button.png" alt="Try in PWD"/>
|
||||
</a>
|
||||
|
||||
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`)
|
||||
Wait for 5 minutes for ERPNext site to be created or check `site-creator` container logs before opening browser on port 80. (username: `Administrator`, password: `admin`)
|
||||
|
||||
# Documentation
|
||||
### Setting up Pre-requisites
|
||||
|
||||
### [Production](#production)
|
||||
This repository requires Docker, docker-compose and Git to be setup on the instance to be used.
|
||||
|
||||
- [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)
|
||||
For Docker basics and best practices. Refer Docker [documentation](http://docs.docker.com).
|
||||
|
||||
### [Custom Images](#custom-images)
|
||||
### Cloning the repository and preliminary steps
|
||||
|
||||
- [Custom Apps](docs/custom-apps.md)
|
||||
- [Build Version 10 Images](docs/build-version-10-images.md)
|
||||
Clone this repository somewhere in your system:
|
||||
|
||||
### [Development](#development)
|
||||
```sh
|
||||
git clone https://github.com/frappe/frappe_docker.git
|
||||
cd frappe_docker
|
||||
```
|
||||
|
||||
- [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)
|
||||
## Production Setup
|
||||
|
||||
### [Troubleshoot](docs/troubleshoot.md)
|
||||
It takes care of the following:
|
||||
|
||||
### Shiloh Setup
|
||||
* Setting up the desired version of Frappe/ERPNext.
|
||||
* Setting up all the system requirements: eg. MariaDB, Node, Redis.
|
||||
* Configure networking for remote access and setting up LetsEncrypt.
|
||||
|
||||
based on: https://raw.githubusercontent.com/frappe/bench/develop/easy-install.py
|
||||
It doesn't take care of the following:
|
||||
|
||||
`git clone ssh://git@githaven.org:2222/Shiloh/frappe_docker.git`
|
||||
* Cron Job to backup sites is not created by default.
|
||||
* Use `CronJob` on k8s or refer wiki for alternatives.
|
||||
|
||||
sites = [erp.sprinklersnorthwest.com]
|
||||
email = support@lasthourhosting.org
|
||||
1. Single Server Installs
|
||||
1. [Single bench](docs/single-bench.md). Easiest Install!
|
||||
2. [Multi bench](docs/multi-bench.md)
|
||||
2. Multi Server Installs
|
||||
1. [Docker Swarm](docs/docker-swarm.md)
|
||||
2. [Kubernetes](https://helm.erpnext.com)
|
||||
3. [Site Operations](docs/site-operations.md)
|
||||
4. [Environment Variables](docs/environment-variables.md)
|
||||
5. [Custom apps for production](docs/custom-apps-for-production.md)
|
||||
6. [Tips for moving deployments](docs/tips-for-moving-deployments.md)
|
||||
7. [Wiki for optional recipes](https://github.com/frappe/frappe_docker/wiki)
|
||||
|
||||
`cd frappe_docker`
|
||||
`cp example.env .env`
|
||||
## Development Setup
|
||||
|
||||
Write inside of env:
|
||||
It takes care of complete setup to develop with Frappe/ERPNext and Bench, Including the following features:
|
||||
|
||||
<!-- Commented items because the script doesn't read them. -->
|
||||
<!-- f"ERPNEXT_VERSION={erpnext_version}\n", -->
|
||||
- VSCode containers integration
|
||||
- VSCode Python debugger
|
||||
- Pre-configured Docker containers for an easy start
|
||||
|
||||
f"DB_PASSWORD={db_pass}\n",
|
||||
[Start development](development).
|
||||
|
||||
<!-- "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", -->
|
||||
## Contributing
|
||||
|
||||
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` -->
|
||||
|
||||
# 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:
|
||||
|
||||
- [Frappe framework](https://github.com/frappe/frappe#contributing),
|
||||
- [ERPNext](https://github.com/frappe/erpnext#contributing),
|
||||
- [Frappe Bench](https://github.com/frappe/bench).
|
||||
- [Frappe Docker Images](CONTRIBUTING.md)
|
||||
- [Frappe Framework](https://github.com/frappe/frappe#contributing)
|
||||
- [ERPNext](https://github.com/frappe/erpnext#contributing)
|
||||
- [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
|
122
build/bench/Dockerfile
Normal file
122
build/bench/Dockerfile
Normal file
@ -0,0 +1,122 @@
|
||||
# Frappe Bench Dockerfile
|
||||
FROM debian:buster-slim
|
||||
|
||||
ARG GIT_REPO=https://github.com/frappe/bench.git
|
||||
ARG GIT_BRANCH=develop
|
||||
|
||||
LABEL author=frappé
|
||||
|
||||
RUN apt-get update -y && apt-get install \
|
||||
git \
|
||||
wkhtmltopdf \
|
||||
mariadb-client \
|
||||
postgresql-client \
|
||||
gettext-base \
|
||||
wget \
|
||||
# for PDF
|
||||
libssl-dev \
|
||||
fonts-cantarell \
|
||||
xfonts-75dpi \
|
||||
xfonts-base \
|
||||
# to work inside the container
|
||||
locales \
|
||||
build-essential \
|
||||
cron \
|
||||
curl \
|
||||
vim \
|
||||
sudo \
|
||||
iputils-ping \
|
||||
watch \
|
||||
tree \
|
||||
nano \
|
||||
software-properties-common \
|
||||
bash-completion \
|
||||
# For psycopg2
|
||||
libpq-dev \
|
||||
# Other
|
||||
libffi-dev \
|
||||
liblcms2-dev \
|
||||
libldap2-dev \
|
||||
libmariadbclient-dev \
|
||||
libsasl2-dev \
|
||||
libtiff5-dev \
|
||||
libwebp-dev \
|
||||
redis-tools \
|
||||
rlwrap \
|
||||
tk8.6-dev \
|
||||
ssh-client \
|
||||
# VSCode container requirements
|
||||
net-tools \
|
||||
# PYTHON
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-setuptools \
|
||||
python3-tk \
|
||||
python-virtualenv \
|
||||
less -y && \
|
||||
apt-get clean && \
|
||||
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, download and install wkhtmltox
|
||||
RUN if [ `uname -m` = 'aarch64' ]; then export ARCH=arm64; fi \
|
||||
&& if [ `uname -m` = 'x86_64' ]; then export ARCH=amd64; fi \
|
||||
&& wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.buster_${ARCH}.deb \
|
||||
&& dpkg -i wkhtmltox_0.12.6-1.buster_${ARCH}.deb && rm wkhtmltox_0.12.6-1.buster_${ARCH}.deb
|
||||
|
||||
# Create new user with home directory, improve docker compatibility with UID/GID 1000, add user to sudo group, allow passwordless sudo, switch to that user and change directory to user home directory
|
||||
RUN groupadd -g 1000 frappe
|
||||
RUN useradd --no-log-init -r -m -u 1000 -g 1000 -G sudo frappe
|
||||
RUN echo "frappe ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
||||
USER frappe
|
||||
WORKDIR /home/frappe
|
||||
|
||||
# Clone and install bench in the local user home directory
|
||||
# For development, bench source is located in ~/.bench
|
||||
RUN git clone ${GIT_REPO} -b ${GIT_BRANCH} .bench \
|
||||
&& pip3 install --user -e .bench
|
||||
|
||||
# Export python executables for Dockerfile
|
||||
ENV PATH=/home/frappe/.local/bin:$PATH
|
||||
# Export python executables for interactive shell
|
||||
RUN echo "export PATH=/home/frappe/.local/bin:\$PATH" >> /home/frappe/.bashrc
|
||||
|
||||
# Print version and verify bashrc is properly sourced so that everything works in the Dockerfile
|
||||
RUN bench --version
|
||||
# Print version and verify bashrc is properly sourced so that everything works in the interactive shell
|
||||
RUN bash -c "bench --version"
|
||||
|
||||
# !!! UPDATE NODEJS PERIODICALLY WITH LATEST VERSIONS !!!
|
||||
# https://nodejs.org/en/about/releases/
|
||||
# https://nodejs.org/download/release/latest-v10.x/
|
||||
# https://nodejs.org/download/release/latest-v14.x/
|
||||
ENV NODE_VERSION=14.17.0
|
||||
ENV NODE_VERSION_FRAPPEV11=10.24.1
|
||||
|
||||
# Install nvm with node
|
||||
RUN wget https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh
|
||||
RUN chmod +x install.sh
|
||||
RUN ./install.sh
|
||||
ENV NVM_DIR=/home/frappe/.nvm
|
||||
|
||||
# Install node for Frappe V11, install yarn
|
||||
RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION_FRAPPEV11}
|
||||
RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION_FRAPPEV11} && npm install -g yarn
|
||||
# Install node for latest frappe, set as default, install node
|
||||
RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION}
|
||||
RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION} && npm install -g yarn
|
||||
RUN . "$NVM_DIR/nvm.sh" && nvm alias default v${NODE_VERSION}
|
||||
ENV PATH="/home/frappe/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"
|
||||
|
||||
# Print version and verify bashrc is properly sourced so that everything works in the Dockerfile
|
||||
RUN node --version \
|
||||
&& npm --version \
|
||||
&& yarn --version
|
||||
# Print version and verify bashrc is properly sourced so that everything works in the interactive shell
|
||||
RUN bash -c "node --version" \
|
||||
&& bash -c "npm --version" \
|
||||
&& bash -c "yarn --version"
|
||||
|
||||
EXPOSE 8000-8005 9000-9005 6787
|
64
build/common/commands/auto_migrate.py
Normal file
64
build/common/commands/auto_migrate.py
Normal file
@ -0,0 +1,64 @@
|
||||
import os
|
||||
import semantic_version
|
||||
import git
|
||||
|
||||
from migrate import migrate_sites
|
||||
from utils import (
|
||||
save_version_file,
|
||||
get_apps,
|
||||
get_container_versions,
|
||||
get_version_file,
|
||||
get_config
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
is_ready = False
|
||||
apps = get_apps()
|
||||
|
||||
container_versions = get_container_versions(apps)
|
||||
|
||||
version_file = get_version_file()
|
||||
|
||||
if not version_file:
|
||||
version_file = container_versions
|
||||
save_version_file(version_file)
|
||||
|
||||
for app in apps:
|
||||
container_version = None
|
||||
file_version = None
|
||||
version_file_hash = None
|
||||
container_hash = None
|
||||
|
||||
repo = git.Repo(os.path.join('..', 'apps', app))
|
||||
branch = repo.active_branch.name
|
||||
|
||||
if branch == 'develop':
|
||||
version_file_hash = version_file.get(app+'_git_hash')
|
||||
container_hash = container_versions.get(app+'_git_hash')
|
||||
if container_hash and version_file_hash:
|
||||
if container_hash != version_file_hash:
|
||||
is_ready = True
|
||||
break
|
||||
|
||||
if version_file.get(app):
|
||||
file_version = semantic_version.Version(version_file.get(app))
|
||||
|
||||
if container_versions.get(app):
|
||||
container_version = semantic_version.Version(container_versions.get(app))
|
||||
|
||||
if file_version and container_version:
|
||||
if container_version > file_version:
|
||||
is_ready = True
|
||||
break
|
||||
|
||||
config = get_config()
|
||||
|
||||
if is_ready and config.get('maintenance_mode') != 1:
|
||||
migrate_sites(maintenance_mode=True)
|
||||
version_file = container_versions
|
||||
save_version_file(version_file)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
11
build/common/commands/background.py
Normal file
11
build/common/commands/background.py
Normal file
@ -0,0 +1,11 @@
|
||||
from frappe.utils.scheduler import start_scheduler
|
||||
|
||||
|
||||
def main():
|
||||
print("Starting background scheduler . . .")
|
||||
start_scheduler()
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
39
build/common/commands/backup.py
Normal file
39
build/common/commands/backup.py
Normal file
@ -0,0 +1,39 @@
|
||||
import os
|
||||
import frappe
|
||||
from frappe.utils.backups import scheduled_backup
|
||||
from frappe.utils import cint, get_sites, now
|
||||
|
||||
|
||||
def backup(sites, with_files=False):
|
||||
for site in sites:
|
||||
frappe.init(site)
|
||||
frappe.connect()
|
||||
odb = scheduled_backup(
|
||||
ignore_files=not with_files,
|
||||
backup_path_db=None,
|
||||
backup_path_files=None,
|
||||
backup_path_private_files=None,
|
||||
force=True
|
||||
)
|
||||
print("database backup taken -", odb.backup_path_db, "- on", now())
|
||||
if with_files:
|
||||
print("files backup taken -", odb.backup_path_files, "- on", now())
|
||||
print("private files backup taken -", odb.backup_path_private_files, "- on", now())
|
||||
frappe.destroy()
|
||||
|
||||
|
||||
def main():
|
||||
installed_sites = ":".join(get_sites())
|
||||
sites = os.environ.get("SITES", installed_sites).split(":")
|
||||
with_files = cint(os.environ.get("WITH_FILES"))
|
||||
|
||||
backup(sites, with_files)
|
||||
|
||||
if frappe.redis_server:
|
||||
frappe.redis_server.connection_pool.disconnect()
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
130
build/common/commands/check_connection.py
Normal file
130
build/common/commands/check_connection.py
Normal file
@ -0,0 +1,130 @@
|
||||
import socket
|
||||
import time
|
||||
from six.moves.urllib.parse import urlparse
|
||||
from utils import get_config
|
||||
from constants import (
|
||||
REDIS_QUEUE_KEY,
|
||||
REDIS_CACHE_KEY,
|
||||
REDIS_SOCKETIO_KEY,
|
||||
DB_HOST_KEY,
|
||||
DB_PORT_KEY,
|
||||
DB_PORT
|
||||
)
|
||||
|
||||
|
||||
def is_open(ip, port, timeout=30):
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(timeout)
|
||||
try:
|
||||
s.connect((ip, int(port)))
|
||||
s.shutdown(socket.SHUT_RDWR)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def check_host(ip, port, retry=10, delay=3, print_attempt=True):
|
||||
ipup = False
|
||||
for i in range(retry):
|
||||
if print_attempt:
|
||||
print("Attempt {i} to connect to {ip}:{port}".format(ip=ip, port=port, i=i+1))
|
||||
if is_open(ip, port):
|
||||
ipup = True
|
||||
break
|
||||
else:
|
||||
time.sleep(delay)
|
||||
return ipup
|
||||
|
||||
|
||||
# Check service
|
||||
def check_service(
|
||||
retry=10,
|
||||
delay=3,
|
||||
print_attempt=True,
|
||||
service_name=None,
|
||||
service_port=None):
|
||||
|
||||
config = get_config()
|
||||
if not service_name:
|
||||
service_name = config.get(DB_HOST_KEY, 'mariadb')
|
||||
if not service_port:
|
||||
service_port = config.get(DB_PORT_KEY, DB_PORT)
|
||||
|
||||
is_db_connected = False
|
||||
is_db_connected = check_host(
|
||||
service_name,
|
||||
service_port,
|
||||
retry,
|
||||
delay,
|
||||
print_attempt)
|
||||
if not is_db_connected:
|
||||
print("Connection to {service_name}:{service_port} timed out".format(
|
||||
service_name=service_name,
|
||||
service_port=service_port,
|
||||
))
|
||||
exit(1)
|
||||
|
||||
|
||||
# Check redis queue
|
||||
def check_redis_queue(retry=10, delay=3, print_attempt=True):
|
||||
check_redis_queue = False
|
||||
config = get_config()
|
||||
redis_queue_url = urlparse(config.get(REDIS_QUEUE_KEY, "redis://redis-queue:6379")).netloc
|
||||
redis_queue, redis_queue_port = redis_queue_url.split(":")
|
||||
check_redis_queue = check_host(
|
||||
redis_queue,
|
||||
redis_queue_port,
|
||||
retry,
|
||||
delay,
|
||||
print_attempt)
|
||||
if not check_redis_queue:
|
||||
print("Connection to redis queue timed out")
|
||||
exit(1)
|
||||
|
||||
|
||||
# Check redis cache
|
||||
def check_redis_cache(retry=10, delay=3, print_attempt=True):
|
||||
check_redis_cache = False
|
||||
config = get_config()
|
||||
redis_cache_url = urlparse(config.get(REDIS_CACHE_KEY, "redis://redis-cache:6379")).netloc
|
||||
redis_cache, redis_cache_port = redis_cache_url.split(":")
|
||||
check_redis_cache = check_host(
|
||||
redis_cache,
|
||||
redis_cache_port,
|
||||
retry,
|
||||
delay,
|
||||
print_attempt)
|
||||
if not check_redis_cache:
|
||||
print("Connection to redis cache timed out")
|
||||
exit(1)
|
||||
|
||||
|
||||
# Check redis socketio
|
||||
def check_redis_socketio(retry=10, delay=3, print_attempt=True):
|
||||
check_redis_socketio = False
|
||||
config = get_config()
|
||||
redis_socketio_url = urlparse(config.get(REDIS_SOCKETIO_KEY, "redis://redis-socketio:6379")).netloc
|
||||
redis_socketio, redis_socketio_port = redis_socketio_url.split(":")
|
||||
check_redis_socketio = check_host(
|
||||
redis_socketio,
|
||||
redis_socketio_port,
|
||||
retry,
|
||||
delay,
|
||||
print_attempt)
|
||||
if not check_redis_socketio:
|
||||
print("Connection to redis socketio timed out")
|
||||
exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
check_service()
|
||||
check_redis_queue()
|
||||
check_redis_cache()
|
||||
check_redis_socketio()
|
||||
print('Connections OK')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
32
build/common/commands/console.py
Normal file
32
build/common/commands/console.py
Normal file
@ -0,0 +1,32 @@
|
||||
import sys
|
||||
import frappe
|
||||
import IPython
|
||||
|
||||
from frappe.utils import get_sites
|
||||
|
||||
|
||||
def console(site):
|
||||
"Start ipython console for a site"
|
||||
if site not in get_sites():
|
||||
print("Site {0} does not exist on the current bench".format(site))
|
||||
return
|
||||
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
frappe.local.lang = frappe.db.get_default("lang")
|
||||
all_apps = frappe.get_installed_apps()
|
||||
for app in all_apps:
|
||||
locals()[app] = __import__(app)
|
||||
print("Apps in this namespace:\n{}".format(", ".join(all_apps)))
|
||||
IPython.embed(display_banner="", header="")
|
||||
|
||||
|
||||
def main():
|
||||
site = sys.argv[-1]
|
||||
console(site)
|
||||
if frappe.redis_server:
|
||||
frappe.redis_server.connection_pool.disconnect()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
13
build/common/commands/constants.py
Normal file
13
build/common/commands/constants.py
Normal file
@ -0,0 +1,13 @@
|
||||
REDIS_QUEUE_KEY = 'redis_queue'
|
||||
REDIS_CACHE_KEY = 'redis_cache'
|
||||
REDIS_SOCKETIO_KEY = 'redis_socketio'
|
||||
DB_HOST_KEY = 'db_host'
|
||||
DB_PORT_KEY = 'db_port'
|
||||
DB_PORT = 3306
|
||||
APP_VERSIONS_JSON_FILE = 'app_versions.json'
|
||||
APPS_TXT_FILE = 'apps.txt'
|
||||
COMMON_SITE_CONFIG_FILE = 'common_site_config.json'
|
||||
DATE_FORMAT = "%Y%m%d_%H%M%S"
|
||||
RDS_DB = 'rds_db'
|
||||
RDS_PRIVILEGES = "SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, CREATE VIEW, EVENT, TRIGGER, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EXECUTE, LOCK TABLES"
|
||||
ARCHIVE_SITES_PATH = '/home/frappe/frappe-bench/sites/archive_sites'
|
61
build/common/commands/doctor.py
Normal file
61
build/common/commands/doctor.py
Normal file
@ -0,0 +1,61 @@
|
||||
import argparse
|
||||
|
||||
from check_connection import (
|
||||
check_service,
|
||||
check_redis_cache,
|
||||
check_redis_queue,
|
||||
check_redis_socketio,
|
||||
)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'-p',
|
||||
'--ping-service',
|
||||
dest='ping_services',
|
||||
action='append',
|
||||
type=str,
|
||||
help='list of services to ping, e.g. doctor -p "postgres:5432" --ping-service "mariadb:3306"',
|
||||
)
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
check_service(retry=1, delay=0, print_attempt=False)
|
||||
print("Bench database Connected")
|
||||
check_redis_cache(retry=1, delay=0, print_attempt=False)
|
||||
print("Redis Cache Connected")
|
||||
check_redis_queue(retry=1, delay=0, print_attempt=False)
|
||||
print("Redis Queue Connected")
|
||||
check_redis_socketio(retry=1, delay=0, print_attempt=False)
|
||||
print("Redis SocketIO Connected")
|
||||
|
||||
if(args.ping_services):
|
||||
for service in args.ping_services:
|
||||
service_name = None
|
||||
service_port = None
|
||||
|
||||
try:
|
||||
service_name, service_port = service.split(':')
|
||||
except ValueError:
|
||||
print('Service should be in format host:port, e.g postgres:5432')
|
||||
exit(1)
|
||||
|
||||
check_service(
|
||||
retry=1,
|
||||
delay=0,
|
||||
print_attempt=False,
|
||||
service_name=service_name,
|
||||
service_port=service_port,
|
||||
)
|
||||
print("{0}:{1} Connected".format(service_name, service_port))
|
||||
|
||||
print("Health check successful")
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
39
build/common/commands/drop.py
Normal file
39
build/common/commands/drop.py
Normal file
@ -0,0 +1,39 @@
|
||||
import os
|
||||
import frappe
|
||||
|
||||
from frappe.commands.site import _drop_site
|
||||
from constants import ARCHIVE_SITES_PATH
|
||||
from utils import get_password
|
||||
|
||||
|
||||
def main():
|
||||
site_name = os.environ.get("SITE_NAME", 'site1.localhost')
|
||||
db_root_username = os.environ.get("DB_ROOT_USER", 'root')
|
||||
mariadb_root_password = get_password("MYSQL_ROOT_PASSWORD", 'admin')
|
||||
postgres_root_password = get_password("POSTGRES_PASSWORD")
|
||||
db_root_password = mariadb_root_password
|
||||
|
||||
if postgres_root_password:
|
||||
db_root_password = postgres_root_password
|
||||
|
||||
force = True if os.environ.get("FORCE", None) else False
|
||||
no_backup = True if os.environ.get("NO_BACKUP", None) else False
|
||||
frappe.init(site_name, new_site=True)
|
||||
|
||||
_drop_site(
|
||||
site=site_name,
|
||||
root_login=db_root_username,
|
||||
root_password=db_root_password,
|
||||
archived_sites_path=ARCHIVE_SITES_PATH,
|
||||
force=force,
|
||||
no_backup=no_backup
|
||||
)
|
||||
|
||||
if frappe.redis_server:
|
||||
frappe.redis_server.connection_pool.disconnect()
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
2
build/common/commands/gevent_patch.py
Normal file
2
build/common/commands/gevent_patch.py
Normal file
@ -0,0 +1,2 @@
|
||||
import gevent.monkey
|
||||
gevent.monkey.patch_all()
|
51
build/common/commands/migrate.py
Normal file
51
build/common/commands/migrate.py
Normal file
@ -0,0 +1,51 @@
|
||||
import os
|
||||
import frappe
|
||||
|
||||
from frappe.utils import cint, get_sites
|
||||
from utils import get_config, save_config
|
||||
|
||||
|
||||
def set_maintenance_mode(enable=True):
|
||||
conf = get_config()
|
||||
|
||||
if enable:
|
||||
conf.update({"maintenance_mode": 1, "pause_scheduler": 1})
|
||||
save_config(conf)
|
||||
|
||||
if not enable:
|
||||
conf.update({"maintenance_mode": 0, "pause_scheduler": 0})
|
||||
save_config(conf)
|
||||
|
||||
|
||||
def migrate_sites(maintenance_mode=False):
|
||||
installed_sites = ":".join(get_sites())
|
||||
sites = os.environ.get("SITES", installed_sites).split(":")
|
||||
if not maintenance_mode:
|
||||
maintenance_mode = cint(os.environ.get("MAINTENANCE_MODE"))
|
||||
|
||||
if maintenance_mode:
|
||||
set_maintenance_mode(True)
|
||||
|
||||
for site in sites:
|
||||
print('Migrating', site)
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
try:
|
||||
from frappe.migrate import migrate
|
||||
migrate()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
# Disable maintenance mode after migration
|
||||
set_maintenance_mode(False)
|
||||
|
||||
|
||||
def main():
|
||||
migrate_sites()
|
||||
if frappe.redis_server:
|
||||
frappe.redis_server.connection_pool.disconnect()
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
141
build/common/commands/new.py
Normal file
141
build/common/commands/new.py
Normal file
@ -0,0 +1,141 @@
|
||||
import os
|
||||
import frappe
|
||||
import semantic_version
|
||||
|
||||
from frappe.installer import update_site_config
|
||||
from constants import COMMON_SITE_CONFIG_FILE, RDS_DB, RDS_PRIVILEGES
|
||||
from utils import (
|
||||
run_command,
|
||||
get_config,
|
||||
get_site_config,
|
||||
get_password,
|
||||
)
|
||||
|
||||
# try to import _new_site from frappe, which could possibly
|
||||
# exist in either commands.py or installer.py, and so we need
|
||||
# to maintain compatibility across all frappe versions.
|
||||
try:
|
||||
# <= version-{11,12}
|
||||
from frappe.commands.site import _new_site
|
||||
except ImportError:
|
||||
# >= version-13 and develop
|
||||
from frappe.installer import _new_site
|
||||
|
||||
|
||||
def main():
|
||||
config = get_config()
|
||||
db_type = "mariadb"
|
||||
db_port = config.get("db_port", 3306)
|
||||
db_host = config.get("db_host")
|
||||
site_name = os.environ.get("SITE_NAME", "site1.localhost")
|
||||
db_root_username = os.environ.get("DB_ROOT_USER", "root")
|
||||
mariadb_root_password = get_password("MYSQL_ROOT_PASSWORD", "admin")
|
||||
postgres_root_password = get_password("POSTGRES_PASSWORD")
|
||||
db_root_password = mariadb_root_password
|
||||
|
||||
if postgres_root_password:
|
||||
db_type = "postgres"
|
||||
db_host = os.environ.get("POSTGRES_HOST")
|
||||
db_port = 5432
|
||||
db_root_password = postgres_root_password
|
||||
if not db_host:
|
||||
db_host = config.get("db_host")
|
||||
print("Environment variable POSTGRES_HOST not found.")
|
||||
print("Using db_host from common_site_config.json")
|
||||
|
||||
sites_path = os.getcwd()
|
||||
common_site_config_path = os.path.join(sites_path, COMMON_SITE_CONFIG_FILE)
|
||||
update_site_config(
|
||||
"root_login",
|
||||
db_root_username,
|
||||
validate=False,
|
||||
site_config_path=common_site_config_path,
|
||||
)
|
||||
update_site_config(
|
||||
"root_password",
|
||||
db_root_password,
|
||||
validate=False,
|
||||
site_config_path=common_site_config_path,
|
||||
)
|
||||
|
||||
force = True if os.environ.get("FORCE", None) else False
|
||||
install_apps = os.environ.get("INSTALL_APPS", None)
|
||||
install_apps = install_apps.split(",") if install_apps else []
|
||||
frappe.init(site_name, new_site=True)
|
||||
|
||||
if semantic_version.Version(frappe.__version__).major > 11:
|
||||
_new_site(
|
||||
None,
|
||||
site_name,
|
||||
mariadb_root_username=db_root_username,
|
||||
mariadb_root_password=db_root_password,
|
||||
admin_password=get_password("ADMIN_PASSWORD", "admin"),
|
||||
verbose=True,
|
||||
install_apps=install_apps,
|
||||
source_sql=None,
|
||||
force=force,
|
||||
db_type=db_type,
|
||||
reinstall=False,
|
||||
db_host=db_host,
|
||||
db_port=db_port,
|
||||
)
|
||||
else:
|
||||
_new_site(
|
||||
None,
|
||||
site_name,
|
||||
mariadb_root_username=db_root_username,
|
||||
mariadb_root_password=db_root_password,
|
||||
admin_password=get_password("ADMIN_PASSWORD", "admin"),
|
||||
verbose=True,
|
||||
install_apps=install_apps,
|
||||
source_sql=None,
|
||||
force=force,
|
||||
reinstall=False,
|
||||
)
|
||||
|
||||
if db_type == "mariadb":
|
||||
site_config = get_site_config(site_name)
|
||||
db_name = site_config.get("db_name")
|
||||
db_password = site_config.get("db_password")
|
||||
|
||||
mysql_command = [
|
||||
"mysql",
|
||||
"-h{db_host}".format(db_host=db_host),
|
||||
"-u{db_root_username}".format(db_root_username=db_root_username),
|
||||
"-p{mariadb_root_password}".format(mariadb_root_password=mariadb_root_password),
|
||||
"-e",
|
||||
]
|
||||
|
||||
# Drop User if exists
|
||||
command = mysql_command + [
|
||||
"DROP USER IF EXISTS '{db_name}'; FLUSH PRIVILEGES;".format(db_name=db_name)
|
||||
]
|
||||
run_command(command)
|
||||
|
||||
# Grant permission to database and set password
|
||||
grant_privileges = "ALL PRIVILEGES"
|
||||
|
||||
# for Amazon RDS
|
||||
if config.get(RDS_DB) or site_config.get(RDS_DB):
|
||||
grant_privileges = RDS_PRIVILEGES
|
||||
|
||||
command = mysql_command + [
|
||||
"\
|
||||
CREATE USER IF NOT EXISTS '{db_name}'@'%' IDENTIFIED BY '{db_password}'; \
|
||||
GRANT {grant_privileges} ON `{db_name}`.* TO '{db_name}'@'%'; \
|
||||
FLUSH PRIVILEGES;".format(
|
||||
db_name=db_name,
|
||||
db_password=db_password,
|
||||
grant_privileges=grant_privileges,
|
||||
)
|
||||
]
|
||||
run_command(command)
|
||||
|
||||
if frappe.redis_server:
|
||||
frappe.redis_server.connection_pool.disconnect()
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
156
build/common/commands/push_backup.py
Normal file
156
build/common/commands/push_backup.py
Normal file
@ -0,0 +1,156 @@
|
||||
import os
|
||||
import time
|
||||
import boto3
|
||||
|
||||
import datetime
|
||||
from glob import glob
|
||||
from frappe.utils import get_sites
|
||||
from constants import DATE_FORMAT
|
||||
from utils import (
|
||||
get_s3_config,
|
||||
upload_file_to_s3,
|
||||
check_s3_environment_variables,
|
||||
)
|
||||
|
||||
|
||||
def get_file_ext():
|
||||
return {
|
||||
"database": "-database.sql.gz",
|
||||
"private_files": "-private-files.tar",
|
||||
"public_files": "-files.tar",
|
||||
"site_config": "-site_config_backup.json"
|
||||
}
|
||||
|
||||
|
||||
def get_backup_details(sitename):
|
||||
backup_details = dict()
|
||||
file_ext = get_file_ext()
|
||||
|
||||
# add trailing slash https://stackoverflow.com/a/15010678
|
||||
site_backup_path = os.path.join(os.getcwd(), sitename, "private", "backups", "")
|
||||
|
||||
if os.path.exists(site_backup_path):
|
||||
for filetype, ext in file_ext.items():
|
||||
site_slug = sitename.replace('.', '_')
|
||||
pattern = site_backup_path + '*-' + site_slug + ext
|
||||
backup_files = list(filter(os.path.isfile, glob(pattern)))
|
||||
|
||||
if len(backup_files) > 0:
|
||||
backup_files.sort(key=lambda file: os.stat(os.path.join(site_backup_path, file)).st_ctime)
|
||||
backup_date = datetime.datetime.strptime(time.ctime(os.path.getmtime(backup_files[0])), "%a %b %d %H:%M:%S %Y")
|
||||
backup_details[filetype] = {
|
||||
"sitename": sitename,
|
||||
"file_size_in_bytes": os.stat(backup_files[-1]).st_size,
|
||||
"file_path": os.path.abspath(backup_files[-1]),
|
||||
"filename": os.path.basename(backup_files[-1]),
|
||||
"backup_date": backup_date.date().strftime("%Y-%m-%d %H:%M:%S")
|
||||
}
|
||||
|
||||
return backup_details
|
||||
|
||||
|
||||
def delete_old_backups(limit, bucket, site_name):
|
||||
all_backups = list()
|
||||
all_backup_dates = list()
|
||||
backup_limit = int(limit)
|
||||
check_s3_environment_variables()
|
||||
bucket_dir = os.environ.get('BUCKET_DIR')
|
||||
oldest_backup_date = None
|
||||
|
||||
s3 = boto3.resource(
|
||||
's3',
|
||||
region_name=os.environ.get('REGION'),
|
||||
aws_access_key_id=os.environ.get('ACCESS_KEY_ID'),
|
||||
aws_secret_access_key=os.environ.get('SECRET_ACCESS_KEY'),
|
||||
endpoint_url=os.environ.get('ENDPOINT_URL')
|
||||
)
|
||||
|
||||
bucket = s3.Bucket(bucket)
|
||||
objects = bucket.meta.client.list_objects_v2(
|
||||
Bucket=bucket.name,
|
||||
Delimiter='/')
|
||||
|
||||
if objects:
|
||||
for obj in objects.get('CommonPrefixes'):
|
||||
if obj.get('Prefix') == bucket_dir + '/':
|
||||
for backup_obj in bucket.objects.filter(Prefix=obj.get('Prefix')):
|
||||
if backup_obj.get()["ContentType"] == "application/x-directory":
|
||||
continue
|
||||
try:
|
||||
# backup_obj.key is bucket_dir/site/date_time/backupfile.extension
|
||||
bucket_dir, site_slug, date_time, backupfile = backup_obj.key.split('/')
|
||||
date_time_object = datetime.datetime.strptime(
|
||||
date_time, DATE_FORMAT
|
||||
)
|
||||
|
||||
if site_name in backup_obj.key:
|
||||
all_backup_dates.append(date_time_object)
|
||||
all_backups.append(backup_obj.key)
|
||||
except IndexError as error:
|
||||
print(error)
|
||||
exit(1)
|
||||
|
||||
if len(all_backup_dates) > 0:
|
||||
oldest_backup_date = min(all_backup_dates)
|
||||
|
||||
if len(all_backups) / 3 > backup_limit:
|
||||
oldest_backup = None
|
||||
for backup in all_backups:
|
||||
try:
|
||||
# backup is bucket_dir/site/date_time/backupfile.extension
|
||||
backup_dir, site_slug, backup_dt_string, filename = backup.split('/')
|
||||
backup_datetime = datetime.datetime.strptime(
|
||||
backup_dt_string, DATE_FORMAT
|
||||
)
|
||||
if backup_datetime == oldest_backup_date:
|
||||
oldest_backup = backup
|
||||
|
||||
except IndexError as error:
|
||||
print(error)
|
||||
exit(1)
|
||||
|
||||
if oldest_backup:
|
||||
for obj in bucket.objects.filter(Prefix=oldest_backup):
|
||||
# delete all keys that are inside the oldest_backup
|
||||
if bucket_dir in obj.key:
|
||||
print('Deleteing ' + obj.key)
|
||||
s3.Object(bucket.name, obj.key).delete()
|
||||
|
||||
|
||||
def main():
|
||||
details = dict()
|
||||
sites = get_sites()
|
||||
conn, bucket = get_s3_config()
|
||||
|
||||
for site in sites:
|
||||
details = get_backup_details(site)
|
||||
db_file = details.get('database', {}).get('file_path')
|
||||
folder = os.environ.get('BUCKET_DIR') + '/' + site + '/'
|
||||
if db_file:
|
||||
folder = os.environ.get('BUCKET_DIR') + '/' + site + '/' + os.path.basename(db_file)[:15] + '/'
|
||||
upload_file_to_s3(db_file, folder, conn, bucket)
|
||||
|
||||
# Archive site_config.json
|
||||
site_config_file = details.get('site_config', {}).get('file_path')
|
||||
if not site_config_file:
|
||||
site_config_file = os.path.join(os.getcwd(), site, 'site_config.json')
|
||||
upload_file_to_s3(site_config_file, folder, conn, bucket)
|
||||
|
||||
public_files = details.get('public_files', {}).get('file_path')
|
||||
if public_files:
|
||||
folder = os.environ.get('BUCKET_DIR') + '/' + site + '/' + os.path.basename(public_files)[:15] + '/'
|
||||
upload_file_to_s3(public_files, folder, conn, bucket)
|
||||
|
||||
private_files = details.get('private_files', {}).get('file_path')
|
||||
if private_files:
|
||||
folder = os.environ.get('BUCKET_DIR') + '/' + site + '/' + os.path.basename(private_files)[:15] + '/'
|
||||
upload_file_to_s3(private_files, folder, conn, bucket)
|
||||
|
||||
delete_old_backups(os.environ.get('BACKUP_LIMIT', '3'), bucket, site)
|
||||
|
||||
print('push-backup complete')
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
256
build/common/commands/restore_backup.py
Normal file
256
build/common/commands/restore_backup.py
Normal file
@ -0,0 +1,256 @@
|
||||
import os
|
||||
import datetime
|
||||
import tarfile
|
||||
import hashlib
|
||||
import frappe
|
||||
import boto3
|
||||
|
||||
from frappe.utils import get_sites, random_string
|
||||
from frappe.installer import (
|
||||
make_conf,
|
||||
get_conf_params,
|
||||
make_site_dirs,
|
||||
update_site_config,
|
||||
)
|
||||
from constants import COMMON_SITE_CONFIG_FILE, DATE_FORMAT, RDS_DB, RDS_PRIVILEGES
|
||||
from utils import (
|
||||
run_command,
|
||||
list_directories,
|
||||
set_key_in_site_config,
|
||||
get_site_config,
|
||||
get_config,
|
||||
get_password,
|
||||
check_s3_environment_variables,
|
||||
)
|
||||
|
||||
|
||||
def get_backup_dir():
|
||||
return os.path.join(os.path.expanduser("~"), "backups")
|
||||
|
||||
|
||||
def decompress_db(database_file, site):
|
||||
command = ["gunzip", "-c", database_file]
|
||||
with open(database_file.replace(".gz", ""), "w") as db_file:
|
||||
print("Extract Database GZip for site {}".format(site))
|
||||
run_command(command, stdout=db_file)
|
||||
|
||||
|
||||
def restore_database(files_base, site_config_path, site):
|
||||
# restore database
|
||||
database_file = files_base + "-database.sql.gz"
|
||||
decompress_db(database_file, site)
|
||||
config = get_config()
|
||||
|
||||
# Set db_type if it exists in backup site_config.json
|
||||
set_key_in_site_config("db_type", site, site_config_path)
|
||||
# Set db_host if it exists in backup site_config.json
|
||||
set_key_in_site_config("db_host", site, site_config_path)
|
||||
# Set db_port if it exists in backup site_config.json
|
||||
set_key_in_site_config("db_port", site, site_config_path)
|
||||
|
||||
# get updated site_config
|
||||
site_config = get_site_config(site)
|
||||
|
||||
# if no db_type exists, default to mariadb
|
||||
db_type = site_config.get("db_type", "mariadb")
|
||||
is_database_restored = False
|
||||
|
||||
if db_type == "mariadb":
|
||||
restore_mariadb(
|
||||
config=config, site_config=site_config, database_file=database_file
|
||||
)
|
||||
is_database_restored = True
|
||||
|
||||
if is_database_restored:
|
||||
# Set encryption_key if it exists in backup site_config.json
|
||||
set_key_in_site_config("encryption_key", site, site_config_path)
|
||||
|
||||
|
||||
def restore_files(files_base):
|
||||
public_files = files_base + "-files.tar"
|
||||
# extract tar
|
||||
public_tar = tarfile.open(public_files)
|
||||
print("Extracting {}".format(public_files))
|
||||
public_tar.extractall()
|
||||
|
||||
|
||||
def restore_private_files(files_base):
|
||||
private_files = files_base + "-private-files.tar"
|
||||
private_tar = tarfile.open(private_files)
|
||||
print("Extracting {}".format(private_files))
|
||||
private_tar.extractall()
|
||||
|
||||
|
||||
def pull_backup_from_s3():
|
||||
check_s3_environment_variables()
|
||||
|
||||
# https://stackoverflow.com/a/54672690
|
||||
s3 = boto3.resource(
|
||||
"s3",
|
||||
region_name=os.environ.get("REGION"),
|
||||
aws_access_key_id=os.environ.get("ACCESS_KEY_ID"),
|
||||
aws_secret_access_key=os.environ.get("SECRET_ACCESS_KEY"),
|
||||
endpoint_url=os.environ.get("ENDPOINT_URL"),
|
||||
)
|
||||
|
||||
bucket_dir = os.environ.get("BUCKET_DIR")
|
||||
bucket_name = os.environ.get("BUCKET_NAME")
|
||||
bucket = s3.Bucket(bucket_name)
|
||||
|
||||
# Change directory to /home/frappe/backups
|
||||
os.chdir(get_backup_dir())
|
||||
|
||||
backup_files = []
|
||||
sites = set()
|
||||
site_timestamps = set()
|
||||
download_backups = []
|
||||
|
||||
for obj in bucket.objects.filter(Prefix=bucket_dir):
|
||||
if obj.get()["ContentType"] == "application/x-directory":
|
||||
continue
|
||||
backup_file = obj.key.replace(os.path.join(bucket_dir, ""), "")
|
||||
backup_files.append(backup_file)
|
||||
site_name, timestamp, backup_type = backup_file.split("/")
|
||||
site_timestamp = site_name + "/" + timestamp
|
||||
sites.add(site_name)
|
||||
site_timestamps.add(site_timestamp)
|
||||
|
||||
# sort sites for latest backups
|
||||
for site in sites:
|
||||
backup_timestamps = []
|
||||
for site_timestamp in site_timestamps:
|
||||
site_name, timestamp = site_timestamp.split("/")
|
||||
if site == site_name:
|
||||
timestamp_datetime = datetime.datetime.strptime(timestamp, DATE_FORMAT)
|
||||
backup_timestamps.append(timestamp)
|
||||
download_backups.append(site + "/" + max(backup_timestamps))
|
||||
|
||||
# Only download latest backups
|
||||
for backup_file in backup_files:
|
||||
for backup in download_backups:
|
||||
if backup in backup_file:
|
||||
if not os.path.exists(os.path.dirname(backup_file)):
|
||||
os.makedirs(os.path.dirname(backup_file))
|
||||
print("Downloading {}".format(backup_file))
|
||||
bucket.download_file(bucket_dir + "/" + backup_file, backup_file)
|
||||
|
||||
os.chdir(os.path.join(os.path.expanduser("~"), "frappe-bench", "sites"))
|
||||
|
||||
|
||||
def restore_mariadb(config, site_config, database_file):
|
||||
db_root_password = get_password("MYSQL_ROOT_PASSWORD")
|
||||
if not db_root_password:
|
||||
print("Variable MYSQL_ROOT_PASSWORD not set")
|
||||
exit(1)
|
||||
|
||||
db_root_user = os.environ.get("DB_ROOT_USER", "root")
|
||||
|
||||
db_host = site_config.get("db_host", config.get("db_host"))
|
||||
db_port = site_config.get("db_port", config.get("db_port", 3306))
|
||||
db_name = site_config.get("db_name")
|
||||
db_password = site_config.get("db_password")
|
||||
|
||||
# mysql command prefix
|
||||
mysql_command = [
|
||||
"mysql",
|
||||
"-u{db_root_user}".format(db_root_user=db_root_user),
|
||||
"-h{db_host}".format(db_host=db_host),
|
||||
"-p{db_root_password}".format(db_root_password=db_root_password),
|
||||
"-P{db_port}".format(db_port=db_port),
|
||||
]
|
||||
|
||||
# drop db if exists for clean restore
|
||||
drop_database = mysql_command + ["-e", "DROP DATABASE IF EXISTS `{db_name}`;".format(db_name=db_name)]
|
||||
run_command(drop_database)
|
||||
|
||||
# create db
|
||||
create_database = mysql_command + [
|
||||
"-e",
|
||||
"CREATE DATABASE IF NOT EXISTS `{db_name}`;".format(db_name=db_name),
|
||||
]
|
||||
run_command(create_database)
|
||||
|
||||
# create user
|
||||
create_user = mysql_command + [
|
||||
"-e",
|
||||
"CREATE USER IF NOT EXISTS '{db_name}'@'%' IDENTIFIED BY '{db_password}'; FLUSH PRIVILEGES;".format(
|
||||
db_name=db_name,
|
||||
db_password=db_password,
|
||||
),
|
||||
]
|
||||
run_command(create_user)
|
||||
|
||||
# grant db privileges to user
|
||||
|
||||
grant_privileges = "ALL PRIVILEGES"
|
||||
|
||||
# for Amazon RDS
|
||||
if config.get(RDS_DB) or site_config.get(RDS_DB):
|
||||
grant_privileges = RDS_PRIVILEGES
|
||||
|
||||
grant_privileges_command = mysql_command + [
|
||||
"-e",
|
||||
"GRANT {grant_privileges} ON `{db_name}`.* TO '{db_name}'@'%' IDENTIFIED BY '{db_password}'; FLUSH PRIVILEGES;".format(
|
||||
grant_privileges=grant_privileges,
|
||||
db_name=db_name,
|
||||
db_password=db_password,
|
||||
),
|
||||
]
|
||||
run_command(grant_privileges_command)
|
||||
|
||||
print("Restoring MariaDB")
|
||||
with open(database_file.replace(".gz", ""), "r") as db_file:
|
||||
run_command(mysql_command + ["{db_name}".format(db_name=db_name)], stdin=db_file)
|
||||
|
||||
|
||||
def main():
|
||||
backup_dir = get_backup_dir()
|
||||
|
||||
if len(list_directories(backup_dir)) == 0:
|
||||
pull_backup_from_s3()
|
||||
|
||||
for site in list_directories(backup_dir):
|
||||
site_slug = site.replace(".", "_")
|
||||
backups = [
|
||||
datetime.datetime.strptime(backup, DATE_FORMAT)
|
||||
for backup in list_directories(os.path.join(backup_dir, site))
|
||||
]
|
||||
latest_backup = max(backups).strftime(DATE_FORMAT)
|
||||
files_base = os.path.join(backup_dir, site, latest_backup, "")
|
||||
files_base += latest_backup + "-" + site_slug
|
||||
site_config_path = files_base + "-site_config_backup.json"
|
||||
if not os.path.exists(site_config_path):
|
||||
site_config_path = os.path.join(backup_dir, site, "site_config.json")
|
||||
if site in get_sites():
|
||||
print("Overwrite site {}".format(site))
|
||||
restore_database(files_base, site_config_path, site)
|
||||
restore_private_files(files_base)
|
||||
restore_files(files_base)
|
||||
else:
|
||||
site_config = get_conf_params(
|
||||
db_name="_" + hashlib.sha1(site.encode()).hexdigest()[:16],
|
||||
db_password=random_string(16),
|
||||
)
|
||||
|
||||
frappe.local.site = site
|
||||
frappe.local.sites_path = os.getcwd()
|
||||
frappe.local.site_path = os.getcwd() + "/" + site
|
||||
make_conf(
|
||||
db_name=site_config.get("db_name"),
|
||||
db_password=site_config.get("db_password"),
|
||||
)
|
||||
make_site_dirs()
|
||||
|
||||
print("Create site {}".format(site))
|
||||
restore_database(files_base, site_config_path, site)
|
||||
restore_private_files(files_base)
|
||||
restore_files(files_base)
|
||||
|
||||
if frappe.redis_server:
|
||||
frappe.redis_server.connection_pool.disconnect()
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
204
build/common/commands/utils.py
Normal file
204
build/common/commands/utils.py
Normal file
@ -0,0 +1,204 @@
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import boto3
|
||||
import git
|
||||
|
||||
from frappe.installer import update_site_config
|
||||
from constants import (
|
||||
APP_VERSIONS_JSON_FILE,
|
||||
APPS_TXT_FILE,
|
||||
COMMON_SITE_CONFIG_FILE
|
||||
)
|
||||
|
||||
def run_command(command, stdout=None, stdin=None, stderr=None):
|
||||
stdout = stdout or subprocess.PIPE
|
||||
stderr = stderr or subprocess.PIPE
|
||||
stdin = stdin or subprocess.PIPE
|
||||
process = subprocess.Popen(command, stdout=stdout, stdin=stdin, stderr=stderr)
|
||||
out, error = process.communicate()
|
||||
if process.returncode:
|
||||
print("Something went wrong:")
|
||||
print("return code: {returncode}".format(returncode=process.returncode))
|
||||
print("stdout:\n{out}".format(out=out))
|
||||
print("\nstderr:\n{error}".format(error=error))
|
||||
exit(process.returncode)
|
||||
|
||||
|
||||
def save_version_file(versions):
|
||||
with open(APP_VERSIONS_JSON_FILE, 'w') as f:
|
||||
return json.dump(versions, f, indent=1, sort_keys=True)
|
||||
|
||||
|
||||
def get_apps():
|
||||
apps = []
|
||||
try:
|
||||
with open(APPS_TXT_FILE) as apps_file:
|
||||
for app in apps_file.readlines():
|
||||
if app.strip():
|
||||
apps.append(app.strip())
|
||||
|
||||
except FileNotFoundError as exception:
|
||||
print(exception)
|
||||
exit(1)
|
||||
except Exception:
|
||||
print(APPS_TXT_FILE + " is not valid")
|
||||
exit(1)
|
||||
|
||||
return apps
|
||||
|
||||
|
||||
def get_container_versions(apps):
|
||||
versions = {}
|
||||
for app in apps:
|
||||
try:
|
||||
version = __import__(app).__version__
|
||||
versions.update({app: version})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
path = os.path.join('..', 'apps', app)
|
||||
repo = git.Repo(path)
|
||||
commit_hash = repo.head.object.hexsha
|
||||
versions.update({app+'_git_hash': commit_hash})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return versions
|
||||
|
||||
|
||||
def get_version_file():
|
||||
versions = None
|
||||
try:
|
||||
with open(APP_VERSIONS_JSON_FILE) as versions_file:
|
||||
versions = json.load(versions_file)
|
||||
except Exception:
|
||||
pass
|
||||
return versions
|
||||
|
||||
|
||||
def get_config():
|
||||
config = None
|
||||
try:
|
||||
with open(COMMON_SITE_CONFIG_FILE) as config_file:
|
||||
config = json.load(config_file)
|
||||
except FileNotFoundError as exception:
|
||||
print(exception)
|
||||
exit(1)
|
||||
except Exception:
|
||||
print(COMMON_SITE_CONFIG_FILE + " is not valid")
|
||||
exit(1)
|
||||
return config
|
||||
|
||||
|
||||
def get_site_config(site_name):
|
||||
site_config = None
|
||||
with open('{site_name}/site_config.json'.format(site_name=site_name)) as site_config_file:
|
||||
site_config = json.load(site_config_file)
|
||||
return site_config
|
||||
|
||||
|
||||
def save_config(config):
|
||||
with open(COMMON_SITE_CONFIG_FILE, 'w') as f:
|
||||
return json.dump(config, f, indent=1, sort_keys=True)
|
||||
|
||||
|
||||
def get_password(env_var, default=None):
|
||||
return os.environ.get(env_var) or get_password_from_secret("{env_var}_FILE".format(env_var=env_var)) or default
|
||||
|
||||
|
||||
def get_password_from_secret(env_var):
|
||||
"""Fetches the secret value from the docker secret file
|
||||
usually located inside /run/secrets/
|
||||
Arguments:
|
||||
env_var {str} -- Name of the environment variable
|
||||
containing the path to the secret file.
|
||||
Returns:
|
||||
[str] -- Secret value
|
||||
"""
|
||||
passwd = None
|
||||
secret_file_path = os.environ.get(env_var)
|
||||
if secret_file_path:
|
||||
with open(secret_file_path) as secret_file:
|
||||
passwd = secret_file.read().strip()
|
||||
|
||||
return passwd
|
||||
|
||||
|
||||
def get_s3_config():
|
||||
check_s3_environment_variables()
|
||||
bucket = os.environ.get('BUCKET_NAME')
|
||||
|
||||
conn = boto3.client(
|
||||
's3',
|
||||
region_name=os.environ.get('REGION'),
|
||||
aws_access_key_id=os.environ.get('ACCESS_KEY_ID'),
|
||||
aws_secret_access_key=os.environ.get('SECRET_ACCESS_KEY'),
|
||||
endpoint_url=os.environ.get('ENDPOINT_URL')
|
||||
)
|
||||
|
||||
return conn, bucket
|
||||
|
||||
|
||||
def upload_file_to_s3(filename, folder, conn, bucket):
|
||||
|
||||
destpath = os.path.join(folder, os.path.basename(filename))
|
||||
try:
|
||||
print("Uploading file:", filename)
|
||||
conn.upload_file(filename, bucket, destpath)
|
||||
|
||||
except Exception as e:
|
||||
print("Error uploading: %s" % (e))
|
||||
exit(1)
|
||||
|
||||
|
||||
def list_directories(path):
|
||||
directories = []
|
||||
for name in os.listdir(path):
|
||||
if os.path.isdir(os.path.join(path, name)):
|
||||
directories.append(name)
|
||||
return directories
|
||||
|
||||
|
||||
def get_site_config_from_path(site_config_path):
|
||||
site_config = dict()
|
||||
if os.path.exists(site_config_path):
|
||||
with open(site_config_path, 'r') as sc:
|
||||
site_config = json.load(sc)
|
||||
return site_config
|
||||
|
||||
|
||||
def set_key_in_site_config(key, site, site_config_path):
|
||||
site_config = get_site_config_from_path(site_config_path)
|
||||
value = site_config.get(key)
|
||||
if value:
|
||||
print('Set {key} in site config for site: {site}'.format(key=key, site=site))
|
||||
update_site_config(key, value,
|
||||
site_config_path=os.path.join(os.getcwd(), site, "site_config.json"))
|
||||
|
||||
|
||||
def check_s3_environment_variables():
|
||||
if 'BUCKET_NAME' not in os.environ:
|
||||
print('Variable BUCKET_NAME not set')
|
||||
exit(1)
|
||||
|
||||
if 'ACCESS_KEY_ID' not in os.environ:
|
||||
print('Variable ACCESS_KEY_ID not set')
|
||||
exit(1)
|
||||
|
||||
if 'SECRET_ACCESS_KEY' not in os.environ:
|
||||
print('Variable SECRET_ACCESS_KEY not set')
|
||||
exit(1)
|
||||
|
||||
if 'ENDPOINT_URL' not in os.environ:
|
||||
print('Variable ENDPOINT_URL not set')
|
||||
exit(1)
|
||||
|
||||
if 'BUCKET_DIR' not in os.environ:
|
||||
print('Variable BUCKET_DIR not set')
|
||||
exit(1)
|
||||
|
||||
if 'REGION' not in os.environ:
|
||||
print('Variable REGION not set')
|
||||
exit(1)
|
12
build/common/commands/worker.py
Normal file
12
build/common/commands/worker.py
Normal file
@ -0,0 +1,12 @@
|
||||
import os
|
||||
from frappe.utils.background_jobs import start_worker
|
||||
|
||||
|
||||
def main():
|
||||
queue = os.environ.get("WORKER_TYPE", "default")
|
||||
start_worker(queue, False)
|
||||
exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
8
build/common/common_site_config.json.template
Executable file
8
build/common/common_site_config.json.template
Executable file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"db_host": "${DB_HOST}",
|
||||
"db_port": ${DB_PORT},
|
||||
"redis_cache": "redis://${REDIS_CACHE}",
|
||||
"redis_queue": "redis://${REDIS_QUEUE}",
|
||||
"redis_socketio": "redis://${REDIS_SOCKETIO}",
|
||||
"socketio_port": ${SOCKETIO_PORT}
|
||||
}
|
118
build/common/nginx-default.conf.template
Normal file
118
build/common/nginx-default.conf.template
Normal file
@ -0,0 +1,118 @@
|
||||
upstream frappe-server {
|
||||
server ${FRAPPE_PY}:${FRAPPE_PY_PORT} fail_timeout=0;
|
||||
}
|
||||
|
||||
upstream socketio-server {
|
||||
server ${FRAPPE_SOCKETIO}:${SOCKETIO_PORT} 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 80;
|
||||
server_name $http_host;
|
||||
root /var/www/html;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
|
||||
# Define ${UPSTREAM_REAL_IP_ADDRESS} as our trusted upstream address, so we will be using
|
||||
# its ${UPSTREAM_REAL_IP_HEADER} address as our remote address
|
||||
set_real_ip_from ${UPSTREAM_REAL_IP_ADDRESS};
|
||||
real_ip_header ${UPSTREAM_REAL_IP_HEADER};
|
||||
real_ip_recursive ${UPSTREAM_REAL_IP_RECURSIVE};
|
||||
|
||||
location /assets {
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location ~ ^/protected/(.*) {
|
||||
internal;
|
||||
try_files /sites/$http_host/$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 Origin $proxy_x_forwarded_proto://$http_host;
|
||||
proxy_set_header X-Frappe-Site-Name ${FRAPPE_SITE_NAME_HEADER};
|
||||
proxy_set_header Host ${HTTP_HOST};
|
||||
|
||||
proxy_pass http://socketio-server;
|
||||
}
|
||||
|
||||
location / {
|
||||
rewrite ^(.+)/$ $1 permanent;
|
||||
rewrite ^(.+)/index\.html$ $1 permanent;
|
||||
rewrite ^(.+)\.html$ $1 permanent;
|
||||
|
||||
location ~ ^/files/.*.(htm|html|svg|xml) {
|
||||
add_header Content-disposition "attachment";
|
||||
try_files /sites/$http_host/public/$uri @webserver;
|
||||
}
|
||||
|
||||
try_files /sites/$http_host/public/$uri @webserver;
|
||||
}
|
||||
|
||||
location @webserver {
|
||||
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 ${HTTP_HOST};
|
||||
proxy_set_header X-Use-X-Accel-Redirect True;
|
||||
proxy_read_timeout ${HTTP_TIMEOUT};
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_pass http://frappe-server;
|
||||
}
|
||||
|
||||
# error pages
|
||||
error_page 502 /502.html;
|
||||
location /502.html {
|
||||
root /var/www/templates;
|
||||
internal;
|
||||
}
|
||||
|
||||
# optimizations
|
||||
sendfile on;
|
||||
keepalive_timeout 15;
|
||||
client_max_body_size 50m;
|
||||
client_body_buffer_size 16K;
|
||||
client_header_buffer_size 1k;
|
||||
|
||||
# enable gzip compresion
|
||||
# based on https://mattstauffer.co/blog/enabling-gzip-on-nginx-servers-including-laravel-forge
|
||||
gzip on;
|
||||
gzip_http_version 1.1;
|
||||
gzip_comp_level 5;
|
||||
gzip_min_length 256;
|
||||
gzip_proxied any;
|
||||
gzip_vary on;
|
||||
gzip_types
|
||||
application/atom+xml
|
||||
application/javascript
|
||||
application/json
|
||||
application/rss+xml
|
||||
application/vnd.ms-fontobject
|
||||
application/x-font-ttf
|
||||
application/font-woff
|
||||
application/x-web-app-manifest+json
|
||||
application/xhtml+xml
|
||||
application/xml
|
||||
font/opentype
|
||||
image/svg+xml
|
||||
image/x-icon
|
||||
text/css
|
||||
text/plain
|
||||
text/x-component;
|
||||
# text/html is always compressed by HttpGzipModule
|
||||
}
|
19
build/common/worker/bench
Executable file
19
build/common/worker/bench
Executable file
@ -0,0 +1,19 @@
|
||||
#!/home/frappe/frappe-bench/env/bin/python
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bench_dir = os.path.join(os.sep, 'home', 'frappe', 'frappe-bench')
|
||||
sites_dir = os.path.join(bench_dir, 'sites')
|
||||
bench_helper = os.path.join(
|
||||
bench_dir, 'apps', 'frappe',
|
||||
'frappe', 'utils', 'bench_helper.py',
|
||||
)
|
||||
cwd = os.getcwd()
|
||||
os.chdir(sites_dir)
|
||||
subprocess.check_call(
|
||||
[sys.executable, bench_helper, 'frappe'] + sys.argv[1:],
|
||||
)
|
173
build/common/worker/docker-entrypoint.sh
Executable file
173
build/common/worker/docker-entrypoint.sh
Executable file
@ -0,0 +1,173 @@
|
||||
#!/bin/bash
|
||||
|
||||
function configureEnv() {
|
||||
if [[ ! -f /home/frappe/frappe-bench/sites/common_site_config.json ]]; then
|
||||
|
||||
if [[ -z "${MARIADB_HOST}" && -z "${POSTGRES_HOST}" ]]; then
|
||||
echo "MARIADB_HOST or POSTGRES_HOST is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${REDIS_CACHE}" ]]; then
|
||||
echo "REDIS_CACHE is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${REDIS_QUEUE}" ]]; then
|
||||
echo "REDIS_QUEUE is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${REDIS_SOCKETIO}" ]]; then
|
||||
echo "REDIS_SOCKETIO is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${SOCKETIO_PORT}" ]]; then
|
||||
echo "SOCKETIO_PORT is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${DB_PORT}" ]]; then
|
||||
export DB_PORT=3306
|
||||
fi
|
||||
|
||||
export DB_HOST="${MARIADB_HOST:-$POSTGRES_HOST}"
|
||||
|
||||
envsubst '${DB_HOST}
|
||||
${DB_PORT}
|
||||
${REDIS_CACHE}
|
||||
${REDIS_QUEUE}
|
||||
${REDIS_SOCKETIO}
|
||||
${SOCKETIO_PORT}' </opt/frappe/common_site_config.json.template >/home/frappe/frappe-bench/sites/common_site_config.json
|
||||
fi
|
||||
}
|
||||
|
||||
function checkConnection() {
|
||||
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/commands/check_connection.py
|
||||
}
|
||||
|
||||
function checkConfigExists() {
|
||||
COUNTER=0
|
||||
while [[ ! -e /home/frappe/frappe-bench/sites/common_site_config.json && ${COUNTER} -le 30 ]]; do
|
||||
sleep 1
|
||||
((COUNTER = COUNTER + 1))
|
||||
echo "config file not created, retry ${COUNTER}" >&2
|
||||
done
|
||||
|
||||
if [[ ! -e /home/frappe/frappe-bench/sites/common_site_config.json ]]; then
|
||||
echo "timeout: config file not created" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ ! -e /home/frappe/frappe-bench/sites/apps.txt ]]; then
|
||||
find /home/frappe/frappe-bench/apps -mindepth 1 -maxdepth 1 -type d -printf '%f\n' |
|
||||
sort -r >/home/frappe/frappe-bench/sites/apps.txt
|
||||
fi
|
||||
|
||||
# symlink node_modules
|
||||
ln -sfn /home/frappe/frappe-bench/sites/assets/frappe/node_modules \
|
||||
/home/frappe/frappe-bench/apps/frappe/node_modules
|
||||
|
||||
case "$1" in
|
||||
|
||||
start)
|
||||
configureEnv
|
||||
checkConnection
|
||||
|
||||
[[ -z "${WORKERS}" ]] && WORKERS='2'
|
||||
|
||||
[[ -z "${FRAPPE_PORT}" ]] && FRAPPE_PORT='8000'
|
||||
|
||||
[[ -z "${WORKER_CLASS}" ]] && WORKER_CLASS='gthread'
|
||||
|
||||
LOAD_CONFIG_FILE=""
|
||||
[[ "${WORKER_CLASS}" == "gevent" ]] &&
|
||||
LOAD_CONFIG_FILE="-c /home/frappe/frappe-bench/commands/gevent_patch.py"
|
||||
|
||||
if [[ -n "${AUTO_MIGRATE}" ]]; then
|
||||
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/commands/auto_migrate.py
|
||||
fi
|
||||
|
||||
/home/frappe/frappe-bench/env/bin/gunicorn ${LOAD_CONFIG_FILE} -b 0.0.0.0:${FRAPPE_PORT} \
|
||||
--worker-tmp-dir /dev/shm \
|
||||
--threads=4 \
|
||||
--workers ${WORKERS} \
|
||||
--worker-class=${WORKER_CLASS} \
|
||||
--log-file=- \
|
||||
-t 120 frappe.app:application --preload
|
||||
;;
|
||||
|
||||
worker)
|
||||
checkConfigExists
|
||||
checkConnection
|
||||
# default WORKER_TYPE=default
|
||||
|
||||
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/commands/worker.py
|
||||
;;
|
||||
|
||||
schedule)
|
||||
checkConfigExists
|
||||
checkConnection
|
||||
|
||||
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/commands/background.py
|
||||
|
||||
;;
|
||||
|
||||
new)
|
||||
checkConfigExists
|
||||
checkConnection
|
||||
|
||||
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/commands/new.py
|
||||
exit
|
||||
;;
|
||||
|
||||
drop)
|
||||
checkConfigExists
|
||||
checkConnection
|
||||
|
||||
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/commands/drop.py
|
||||
exit
|
||||
;;
|
||||
|
||||
migrate)
|
||||
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/commands/migrate.py
|
||||
exit
|
||||
;;
|
||||
|
||||
doctor)
|
||||
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/commands/doctor.py "${@:2}"
|
||||
exit
|
||||
;;
|
||||
|
||||
backup)
|
||||
|
||||
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/commands/backup.py
|
||||
exit
|
||||
;;
|
||||
|
||||
console)
|
||||
if [[ -z "$2" ]]; then
|
||||
echo "Need to specify a sitename with the command:" >&2
|
||||
echo "console <sitename>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/commands/console.py "$2"
|
||||
exit
|
||||
;;
|
||||
|
||||
push-backup)
|
||||
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/commands/push_backup.py
|
||||
exit
|
||||
;;
|
||||
|
||||
restore-backup)
|
||||
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/commands/restore_backup.py
|
||||
exit
|
||||
;;
|
||||
*)
|
||||
exec "$@"
|
||||
;;
|
||||
esac
|
45
build/common/worker/healthcheck.sh
Executable file
45
build/common/worker/healthcheck.sh
Executable file
@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
set -ea
|
||||
|
||||
function getUrl() {
|
||||
grep "$2" "$1" | awk -v word="$2" '$word { gsub(/[",]/,"",$2); print $2}' | tr -d '\n'
|
||||
}
|
||||
|
||||
COMMON_SITE_CONFIG_JSON='/home/frappe/frappe-bench/sites/common_site_config.json'
|
||||
|
||||
# Set DB Host and port
|
||||
DB_HOST=$(getUrl "${COMMON_SITE_CONFIG_JSON}" "db_host")
|
||||
DB_PORT=$(getUrl "${COMMON_SITE_CONFIG_JSON}" "db_port")
|
||||
if [[ -z "${DB_PORT}" ]]; then
|
||||
DB_PORT=3306
|
||||
fi
|
||||
|
||||
# Set REDIS host:port
|
||||
REDIS_CACHE=$(getUrl "${COMMON_SITE_CONFIG_JSON}" "redis_cache" | sed 's|redis://||g')
|
||||
if [[ "${REDIS_CACHE}" == *"/"* ]]; then
|
||||
REDIS_CACHE=$(echo ${REDIS_CACHE} | cut -f1 -d"/")
|
||||
fi
|
||||
|
||||
REDIS_QUEUE=$(getUrl "${COMMON_SITE_CONFIG_JSON}" "redis_queue" | sed 's|redis://||g')
|
||||
if [[ "${REDIS_QUEUE}" == *"/"* ]]; then
|
||||
REDIS_QUEUE=$(echo ${REDIS_QUEUE} | cut -f1 -d"/")
|
||||
fi
|
||||
|
||||
REDIS_SOCKETIO=$(getUrl "${COMMON_SITE_CONFIG_JSON}" "redis_socketio" | sed 's|redis://||g')
|
||||
if [[ "${REDIS_SOCKETIO}" == *"/"* ]]; then
|
||||
REDIS_SOCKETIO=$(echo ${REDIS_SOCKETIO} | cut -f1 -d"/")
|
||||
fi
|
||||
|
||||
echo "Check ${DB_HOST}:${DB_PORT}"
|
||||
wait-for-it "${DB_HOST}:${DB_PORT}" -t 1
|
||||
echo "Check ${REDIS_CACHE}"
|
||||
wait-for-it "${REDIS_CACHE}" -t 1
|
||||
echo "Check ${REDIS_QUEUE}"
|
||||
wait-for-it "${REDIS_QUEUE}" -t 1
|
||||
echo "Check ${REDIS_SOCKETIO}"
|
||||
wait-for-it "${REDIS_SOCKETIO}" -t 1
|
||||
|
||||
if [[ "$1" = "-p" || "$1" = "--ping-service" ]]; then
|
||||
echo "Check $2"
|
||||
wait-for-it "$2" -t 1
|
||||
fi
|
10
build/common/worker/install_app.sh
Executable file
10
build/common/worker/install_app.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
APP_NAME=${1}
|
||||
APP_REPO=${2}
|
||||
APP_BRANCH=${3}
|
||||
|
||||
[[ -n "${APP_BRANCH}" ]] && BRANCH="-b ${APP_BRANCH}"
|
||||
|
||||
git clone --depth 1 -o upstream ${APP_REPO} ${BRANCH} /home/frappe/frappe-bench/apps/${APP_NAME}
|
||||
/home/frappe/frappe-bench/env/bin/pip install --no-cache-dir -e /home/frappe/frappe-bench/apps/${APP_NAME}
|
17
build/erpnext-nginx/Dockerfile
Normal file
17
build/erpnext-nginx/Dockerfile
Normal file
@ -0,0 +1,17 @@
|
||||
ARG IMAGE_TAG=v10
|
||||
ARG DOCKER_REGISTRY_PREFIX=frappe
|
||||
FROM ${DOCKER_REGISTRY_PREFIX}/erpnext-worker:${IMAGE_TAG}
|
||||
|
||||
COPY build/erpnext-nginx/install_app.sh /install_app
|
||||
|
||||
RUN /install_app
|
||||
|
||||
FROM ${DOCKER_REGISTRY_PREFIX}/frappe-nginx:${IMAGE_TAG}
|
||||
|
||||
COPY --from=0 /home/frappe/sites /var/www/html
|
||||
RUN echo "erpnext" >> /var/www/html/apps.txt
|
||||
|
||||
VOLUME [ "/assets" ]
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
12
build/erpnext-nginx/install_app.sh
Executable file
12
build/erpnext-nginx/install_app.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
npm i yarn -g
|
||||
cd /home/frappe/frappe-bench/apps/frappe
|
||||
rm -fr node_modules && yarn
|
||||
cd /home/frappe/frappe-bench/sites
|
||||
echo "frappe" > /home/frappe/frappe-bench/sites/apps.txt
|
||||
echo "erpnext" >> /home/frappe/frappe-bench/sites/apps.txt
|
||||
/home/frappe/frappe-bench/env/bin/python -c "import frappe; frappe.init(''); import frappe.build; frappe.build.setup(); frappe.build.make_asset_dirs(make_copy=True)"
|
||||
node --use_strict ../apps/frappe/frappe/build.js --build
|
||||
mkdir -p /home/frappe/sites
|
||||
cp -R assets /home/frappe/sites
|
8
build/erpnext-worker/Dockerfile
Normal file
8
build/erpnext-worker/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
||||
ARG IMAGE_TAG=v10
|
||||
ARG DOCKER_REGISTRY_PREFIX=frappe
|
||||
FROM ${DOCKER_REGISTRY_PREFIX}/frappe-worker:${IMAGE_TAG}
|
||||
|
||||
ARG GIT_REPO=https://github.com/frappe/erpnext
|
||||
ARG GIT_BRANCH=v10.x.x
|
||||
|
||||
RUN install_app erpnext ${GIT_REPO} ${GIT_BRANCH}
|
34
build/frappe-nginx/Dockerfile
Normal file
34
build/frappe-nginx/Dockerfile
Normal file
@ -0,0 +1,34 @@
|
||||
# This image uses nvm and same base image as the worker image.
|
||||
# This is done to ensures that node-sass binary remains common.
|
||||
# node-sass is required to enable website theme feature used
|
||||
# by Website Manager role in Frappe Framework
|
||||
ARG IMAGE_TAG=v10
|
||||
ARG DOCKER_REGISTRY_PREFIX=frappe
|
||||
FROM ${DOCKER_REGISTRY_PREFIX}/frappe-worker:${IMAGE_TAG}
|
||||
|
||||
RUN npm i yarn -g \
|
||||
&& cd /home/frappe/frappe-bench/apps/frappe \
|
||||
&& rm -fr node_modules && yarn \
|
||||
&& cd /home/frappe/frappe-bench/sites \
|
||||
&& echo "frappe" > /home/frappe/frappe-bench/sites/apps.txt \
|
||||
&& /home/frappe/frappe-bench/env/bin/python -c "import frappe; frappe.init(''); import frappe.build; frappe.build.setup(); frappe.build.make_asset_dirs(make_copy=True)" \
|
||||
&& node --use_strict ../apps/frappe/frappe/build.js --build \
|
||||
&& mkdir -p /home/frappe/sites \
|
||||
&& cp -R assets /home/frappe/sites
|
||||
|
||||
RUN git clone --depth 1 https://github.com/frappe/bench /tmp/bench \
|
||||
&& mkdir -p /home/frappe/www/error_pages \
|
||||
&& cp -r /tmp/bench/bench/config/templates /home/frappe/www/error_pages
|
||||
|
||||
FROM nginx:latest
|
||||
COPY --from=0 /home/frappe/sites /var/www/html
|
||||
COPY --from=0 /home/frappe/www/error_pages /var/www/
|
||||
COPY build/common/nginx-default.conf.template /etc/nginx/conf.d/default.conf.template
|
||||
COPY build/frappe-nginx/docker-entrypoint.sh /
|
||||
|
||||
RUN apt-get update && apt-get install -y rsync && apt-get clean
|
||||
|
||||
VOLUME [ "/assets" ]
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
57
build/frappe-nginx/docker-entrypoint.sh
Executable file
57
build/frappe-nginx/docker-entrypoint.sh
Executable file
@ -0,0 +1,57 @@
|
||||
#!/bin/bash -ae
|
||||
|
||||
## Thanks
|
||||
# https://serverfault.com/a/919212
|
||||
##
|
||||
|
||||
rsync -a --delete /var/www/html/assets/* /assets
|
||||
|
||||
touch /var/www/html/sites/.build -r "$(ls -td /assets/* | head -n 1)"
|
||||
|
||||
[[ -z "${FRAPPE_PY}" ]] && FRAPPE_PY='0.0.0.0'
|
||||
|
||||
[[ -z "${FRAPPE_PY_PORT}" ]] && FRAPPE_PY_PORT='8000'
|
||||
|
||||
[[ -z "${FRAPPE_SOCKETIO}" ]] && FRAPPE_SOCKETIO='0.0.0.0'
|
||||
|
||||
[[ -z "${SOCKETIO_PORT}" ]] && SOCKETIO_PORT='9000'
|
||||
|
||||
[[ -z "${HTTP_TIMEOUT}" ]] && HTTP_TIMEOUT='120'
|
||||
|
||||
[[ -z "${UPSTREAM_REAL_IP_ADDRESS}" ]] && UPSTREAM_REAL_IP_ADDRESS='127.0.0.1'
|
||||
|
||||
[[ -z "${UPSTREAM_REAL_IP_RECURSIVE}" ]] && UPSTREAM_REAL_IP_RECURSIVE='off'
|
||||
|
||||
[[ -z "${UPSTREAM_REAL_IP_HEADER}" ]] && UPSTREAM_REAL_IP_HEADER='X-Forwarded-For'
|
||||
|
||||
[[ -z "${FRAPPE_SITE_NAME_HEADER}" ]] && FRAPPE_SITE_NAME_HEADER="\$host"
|
||||
|
||||
[[ -z "${HTTP_HOST}" ]] && HTTP_HOST="\$http_host"
|
||||
|
||||
[[ -z "${SKIP_NGINX_TEMPLATE_GENERATION}" ]] && SKIP_NGINX_TEMPLATE_GENERATION='0'
|
||||
|
||||
if [[ ${SKIP_NGINX_TEMPLATE_GENERATION} == 1 ]]; then
|
||||
echo "Skipping default NGINX template generation. Please mount your own NGINX config file inside /etc/nginx/conf.d"
|
||||
else
|
||||
echo "Generating default template"
|
||||
envsubst '${FRAPPE_PY}
|
||||
${FRAPPE_PY_PORT}
|
||||
${FRAPPE_SOCKETIO}
|
||||
${SOCKETIO_PORT}
|
||||
${HTTP_TIMEOUT}
|
||||
${UPSTREAM_REAL_IP_ADDRESS}
|
||||
${UPSTREAM_REAL_IP_RECURSIVE}
|
||||
${FRAPPE_SITE_NAME_HEADER}
|
||||
${HTTP_HOST}
|
||||
${UPSTREAM_REAL_IP_HEADER}' \
|
||||
</etc/nginx/conf.d/default.conf.template >/etc/nginx/conf.d/default.conf
|
||||
fi
|
||||
|
||||
echo "Waiting for frappe-python to be available on ${FRAPPE_PY} port ${FRAPPE_PY_PORT}"
|
||||
timeout 10 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/$0/$1; do sleep 1; done' ${FRAPPE_PY} ${FRAPPE_PY_PORT}
|
||||
echo "Frappe-python available on ${FRAPPE_PY} port ${FRAPPE_PY_PORT}"
|
||||
echo "Waiting for frappe-socketio to be available on ${FRAPPE_SOCKETIO} port ${SOCKETIO_PORT}"
|
||||
timeout 10 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/$0/$1; do sleep 1; done' ${FRAPPE_SOCKETIO} ${SOCKETIO_PORT}
|
||||
echo "Frappe-socketio available on ${FRAPPE_SOCKETIO} port ${SOCKETIO_PORT}"
|
||||
|
||||
exec "$@"
|
35
build/frappe-socketio/Dockerfile
Normal file
35
build/frappe-socketio/Dockerfile
Normal file
@ -0,0 +1,35 @@
|
||||
FROM alpine/git
|
||||
|
||||
ARG GIT_REPO=https://github.com/frappe/frappe.git
|
||||
ARG GIT_BRANCH=v10.x.x
|
||||
|
||||
RUN git clone ${GIT_REPO} /opt/frappe -b ${GIT_BRANCH} --depth 1
|
||||
|
||||
FROM node:8-slim
|
||||
|
||||
# Add frappe user
|
||||
RUN useradd -ms /bin/bash frappe
|
||||
|
||||
# Create bench directories and set ownership
|
||||
RUN mkdir -p /home/frappe/frappe-bench/sites /home/frappe/frappe-bench/apps/frappe \
|
||||
&& chown -R frappe:frappe /home/frappe
|
||||
|
||||
# Download socketio and purge curl package
|
||||
COPY --from=0 /opt/frappe/socketio.js /home/frappe/frappe-bench/apps/frappe/socketio.js
|
||||
COPY --from=0 /opt/frappe/package.json /home/frappe/frappe-bench/apps/frappe/package.json
|
||||
|
||||
RUN cd /home/frappe/frappe-bench/apps/frappe \
|
||||
&& npm install --only=production \
|
||||
&& node --version \
|
||||
&& npm --version
|
||||
|
||||
# Setup docker-entrypoint
|
||||
COPY build/frappe-socketio/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
RUN ln -s /usr/local/bin/docker-entrypoint.sh / # backwards compat
|
||||
|
||||
USER frappe
|
||||
|
||||
WORKDIR /home/frappe/frappe-bench
|
||||
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
CMD ["start"]
|
27
build/frappe-socketio/docker-entrypoint.sh
Executable file
27
build/frappe-socketio/docker-entrypoint.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
function checkConfigExists() {
|
||||
COUNTER=0
|
||||
while [[ ! -e /home/frappe/frappe-bench/sites/common_site_config.json && ${COUNTER} -le 30 ]]; do
|
||||
((COUNTER = COUNTER + 1))
|
||||
echo "config file not created, retry ${COUNTER}"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [[ ! -e /home/frappe/frappe-bench/sites/common_site_config.json ]]; then
|
||||
echo "timeout: config file not created"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$1" == 'start' ]]; then
|
||||
checkConfigExists
|
||||
node /home/frappe/frappe-bench/apps/frappe/socketio.js
|
||||
|
||||
elif [[ "$1" == 'doctor' ]]; then
|
||||
node /home/frappe/frappe-bench/apps/frappe/health.js
|
||||
|
||||
else
|
||||
exec -c "$@"
|
||||
|
||||
fi
|
17
build/frappe-socketio/package.json
Normal file
17
build/frappe-socketio/package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "frappe-socketio",
|
||||
"version": "1.0.1",
|
||||
"description": "Frappe SocketIO Server",
|
||||
"main": "socketio.js",
|
||||
"scripts": {
|
||||
"start": "node socketio.js"
|
||||
},
|
||||
"author": "Revant Nandgaonkar",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express": "^4.17.1",
|
||||
"redis": "^3.1.1",
|
||||
"socket.io": "^2.4.0",
|
||||
"superagent": "^5.1.0"
|
||||
}
|
||||
}
|
87
build/frappe-worker/Dockerfile
Normal file
87
build/frappe-worker/Dockerfile
Normal file
@ -0,0 +1,87 @@
|
||||
FROM python:2.7-stretch
|
||||
|
||||
# Add non root user without password
|
||||
RUN useradd -ms /bin/bash frappe
|
||||
|
||||
ARG GIT_REPO=https://github.com/frappe/frappe
|
||||
ARG GIT_BRANCH=v10.x.x
|
||||
ARG ARCH=amd64
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENV NVM_DIR=/home/frappe/.nvm
|
||||
ENV NODE_VERSION=8.17.0
|
||||
ENV PATH="/home/frappe/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"
|
||||
|
||||
# Install dependencies
|
||||
WORKDIR /home/frappe/frappe-bench
|
||||
RUN apt-get update -y && apt-get install \
|
||||
# for frappe framework
|
||||
git \
|
||||
mariadb-client \
|
||||
gettext-base \
|
||||
wget \
|
||||
wait-for-it \
|
||||
# for PDF
|
||||
libjpeg62-turbo \
|
||||
libx11-6 \
|
||||
libxcb1 \
|
||||
libxext6 \
|
||||
libxrender1 \
|
||||
libssl-dev \
|
||||
fonts-cantarell \
|
||||
xfonts-75dpi \
|
||||
xfonts-base \
|
||||
libxml2 \
|
||||
libffi-dev \
|
||||
libjpeg-dev \
|
||||
zlib1g-dev \
|
||||
# For psycopg2
|
||||
libpq-dev \
|
||||
# For arm64 python wheel builds
|
||||
gcc \
|
||||
g++ -y \
|
||||
# Detect arch, download and install wkhtmltox
|
||||
&& if [ `uname -m` = 'aarch64' ]; then export ARCH=arm64; fi \
|
||||
&& if [ `uname -m` = 'x86_64' ]; then export ARCH=amd64; fi \
|
||||
&& wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.stretch_${ARCH}.deb \
|
||||
&& dpkg -i wkhtmltox_0.12.6-1.stretch_${ARCH}.deb && rm wkhtmltox_0.12.6-1.stretch_${ARCH}.deb \
|
||||
&& wget https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh \
|
||||
&& chown -R frappe:frappe /home/frappe
|
||||
|
||||
# Setup docker-entrypoint
|
||||
COPY build/common/worker/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
RUN ln -s /usr/local/bin/docker-entrypoint.sh / # backwards compat
|
||||
|
||||
USER frappe
|
||||
# Install nvm with node
|
||||
RUN bash install.sh \
|
||||
&& . "$NVM_DIR/nvm.sh" \
|
||||
&& nvm install ${NODE_VERSION} \
|
||||
&& nvm use v${NODE_VERSION} \
|
||||
&& nvm alias default v${NODE_VERSION}
|
||||
|
||||
# Create frappe-bench directories
|
||||
RUN mkdir -p apps logs commands sites /home/frappe/backups
|
||||
|
||||
# Setup python environment
|
||||
RUN virtualenv env \
|
||||
&& . env/bin/activate \
|
||||
&& pip install --upgrade pip \
|
||||
&& pip install gevent \
|
||||
&& cd apps \
|
||||
&& git clone --depth 1 -o upstream ${GIT_REPO} --branch ${GIT_BRANCH} \
|
||||
&& pip install --no-cache-dir -e /home/frappe/frappe-bench/apps/frappe
|
||||
|
||||
# Copy scripts and templates
|
||||
COPY build/common/commands/* /home/frappe/frappe-bench/commands/
|
||||
COPY build/common/common_site_config.json.template /opt/frappe/common_site_config.json.template
|
||||
COPY build/common/worker/install_app.sh /usr/local/bin/install_app
|
||||
COPY build/common/worker/bench /usr/local/bin/bench
|
||||
COPY build/common/worker/healthcheck.sh /usr/local/bin/healthcheck.sh
|
||||
|
||||
# Use sites volume as working directory
|
||||
WORKDIR /home/frappe/frappe-bench/sites
|
||||
|
||||
VOLUME [ "/home/frappe/frappe-bench/sites", "/home/frappe/backups", "/home/frappe/frappe-bench/logs" ]
|
||||
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
CMD ["start"]
|
90
compose.yaml
90
compose.yaml
@ -1,90 +0,0 @@
|
||||
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]
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
|
||||
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;
|
||||
environment:
|
||||
DB_HOST: ${DB_HOST}
|
||||
DB_PORT: ${DB_PORT}
|
||||
REDIS_CACHE: ${REDIS_CACHE}
|
||||
REDIS_QUEUE: ${REDIS_QUEUE}
|
||||
SOCKETIO_PORT: 9000
|
||||
depends_on: {}
|
||||
|
||||
backend:
|
||||
<<: *backend_defaults
|
||||
|
||||
frontend:
|
||||
<<: *customizable_image
|
||||
command:
|
||||
- nginx-entrypoint.sh
|
||||
environment:
|
||||
BACKEND: backend:8000
|
||||
SOCKETIO: websocket:9000
|
||||
FRAPPE_SITE_NAME_HEADER: ${FRAPPE_SITE_NAME_HEADER:-$$host}
|
||||
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}
|
||||
CLIENT_MAX_BODY_SIZE: ${CLIENT_MAX_BODY_SIZE:-50m}
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
depends_on:
|
||||
- backend
|
||||
- websocket
|
||||
|
||||
websocket:
|
||||
<<: [*depends_on_configurator, *customizable_image]
|
||||
command:
|
||||
- node
|
||||
- /home/frappe/frappe-bench/apps/frappe/socketio.js
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
|
||||
queue-short:
|
||||
<<: *backend_defaults
|
||||
command: bench worker --queue short,default
|
||||
|
||||
queue-long:
|
||||
<<: *backend_defaults
|
||||
command: bench worker --queue long,default,short
|
||||
|
||||
scheduler:
|
||||
<<: *backend_defaults
|
||||
command: bench schedule
|
||||
|
||||
# ERPNext requires local assets access (Frappe does not)
|
||||
volumes:
|
||||
sites:
|
BIN
deploy_key.enc
Normal file
BIN
deploy_key.enc
Normal file
Binary file not shown.
@ -1,32 +1,19 @@
|
||||
{
|
||||
"name": "Frappe Bench",
|
||||
"forwardPorts": [8000, 9000, 6787],
|
||||
"appPort": [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.shell.linux": "/bin/bash"
|
||||
},
|
||||
"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",
|
||||
"auchenberg.vscode-browser-preview",
|
||||
"grapecity.gc-excelviewer",
|
||||
"mtxr.sqltools",
|
||||
"visualstudioexptteam.vscodeintellicode"
|
||||
]
|
||||
}
|
||||
|
@ -1,87 +1,42 @@
|
||||
version: "3.7"
|
||||
services:
|
||||
mariadb:
|
||||
image: docker.io/mariadb:10.6
|
||||
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
|
||||
image: mariadb:10.3
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: 123
|
||||
- MYSQL_ROOT_PASSWORD=123
|
||||
- MYSQL_USER=root
|
||||
volumes:
|
||||
- mariadb-data:/var/lib/mysql
|
||||
- ../installation/frappe-mariadb.cnf:/etc/mysql/conf.d/frappe.cnf
|
||||
- mariadb-vol:/var/lib/mysql
|
||||
|
||||
# Enable PostgreSQL only if you use it, see development/README.md for more information.
|
||||
# postgresql:
|
||||
# image: postgres:11.8
|
||||
# restart: on-failure
|
||||
# environment:
|
||||
# POSTGRES_PASSWORD: 123
|
||||
# - POSTGRES_PASSWORD=123
|
||||
# volumes:
|
||||
# - postgresql-data:/var/lib/postgresql/data
|
||||
|
||||
# Enable Mailpit if you need to test outgoing mail services
|
||||
# See https://mailpit.axllent.org/
|
||||
# mailpit:
|
||||
# image: axllent/mailpit
|
||||
# volumes:
|
||||
# - mailpit-data:/data
|
||||
# ports:
|
||||
# - 8025:8025
|
||||
# - 1025:1025
|
||||
# environment:
|
||||
# MP_MAX_MESSAGES: 5000
|
||||
# MP_DATA_FILE: /data/mailpit.db
|
||||
# MP_SMTP_AUTH_ACCEPT_ANY: 1
|
||||
# MP_SMTP_AUTH_ALLOW_INSECURE: 1
|
||||
# - postgresql-vol:/var/lib/postgresql/data
|
||||
|
||||
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
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
# Enable if you require git cloning
|
||||
# - ${HOME}/.ssh:/home/frappe/.ssh
|
||||
working_dir: /workspace/development
|
||||
ports:
|
||||
- 8000-8005:8000-8005
|
||||
- 9000-9005:9000-9005
|
||||
# enable the below service if you need Cypress UI Tests to be executed
|
||||
# Before enabling ensure install_x11_deps.sh has been executed and display variable is exported.
|
||||
# Run install_x11_deps.sh again if DISPLAY is not set
|
||||
# ui-tester:
|
||||
# # pass custom command to start Cypress otherwise it will use the entrypoint
|
||||
# # specified in the Cypress Docker image.
|
||||
# # also pass "--project <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"
|
||||
- "8000-8005:8000-8005"
|
||||
- "9000-9005:9000-9005"
|
||||
|
||||
volumes:
|
||||
mariadb-data:
|
||||
#postgresql-data:
|
||||
#mailpit-data:
|
||||
mariadb-vol:
|
||||
postgresql-vol:
|
||||
|
71
development/.vscode/launch.json
vendored
Normal file
71
development/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Bench Web",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"args": [
|
||||
"frappe", "serve", "--port", "8000", "--noreload", "--nothreading"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"env": {
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Bench Default Worker",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"args": [
|
||||
"frappe", "worker", "--queue", "default"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"env": {
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Bench Short Worker",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"args": [
|
||||
"frappe", "worker", "--queue", "short"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"env": {
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Bench Long Worker",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"args": [
|
||||
"frappe", "worker", "--queue", "long"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"env": {
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Honcho SocketIO Watch Schedule Worker",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "/home/frappe/.local/bin/honcho",
|
||||
"cwd": "${workspaceFolder}/frappe-bench",
|
||||
"console": "internalConsole",
|
||||
"args": [
|
||||
"start", "socketio", "watch", "schedule", "worker_short", "worker_long", "worker_default"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
4
development/.vscode/settings.json
vendored
Normal file
4
development/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"python.pythonPath": "frappe-bench/env/bin/python",
|
||||
"debug.node.autoAttach": "disabled"
|
||||
}
|
287
development/README.md
Normal file
287
development/README.md
Normal file
@ -0,0 +1,287 @@
|
||||
# Getting Started
|
||||
|
||||
## Prerequisites
|
||||
|
||||
In order to start developing you need to satisfy the following prerequisites:
|
||||
|
||||
- Docker
|
||||
- docker-compose
|
||||
- user added to docker group
|
||||
|
||||
It is recommended you allocate at least 4GB of RAM to docker:
|
||||
|
||||
- [Instructions for Windows](https://docs.docker.com/docker-for-windows/#resources)
|
||||
- [Instructions for macOS](https://docs.docker.com/docker-for-mac/#resources)
|
||||
|
||||
## Bootstrap Containers for development
|
||||
|
||||
Clone and change directory to frappe_docker directory
|
||||
|
||||
```shell
|
||||
git clone https://github.com/frappe/frappe_docker.git
|
||||
cd frappe_docker
|
||||
```
|
||||
|
||||
Copy example devcontainer config from `devcontainer-example` to `.devcontainer`
|
||||
|
||||
```shell
|
||||
cp -R devcontainer-example .devcontainer
|
||||
```
|
||||
|
||||
## Use VSCode Remote Containers extension
|
||||
|
||||
For most people getting started with Frappe development, the best solution is to use [VSCode Remote - Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers).
|
||||
|
||||
Before opening the folder in container, determine the database that you want to use. The default is MariaDB.
|
||||
If you want to use PostgreSQL instead, edit `.devcontainer/docker-compose.yml` and uncomment the section for `postgresql` service, and you may also want to comment `mariadb` as well.
|
||||
|
||||
VSCode should automatically inquire you to install the required extensions, that can also be installed manually as follows:
|
||||
|
||||
- Install Remote - Containers for VSCode
|
||||
- through command line `code --install-extension ms-vscode-remote.remote-containers`
|
||||
- clicking on the Install button in the Vistual Studio Marketplace: [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
|
||||
- View: Extensions command in VSCode (Windows: Ctrl+Shift+X; macOS: Cmd+Shift+X) then search for extension `ms-vscode-remote.remote-containers`
|
||||
|
||||
After the extensions are installed, you can:
|
||||
|
||||
- Open frappe_docker folder in VS Code.
|
||||
- `code .`
|
||||
- Launch the command, from Command Palette (Ctrl + Shift + P) `Execute Remote Containers : Reopen in Container`. You can also click in the bottom left corner to access the remote container menu.
|
||||
|
||||
Notes:
|
||||
|
||||
- The `development` directory is ignored by git. It is mounted and available inside the container. Create all your benches (installations of bench, the tool that manages frappe) inside this directory.
|
||||
- nvm with node v12 and v10 is installed. Check with `nvm ls`. Node v12 is used by default.
|
||||
|
||||
### Setup first bench
|
||||
|
||||
Run the following commands in the terminal inside the container. You might need to create a new terminal in VSCode.
|
||||
|
||||
```shell
|
||||
bench init --skip-redis-config-generation --frappe-branch version-12 frappe-bench
|
||||
cd frappe-bench
|
||||
```
|
||||
|
||||
### Setup hosts
|
||||
|
||||
We need to tell bench to use the right containers instead of localhost. Run the following commands inside the container:
|
||||
|
||||
```shell
|
||||
bench set-mariadb-host mariadb
|
||||
bench set-redis-cache-host redis-cache:6379
|
||||
bench set-redis-queue-host redis-queue:6379
|
||||
bench set-redis-socketio-host redis-socketio:6379
|
||||
```
|
||||
|
||||
### Edit Honcho's Procfile
|
||||
|
||||
Note : With the option '--skip-redis-config-generation' during bench init, these actions are no more needed. But at least, take a look to ProcFile to see what going on when bench launch honcho on start command
|
||||
|
||||
Honcho is the tool used by Bench to manage all the processes Frappe requires. Usually, these all run in localhost, but in this case, we have external containers for Redis. For this reason, we have to stop Honcho from trying to start Redis processes.
|
||||
|
||||
Open the Procfile file and remove the three lines containing the configuration from Redis, either by editing manually the file:
|
||||
|
||||
```shell
|
||||
code Procfile
|
||||
```
|
||||
|
||||
Or running the following command:
|
||||
```shell
|
||||
sed -i '/redis/d' ./Procfile
|
||||
```
|
||||
|
||||
### Create a new site with bench
|
||||
|
||||
You can create a new site with the following command:
|
||||
|
||||
```shell
|
||||
bench new-site sitename --no-mariadb-socket
|
||||
```
|
||||
sitename MUST end with .localhost for trying deployments locally.
|
||||
|
||||
for example:
|
||||
|
||||
```shell
|
||||
bench new-site mysite.localhost --no-mariadb-socket
|
||||
```
|
||||
|
||||
The same command can be run non-interactively as well:
|
||||
|
||||
```shell
|
||||
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 `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.
|
||||
|
||||
To setup site with PostgreSQL as database use option `--db-type postgres` and `--db-host postgresql`. (Available only v12 onwards, currently NOT available for ERPNext).
|
||||
|
||||
Example:
|
||||
|
||||
```shell
|
||||
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`,
|
||||
|
||||
```shell
|
||||
bench config set-common-config -c root_login postgres
|
||||
bench config set-common-config -c root_password '"123"'
|
||||
```
|
||||
|
||||
Note: If PostgreSQL is not required, the postgresql service / container can be stopped.
|
||||
|
||||
### Set bench developer mode on the new site
|
||||
|
||||
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 mysite.localhost set-config developer_mode 1
|
||||
bench --site mysite.localhost clear-cache
|
||||
```
|
||||
|
||||
### Install an app
|
||||
|
||||
To install an app we need to fetch it from the appropriate git repo, then install in on the appropriate site:
|
||||
|
||||
You can check [VSCode container remote extension documentation](https://code.visualstudio.com/docs/remote/containers#_sharing-git-credentials-with-your-container) regarding git credential sharing.
|
||||
|
||||
To install custom app
|
||||
|
||||
```shell
|
||||
# --branch is optional, use it to point to branch on custom app repository
|
||||
bench get-app --branch version-12 myapp https://github.com/myusername/myapp.git
|
||||
bench --site mysite.localhost install-app myapp
|
||||
```
|
||||
|
||||
To install ERPNext (from the version-12 branch):
|
||||
|
||||
```shell
|
||||
bench get-app --branch version-12 erpnext https://github.com/frappe/erpnext.git
|
||||
bench --site mysite.localhost install-app erpnext
|
||||
```
|
||||
|
||||
Note: Both frappe and erpnext must be on branch with same name. e.g. version-12
|
||||
|
||||
### Start Frappe without debugging
|
||||
|
||||
Execute following command from the `frappe-bench` directory.
|
||||
|
||||
```shell
|
||||
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 [mysite.localhost:8000](http://mysite.localhost:8000)
|
||||
Note: To start bench with debugger refer section for debugging.
|
||||
|
||||
### 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:
|
||||
|
||||
- Click on the extension icon inside VSCode
|
||||
- Search `ms-python.python`
|
||||
- Click on `Install on Dev Container: Frappe Bench`
|
||||
- Click on 'Reload'
|
||||
|
||||
We need to start bench separately through the VSCode debugger. For this reason, **instead** of running `bench start` you should run the following command inside the frappe-bench directory:
|
||||
|
||||
```shell
|
||||
honcho start \
|
||||
socketio \
|
||||
watch \
|
||||
schedule \
|
||||
worker_short \
|
||||
worker_long \
|
||||
worker_default
|
||||
```
|
||||
|
||||
Alternatively you can use the VSCode launch configuration "Honcho SocketIO Watch Schedule Worker" which launches the same command as above.
|
||||
|
||||
This command starts all processes with the exception of Redis (which is already running in separate container) and the `web` process. The latter can can finally be started from the debugger tab of VSCode by clicking on the "play" button.
|
||||
|
||||
You can now login with user `Administrator` and the password you choose when creating the site, if you followed this guide's unattended install that password is going to be `admin`.
|
||||
|
||||
To debug workers, skip starting worker with honcho and start it with VSCode debugger.
|
||||
|
||||
## Developing using the interactive console
|
||||
|
||||
You can launch a simple interactive shell console in the terminal with:
|
||||
|
||||
```shell
|
||||
bench --site mysite.localhost console
|
||||
```
|
||||
|
||||
More likely, you may want to launch VSCode interactive console based on Jupyter kernel.
|
||||
|
||||
Launch VSCode command palette (cmd+shift+p or ctrl+shift+p), run the command `Python: Select interpreter to start Jupyter server` and select `/workspace/development/frappe-bench/env/bin/python`.
|
||||
|
||||
The first step is installing and updating the required software. Usually the frappe framework may require an older version of Jupyter, while VSCode likes to move fast, this can [cause issues](https://github.com/jupyter/jupyter_console/issues/158). For this reason we need to run the following command.
|
||||
|
||||
```shell
|
||||
/workspace/development/frappe-bench/env/bin/python -m pip install --upgrade jupyter ipykernel ipython
|
||||
```
|
||||
|
||||
Then, run the commmand `Python: Show Python interactive window` from the VSCode command palette.
|
||||
|
||||
Replace `mysite.localhost` with your site and run the following code in a Jupyter cell:
|
||||
|
||||
```python
|
||||
import frappe
|
||||
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()
|
||||
```
|
||||
|
||||
The first command can take a few seconds to be executed, this is to be expected.
|
||||
|
||||
### Fixing MariaDB issues after rebuilding the container
|
||||
|
||||
For any reason after rebuilding the container if you are not be able to access MariaDB correctly with the previous configuration. Follow these instructions.
|
||||
|
||||
The parameter `'db_name'@'%'` needs to be set in MariaDB and permission to the site database suitably assigned to the user.
|
||||
|
||||
This step has to be repeated for all sites available under the current bench.
|
||||
Example shows the queries to be executed for site `localhost`
|
||||
|
||||
Open sites/localhost/site_config.json:
|
||||
|
||||
|
||||
```shell
|
||||
code sites/localhost/site_config.json
|
||||
```
|
||||
|
||||
and take note of the parameters `db_name` and `db_password`.
|
||||
|
||||
Enter MariaDB Interactive shell:
|
||||
|
||||
```shell
|
||||
mysql -uroot -p123 -hmariadb
|
||||
```
|
||||
|
||||
Execute following queries replacing `db_name` and `db_password` with the values found in site_config.json.
|
||||
|
||||
```sql
|
||||
UPDATE mysql.user SET Host = '%' where User = 'db_name'; FLUSH PRIVILEGES;
|
||||
SET PASSWORD FOR 'db_name'@'%' = PASSWORD('db_password'); FLUSH PRIVILEGES;
|
||||
GRANT ALL PRIVILEGES ON `db_name`.* TO 'db_name'@'%'; FLUSH PRIVILEGES;
|
||||
EXIT;
|
||||
```
|
||||
|
||||
## Manually start containers
|
||||
|
||||
In case you don't use VSCode, you may start the containers manually with the following command:
|
||||
|
||||
### Running the containers
|
||||
```shell
|
||||
docker-compose -f .devcontainer/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
And enter the interactive shell for the development container with the following command:
|
||||
|
||||
```shell
|
||||
docker exec -e "TERM=xterm-256color" -w /workspace/development -it devcontainer_frappe_1 bash
|
||||
```
|
@ -1,6 +0,0 @@
|
||||
[
|
||||
{
|
||||
"url": "https://github.com/frappe/erpnext.git",
|
||||
"branch": "version-15"
|
||||
}
|
||||
]
|
@ -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()
|
@ -1,69 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Bench Web",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"args": [
|
||||
"frappe",
|
||||
"serve",
|
||||
"--port",
|
||||
"8000",
|
||||
"--noreload",
|
||||
"--nothreading"
|
||||
],
|
||||
"pythonPath": "${workspaceFolder}/frappe-bench/env/bin/python",
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"env": {
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Bench Short Worker",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"args": ["frappe", "worker", "--queue", "short"],
|
||||
"pythonPath": "${workspaceFolder}/frappe-bench/env/bin/python",
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"env": {
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Bench Long Worker",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"args": ["frappe", "worker", "--queue", "long"],
|
||||
"pythonPath": "${workspaceFolder}/frappe-bench/env/bin/python",
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"env": {
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Honcho SocketIO Watch Schedule Worker",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "/home/frappe/.local/bin/honcho",
|
||||
"pythonPath": "${workspaceFolder}/frappe-bench/env/bin/python",
|
||||
"cwd": "${workspaceFolder}/frappe-bench",
|
||||
"console": "internalConsole",
|
||||
"args": [
|
||||
"start",
|
||||
"socketio",
|
||||
"watch",
|
||||
"schedule",
|
||||
"worker_short",
|
||||
"worker_long",
|
||||
"worker_default"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
# Docker Buildx Bake build definition file
|
||||
# Reference: https://github.com/docker/buildx/blob/master/docs/reference/buildx_bake.md
|
||||
|
||||
variable "REGISTRY_USER" {
|
||||
default = "frappe"
|
||||
}
|
||||
|
||||
variable PYTHON_VERSION {
|
||||
default = "3.11.6"
|
||||
}
|
||||
variable NODE_VERSION {
|
||||
default = "18.18.2"
|
||||
}
|
||||
|
||||
variable "FRAPPE_VERSION" {
|
||||
default = "develop"
|
||||
}
|
||||
|
||||
variable "ERPNEXT_VERSION" {
|
||||
default = "develop"
|
||||
}
|
||||
|
||||
variable "FRAPPE_REPO" {
|
||||
default = "https://github.com/frappe/frappe"
|
||||
}
|
||||
|
||||
variable "ERPNEXT_REPO" {
|
||||
default = "https://github.com/frappe/erpnext"
|
||||
}
|
||||
|
||||
variable "BENCH_REPO" {
|
||||
default = "https://github.com/frappe/bench"
|
||||
}
|
||||
|
||||
variable "LATEST_BENCH_RELEASE" {
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
# Bench image
|
||||
|
||||
target "bench" {
|
||||
args = {
|
||||
GIT_REPO = "${BENCH_REPO}"
|
||||
}
|
||||
context = "images/bench"
|
||||
target = "bench"
|
||||
tags = [
|
||||
"frappe/bench:${LATEST_BENCH_RELEASE}",
|
||||
"frappe/bench:latest",
|
||||
]
|
||||
}
|
||||
|
||||
target "bench-test" {
|
||||
inherits = ["bench"]
|
||||
target = "bench-test"
|
||||
}
|
||||
|
||||
# Main images
|
||||
# Base for all other targets
|
||||
|
||||
group "default" {
|
||||
targets = ["erpnext"]
|
||||
}
|
||||
|
||||
function "tag" {
|
||||
params = [repo, version]
|
||||
result = [
|
||||
# If `version` param is develop (development build) then use tag `latest`
|
||||
"${version}" == "develop" ? "${REGISTRY_USER}/${repo}:latest" : "${REGISTRY_USER}/${repo}:${version}",
|
||||
# Make short tag for major version if possible. For example, from v13.16.0 make v13.
|
||||
can(regex("(v[0-9]+)[.]", "${version}")) ? "${REGISTRY_USER}/${repo}:${regex("(v[0-9]+)[.]", "${version}")[0]}" : "",
|
||||
]
|
||||
}
|
||||
|
||||
target "default-args" {
|
||||
args = {
|
||||
FRAPPE_PATH = "${FRAPPE_REPO}"
|
||||
ERPNEXT_PATH = "${ERPNEXT_REPO}"
|
||||
BENCH_REPO = "${BENCH_REPO}"
|
||||
FRAPPE_BRANCH = "${FRAPPE_VERSION}"
|
||||
ERPNEXT_BRANCH = "${ERPNEXT_VERSION}"
|
||||
PYTHON_VERSION = "${PYTHON_VERSION}"
|
||||
NODE_VERSION = "${NODE_VERSION}"
|
||||
}
|
||||
}
|
||||
|
||||
target "erpnext" {
|
||||
inherits = ["default-args"]
|
||||
context = "."
|
||||
dockerfile = "images/production/Containerfile"
|
||||
target = "erpnext"
|
||||
tags = tag("erpnext", "${ERPNEXT_VERSION}")
|
||||
}
|
174
docker-compose.yml
Normal file
174
docker-compose.yml
Normal file
@ -0,0 +1,174 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: "traefik:v2.2"
|
||||
command:
|
||||
- "--log.level=DEBUG"
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--entrypoints.websecure.address=:443"
|
||||
- "--certificatesresolvers.myresolver.acme.httpchallenge=true"
|
||||
- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
|
||||
- "--certificatesresolvers.myresolver.acme.email=${LETSENCRYPT_EMAIL}"
|
||||
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
|
||||
labels:
|
||||
# enable traefik
|
||||
- "traefik.enable=true"
|
||||
# global redirect to https for production only
|
||||
- "${HTTPS_REDIRECT_RULE_LABEL}"
|
||||
- "${HTTPS_REDIRECT_ENTRYPOINT_LABEL}"
|
||||
- "${HTTPS_REDIRECT_MIDDLEWARE_LABEL}"
|
||||
# middleware redirect for production only
|
||||
- "${HTTPS_USE_REDIRECT_MIDDLEWARE_LABEL}"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- cert-vol:/letsencrypt
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
userns_mode: "host"
|
||||
|
||||
erpnext-nginx:
|
||||
image: frappe/erpnext-nginx:${ERPNEXT_VERSION}
|
||||
restart: on-failure
|
||||
environment:
|
||||
- FRAPPE_PY=erpnext-python
|
||||
- FRAPPE_PY_PORT=8000
|
||||
- FRAPPE_SOCKETIO=frappe-socketio
|
||||
- SOCKETIO_PORT=9000
|
||||
- SKIP_NGINX_TEMPLATE_GENERATION=${SKIP_NGINX_TEMPLATE_GENERATION}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.erpnext-nginx.rule=Host(${SITES})"
|
||||
- "${ENTRYPOINT_LABEL}"
|
||||
- "${CERT_RESOLVER_LABEL}"
|
||||
- "traefik.http.services.erpnext-nginx.loadbalancer.server.port=80"
|
||||
volumes:
|
||||
- sites-vol:/var/www/html/sites:rw
|
||||
- assets-vol:/assets:rw
|
||||
|
||||
erpnext-python:
|
||||
image: frappe/erpnext-worker:${ERPNEXT_VERSION}
|
||||
restart: on-failure
|
||||
environment:
|
||||
- MARIADB_HOST=${MARIADB_HOST}
|
||||
- REDIS_CACHE=redis-cache:6379
|
||||
- REDIS_QUEUE=redis-queue:6379
|
||||
- REDIS_SOCKETIO=redis-socketio:6379
|
||||
- SOCKETIO_PORT=9000
|
||||
- AUTO_MIGRATE=1
|
||||
- WORKER_CLASS=${WORKER_CLASS}
|
||||
volumes:
|
||||
- sites-vol:/home/frappe/frappe-bench/sites:rw
|
||||
- assets-vol:/home/frappe/frappe-bench/sites/assets:rw
|
||||
|
||||
frappe-socketio:
|
||||
image: frappe/frappe-socketio:${FRAPPE_VERSION}
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
- redis-socketio
|
||||
volumes:
|
||||
- sites-vol:/home/frappe/frappe-bench/sites:rw
|
||||
- logs-vol:/home/frappe/frappe-bench/logs:rw
|
||||
|
||||
erpnext-worker-default:
|
||||
image: frappe/erpnext-worker:${ERPNEXT_VERSION}
|
||||
restart: on-failure
|
||||
command: worker
|
||||
depends_on:
|
||||
- redis-queue
|
||||
- redis-cache
|
||||
volumes:
|
||||
- sites-vol:/home/frappe/frappe-bench/sites:rw
|
||||
- logs-vol:/home/frappe/frappe-bench/logs:rw
|
||||
|
||||
erpnext-worker-short:
|
||||
image: frappe/erpnext-worker:${ERPNEXT_VERSION}
|
||||
restart: on-failure
|
||||
command: worker
|
||||
environment:
|
||||
- WORKER_TYPE=short
|
||||
depends_on:
|
||||
- redis-queue
|
||||
- redis-cache
|
||||
volumes:
|
||||
- sites-vol:/home/frappe/frappe-bench/sites:rw
|
||||
- logs-vol:/home/frappe/frappe-bench/logs:rw
|
||||
|
||||
erpnext-worker-long:
|
||||
image: frappe/erpnext-worker:${ERPNEXT_VERSION}
|
||||
restart: on-failure
|
||||
command: worker
|
||||
environment:
|
||||
- WORKER_TYPE=long
|
||||
depends_on:
|
||||
- redis-queue
|
||||
- redis-cache
|
||||
volumes:
|
||||
- sites-vol:/home/frappe/frappe-bench/sites:rw
|
||||
|
||||
erpnext-schedule:
|
||||
image: frappe/erpnext-worker:${ERPNEXT_VERSION}
|
||||
restart: on-failure
|
||||
command: schedule
|
||||
depends_on:
|
||||
- redis-queue
|
||||
- redis-cache
|
||||
volumes:
|
||||
- sites-vol:/home/frappe/frappe-bench/sites:rw
|
||||
- logs-vol:/home/frappe/frappe-bench/logs:rw
|
||||
|
||||
redis-cache:
|
||||
image: redis:latest
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- redis-cache-vol:/data
|
||||
|
||||
redis-queue:
|
||||
image: redis:latest
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- redis-queue-vol:/data
|
||||
|
||||
redis-socketio:
|
||||
image: redis:latest
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- redis-socketio-vol:/data
|
||||
|
||||
mariadb:
|
||||
image: mariadb:10.3
|
||||
restart: on-failure
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
|
||||
volumes:
|
||||
- ./installation/frappe-mariadb.cnf:/etc/mysql/conf.d/frappe.cnf
|
||||
- mariadb-vol:/var/lib/mysql
|
||||
|
||||
site-creator:
|
||||
image: frappe/erpnext-worker:${ERPNEXT_VERSION}
|
||||
restart: "no"
|
||||
command: new
|
||||
depends_on:
|
||||
- erpnext-python
|
||||
environment:
|
||||
- SITE_NAME=${SITE_NAME}
|
||||
- DB_ROOT_USER=${DB_ROOT_USER}
|
||||
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||
- INSTALL_APPS=${INSTALL_APPS}
|
||||
volumes:
|
||||
- sites-vol:/home/frappe/frappe-bench/sites:rw
|
||||
- logs-vol:/home/frappe/frappe-bench/logs:rw
|
||||
|
||||
volumes:
|
||||
mariadb-vol:
|
||||
redis-cache-vol:
|
||||
redis-queue-vol:
|
||||
redis-socketio-vol:
|
||||
assets-vol:
|
||||
sites-vol:
|
||||
cert-vol:
|
||||
logs-vol:
|
@ -1,58 +0,0 @@
|
||||
Create backup service or stack.
|
||||
|
||||
```yaml
|
||||
# backup-job.yml
|
||||
version: "3.7"
|
||||
services:
|
||||
backup:
|
||||
image: frappe/erpnext:${VERSION}
|
||||
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
|
||||
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
|
||||
volumes:
|
||||
- "sites:/home/frappe/frappe-bench/sites"
|
||||
networks:
|
||||
- erpnext-network
|
||||
|
||||
networks:
|
||||
erpnext-network:
|
||||
external: true
|
||||
name: ${PROJECT_NAME:-erpnext}_default
|
||||
|
||||
volumes:
|
||||
sites:
|
||||
external: true
|
||||
name: ${PROJECT_NAME:-erpnext}_sites
|
||||
```
|
||||
|
||||
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,16 +0,0 @@
|
||||
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.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Bench Console",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py",
|
||||
"args": ["frappe", "--site", "development.localhost", "console"],
|
||||
"pythonPath": "${workspaceFolder}/frappe-bench/env/bin/python",
|
||||
"cwd": "${workspaceFolder}/frappe-bench/sites",
|
||||
"env": {
|
||||
"DEV_SERVER": "1"
|
||||
}
|
||||
}
|
||||
```
|
@ -1,16 +0,0 @@
|
||||
Clone the version-10 branch of this repo
|
||||
|
||||
```shell
|
||||
git clone https://github.com/frappe/frappe_docker.git -b version-10 && cd frappe_docker
|
||||
```
|
||||
|
||||
Build the images
|
||||
|
||||
```shell
|
||||
export DOCKER_REGISTRY_PREFIX=frappe
|
||||
docker build -t ${DOCKER_REGISTRY_PREFIX}/frappe-socketio:v10 -f build/frappe-socketio/Dockerfile .
|
||||
docker build -t ${DOCKER_REGISTRY_PREFIX}/frappe-nginx:v10 -f build/frappe-nginx/Dockerfile .
|
||||
docker build -t ${DOCKER_REGISTRY_PREFIX}/erpnext-nginx:v10 -f build/erpnext-nginx/Dockerfile .
|
||||
docker build -t ${DOCKER_REGISTRY_PREFIX}/frappe-worker:v10 -f build/frappe-worker/Dockerfile .
|
||||
docker build -t ${DOCKER_REGISTRY_PREFIX}/erpnext-worker:v10 -f build/erpnext-worker/Dockerfile .
|
||||
```
|
@ -1,13 +0,0 @@
|
||||
Add following to frappe container from the `.devcontainer/docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
...
|
||||
frappe:
|
||||
...
|
||||
extra_hosts:
|
||||
app1.localhost: 172.17.0.1
|
||||
app2.localhost: 172.17.0.1
|
||||
...
|
||||
```
|
||||
|
||||
This is makes the domain names `app1.localhost` and `app2.localhost` connect to docker host and connect to services running on `localhost`.
|
58
docs/custom-apps-for-production.md
Normal file
58
docs/custom-apps-for-production.md
Normal file
@ -0,0 +1,58 @@
|
||||
# Custom apps
|
||||
|
||||
To add your own Frappe/ERPNext apps to the image, we'll need to create a custom image with the help of a unique wrapper script
|
||||
|
||||
> For the sake of simplicity, in this example, we'll be using a place holder called `[custom]`, and we'll be building off the edge image.
|
||||
|
||||
Create two directories called `[custom]-worker` and `[custom]-nginx` in the `build` directory.
|
||||
|
||||
```shell
|
||||
cd frappe_docker
|
||||
mkdir ./build/[custom]-worker ./build/[custom]-nginx
|
||||
```
|
||||
|
||||
Create a `Dockerfile` in `./build/[custom]-worker` with the following content:
|
||||
|
||||
```Dockerfile
|
||||
FROM frappe/erpnext-worker:edge
|
||||
|
||||
RUN install_app [custom] https://github.com/[username]/[custom] [branch]
|
||||
# Only add the branch if you are using a specific tag or branch.
|
||||
```
|
||||
|
||||
**Note:** Replace `https://github.com/[username]/[custom]` above with your custom app's Git repository URL (may include credentials if needed). Your custom app Git repository **must** be named exactly as the custom app's name, and use the same branch name as Frappe/ERPNext branch name that you use.
|
||||
|
||||
Create a `Dockerfile` in `./build/[custom]-nginx` with the following content:
|
||||
|
||||
```Dockerfile
|
||||
FROM bitnami/node:12-prod
|
||||
|
||||
COPY build/[custom]-nginx/install_app.sh /install_app
|
||||
|
||||
RUN /install_app [custom] https://github.com/[username]/[custom] [branch]
|
||||
|
||||
FROM frappe/erpnext-nginx:edge
|
||||
|
||||
COPY --from=0 /home/frappe/frappe-bench/sites/ /var/www/html/
|
||||
COPY --from=0 /rsync /rsync
|
||||
RUN echo -n "\n[custom]" >> /var/www/html/apps.txt
|
||||
|
||||
VOLUME [ "/assets" ]
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
Copy over the `install_app.sh` file from `./build/erpnext-nginx`
|
||||
|
||||
```shell
|
||||
cp ./build/erpnext-nginx/install_app.sh ./build/[custom]-nginx
|
||||
```
|
||||
|
||||
Open up `./installation/docker-compose-custom.yml` and replace all instances of `[app]` with the name of your app.
|
||||
|
||||
```shell
|
||||
sed -i "s#\[app\]#[custom]#" ./installation/docker-compose-custom.yml
|
||||
```
|
||||
|
||||
Install like usual, except that when you set the `INSTALL_APPS` variable to `erpnext,[custom]`.
|
@ -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,417 +0,0 @@
|
||||
# Getting Started
|
||||
|
||||
## Prerequisites
|
||||
|
||||
In order to start developing you need to satisfy the following prerequisites:
|
||||
|
||||
- Docker
|
||||
- docker-compose
|
||||
- user added to docker group
|
||||
|
||||
It is recommended you allocate at least 4GB of RAM to docker:
|
||||
|
||||
- [Instructions for Windows](https://docs.docker.com/docker-for-windows/#resources)
|
||||
- [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
|
||||
|
||||
Clone and change directory to frappe_docker directory
|
||||
|
||||
```shell
|
||||
git clone https://github.com/frappe/frappe_docker.git
|
||||
cd frappe_docker
|
||||
```
|
||||
|
||||
Copy example devcontainer config from `devcontainer-example` to `.devcontainer`
|
||||
|
||||
```shell
|
||||
cp -R devcontainer-example .devcontainer
|
||||
```
|
||||
|
||||
Copy example vscode config for devcontainer from `development/vscode-example` to `development/.vscode`. This will setup basic configuration for debugging.
|
||||
|
||||
```shell
|
||||
cp -R development/vscode-example development/.vscode
|
||||
```
|
||||
|
||||
## Use VSCode Remote Containers extension
|
||||
|
||||
For most people getting started with Frappe development, the best solution is to use [VSCode Remote - Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers).
|
||||
|
||||
Before opening the folder in container, determine the database that you want to use. The default is MariaDB.
|
||||
If you want to use PostgreSQL instead, edit `.devcontainer/docker-compose.yml` and uncomment the section for `postgresql` service, and you may also want to comment `mariadb` as well.
|
||||
|
||||
VSCode should automatically inquire you to install the required extensions, that can also be installed manually as follows:
|
||||
|
||||
- Install Remote - Containers for VSCode
|
||||
- through command line `code --install-extension ms-vscode-remote.remote-containers`
|
||||
- clicking on the Install button in the Vistual Studio Marketplace: [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
|
||||
- View: Extensions command in VSCode (Windows: Ctrl+Shift+X; macOS: Cmd+Shift+X) then search for extension `ms-vscode-remote.remote-containers`
|
||||
|
||||
After the extensions are installed, you can:
|
||||
|
||||
- Open frappe_docker folder in VS Code.
|
||||
- `code .`
|
||||
- Launch the command, from Command Palette (Ctrl + Shift + P) `Remote-Containers: Reopen in Container`. You can also click in the bottom left corner to access the remote container menu.
|
||||
|
||||
Notes:
|
||||
|
||||
- The `development` directory is ignored by git. It is mounted and available inside the container. Create all your benches (installations of bench, the tool that manages frappe) inside this directory.
|
||||
- Node v14 and v10 are installed. Check with `nvm ls`. Node v14 is used by default.
|
||||
|
||||
### Setup first bench
|
||||
|
||||
> Jump to [scripts](#setup-bench--new-site-using-script) section to setup a bench automatically. Alternatively, you can setup a bench manually using below guide.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
To setup frappe framework version 14 bench set `PYENV_VERSION` environment variable to `3.10.5` (default) and use NodeJS version 16 (default),
|
||||
|
||||
```shell
|
||||
# Use default environments
|
||||
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
|
||||
# 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,
|
||||
|
||||
```shell
|
||||
nvm use v14
|
||||
PYENV_VERSION=3.9.17 bench init --skip-redis-config-generation --frappe-branch version-13 frappe-bench
|
||||
cd frappe-bench
|
||||
```
|
||||
|
||||
### Setup hosts
|
||||
|
||||
We need to tell bench to use the right containers instead of localhost. Run the following commands inside the container:
|
||||
|
||||
```shell
|
||||
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
|
||||
```
|
||||
|
||||
For any reason the above commands fail, set the values in `common_site_config.json` manually.
|
||||
|
||||
```json
|
||||
{
|
||||
"db_host": "mariadb",
|
||||
"redis_cache": "redis://redis-cache:6379",
|
||||
"redis_queue": "redis://redis-queue:6379",
|
||||
"redis_socketio": "redis://redis-queue:6379"
|
||||
}
|
||||
```
|
||||
|
||||
### Edit Honcho's Procfile
|
||||
|
||||
Note : With the option '--skip-redis-config-generation' during bench init, these actions are no more needed. But at least, take a look to ProcFile to see what going on when bench launch honcho on start command
|
||||
|
||||
Honcho is the tool used by Bench to manage all the processes Frappe requires. Usually, these all run in localhost, but in this case, we have external containers for Redis. For this reason, we have to stop Honcho from trying to start Redis processes.
|
||||
|
||||
Honcho is installed in global python environment along with bench. To make it available locally you've to install it in every `frappe-bench/env` you create. Install it using command `./env/bin/pip install honcho`. It is required locally if you wish to use is as part of VSCode launch configuration.
|
||||
|
||||
Open the Procfile file and remove the three lines containing the configuration from Redis, either by editing manually the file:
|
||||
|
||||
```shell
|
||||
code Procfile
|
||||
```
|
||||
|
||||
Or running the following command:
|
||||
|
||||
```shell
|
||||
sed -i '/redis/d' ./Procfile
|
||||
```
|
||||
|
||||
### Create a new site with bench
|
||||
|
||||
You can create a new site with the following command:
|
||||
|
||||
```shell
|
||||
bench new-site --no-mariadb-socket sitename
|
||||
```
|
||||
|
||||
sitename MUST end with .localhost for trying deployments locally.
|
||||
|
||||
for example:
|
||||
|
||||
```shell
|
||||
bench new-site --no-mariadb-socket development.localhost
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
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`.
|
||||
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.
|
||||
|
||||
To setup site with PostgreSQL as database use option `--db-type postgres` and `--db-host postgresql`. (Available only v12 onwards, currently NOT available for ERPNext).
|
||||
|
||||
Example:
|
||||
|
||||
```shell
|
||||
bench new-site --db-type postgres --db-host postgresql mypgsql.localhost
|
||||
```
|
||||
|
||||
To avoid entering postgresql username and root password, set it in `common_site_config.json`,
|
||||
|
||||
```shell
|
||||
bench config set-common-config -c root_login postgres
|
||||
bench config set-common-config -c root_password '"123"'
|
||||
```
|
||||
|
||||
Note: If PostgreSQL is not required, the postgresql service / container can be stopped.
|
||||
|
||||
### Set bench developer mode on the new site
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
### Install an app
|
||||
|
||||
To install an app we need to fetch it from the appropriate git repo, then install in on the appropriate site:
|
||||
|
||||
You can check [VSCode container remote extension documentation](https://code.visualstudio.com/docs/remote/containers#_sharing-git-credentials-with-your-container) regarding git credential sharing.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```shell
|
||||
bench get-app --branch version-14 --resolve-deps erpnext
|
||||
bench --site development.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
|
||||
```
|
||||
|
||||
Note: Both frappe and erpnext must be on branch with same name. e.g. version-14
|
||||
|
||||
### Start Frappe without debugging
|
||||
|
||||
Execute following command from the `frappe-bench` directory.
|
||||
|
||||
```shell
|
||||
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)
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
||||
> 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
|
||||
|
||||
```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)
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
- Click on the extension icon inside VSCode
|
||||
- Search `ms-python.python`
|
||||
- Click on `Install on Dev Container: Frappe Bench`
|
||||
- Click on 'Reload'
|
||||
|
||||
We need to start bench separately through the VSCode debugger. For this reason, **instead** of running `bench start` you should run the following command inside the frappe-bench directory:
|
||||
|
||||
```shell
|
||||
honcho start \
|
||||
socketio \
|
||||
watch \
|
||||
schedule \
|
||||
worker_short \
|
||||
worker_long
|
||||
```
|
||||
|
||||
Alternatively you can use the VSCode launch configuration "Honcho SocketIO Watch Schedule Worker" which launches the same command as above.
|
||||
|
||||
This command starts all processes with the exception of Redis (which is already running in separate container) and the `web` process. The latter can can finally be started from the debugger tab of VSCode by clicking on the "play" button.
|
||||
|
||||
You can now login with user `Administrator` and the password you choose when creating the site, if you followed this guide's unattended install that password is going to be `admin`.
|
||||
|
||||
To debug workers, skip starting worker with honcho and start it with VSCode debugger.
|
||||
|
||||
For advance vscode configuration in the devcontainer, change the config files in `development/.vscode`.
|
||||
|
||||
## Developing using the interactive console
|
||||
|
||||
You can launch a simple interactive shell console in the terminal with:
|
||||
|
||||
```shell
|
||||
bench --site development.localhost console
|
||||
```
|
||||
|
||||
More likely, you may want to launch VSCode interactive console based on Jupyter kernel.
|
||||
|
||||
Launch VSCode command palette (cmd+shift+p or ctrl+shift+p), run the command `Python: Select interpreter to start Jupyter server` and select `/workspace/development/frappe-bench/env/bin/python`.
|
||||
|
||||
The first step is installing and updating the required software. Usually the frappe framework may require an older version of Jupyter, while VSCode likes to move fast, this can [cause issues](https://github.com/jupyter/jupyter_console/issues/158). For this reason we need to run the following command.
|
||||
|
||||
```shell
|
||||
/workspace/development/frappe-bench/env/bin/python -m pip install --upgrade jupyter ipykernel ipython
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```python
|
||||
import frappe
|
||||
|
||||
frappe.init(site='development.localhost', sites_path='/workspace/development/frappe-bench/sites')
|
||||
frappe.connect()
|
||||
frappe.local.lang = frappe.db.get_default('lang')
|
||||
frappe.db.connect()
|
||||
```
|
||||
|
||||
The first command can take a few seconds to be executed, this is to be expected.
|
||||
|
||||
## Manually start containers
|
||||
|
||||
In case you don't use VSCode, you may start the containers manually with the following command:
|
||||
|
||||
### Running the containers
|
||||
|
||||
```shell
|
||||
docker-compose -f .devcontainer/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
And enter the interactive shell for the development container with the following command:
|
||||
|
||||
```shell
|
||||
docker exec -e "TERM=xterm-256color" -w /workspace/development -it devcontainer-frappe-1 bash
|
||||
```
|
||||
|
||||
## Use additional services during development
|
||||
|
||||
Add any service that is needed for development in the `.devcontainer/docker-compose.yml` then rebuild and reopen in devcontainer.
|
||||
|
||||
e.g.
|
||||
|
||||
```yaml
|
||||
...
|
||||
services:
|
||||
...
|
||||
postgresql:
|
||||
image: postgres:11.8
|
||||
environment:
|
||||
POSTGRES_PASSWORD: 123
|
||||
volumes:
|
||||
- postgresql-data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
volumes:
|
||||
...
|
||||
postgresql-data:
|
||||
```
|
||||
|
||||
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.
|
295
docs/docker-swarm.md
Normal file
295
docs/docker-swarm.md
Normal file
@ -0,0 +1,295 @@
|
||||
### Prerequisites
|
||||
|
||||
IMPORTANT: All commands are executed on live server with public IP and DNS Configured.
|
||||
|
||||
#### Setup docker swarm
|
||||
|
||||
Follow [dockerswarm.rocks](https://dockerswarm.rocks) guide to setup Docker swarm, Traefik and Portainer.
|
||||
|
||||
Use Portainer for rest of the guide
|
||||
|
||||
### Create Config
|
||||
|
||||
Configs > Add Config > `frappe-mariadb-config`
|
||||
|
||||
```
|
||||
[mysqld]
|
||||
character-set-client-handshake = FALSE
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
|
||||
[mysql]
|
||||
default-character-set = utf8mb4
|
||||
```
|
||||
|
||||
### Create Secret
|
||||
|
||||
Secret > Add Secret > `frappe-mariadb-root-password`
|
||||
|
||||
```
|
||||
longsecretpassword
|
||||
```
|
||||
|
||||
Note down this password.
|
||||
It is only available in mariadb containers at location `/run/secrets/frappe-mariadb-root-password` later
|
||||
|
||||
### Deploy MariaDB Replication
|
||||
|
||||
Stacks > Add Stacks > `frappe-mariadb`
|
||||
|
||||
```yaml
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
mariadb-master:
|
||||
image: 'bitnami/mariadb:10.3'
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
configs:
|
||||
- source: frappe-mariadb-config
|
||||
target: /opt/bitnami/mariadb/conf/bitnami/my_custom.cnf
|
||||
networks:
|
||||
- frappe-network
|
||||
secrets:
|
||||
- frappe-mariadb-root-password
|
||||
volumes:
|
||||
- 'mariadb_master_data:/bitnami/mariadb'
|
||||
environment:
|
||||
- MARIADB_REPLICATION_MODE=master
|
||||
- MARIADB_REPLICATION_USER=repl_user
|
||||
- MARIADB_REPLICATION_PASSWORD_FILE=/run/secrets/frappe-mariadb-root-password
|
||||
- MARIADB_ROOT_PASSWORD_FILE=/run/secrets/frappe-mariadb-root-password
|
||||
|
||||
mariadb-slave:
|
||||
image: 'bitnami/mariadb:10.3'
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
configs:
|
||||
- source: frappe-mariadb-config
|
||||
target: /opt/bitnami/mariadb/conf/bitnami/my_custom.cnf
|
||||
networks:
|
||||
- frappe-network
|
||||
secrets:
|
||||
- frappe-mariadb-root-password
|
||||
volumes:
|
||||
- 'mariadb_slave_data:/bitnami/mariadb'
|
||||
environment:
|
||||
- MARIADB_REPLICATION_MODE=slave
|
||||
- MARIADB_REPLICATION_USER=repl_user
|
||||
- MARIADB_REPLICATION_PASSWORD_FILE=/run/secrets/frappe-mariadb-root-password
|
||||
- MARIADB_MASTER_HOST=mariadb-master
|
||||
- MARIADB_MASTER_PORT_NUMBER=3306
|
||||
- MARIADB_MASTER_ROOT_PASSWORD_FILE=/run/secrets/frappe-mariadb-root-password
|
||||
|
||||
volumes:
|
||||
mariadb_master_data:
|
||||
mariadb_slave_data:
|
||||
|
||||
configs:
|
||||
frappe-mariadb-config:
|
||||
external: true
|
||||
|
||||
secrets:
|
||||
frappe-mariadb-root-password:
|
||||
external: true
|
||||
|
||||
networks:
|
||||
frappe-network:
|
||||
name: frappe-network
|
||||
attachable: true
|
||||
```
|
||||
|
||||
### Deploy Frappe/ERPNext
|
||||
|
||||
Stacks > Add Stacks > `frappe-bench-v13`
|
||||
|
||||
```yaml
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
redis-cache:
|
||||
image: redis:latest
|
||||
volumes:
|
||||
- redis-cache-vol:/data
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
networks:
|
||||
- frappe-network
|
||||
|
||||
redis-queue:
|
||||
image: redis:latest
|
||||
volumes:
|
||||
- redis-queue-vol:/data
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
networks:
|
||||
- frappe-network
|
||||
|
||||
redis-socketio:
|
||||
image: redis:latest
|
||||
volumes:
|
||||
- redis-socketio-vol:/data
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
networks:
|
||||
- frappe-network
|
||||
|
||||
erpnext-nginx:
|
||||
image: frappe/erpnext-nginx:${ERPNEXT_VERSION?Variable ERPNEXT_VERSION not set}
|
||||
environment:
|
||||
- UPSTREAM_REAL_IP_ADDRESS=10.0.0.0/8
|
||||
- FRAPPE_PY=erpnext-python
|
||||
- FRAPPE_PY_PORT=8000
|
||||
- FRAPPE_SOCKETIO=frappe-socketio
|
||||
- SOCKETIO_PORT=9000
|
||||
volumes:
|
||||
- sites-vol:/var/www/html/sites:rw
|
||||
- assets-vol:/assets:rw
|
||||
networks:
|
||||
- frappe-network
|
||||
- traefik-public
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
labels:
|
||||
- "traefik.docker.network=traefik-public"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.constraint-label=traefik-public"
|
||||
- "traefik.http.routers.erpnext-nginx.rule=Host(${SITES?Variable SITES not set})"
|
||||
- "traefik.http.routers.erpnext-nginx.entrypoints=http"
|
||||
- "traefik.http.routers.erpnext-nginx.middlewares=https-redirect"
|
||||
- "traefik.http.routers.erpnext-nginx-https.rule=Host(${SITES?Variable SITES not set})"
|
||||
- "traefik.http.routers.erpnext-nginx-https.entrypoints=https"
|
||||
- "traefik.http.routers.erpnext-nginx-https.tls=true"
|
||||
- "traefik.http.routers.erpnext-nginx-https.tls.certresolver=le"
|
||||
- "traefik.http.services.erpnext-nginx.loadbalancer.server.port=80"
|
||||
|
||||
erpnext-python:
|
||||
image: frappe/erpnext-worker:${ERPNEXT_VERSION?Variable ERPNEXT_VERSION not set}
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
environment:
|
||||
- MARIADB_HOST=${MARIADB_HOST?Variable MARIADB_HOST not set}
|
||||
- REDIS_CACHE=redis-cache:6379
|
||||
- REDIS_QUEUE=redis-queue:6379
|
||||
- REDIS_SOCKETIO=redis-socketio:6379
|
||||
- SOCKETIO_PORT=9000
|
||||
- AUTO_MIGRATE=1
|
||||
volumes:
|
||||
- sites-vol:/home/frappe/frappe-bench/sites:rw
|
||||
- assets-vol:/home/frappe/frappe-bench/sites/assets:rw
|
||||
networks:
|
||||
- frappe-network
|
||||
|
||||
frappe-socketio:
|
||||
image: frappe/frappe-socketio:${FRAPPE_VERSION?Variable FRAPPE_VERSION not set}
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
volumes:
|
||||
- sites-vol:/home/frappe/frappe-bench/sites:rw
|
||||
networks:
|
||||
- frappe-network
|
||||
|
||||
erpnext-worker-default:
|
||||
image: frappe/erpnext-worker:${ERPNEXT_VERSION?Variable ERPNEXT_VERSION not set}
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command: worker
|
||||
volumes:
|
||||
- sites-vol:/home/frappe/frappe-bench/sites:rw
|
||||
networks:
|
||||
- frappe-network
|
||||
|
||||
erpnext-worker-short:
|
||||
image: frappe/erpnext-worker:${ERPNEXT_VERSION?Variable ERPNEXT_VERSION not set}
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command: worker
|
||||
environment:
|
||||
- WORKER_TYPE=short
|
||||
volumes:
|
||||
- sites-vol:/home/frappe/frappe-bench/sites:rw
|
||||
networks:
|
||||
- frappe-network
|
||||
|
||||
erpnext-worker-long:
|
||||
image: frappe/erpnext-worker:${ERPNEXT_VERSION?Variable ERPNEXT_VERSION not set}
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command: worker
|
||||
environment:
|
||||
- WORKER_TYPE=long
|
||||
volumes:
|
||||
- sites-vol:/home/frappe/frappe-bench/sites:rw
|
||||
networks:
|
||||
- frappe-network
|
||||
|
||||
frappe-schedule:
|
||||
image: frappe/erpnext-worker:${ERPNEXT_VERSION?Variable ERPNEXT_VERSION not set}
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command: schedule
|
||||
volumes:
|
||||
- sites-vol:/home/frappe/frappe-bench/sites:rw
|
||||
networks:
|
||||
- frappe-network
|
||||
|
||||
volumes:
|
||||
redis-cache-vol:
|
||||
redis-queue-vol:
|
||||
redis-socketio-vol:
|
||||
assets-vol:
|
||||
sites-vol:
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
frappe-network:
|
||||
external: true
|
||||
```
|
||||
|
||||
Use environment variables:
|
||||
|
||||
- `ERPNEXT_VERSION` variable to be set to desired version of ERPNext. e.g. 12.10.0
|
||||
- `FRAPPE_VERSION` variable to be set to desired version of Frappe Framework. e.g. 12.7.0
|
||||
- `MARIADB_HOST=frappe-mariadb_mariadb-master`
|
||||
- `SITES` variable is list of sites in back tick and separated by comma
|
||||
```
|
||||
SITES=`site1.example.com`,`site2.example.com`
|
||||
```
|
||||
|
||||
### Create new site job
|
||||
|
||||
1. Containers > Add Container > `add-site1-example-com`
|
||||
2. Select Image frappe/erpnext-worker:v13
|
||||
3. Set command as `new`
|
||||
4. Select network `frappe-network`
|
||||
5. Select Volume `frappe-bench-v13_sites_vol` and mount in container `/home/frappe/frappe-bench/sites`
|
||||
6. Env variables:
|
||||
- MYSQL_ROOT_PASSWORD=longsecretpassword
|
||||
- SITE_NAME=site1.example.com
|
||||
- INSTALL_APPS=erpnext
|
||||
7. Start container
|
||||
|
||||
### Migrate Sites job
|
||||
|
||||
1. Containers > Add Container > `migrate-sites`
|
||||
2. Select Image frappe/erpnext-worker:v13
|
||||
3. Set command as `migrate`
|
||||
4. Select network `frappe-network`
|
||||
5. Select Volume `frappe-bench-v13_sites_vol` and mount in container `/home/frappe/frappe-bench/sites`
|
||||
6. Env variables:
|
||||
- MAINTENANCE_MODE=1
|
||||
7. Start container
|
||||
|
@ -1,56 +1,33 @@
|
||||
## Environment Variables
|
||||
List of environment variables for containers
|
||||
|
||||
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:
|
||||
### frappe-worker and erpnext-worker
|
||||
|
||||
- `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`
|
||||
Following environment variables are set into sites volume as `common_site_config.json`. It means once the file is created in volume, the variables won't have any effect, you need to edit the common_site_config.json to update the configuration
|
||||
|
||||
To bypass `nginx-entrypoint.sh`, mount desired `/etc/nginx/conf.d/default.conf` and run `nginx -g 'daemon off;'` as container command.
|
||||
- `DB_HOST`: MariaDB host, domain name or ip address.
|
||||
- `DB_PORT`: MariaDB port.
|
||||
- `REDIS_CACHE`: Redis cache host, domain name or ip address.
|
||||
- `REDIS_QUEUE`: Redis queue host, domain name or ip address.
|
||||
- `REDIS_SOCKETIO`: Redis queue host, domain name or ip address.
|
||||
- `SOCKETIO_PORT: `: Port on which the SocketIO should start.
|
||||
- `WORKER_CLASS`: Optionally set gunicorn worker-class. Supports gevent only, defaults to gthread.
|
||||
|
||||
## Configuration
|
||||
### frappe-nginx and erpnext-nginx
|
||||
|
||||
We use environment variables to configure our setup. docker-compose uses variables from `.env` file. To get started, copy `example.env` to `.env`.
|
||||
These variables are set on every container start. Change in these variables will reflect on every container start.
|
||||
|
||||
### `FRAPPE_VERSION`
|
||||
- `FRAPPE_PY`: Gunicorn host to reverse proxy. Default: 0.0.0.0
|
||||
- `FRAPPE_PY_PORT`: Gunicorn port to reverse proxy. Default: 8000
|
||||
- `FRAPPE_SOCKETIO`: SocketIO host to reverse proxy. Default: 0.0.0.0
|
||||
- `SOCKETIO_PORT`: SocketIO port to reverse proxy. Default: 9000
|
||||
- `HTTP_TIMEOUT`: Nginx http timeout. Default: 120
|
||||
- `UPSTREAM_REAL_IP_ADDRESS `: The trusted address (or ip range) of upstream proxy servers. If set, this will tell nginx to trust the X-Forwarded-For header from these proxy servers in determining the real IP address of connecting clients. Default: 127.0.0.1
|
||||
- `UPSTREAM_REAL_IP_RECURSIVE`: When set to `on`, this will tell nginx to not just look to the last upstream proxy server in determining the real IP address. Default: off
|
||||
- `UPSTREAM_REAL_IP_HEADER`: Set this to the header name sent by your upstream proxy server to indicate the real IP of connecting clients. Default: X-Forwarded-For
|
||||
- `FRAPPE_SITE_NAME_HEADER`: NGINX `X-Frappe-Site-Name` header in the HTTP request which matches a site name. Default: `$host`
|
||||
- `HTTP_HOST`: NGINX `Host` header in the HTTP request which matches a site name. Default: `$http_host`
|
||||
- `SKIP_NGINX_TEMPLATE_GENERATION`: When set to `1`, this will not generate a default NGINX configuration. The config file must be mounted inside the container (`/etc/nginx/conf.d`) by the user in this case. Default: `0`
|
||||
|
||||
Frappe framework release. You can find all releases [here](https://github.com/frappe/frappe/releases).
|
||||
### frappe-socketio
|
||||
|
||||
### `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.
|
||||
This container takes configuration from `common_site_config.json` which is already created by erpnext gunicorn container. It doesn't use any environment variables.
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 31 KiB |
Binary file not shown.
Before Width: | Height: | Size: 50 KiB |
@ -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
|
||||
```
|
194
docs/multi-bench.md
Normal file
194
docs/multi-bench.md
Normal file
@ -0,0 +1,194 @@
|
||||
# Multi bench
|
||||
|
||||
This setup separates all services such that only required ones can be deployed.
|
||||
|
||||
This is suitable when multiple services are installed on cluster with shared proxy/router, database, cache etc.
|
||||
|
||||
Make sure you've cloned this repository and switch to the directory before executing following commands.
|
||||
|
||||
## Setup Environment Variables
|
||||
|
||||
Copy the example docker environment file to `.env`:
|
||||
|
||||
```sh
|
||||
cp env-example .env
|
||||
```
|
||||
|
||||
To get started, copy the existing `env-example` file to `.env`. By default, the file will contain the following variables:
|
||||
|
||||
- `VERSION=edge`
|
||||
- In this case, `edge` corresponds to `develop`. To setup any other version, you may use the branch name or version specific tags. (eg. v13.0.0, version-12, v11.1.15, v11)
|
||||
- `MYSQL_ROOT_PASSWORD=admin`
|
||||
- Bootstraps a MariaDB container with this value set as the root password. If a managed MariaDB instance is used, there is no need to set the password here.
|
||||
- `MARIADB_HOST=mariadb`
|
||||
- Sets the hostname to `mariadb`. This is required if the database is managed by the containerized MariaDB instance.
|
||||
- In case of a separately managed database setups, set the value to the database's hostname/IP/domain.
|
||||
- `SITES=site1.domain.com,site2.domain.com`
|
||||
- List of sites that are part of the deployment "bench" Each site is separated by a comma(,).
|
||||
- If LetsEncrypt is being setup, make sure that the DNS for all the site's domains correctly point to the current instance.
|
||||
- `LETSENCRYPT_EMAIL=your.email@your.domain.com`
|
||||
- Email for LetsEncrypt expiry notification. This is only required if you are setting up LetsEncrypt.
|
||||
|
||||
Notes:
|
||||
|
||||
- docker-compose-erpnext.yml and docker-compose-frappe.yml set `AUTO_MIGRATE` environment variable to `1`.
|
||||
- `AUTO_MIGRATE` checks if there is semver bump or git hash change in case of develop branch and automatically migrates the sites on container start up.
|
||||
- It is good practice to use image tag for specific version instead of latest. e.g `frappe-socketio:v12.5.1`, `erpnext-nginx:v12.7.1`.
|
||||
|
||||
## Local deployment for testing
|
||||
|
||||
For trying out locally or to develop apps using ERPNext REST API port 80 must be published.
|
||||
Following command will start the needed containers and expose ports.
|
||||
|
||||
For Erpnext:
|
||||
|
||||
```sh
|
||||
docker-compose \
|
||||
--project-name <project-name> \
|
||||
-f installation/docker-compose-common.yml \
|
||||
-f installation/docker-compose-erpnext.yml \
|
||||
-f installation/erpnext-publish.yml \
|
||||
up -d
|
||||
```
|
||||
|
||||
For Frappe:
|
||||
|
||||
```sh
|
||||
docker-compose \
|
||||
--project-name <project-name> \
|
||||
-f installation/docker-compose-common.yml \
|
||||
-f installation/docker-compose-frappe.yml \
|
||||
-f installation/frappe-publish.yml \
|
||||
up -d
|
||||
```
|
||||
|
||||
Make sure to replace `<project-name>` with the desired name you wish to set for the project.
|
||||
|
||||
Notes:
|
||||
|
||||
- New site (first site) needs to be added after starting the services.
|
||||
- The local deployment is for testing and REST API development purpose only
|
||||
- A complete development environment is available [here](../development)
|
||||
- The site names are limited to patterns matching \*.localhost by default
|
||||
- Additional site name patterns can be added by editing /etc/hosts of your host machine
|
||||
|
||||
## Deployment for production
|
||||
|
||||
### Setup Letsencrypt Nginx Proxy Companion
|
||||
|
||||
Letsencrypt Nginx Proxy Companion can optionally be setup to provide SSL. This is recommended for instances accessed over the internet.
|
||||
|
||||
Your DNS will need to be configured correctly for Letsencrypt to verify your domain.
|
||||
|
||||
To setup the proxy companion, run the following commands:
|
||||
|
||||
```sh
|
||||
cd $HOME
|
||||
git clone https://github.com/evertramos/docker-compose-letsencrypt-nginx-proxy-companion.git
|
||||
cd docker-compose-letsencrypt-nginx-proxy-companion
|
||||
cp .env.sample .env
|
||||
./start.sh
|
||||
```
|
||||
|
||||
It will create the required network and configure containers for Letencrypt ACME.
|
||||
|
||||
For more details, see the [Letsencrypt Nginx Proxy Companion github repo](https://github.com/evertramos/docker-compose-letsencrypt-nginx-proxy-companion). Letsencrypt Nginx Proxy Companion github repo works by automatically proxying to containers with the `VIRTUAL_HOST` environmental variable.
|
||||
|
||||
Notes:
|
||||
|
||||
- `SITES` variables from `env-example` is set as `VIRTUAL_HOST`
|
||||
- `LETSENCRYPT_EMAIL` variables from `env-example` is used as it is.
|
||||
- This is simple nginx + letsencrypt solution. Any other solution can be setup. Above two variables can be re-used or removed in case any other reverse-proxy is used.
|
||||
|
||||
### Start Frappe/ERPNext Services
|
||||
|
||||
To start the Frappe/ERPNext services for production, run the following command:
|
||||
|
||||
```sh
|
||||
docker-compose \
|
||||
--project-name <project-name> \
|
||||
-f installation/docker-compose-common.yml \
|
||||
-f installation/docker-compose-erpnext.yml \
|
||||
-f installation/docker-compose-networks.yml \
|
||||
up -d
|
||||
```
|
||||
|
||||
Make sure to replace `<project-name>` with any desired name you wish to set for the project.
|
||||
|
||||
Notes:
|
||||
|
||||
- Use `docker-compose-frappe.yml` in case you need only Frappe without ERPNext.
|
||||
- New site (first site) needs to be added after starting the services.
|
||||
|
||||
## Docker containers
|
||||
|
||||
This repository contains the following docker-compose files, each one containing the described images:
|
||||
* docker-compose-common.yml
|
||||
* redis-cache
|
||||
* volume: redis-cache-vol
|
||||
* redis-queue
|
||||
* volume: redis-queue-vol
|
||||
* redis-socketio
|
||||
* volume: redis-socketio-vol
|
||||
* mariadb: main database
|
||||
* volume: mariadb-vol
|
||||
* docker-compose-erpnext.yml
|
||||
* erpnext-nginx: serves static assets and proxies web request to the appropriate container, allowing to offer all services on the same port.
|
||||
* volume: assets-vol
|
||||
* erpnext-python: main application code
|
||||
* frappe-socketio: enables realtime communication to the user interface through websockets
|
||||
* frappe-worker-default: background runner
|
||||
* frappe-worker-short: background runner for short-running jobs
|
||||
* frappe-worker-long: background runner for long-running jobs
|
||||
* frappe-schedule
|
||||
|
||||
* docker-compose-frappe.yml
|
||||
* frappe-nginx: serves static assets and proxies web request to the appropriate container, allowing to offer all services on the same port.
|
||||
* volume: assets-vol, sites-vol
|
||||
* erpnext-python: main application code
|
||||
* volume: sites-vol
|
||||
* frappe-socketio: enables realtime communication to the user interface through websockets
|
||||
* volume: sites-vol
|
||||
* frappe-worker-default: background runner
|
||||
* volume: sites-vol
|
||||
* frappe-worker-short: background runner for short-running jobs
|
||||
* volume: sites-vol
|
||||
* frappe-worker-long: background runner for long-running jobs
|
||||
* volume: sites-vol
|
||||
* frappe-schedule
|
||||
* volume: sites-vol
|
||||
|
||||
* docker-compose-networks.yml: this yaml define the network to communicate with *Letsencrypt Nginx Proxy Companion*.
|
||||
|
||||
* erpnext-publish.yml: this yml extends erpnext-nginx service to publish port 80, can only be used with docker-compose-erpnext.yml
|
||||
|
||||
* frappe-publish.yml: this yml extends frappe-nginx service to publish port 80, can only be used with docker-compose-frappe.yml
|
||||
|
||||
## Updating and Migrating Sites
|
||||
|
||||
Switch to the root of the `frappe_docker` directory before running the following commands:
|
||||
|
||||
```sh
|
||||
# Update environment variable VERSION
|
||||
nano .env
|
||||
|
||||
# Pull new images
|
||||
docker-compose \
|
||||
-f installation/docker-compose-common.yml \
|
||||
-f installation/docker-compose-erpnext.yml \
|
||||
pull
|
||||
|
||||
# Restart containers
|
||||
docker-compose \
|
||||
--project-name <project-name> \
|
||||
-f installation/docker-compose-common.yml \
|
||||
-f installation/docker-compose-erpnext.yml \
|
||||
-f installation/docker-compose-networks.yml \
|
||||
up -d
|
||||
|
||||
docker run \
|
||||
-e "MAINTENANCE_MODE=1" \
|
||||
-v <project-name>_sites-vol:/home/frappe/frappe-bench/sites \
|
||||
--network <project-name>_default \
|
||||
frappe/erpnext-worker:$VERSION migrate
|
||||
```
|
@ -1,69 +0,0 @@
|
||||
WARNING: Do not use this in production if the site is going to be served over plain http.
|
||||
|
||||
### Step 1
|
||||
|
||||
Remove the traefik service from docker-compose.yml
|
||||
|
||||
### Step 2
|
||||
|
||||
Add service for each port that needs to be exposed.
|
||||
|
||||
e.g. `port-site-1`, `port-site-2`, `port-site-3`.
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
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.
|
@ -1,131 +0,0 @@
|
||||
# Containerized Production Setup
|
||||
|
||||
Make sure you've cloned this repository and switch to the directory before executing following commands.
|
||||
|
||||
Commands will generate YAML as per the environment for setup.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [docker](https://docker.com/get-started)
|
||||
- [docker compose v2](https://docs.docker.com/compose/cli-command)
|
||||
|
||||
## Setup Environment Variables
|
||||
|
||||
Copy the example docker environment file to `.env`:
|
||||
|
||||
```sh
|
||||
cp example.env .env
|
||||
```
|
||||
|
||||
Note: To know more about environment variable [read here](./environment-variables.md). Set the necessary variables in the `.env` file.
|
||||
|
||||
## Generate docker-compose.yml for variety of setups
|
||||
|
||||
Notes:
|
||||
|
||||
- Make sure to replace `<project-name>` with the desired name you wish to set for the project.
|
||||
- This setup is not to be used for development. A complete development environment is available [here](../development)
|
||||
|
||||
### Store the yaml files
|
||||
|
||||
YAML files generated by `docker compose config` command can be stored in a directory. We will create a directory called `gitops` in the user's home.
|
||||
|
||||
```shell
|
||||
mkdir ~/gitops
|
||||
```
|
||||
|
||||
You can make the directory into a private git repo which stores the yaml and secrets. It can help in tracking changes.
|
||||
|
||||
Instead of `docker compose config`, you can directly use `docker compose up` to start the containers and skip storing the yamls in `gitops` directory.
|
||||
|
||||
### 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.
|
||||
|
||||
```sh
|
||||
# Generate YAML
|
||||
docker compose -f compose.yaml -f overrides/compose.noproxy.yaml config > ~/gitops/docker-compose.yml
|
||||
|
||||
# Start containers
|
||||
docker compose --project-name <project-name> -f ~/gitops/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
```sh
|
||||
# Generate YAML
|
||||
docker compose -f compose.yaml \
|
||||
-f overrides/compose.proxy.yaml \
|
||||
config > ~/gitops/docker-compose.yml
|
||||
|
||||
# Start containers
|
||||
docker compose --project-name <project-name> -f ~/gitops/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
### 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 \
|
||||
-f overrides/compose.mariadb.yaml \
|
||||
-f overrides/compose.redis.yaml \
|
||||
-f overrides/compose.https.yaml \
|
||||
config > ~/gitops/docker-compose.yml
|
||||
|
||||
# Start containers
|
||||
docker compose --project-name <project-name> -f ~/gitops/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
### 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.mariadb.yaml \
|
||||
-f overrides/compose.redis.yaml \
|
||||
-f overrides/compose.https.yaml \
|
||||
config > ~/gitops/docker-compose.yml
|
||||
|
||||
# Start containers
|
||||
docker compose --project-name <project-name> -f ~/gitops/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
## Create first site
|
||||
|
||||
After starting containers, the first site needs to be created. Refer [site operations](./site-operations.md#setup-new-site).
|
||||
|
||||
## Updating Images
|
||||
|
||||
Switch to the root of the `frappe_docker` directory before running the following commands:
|
||||
|
||||
```sh
|
||||
# Update environment variables ERPNEXT_VERSION and FRAPPE_VERSION
|
||||
nano .env
|
||||
|
||||
# Pull new images
|
||||
docker compose -f compose.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
|
||||
|
||||
# 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
|
129
docs/single-bench.md
Normal file
129
docs/single-bench.md
Normal file
@ -0,0 +1,129 @@
|
||||
# Single Bench
|
||||
|
||||
This setup starts traefik service as part of single docker-compose project. It is quick to get started locally or on production for a single server with single deployment.
|
||||
|
||||
This is not suitable when multiple services are installed on cluster with shared proxy/router, database, cache etc.
|
||||
|
||||
Make sure you've cloned this repository and switch to the directory before executing following commands.
|
||||
|
||||
## Setup Environment Variables
|
||||
|
||||
Copy the example docker environment file to `.env`:
|
||||
|
||||
For local setup
|
||||
|
||||
```sh
|
||||
cp env-local .env
|
||||
```
|
||||
|
||||
For production
|
||||
|
||||
```sh
|
||||
cp env-production .env
|
||||
|
||||
```
|
||||
|
||||
To get started, copy the existing `env-local` or `env-production` file to `.env`. By default, the file will contain the following variables:
|
||||
|
||||
- `ERPNEXT_VERSION=edge`
|
||||
- In this case, `edge` corresponds to `develop`. To setup any other version, you may use the branch name or version specific tags. (eg. v13.0.0, version-12, v11.1.15, v11).
|
||||
- `FRAPPE_VERSION=edge`
|
||||
- In this case, `edge` corresponds to `develop`. To setup any other version, you may use the branch name or version specific tags. (eg. v13.0.0, version-12, v11.1.15, v11).
|
||||
- `MARIADB_HOST=mariadb`
|
||||
- Sets the hostname to `mariadb`. This is required if the database is managed by the containerized MariaDB instance.
|
||||
- `MYSQL_ROOT_PASSWORD=admin`
|
||||
- Bootstraps a MariaDB container with this value set as the root password. If a managed MariaDB instance is used, there is no need to set the password here.
|
||||
- In case of a separately managed database setups, set the value to the database's hostname/IP/domain.
|
||||
- `SITE_NAME=erp.example.com`
|
||||
- Creates this site after starting all services and installs ERPNext. Site name must be resolvable by users machines and the ERPNext components. e.g. `erp.example.com` or `mysite.localhost`.
|
||||
- ``SITES=`erp.example.com` ``
|
||||
- List of sites that are part of the deployment "bench" Each site is separated by a comma(,) and quoted in backtick (`). By default site created by ``SITE_NAME`` variable is added here.
|
||||
- If LetsEncrypt is being setup, make sure that the DNS for all the site's domains correctly point to the current instance.
|
||||
- `DB_ROOT_USER=root`
|
||||
- MariaDB root username
|
||||
- `ADMIN_PASSWORD=admin`
|
||||
- Password for the `Administrator` user, credentials after install `Administrator:$ADMIN_PASSWORD`.
|
||||
- `INSTALL_APPS=erpnext`
|
||||
- Apps to install, the app must be already in the container image, to install other application read the [instructions on installing custom apps](./custom-apps-for-production.md).
|
||||
- `LETSENCRYPT_EMAIL=email@example.com`
|
||||
- Email for LetsEncrypt expiry notification. This is only required if you are setting up LetsEncrypt.
|
||||
- `ENTRYPOINT_LABEL=traefik.http.routers.erpnext-nginx.entrypoints=websecure`
|
||||
- Related to the traefik configuration, says all traffic from outside should come from HTTP or HTTPS, for local development should be web, for production websecure. if redirection is needed, read below.
|
||||
- `CERT_RESOLVER_LABEL=traefik.http.routers.erpnext-nginx.tls.certresolver=myresolver`
|
||||
- Which traefik resolver to use to get TLS certificate, sets `erpnext.local.no-cert-resolver` for local setup.
|
||||
- ``HTTPS_REDIRECT_RULE_LABEL=traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`) ``
|
||||
- Related to the traefik https redirection configuration, sets `erpnext.local.no-redirect-rule` for local setup.
|
||||
- `HTTPS_REDIRECT_ENTRYPOINT_LABEL=traefik.http.routers.http-catchall.entrypoints=web`
|
||||
- Related to the traefik https redirection configuration, sets `erpnext.local.no-entrypoint` for local setup.
|
||||
- `HTTPS_REDIRECT_MIDDLEWARE_LABEL=traefik.http.routers.http-catchall.middlewares=redirect-to-https`
|
||||
- Related to the traefik https redirection configuration, sets `erpnext.local.no-middleware` for local setup.
|
||||
- `HTTPS_USE_REDIRECT_MIDDLEWARE_LABEL=traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https`
|
||||
- Related to the traefik https redirection configuration, sets `erpnext.local-no-redirect-middleware` for local setup.
|
||||
|
||||
Notes:
|
||||
|
||||
- `AUTO_MIGRATE` variable is set to `1` by default. It checks if there is semver bump or git hash change in case of develop branch and automatically migrates the sites on container start up.
|
||||
- It is good practice to use image tag for specific version instead of latest. e.g `frappe-socketio:v12.5.1`, `erpnext-nginx:v12.7.1`.
|
||||
|
||||
## Start containers
|
||||
|
||||
Execute the following command:
|
||||
|
||||
```sh
|
||||
docker-compose --project-name <project-name> up -d
|
||||
```
|
||||
|
||||
Make sure to replace `<project-name>` with the desired name you wish to set for the project.
|
||||
|
||||
Notes:
|
||||
|
||||
- If it is the first time running and site is being initialized, *it can take multiple minutes for the site to be up*. Monitor `site-creator` container logs to check progress. Use command `docker logs <project-name>_site-creator_1 -f`
|
||||
- After the site is ready the username is `Administrator` and the password is `$ADMIN_PASSWORD`
|
||||
- The local deployment is for testing and REST API development purpose only
|
||||
- A complete development environment is available [here](../development)
|
||||
|
||||
## Docker containers
|
||||
|
||||
The docker-compose file contains following services:
|
||||
|
||||
* traefik: manages letsencrypt
|
||||
* volume: cert-vol
|
||||
* redis-cache: cache store
|
||||
* volume: redis-cache-vol
|
||||
* redis-queue: used by workers
|
||||
* volume: redis-queue-vol
|
||||
* redis-socketio: used by socketio service
|
||||
* volume: redis-socketio-vol
|
||||
* mariadb: main database
|
||||
* volume: mariadb-vol
|
||||
* erpnext-nginx: serves static assets and proxies web request to the appropriate container, allowing to offer all services on the same port.
|
||||
* volume: assets-vol and sites-vol
|
||||
* erpnext-python: main application code
|
||||
* volume: sites-vol and sites-vol
|
||||
* frappe-socketio: enables realtime communication to the user interface through websockets
|
||||
* volume: sites-vol
|
||||
* erpnext-worker-default: background runner
|
||||
* volume: sites-vol
|
||||
* erpnext-worker-short: background runner for short-running jobs
|
||||
* volume: sites-vol
|
||||
* erpnext-worker-long: background runner for long-running jobs
|
||||
* volume: sites-vol
|
||||
* erpnext-schedule
|
||||
* volume: sites-vol
|
||||
* site-creator: run once container to create new site.
|
||||
* volume: sites-vol
|
||||
|
||||
## Updating and Migrating Sites
|
||||
|
||||
Switch to the root of the `frappe_docker` directory before running the following commands:
|
||||
|
||||
```sh
|
||||
# Update environment variables ERPNEXT_VERSION and FRAPPE_VERSION
|
||||
nano .env
|
||||
|
||||
# Pull new images
|
||||
docker-compose pull
|
||||
|
||||
# Restart containers
|
||||
docker-compose --project-name <project-name> up -d
|
||||
```
|
@ -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.
|
@ -1,288 +0,0 @@
|
||||
### Single Server Example
|
||||
|
||||
In this use case we have a single server with a static IP attached to it. It can be used in scenarios where one powerful VM has multiple benches and applications or one entry level VM with single site. For single bench, single site setup follow only up to the point where first bench and first site is added. If you choose this setup you can only scale vertically. If you need to scale horizontally you'll need to backup the sites and restore them on to cluster setup.
|
||||
|
||||
We will setup the following:
|
||||
|
||||
- Install docker and docker compose v2 on linux server.
|
||||
- Install traefik service for internal load balancer and letsencrypt.
|
||||
- Install MariaDB with containers.
|
||||
- Setup project called `erpnext-one` and create sites `one.example.com` and `two.example.com` in the project.
|
||||
- Setup project called `erpnext-two` and create sites `three.example.com` and `four.example.com` in the project.
|
||||
|
||||
Explanation:
|
||||
|
||||
Single instance of **Traefik** will be installed and act as internal loadbalancer for multiple benches and sites hosted on the server. It can also load balance other applications along with frappe benches, e.g. wordpress, metabase, etc. We only expose the ports `80` and `443` once with this instance of traefik. Traefik will also take care of letsencrypt automation for all sites installed on the server. _Why choose Traefik over Nginx Proxy Manager?_ Traefik doesn't need additional DB service and can store certificates in a json file in a volume.
|
||||
|
||||
Single instance of **MariaDB** will be installed and act as database service for all the benches/projects installed on the server.
|
||||
|
||||
Each instance of ERPNext project (bench) will have its own redis, socketio, gunicorn, nginx, workers and scheduler. It will connect to internal MariaDB by connecting to MariaDB network. It will expose sites to public through Traefik by connecting to Traefik network.
|
||||
|
||||
### Install Docker
|
||||
|
||||
Easiest way to install docker is to use the [convenience script](https://docs.docker.com/engine/install/ubuntu/#install-using-the-convenience-script).
|
||||
|
||||
```shell
|
||||
curl -fsSL https://get.docker.com | bash
|
||||
```
|
||||
|
||||
Note: The documentation assumes Ubuntu LTS server is used. Use any distribution as long as the docker convenience script works. If the convenience script doesn't work, you'll need to install docker manually.
|
||||
|
||||
### Install Compose V2
|
||||
|
||||
Refer [original documentation](https://docs.docker.com/compose/cli-command/#install-on-linux) for updated version.
|
||||
|
||||
```shell
|
||||
DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
|
||||
mkdir -p $DOCKER_CONFIG/cli-plugins
|
||||
curl -SL https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose
|
||||
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.
|
||||
|
||||
```shell
|
||||
git clone https://github.com/frappe/frappe_docker
|
||||
cd frappe_docker
|
||||
```
|
||||
|
||||
Create configuration and resources directory
|
||||
|
||||
```shell
|
||||
mkdir ~/gitops
|
||||
```
|
||||
|
||||
The `~/gitops` directory will store all the resources that we use for setup. We will also keep the environment files in this directory as there will be multiple projects with different environment variables. You can create a private repo for this directory and track the changes there.
|
||||
|
||||
### Install Traefik
|
||||
|
||||
Basic Traefik setup using docker compose.
|
||||
|
||||
Create a file called `traefik.env` in `~/gitops`
|
||||
|
||||
```shell
|
||||
echo 'TRAEFIK_DOMAIN=traefik.example.com' > ~/gitops/traefik.env
|
||||
echo 'EMAIL=admin@example.com' >> ~/gitops/traefik.env
|
||||
echo 'HASHED_PASSWORD='$(openssl passwd -apr1 changeit | sed 's/\$/\\\$/g') >> ~/gitops/traefik.env
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
- Change the domain from `traefik.example.com` to the one used in production. DNS entry needs to point to the Server IP.
|
||||
- Change the letsencrypt notification email from `admin@example.com` to correct email.
|
||||
- Change the password from `changeit` to more secure.
|
||||
|
||||
env file generated at location `~/gitops/traefik.env` will look like following:
|
||||
|
||||
```env
|
||||
TRAEFIK_DOMAIN=traefik.example.com
|
||||
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
|
||||
```
|
||||
|
||||
This will make the traefik dashboard available on `traefik.example.com` and all certificates will reside in the Docker volume `cert-data`.
|
||||
|
||||
For LAN setup deploy the traefik container without overriding `overrides/compose.traefik-ssl.yaml`.
|
||||
|
||||
### Install MariaDB
|
||||
|
||||
Basic MariaDB setup using docker compose.
|
||||
|
||||
Create a file called `mariadb.env` in `~/gitops`
|
||||
|
||||
```shell
|
||||
echo "DB_PASSWORD=changeit" > ~/gitops/mariadb.env
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
- Change the password from `changeit` to more secure.
|
||||
|
||||
env file generated at location `~/gitops/mariadb.env` will look like following:
|
||||
|
||||
```env
|
||||
DB_PASSWORD=changeit
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
This will make `mariadb-database` service available under `mariadb-network`. Data will reside in `/data/mariadb`.
|
||||
|
||||
### Install ERPNext
|
||||
|
||||
#### Create first bench
|
||||
|
||||
Create first bench called `erpnext-one` with `one.example.com` and `two.example.com`
|
||||
|
||||
Create a file called `erpnext-one.env` in `~/gitops`
|
||||
|
||||
```shell
|
||||
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 "BENCH_NETWORK=erpnext-one" >> ~/gitops/erpnext-one.env
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
- Change the password from `changeit` to the one set for MariaDB compose in the previous step.
|
||||
|
||||
env file is generated at location `~/gitops/erpnext-one.env`.
|
||||
|
||||
Create a yaml file called `erpnext-one.yaml` in `~/gitops` directory:
|
||||
|
||||
```shell
|
||||
docker compose --project-name erpnext-one \
|
||||
--env-file ~/gitops/erpnext-one.env \
|
||||
-f compose.yaml \
|
||||
-f overrides/compose.redis.yaml \
|
||||
-f overrides/compose.multi-bench.yaml \
|
||||
-f overrides/compose.multi-bench-ssl.yaml config > ~/gitops/erpnext-one.yaml
|
||||
```
|
||||
|
||||
For LAN setup do not override `compose.multi-bench-ssl.yaml`.
|
||||
|
||||
Use the above command after any changes are made to `erpnext-one.env` file to regenerate `~/gitops/erpnext-one.yaml`. e.g. after changing version to migrate the bench.
|
||||
|
||||
Deploy `erpnext-one` containers:
|
||||
|
||||
```shell
|
||||
docker compose --project-name erpnext-one -f ~/gitops/erpnext-one.yaml up -d
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
You can stop here and have a single bench single site setup complete. Continue to add one more site to the current bench.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
#### Create second bench
|
||||
|
||||
Setting up additional bench is optional. Continue only if you need multi bench setup.
|
||||
|
||||
Create second bench called `erpnext-two` with `three.example.com` and `four.example.com`
|
||||
|
||||
Create a file called `erpnext-two.env` in `~/gitops`
|
||||
|
||||
```shell
|
||||
curl -sL https://raw.githubusercontent.com/frappe/frappe_docker/main/example.env -o ~/gitops/erpnext-two.env
|
||||
sed -i 's/DB_PASSWORD=123/DB_PASSWORD=changeit/g' ~/gitops/erpnext-two.env
|
||||
sed -i 's/DB_HOST=/DB_HOST=mariadb-database/g' ~/gitops/erpnext-two.env
|
||||
sed -i 's/DB_PORT=/DB_PORT=3306/g' ~/gitops/erpnext-two.env
|
||||
echo "ROUTER=erpnext-two" >> ~/gitops/erpnext-two.env
|
||||
echo "SITES=\`three.example.com\`,\`four.example.com\`" >> ~/gitops/erpnext-two.env
|
||||
echo "BENCH_NETWORK=erpnext-two" >> ~/gitops/erpnext-two.env
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
- Change the password from `changeit` to the one set for MariaDB compose in the previous step.
|
||||
|
||||
env file is generated at location `~/gitops/erpnext-two.env`.
|
||||
|
||||
Create a yaml file called `erpnext-two.yaml` in `~/gitops` directory:
|
||||
|
||||
```shell
|
||||
docker compose --project-name erpnext-two \
|
||||
--env-file ~/gitops/erpnext-two.env \
|
||||
-f compose.yaml \
|
||||
-f overrides/compose.redis.yaml \
|
||||
-f overrides/compose.multi-bench.yaml \
|
||||
-f overrides/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.
|
||||
|
||||
Deploy `erpnext-two` containers:
|
||||
|
||||
```shell
|
||||
docker compose --project-name erpnext-two -f ~/gitops/erpnext-two.yaml up -d
|
||||
```
|
||||
|
||||
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
|
||||
# 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
|
||||
```
|
||||
|
||||
#### Create custom domain to existing site
|
||||
|
||||
In case you need to point custom domain to existing site follow these steps.
|
||||
Also useful if custom domain is required for LAN based access.
|
||||
|
||||
Create environment file
|
||||
|
||||
```shell
|
||||
echo "ROUTER=custom-one-example" > ~/gitops/custom-one-example.env
|
||||
echo "SITES=\`custom-one.example.com\`" >> ~/gitops/custom-one-example.env
|
||||
echo "BASE_SITE=one.example.com" >> ~/gitops/custom-one-example.env
|
||||
echo "BENCH_NETWORK=erpnext-one" >> ~/gitops/custom-one-example.env
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
- Change the file name from `custom-one-example.env` to a logical one.
|
||||
- Change `ROUTER` variable from `custom-one.example.com` to the one being added.
|
||||
- Change `SITES` variable from `custom-one.example.com` to the one being added. You can add multiple sites quoted in backtick (`) and separated by commas.
|
||||
- Change `BASE_SITE` variable from `one.example.com` to the one which is being pointed to.
|
||||
- Change `BENCH_NETWORK` variable from `erpnext-one` to the one which was created with the bench.
|
||||
|
||||
env file is generated at location mentioned in command.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
For LAN setup do not override `compose.custom-domain-ssl.yaml`.
|
||||
|
||||
Deploy `erpnext-two` containers:
|
||||
|
||||
```shell
|
||||
docker compose --project-name custom-one-example -f ~/gitops/custom-one-example.yaml up -d
|
||||
```
|
||||
|
||||
### Site operations
|
||||
|
||||
Refer: [site operations](./site-operations.md)
|
@ -1,53 +1,190 @@
|
||||
# Site operations
|
||||
|
||||
> 💡 You should setup `--project-name` option in `docker-compose` commands if you have non-standard project name.
|
||||
Create and use env file to pass environment variables to containers,
|
||||
|
||||
## Setup new site
|
||||
```sh
|
||||
source .env
|
||||
```
|
||||
|
||||
Or specify environment variables instead of passing secrets as command arguments. Refer notes section for environment variables required
|
||||
|
||||
## Setup New Site
|
||||
|
||||
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.
|
||||
- Wait for the database service to start before trying to create a new site.
|
||||
- If new site creation fails, retry after the MariaDB container is up and running.
|
||||
- If you're using a managed database instance, make sure that the database is running before setting up a new site.
|
||||
|
||||
#### MariaDB Site
|
||||
|
||||
```sh
|
||||
docker-compose exec backend bench new-site --no-mariadb-socket --mariadb-root-password <db-password> --admin-password <admin-password> <site-name>
|
||||
# Create ERPNext site
|
||||
docker run \
|
||||
-e "SITE_NAME=$SITE_NAME" \
|
||||
-e "DB_ROOT_USER=$DB_ROOT_USER" \
|
||||
-e "MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD" \
|
||||
-e "ADMIN_PASSWORD=$ADMIN_PASSWORD" \
|
||||
-e "INSTALL_APPS=erpnext" \
|
||||
-v <project-name>_sites-vol:/home/frappe/frappe-bench/sites \
|
||||
--network <project-name>_default \
|
||||
frappe/erpnext-worker:$VERSION new
|
||||
```
|
||||
|
||||
If you need to install some app, specify `--install-app`. To see all options, just run `bench new-site --help`.
|
||||
#### PostgreSQL Site
|
||||
|
||||
To create Postgres site (assuming you already use [Postgres compose override](images-and-compose-files.md#overrides)) you need have to do set `root_login` and `root_password` in common config before that:
|
||||
PostgreSQL is only available v12 onwards. It is NOT available for ERPNext.
|
||||
It is available as part of `frappe/erpnext-worker`. It inherits from `frappe/frappe-worker`.
|
||||
|
||||
```sh
|
||||
docker-compose exec backend bench set-config -g root_login <root-login>
|
||||
docker-compose exec backend bench set-config -g root_password <root-password>
|
||||
# Create ERPNext site
|
||||
docker run \
|
||||
-e "SITE_NAME=$SITE_NAME" \
|
||||
-e "DB_ROOT_USER=$DB_ROOT_USER" \
|
||||
-e "POSTGRES_HOST=$POSTGRES_HOST" \
|
||||
-e "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" \
|
||||
-e "ADMIN_PASSWORD=$ADMIN_PASSWORD" \
|
||||
-v <project-name>_sites-vol:/home/frappe/frappe-bench/sites \
|
||||
--network <project-name>_default \
|
||||
frappe/erpnext-worker:$VERSION new
|
||||
```
|
||||
|
||||
Also command is slightly different:
|
||||
Environment Variables needed:
|
||||
|
||||
- `SITE_NAME`: name of the new site to create. Site name is domain name that resolves. e.g. `erp.example.com` or `erp.localhost`.
|
||||
- `DB_ROOT_USER`: MariaDB/PostgreSQL Root user.
|
||||
- `MYSQL_ROOT_PASSWORD`: In case of the MariaDB docker container use the one set in `MYSQL_ROOT_PASSWORD` in previous steps. In case of a managed database use the appropriate password.
|
||||
- `MYSQL_ROOT_PASSWORD_FILE` - When the MariaDB root password is stored using docker secrets.
|
||||
- `ADMIN_PASSWORD`: set the administrator password for the new site.
|
||||
- `ADMIN_PASSWORD_FILE`: set the administrator password for the new site using docker secrets.
|
||||
- `INSTALL_APPS=erpnext`: available only in erpnext-worker and erpnext containers (or other containers with custom apps). Installs ERPNext (and/or the specified apps, comma-delinieated) on this new site.
|
||||
- `FORCE=1`: optional variable which force installation of the same site.
|
||||
|
||||
Environment Variables for PostgreSQL only:
|
||||
|
||||
- `POSTGRES_HOST`: host for PostgreSQL server
|
||||
- `POSTGRES_PASSWORD`: Password for `postgres`. The database root user.
|
||||
|
||||
Notes:
|
||||
|
||||
- To setup existing frappe-bench deployment with default database as PostgreSQL edit the common_site_config.json and set `db_host` to PostgreSQL hostname and `db_port` to PostgreSQL port.
|
||||
- To create new frappe-bench deployment with default database as PostgreSQL use `POSTGRES_HOST` and `DB_PORT` environment variables in `erpnext-python` service instead of `MARIADB_HOST`
|
||||
|
||||
## Add sites to proxy
|
||||
|
||||
Change `SITES` variable to the list of sites created encapsulated in backtick and separated by comma with no space. e.g. ``SITES=`site1.example.com`,`site2.example.com` ``.
|
||||
|
||||
Reload variables with following command.
|
||||
|
||||
```sh
|
||||
docker-compose exec backend bench new-site --no-mariadb-socket --db-type postgres --admin-password <admin-password> <site-name>
|
||||
docker-compose --project-name <project-name> up -d
|
||||
```
|
||||
|
||||
## Push backup to S3 storage
|
||||
## Backup Sites
|
||||
|
||||
We have the script that helps to push latest backup to S3.
|
||||
Environment Variables
|
||||
|
||||
- `SITES` is list of sites separated by `:` colon to migrate. e.g. `SITES=site1.domain.com` or `SITES=site1.domain.com:site2.domain.com` By default all sites in bench will be backed up.
|
||||
- `WITH_FILES` if set to 1, it will backup user-uploaded files.
|
||||
- By default `backup` takes mariadb dump and gzips it. Example file, `20200325_221230-test_localhost-database.sql.gz`
|
||||
- If `WITH_FILES` is set then it will also backup public and private files of each site as uncompressed tarball. Example files, `20200325_221230-test_localhost-files.tar` and `20200325_221230-test_localhost-private-files.tar`
|
||||
- All the files generated by backup are placed at volume location `sites-vol:/{site-name}/private/backups/*`
|
||||
|
||||
```sh
|
||||
docker-compose exec backend push_backup.py --site-name <site-name> --bucket <bucket> --region-name <region> --endpoint-url <endpoint-url> --aws-access-key-id <access-key> --aws-secret-access-key <secret-key>
|
||||
docker run \
|
||||
-e "SITES=site1.domain.com:site2.domain.com" \
|
||||
-e "WITH_FILES=1" \
|
||||
-v <project-name>_sites-vol:/home/frappe/frappe-bench/sites \
|
||||
--network <project-name>_default \
|
||||
frappe/erpnext-worker:$VERSION backup
|
||||
```
|
||||
|
||||
Note that you can restore backup only manually.
|
||||
The backup will be available in the `sites-vol` volume.
|
||||
|
||||
## Push backup to s3 compatible storage
|
||||
|
||||
Environment Variables
|
||||
|
||||
- `BUCKET_NAME`, Required to set bucket created on S3 compatible storage.
|
||||
- `REGION`, Required to set region for S3 compatible storage.
|
||||
- `ACCESS_KEY_ID`, Required to set access key.
|
||||
- `SECRET_ACCESS_KEY`, Required to set secret access key.
|
||||
- `ENDPOINT_URL`, Required to set URL of S3 compatible storage.
|
||||
- `BUCKET_DIR`, Required to set directory in bucket where sites from this deployment will be backed up.
|
||||
- `BACKUP_LIMIT`, Optionally set this to limit number of backups in bucket directory. Defaults to 3.
|
||||
|
||||
```sh
|
||||
docker run \
|
||||
-e "BUCKET_NAME=backups" \
|
||||
-e "REGION=region" \
|
||||
-e "ACCESS_KEY_ID=access_id_from_provider" \
|
||||
-e "SECRET_ACCESS_KEY=secret_access_from_provider" \
|
||||
-e "ENDPOINT_URL=https://region.storage-provider.com" \
|
||||
-e "BUCKET_DIR=frappe-bench" \
|
||||
-v <project-name>_sites-vol:/home/frappe/frappe-bench/sites \
|
||||
--network <project-name>_default \
|
||||
frappe/frappe-worker:$VERSION push-backup
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
- Above example will backup files in bucket called `backup` at location `frappe-bench-v13/site.name.com/DATE_TIME/DATE_TIME-site_name_com-{filetype}.{extension}`,
|
||||
- example DATE_TIME: 20200325_042020.
|
||||
- example filetype: database, files or private-files
|
||||
- example extension: sql.gz or tar
|
||||
|
||||
## Restore backups
|
||||
|
||||
Environment Variables
|
||||
|
||||
- `MYSQL_ROOT_PASSWORD` or `MYSQL_ROOT_PASSWORD_FILE`(when using docker secrets), Required to restore mariadb backups.
|
||||
- `BUCKET_NAME`, Required to set bucket created on S3 compatible storage.
|
||||
- `ACCESS_KEY_ID`, Required to set access key.
|
||||
- `SECRET_ACCESS_KEY`, Required to set secret access key.
|
||||
- `ENDPOINT_URL`, Required to set URL of S3 compatible storage.
|
||||
- `REGION`, Required to set region for s3 compatible storage.
|
||||
- `BUCKET_DIR`, Required to set directory in bucket where sites from this deployment will be backed up.
|
||||
|
||||
```sh
|
||||
docker run \
|
||||
-e "MYSQL_ROOT_PASSWORD=admin" \
|
||||
-e "BUCKET_NAME=backups" \
|
||||
-e "REGION=region" \
|
||||
-e "ACCESS_KEY_ID=access_id_from_provider" \
|
||||
-e "SECRET_ACCESS_KEY=secret_access_from_provider" \
|
||||
-e "ENDPOINT_URL=https://region.storage-provider.com" \
|
||||
-e "BUCKET_DIR=frappe-bench" \
|
||||
-v <project-name>_sites-vol:/home/frappe/frappe-bench/sites \
|
||||
-v ./backups:/home/frappe/backups \
|
||||
--network <project-name>_default \
|
||||
frappe/frappe-worker:$VERSION restore-backup
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
- Volume must be mounted at location `/home/frappe/backups` for restoring sites
|
||||
- If no backup files are found in volume, it will use s3 credentials to pull backups
|
||||
- Backup structure for mounted volume or downloaded from s3:
|
||||
- /home/frappe/backups
|
||||
- site1.domain.com
|
||||
- 20200420_162000
|
||||
- 20200420_162000-site1_domain_com-*
|
||||
- site2.domain.com
|
||||
- 20200420_162000
|
||||
- 20200420_162000-site2_domain_com-*
|
||||
|
||||
## Edit configs
|
||||
|
||||
Editing config manually might be required in some cases,
|
||||
one such case is to use Amazon RDS (or any other DBaaS).
|
||||
For full instructions, refer to the [wiki](<https://github.com/frappe/frappe/wiki/Using-Frappe-with-Amazon-RDS-(or-any-other-DBaaS)>). Common question can be found in Issues and on forum.
|
||||
For full instructions, refer to the [wiki](https://github.com/frappe/frappe/wiki/Using-Frappe-with-Amazon-RDS-(or-any-other-DBaaS)). Common question can be found in Issues and on forum.
|
||||
|
||||
`common_site_config.json` or `site_config.json` from `sites` volume has to be edited using following command:
|
||||
`common_site_config.json` or `site_config.json` from `sites-vol` volume has to be edited using following command:
|
||||
|
||||
```sh
|
||||
docker run --rm -it \
|
||||
-v <project-name>_sites:/sites \
|
||||
docker run \
|
||||
-it \
|
||||
-v <project-name>_sites-vol:/sites \
|
||||
alpine vi /sites/common_site_config.json
|
||||
```
|
||||
|
||||
@ -58,7 +195,8 @@ Instead of `alpine` use any image of your choice.
|
||||
For socketio and gunicorn service ping the hostname:port and that will be sufficient. For workers and scheduler, there is a command that needs to be executed.
|
||||
|
||||
```shell
|
||||
docker-compose exec backend healthcheck.sh --ping-service mongodb:27017
|
||||
docker exec -it <project-name>_erpnext-worker-d \
|
||||
docker-entrypoint.sh doctor -p postgresql:5432 --ping-service mongodb:27017
|
||||
```
|
||||
|
||||
Additional services can be pinged as part of health check with option `-p` or `--ping-service`.
|
||||
@ -66,20 +204,87 @@ Additional services can be pinged as part of health check with option `-p` or `-
|
||||
This check ensures that given service should be connected along with services in common_site_config.json.
|
||||
If connection to service(s) fails, the command fails with exit code 1.
|
||||
|
||||
---
|
||||
## Frappe internal commands using bench helper
|
||||
|
||||
For reference of commands like `backup`, `drop-site` or `migrate` check [official guide](https://frappeframework.com/docs/v13/user/en/bench/frappe-commands) or run:
|
||||
To execute commands using bench helper.
|
||||
|
||||
```sh
|
||||
docker-compose exec backend bench --help
|
||||
```shell
|
||||
docker run \
|
||||
-v <project-name>_sites-vol:/home/frappe/frappe-bench/sites \
|
||||
--network <project-name>_default \
|
||||
--user frappe \
|
||||
frappe/frappe-worker:$VERSION bench --help
|
||||
```
|
||||
|
||||
## Migrate site
|
||||
Example command to clear cache
|
||||
|
||||
Note:
|
||||
```shell
|
||||
docker run \
|
||||
-v <project-name>_sites-vol:/home/frappe/frappe-bench/sites \
|
||||
--network <project-name>_default \
|
||||
--user frappe \
|
||||
frappe/frappe-worker:$VERSION bench --site erp.mysite.com clear-cache
|
||||
```
|
||||
|
||||
- Wait for the `db` service to start and `configurator` to exit before trying to migrate a site. Usually this takes up to 10 seconds.
|
||||
Notes:
|
||||
|
||||
- Use it to install/uninstall custom apps, add system manager user, etc.
|
||||
- To run the command as non root user add the command option `--user frappe`.
|
||||
|
||||
|
||||
## Delete/Drop Site
|
||||
|
||||
#### MariaDB Site
|
||||
|
||||
```sh
|
||||
docker-compose exec backend bench --site <site-name> migrate
|
||||
# Delete/Drop ERPNext site
|
||||
docker run \
|
||||
-e "SITE_NAME=$SITE_NAME" \
|
||||
-e "DB_ROOT_USER=$DB_ROOT_USER" \
|
||||
-e "MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD" \
|
||||
-v <project-name>_sites-vol:/home/frappe/frappe-bench/sites \
|
||||
--network <project-name>_default \
|
||||
frappe/erpnext-worker:$VERSION drop
|
||||
```
|
||||
|
||||
#### PostgreSQL Site
|
||||
|
||||
```sh
|
||||
# Delete/Drop ERPNext site
|
||||
docker run \
|
||||
-e "SITE_NAME=$SITE_NAME" \
|
||||
-e "DB_ROOT_USER=$DB_ROOT_USER" \
|
||||
-e "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" \
|
||||
-v <project-name>_sites-vol:/home/frappe/frappe-bench/sites \
|
||||
--network <project-name>_default \
|
||||
frappe/erpnext-worker:$VERSION drop
|
||||
```
|
||||
|
||||
Environment Variables needed:
|
||||
|
||||
- `SITE_NAME`: name of the site to be deleted. Site name is domain name that resolves. e.g. `erp.example.com` or `erp.localhost`.
|
||||
- `DB_ROOT_USER`: MariaDB/PostgreSQL Root user.
|
||||
- `MYSQL_ROOT_PASSWORD`: Root User password for MariaDB.
|
||||
- `FORCE=1`: optional variable which force deletion of the same site.
|
||||
- `NO_BACKUP=1`: option variable to skip the process of taking backup before deleting the site.
|
||||
|
||||
Environment Variables for PostgreSQL only:
|
||||
|
||||
- `POSTGRES_PASSWORD`: Password for `postgres`. The database root user.
|
||||
|
||||
## Migrate Site
|
||||
|
||||
```sh
|
||||
# Migrate ERPNext site
|
||||
docker run \
|
||||
-e "MAINTENANCE_MODE=1" \
|
||||
-v <project-name>_sites-vol:/home/frappe/frappe-bench/sites \
|
||||
-v <project-name>_assets-vol:/home/frappe/frappe-bench/sites/assets \
|
||||
--network <project-name>_default \
|
||||
frappe/erpnext-worker:$ERPNEXT_VERSION migrate
|
||||
```
|
||||
|
||||
Environment Variables needed:
|
||||
|
||||
- `MAINTENANCE_MODE`: If set to `1`, this will ensure the bench is switched to maintenance mode during migration.
|
||||
- `SITES`: Optional list of sites to be migrated, separated by colon (`:`). e.g. `erp.site1.com:erp.site2.com`. If not used, all sites will be migrated by default.
|
||||
|
15
docs/tips-for-moving-deployments.md
Normal file
15
docs/tips-for-moving-deployments.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Tips for moving deployments
|
||||
|
||||
- Take regular automatic backups and push the files to S3 compatible cloud. Setup backup and push with cronjobs
|
||||
- Use regular cron for single machine installs
|
||||
- Use [swarm-cronjob](https://github.com/crazy-max/swarm-cronjob) for docker swarm
|
||||
- Use Kubernetes CronJob
|
||||
- It makes it easy to transfer data from cloud to any new deployment.
|
||||
- They are just [site operations](site-operations.md) that can be manually pipelined as per need.
|
||||
- Remember to restore encryption keys and other custom configuration from `site_config.json`.
|
||||
- Steps to move deployment:
|
||||
- [Take backup](site-operations.md#backup-sites)
|
||||
- [Push backup to cloud](site-operations.md#push-backup-to-s3-compatible-storage)
|
||||
- Create new deployment type anywhere
|
||||
- [Restore backup from cloud](site-operations.md#restore-backups)
|
||||
- [Restore `site_config.json` from cloud](site-operations.md#edit-configs)
|
@ -1,55 +0,0 @@
|
||||
1. [Fixing MariaDB issues after rebuilding the container](#fixing-mariadb-issues-after-rebuilding-the-container)
|
||||
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)
|
||||
|
||||
### Fixing MariaDB issues after rebuilding the container
|
||||
|
||||
For any reason after rebuilding the container if you are not be able to access MariaDB correctly with the previous configuration. Follow these instructions.
|
||||
|
||||
The parameter `'db_name'@'%'` needs to be set in MariaDB and permission to the site database suitably assigned to the user.
|
||||
|
||||
This step has to be repeated for all sites available under the current bench.
|
||||
Example shows the queries to be executed for site `localhost`
|
||||
|
||||
Open sites/localhost/site_config.json:
|
||||
|
||||
```shell
|
||||
code sites/localhost/site_config.json
|
||||
```
|
||||
|
||||
and take note of the parameters `db_name` and `db_password`.
|
||||
|
||||
Enter MariaDB Interactive shell:
|
||||
|
||||
```shell
|
||||
mysql -uroot -p123 -hmariadb
|
||||
```
|
||||
|
||||
Execute following queries replacing `db_name` and `db_password` with the values found in site_config.json.
|
||||
|
||||
```sql
|
||||
UPDATE mysql.user SET Host = '%' where User = 'db_name'; FLUSH PRIVILEGES;
|
||||
SET PASSWORD FOR 'db_name'@'%' = PASSWORD('db_password'); FLUSH PRIVILEGES;
|
||||
GRANT ALL PRIVILEGES ON `db_name`.* TO 'db_name'@'%'; FLUSH PRIVILEGES;
|
||||
EXIT;
|
||||
```
|
||||
|
||||
Note: For MariaDB 10.4 and above use `mysql.global_priv` instead of `mysql.user`.
|
||||
|
||||
### 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.
|
||||
|
||||
### 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)
|
||||
- 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
|
@ -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()
|
7
env-example
Normal file
7
env-example
Normal file
@ -0,0 +1,7 @@
|
||||
ERPNEXT_VERSION=edge
|
||||
FRAPPE_VERSION=edge
|
||||
MARIADB_HOST=mariadb
|
||||
MYSQL_ROOT_PASSWORD=admin
|
||||
SITES=your.domain.com
|
||||
LETSENCRYPT_EMAIL=your.email@your.domain.com
|
||||
SKIP_NGINX_TEMPLATE_GENERATION=0
|
18
env-local
Normal file
18
env-local
Normal file
@ -0,0 +1,18 @@
|
||||
LETSENCRYPT_EMAIL=email@example.com
|
||||
ERPNEXT_VERSION=edge
|
||||
FRAPPE_VERSION=edge
|
||||
MARIADB_HOST=mariadb
|
||||
MYSQL_ROOT_PASSWORD=admin
|
||||
SITE_NAME=mysite.localhost
|
||||
SITES=`mysite.localhost`
|
||||
DB_ROOT_USER=root
|
||||
ADMIN_PASSWORD=admin
|
||||
INSTALL_APPS=erpnext
|
||||
ENTRYPOINT_LABEL=traefik.http.routers.erpnext-nginx.entrypoints=web
|
||||
CERT_RESOLVER_LABEL=erpnext.local.no-cert-resolver
|
||||
HTTPS_REDIRECT_RULE_LABEL=erpnext.local.no-redirect-rule
|
||||
HTTPS_REDIRECT_ENTRYPOINT_LABEL=erpnext.local.no-entrypoint
|
||||
HTTPS_REDIRECT_MIDDLEWARE_LABEL=erpnext.local.no-middleware
|
||||
HTTPS_USE_REDIRECT_MIDDLEWARE_LABEL=erpnext.local-no-redirect-middleware
|
||||
SKIP_NGINX_TEMPLATE_GENERATION=0
|
||||
WORKER_CLASS=gthread
|
18
env-production
Normal file
18
env-production
Normal file
@ -0,0 +1,18 @@
|
||||
LETSENCRYPT_EMAIL=email@example.com
|
||||
ERPNEXT_VERSION=edge
|
||||
FRAPPE_VERSION=edge
|
||||
MARIADB_HOST=mariadb
|
||||
MYSQL_ROOT_PASSWORD=admin
|
||||
SITE_NAME=erp.example.com
|
||||
SITES=`erp.example.com`
|
||||
DB_ROOT_USER=root
|
||||
ADMIN_PASSWORD=admin
|
||||
INSTALL_APPS=erpnext
|
||||
ENTRYPOINT_LABEL=traefik.http.routers.erpnext-nginx.entrypoints=websecure
|
||||
CERT_RESOLVER_LABEL=traefik.http.routers.erpnext-nginx.tls.certresolver=myresolver
|
||||
HTTPS_REDIRECT_RULE_LABEL=traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)
|
||||
HTTPS_REDIRECT_ENTRYPOINT_LABEL=traefik.http.routers.http-catchall.entrypoints=web
|
||||
HTTPS_REDIRECT_MIDDLEWARE_LABEL=traefik.http.routers.http-catchall.middlewares=redirect-to-https
|
||||
HTTPS_USE_REDIRECT_MIDDLEWARE_LABEL=traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
|
||||
SKIP_NGINX_TEMPLATE_GENERATION=0
|
||||
WORKER_CLASS=gthread
|
49
example.env
49
example.env
@ -1,49 +0,0 @@
|
||||
# Reference: https://github.com/frappe/frappe_docker/blob/main/docs/images-and-compose-files.md
|
||||
|
||||
ERPNEXT_VERSION=v15.16.2
|
||||
|
||||
DB_PASSWORD=123
|
||||
|
||||
# Only if you use external database
|
||||
DB_HOST=
|
||||
DB_PORT=
|
||||
|
||||
# Only if you use external Redis
|
||||
REDIS_CACHE=
|
||||
REDIS_QUEUE=
|
||||
|
||||
# Only with HTTPS override
|
||||
LETSENCRYPT_EMAIL=mail@example.com
|
||||
|
||||
# These environment variables are 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`.
|
||||
FRAPPE_SITE_NAME_HEADER=
|
||||
|
||||
# Default value is `127.0.0.1`. Set IP address as our trusted upstream address.
|
||||
UPSTREAM_REAL_IP_ADDRESS=
|
||||
|
||||
# Default value is `X-Forwarded-For`. Set request header field whose value will be used to replace the client address
|
||||
UPSTREAM_REAL_IP_HEADER=
|
||||
|
||||
# Allowed values are on|off. Default value is `off`. If recursive search is disabled,
|
||||
# the original client address that matches one of the trusted addresses
|
||||
# is replaced by the last address sent in the request header field defined by the real_ip_header directive.
|
||||
# If recursive search is enabled, the original client address that matches one of the trusted addresses is replaced by the last non-trusted address sent in the request header field.
|
||||
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=
|
||||
|
||||
# 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`
|
255
frappe-installer
Executable file
255
frappe-installer
Executable file
@ -0,0 +1,255 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
[[ -z "${DEBUG}" && "${DEBUG}" == 1 ]] && set -o xtrace
|
||||
|
||||
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$__dir"
|
||||
|
||||
env_url="https://raw.githubusercontent.com/frappe/frappe_docker/master/installation/env-example"
|
||||
docker_nginx_url="https://github.com/evertramos/docker-compose-letsencrypt-nginx-proxy-companion"
|
||||
frappe_docker_url="https://github.com/frappe/frappe_docker"
|
||||
env_file="$__dir/.env"
|
||||
|
||||
function check_root() {
|
||||
if [[ $EUID != 0 ]]; then
|
||||
echo "This script must be run as root. Login as root or use sudo." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function check_git() {
|
||||
if [[ ! -x "$(command -v git)" ]]; then
|
||||
echo "Git is not installed. Please install git before continuing." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function check_docker() {
|
||||
if [[ ! -x "$(command -v docker)" ]]; then
|
||||
read -rp "No docker installation found. Press Enter to install docker or ctrl+c to exit." >&2
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
fi
|
||||
if [[ ! -x "$(command -v docker)" ]]; then
|
||||
echo "Docker installation failed. Exiting." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function check_env() {
|
||||
if [[ ! -f "$env_file" ]]; then
|
||||
cat <<CHOOSE >&2
|
||||
No environment file found. This file is required for setting up Frappe/ERPNext Docker.
|
||||
Would you like to fetch the default environment file?
|
||||
(NOTE: You will be prompted to set it up later)
|
||||
CHOOSE
|
||||
read -rp "Press Enter to fetch the configuration file, or create a .env file and re-run the script."
|
||||
curl -fsSL "$env_url" -o "$env_file"
|
||||
fi
|
||||
}
|
||||
|
||||
function clone_repository() {
|
||||
echo "Cloning Repository: $1"
|
||||
git clone "$2"
|
||||
}
|
||||
|
||||
function get_config() {
|
||||
if [[ -n "$2" ]]; then
|
||||
config_file="$2"
|
||||
else
|
||||
config_file="$env_file"
|
||||
fi
|
||||
line=$(grep -E "^$=" "$config_file")
|
||||
line_result=$(echo "$line" | awk -F"=" '{print $2}')
|
||||
}
|
||||
|
||||
function get_install_version() {
|
||||
cat <<CHOOSE >&2
|
||||
Choose a version you would like to setup [current: $1]:
|
||||
1. develop (edge)
|
||||
2. version-12
|
||||
3. version-11
|
||||
Please enter your choice [1-3]:
|
||||
CHOOSE
|
||||
select choice in "1" "2" "3"; do
|
||||
case ${choice} in
|
||||
1) version="edge" ;;
|
||||
2) version="version-12" ;;
|
||||
3) version="version-11" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
function prompt_config() {
|
||||
# inspired by discourse_docker
|
||||
get_config "VERSION"
|
||||
local install_version=$line_result
|
||||
get_config "MYSQL_ROOT_PASSWORD"
|
||||
local mysql_password=$line_result
|
||||
get_config "SITES"
|
||||
local sites=$line_result
|
||||
get_config "LETSENCRYPT_EMAIL"
|
||||
local letsencrypt_email=$line_result
|
||||
|
||||
echo "Would you like to setup networking for docker? [y/n]"
|
||||
echo "This is required if you wish to access the instance from other machines."
|
||||
select choice in "y" "n"; do
|
||||
case $choice in
|
||||
y) setup_networking=1 ;;
|
||||
n)
|
||||
setup_networking=0
|
||||
setup_letsencrypt=0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
if [[ -n "$letsencrypt_email" && "$setup_networking" -ne "0" ]]; then
|
||||
echo "Would you like to setup LetsEncrypt? [y/n]"
|
||||
select choice in "y" "n"; do
|
||||
case $choice in
|
||||
y)
|
||||
setup_letsencrypt=1
|
||||
echo "Please ensure that all the required domains point to this IP address."
|
||||
read -rp "Enter an Email Address to setup LetsEncrypt with: " letsencrypt_email
|
||||
;;
|
||||
n)
|
||||
setup_letsencrypt=0
|
||||
echo "Skipping LetsEncrypt Setup."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
local new_value=""
|
||||
local config_state="n"
|
||||
|
||||
echo
|
||||
|
||||
get_install_version "$install_version"
|
||||
install_version="$version"
|
||||
|
||||
while [[ "$config_state" == "n" ]]; do
|
||||
if [[ -n "$mysql_password" ]]; then
|
||||
read -srp "Enter MySQL Password [$mysql_password]: " new_value
|
||||
if [[ -n "$new_value" ]]; then
|
||||
mysql_password="$new_value"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$sites" ]]; then
|
||||
read -rp "Enter sitename to setup [$sites]: " new_value
|
||||
if [[ -n "$new_value" ]]; then
|
||||
sites="$new_value"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$setup_letsencrypt" != "0" ]]; then
|
||||
read -rp "Enter email address for LetsEncrypt [$letsencrypt_email]: " new_value
|
||||
if [[ -n "$new_value" ]]; then
|
||||
letsencrypt_email=$new_value
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Current Configuration:"
|
||||
echo "Version: $([[ "$install_version" == "edge" ]] && echo "develop" || echo "$install_version")"
|
||||
echo "MySQL Root Password: $mysql_password"
|
||||
echo "Sites: $sites"
|
||||
|
||||
if [[ "$setup_letsencrypt" != "0" ]]; then
|
||||
echo "LetsEncrypt Email Address: $letsencrypt_email"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Does this configuration look okay?"
|
||||
read -rp "Press Enter to continue, 'n' to try again, or ctrl+c to exit: " config_state
|
||||
done
|
||||
|
||||
echo "Saving the current configuration file to $env_file"
|
||||
|
||||
cat <<EOF >"$env_file"
|
||||
VERSION=$install_version
|
||||
MYSQL_ROOT_PASSWORD=$mysql_password
|
||||
SITES=$sites
|
||||
$([ "$setup_letsencrypt" -ne "0" ] && echo "LETSENCRYPT_EMAIL=$letsencrypt_email")
|
||||
EOF
|
||||
setup_configuration=$(<"$env_file")
|
||||
}
|
||||
|
||||
setup_user() {
|
||||
echo "The rest of the setup requires a user account."
|
||||
echo "You may use an existing account, or set up a new one right away."
|
||||
read -rp "Enter username: " username
|
||||
if grep -E "^$username" /etc/passwd >/dev/null; then
|
||||
echo "User $username already exists."
|
||||
else
|
||||
read -rsp "Enter password: " password
|
||||
password="$(perl -e 'print crypt($ARGV[0], "password")' "$password")"
|
||||
if useradd -m -p "$password" "$username" -s "$(command -v bash)"; then
|
||||
echo "User $username has been added to the system."
|
||||
else
|
||||
echo "Failed to add user to the system."
|
||||
echo "Please add a user manually and re-run the script."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! getent group docker >/dev/null 2>&1; then
|
||||
echo "Creating group: docker"
|
||||
groupadd docker
|
||||
fi
|
||||
echo "Adding user $username to group: docker"
|
||||
usermod -aG docker "$username"
|
||||
newgrp docker
|
||||
}
|
||||
|
||||
install() {
|
||||
if [[ "$setup_letsencrypt" != "0" && "$setup_networking" != "0" ]]; then
|
||||
echo "Setting up NGINX Proxy for LetsEncrypt"
|
||||
clone_repository "Docker Compose LetsEncrypt NGINX Proxy Companion" "$docker_nginx_url"
|
||||
cd "$(basename "$docker_nginx_url")"
|
||||
if [[ -f .env.sample ]]; then
|
||||
cp .env.sample env
|
||||
fi
|
||||
./start.sh >/dev/null 2>&1
|
||||
cd "$(eval echo ~"$username")"
|
||||
fi
|
||||
|
||||
echo "Setting up Frappe/ERPNext"
|
||||
clone_repository "Frappe/ERPNext Docker" "$frappe_docker_url"
|
||||
cd "$(basename "$frappe_docker_url")"
|
||||
echo "$setup_configuration" >.env
|
||||
echo "Enter a name for the project."
|
||||
read -rp "This project name will be used to setup the docker instance: [erpnext_docker]" project_name
|
||||
if [[ -z "$project_name" ]]; then
|
||||
echo "Setting the project name to erpnext_docker"
|
||||
project_name="erpnext_docker"
|
||||
fi
|
||||
|
||||
docker-compose \
|
||||
--project-name "$project_name" \
|
||||
--project-directory . up -d \
|
||||
-f installation/docker-compose-frappe.yml \
|
||||
-f installation/docker-compose-erpnext.yml \
|
||||
-f installation/docker-compose-common.yml \
|
||||
"$( ((setup_networking == 1)) && printf %s '-f installation/docker-compose-networks.yml')"
|
||||
|
||||
get_config "SITES" "$(pwd)/.env"
|
||||
local sites=$line_result
|
||||
|
||||
docker exec \
|
||||
-e "SITE_NAME=$sites" \
|
||||
-e "INSTALL_ERPNEXT=1" \
|
||||
-it "$project_name"_erpnext-python_1 docker-entrypoint.sh new
|
||||
|
||||
echo "Installation Complete!"
|
||||
}
|
||||
|
||||
check_root
|
||||
check_git
|
||||
check_docker
|
||||
check_env
|
||||
|
||||
prompt_config
|
||||
setup_user
|
||||
install
|
15
greetings.yml
Normal file
15
greetings.yml
Normal file
@ -0,0 +1,15 @@
|
||||
name: Greetings
|
||||
|
||||
on: [pull_request, issues]
|
||||
|
||||
jobs:
|
||||
greeting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: |
|
||||
Hello! We're very happy to see your first issue. If your issue is about a problem, go back and check you have copy-pasted all the debug logs you can so we can help you as fast as possible!
|
||||
pr-message: |
|
||||
Hello! Thank you about this PR. Since this is your first PR, please make sure you have described the improvements and your code is well documented.
|
@ -1,161 +0,0 @@
|
||||
FROM debian:bookworm-slim as bench
|
||||
|
||||
LABEL author=frappé
|
||||
|
||||
ARG GIT_REPO=https://github.com/frappe/bench.git
|
||||
ARG GIT_BRANCH=v5.x
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
|
||||
# For frappe framework
|
||||
git \
|
||||
mariadb-client \
|
||||
postgresql-client \
|
||||
gettext-base \
|
||||
wget \
|
||||
# for PDF
|
||||
libssl-dev \
|
||||
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 \
|
||||
cron \
|
||||
curl \
|
||||
vim \
|
||||
sudo \
|
||||
iputils-ping \
|
||||
watch \
|
||||
tree \
|
||||
nano \
|
||||
less \
|
||||
software-properties-common \
|
||||
bash-completion \
|
||||
# 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 \
|
||||
ssh-client \
|
||||
# VSCode container requirements
|
||||
net-tools \
|
||||
# For pyenv build dependencies
|
||||
# https://github.com/frappe/frappe_docker/issues/840#issuecomment-1185206895
|
||||
make \
|
||||
# For pandas
|
||||
libbz2-dev \
|
||||
# For bench execute
|
||||
libsqlite3-dev \
|
||||
# For other dependencies
|
||||
zlib1g-dev \
|
||||
libreadline-dev \
|
||||
llvm \
|
||||
libncurses5-dev \
|
||||
libncursesw5-dev \
|
||||
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
|
||||
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 \
|
||||
&& wget -q https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \
|
||||
&& dpkg -i $downloaded_file \
|
||||
&& rm $downloaded_file
|
||||
|
||||
# Create new user with home directory, improve docker compatibility with UID/GID 1000,
|
||||
# add user to sudo group, allow passwordless sudo, switch to that user
|
||||
# and change directory to user home directory
|
||||
RUN groupadd -g 1000 frappe \
|
||||
&& useradd --no-log-init -r -m -u 1000 -g 1000 -G sudo frappe \
|
||||
&& echo "frappe ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
||||
|
||||
USER frappe
|
||||
WORKDIR /home/frappe
|
||||
|
||||
# Install Python via pyenv
|
||||
ENV PYTHON_VERSION_V14=3.10.13
|
||||
ENV PYTHON_VERSION=3.11.6
|
||||
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 \
|
||||
&& PYENV_VERSION=$PYTHON_VERSION_V14 pip install --no-cache-dir virtualenv \
|
||||
&& PYENV_VERSION=$PYTHON_VERSION pip install --no-cache-dir virtualenv \
|
||||
&& pyenv global $PYTHON_VERSION $PYTHON_VERSION_v14 \
|
||||
&& 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
|
||||
|
||||
# Clone and install bench in the local user home directory
|
||||
# For development, bench source is located in ~/.bench
|
||||
ENV PATH /home/frappe/.local/bin:$PATH
|
||||
# Skip editable-bench warning
|
||||
# https://github.com/frappe/bench/commit/20560c97c4246b2480d7358c722bc9ad13606138
|
||||
RUN git clone ${GIT_REPO} --depth 1 -b ${GIT_BRANCH} .bench \
|
||||
&& pip install --no-cache-dir --user -e .bench \
|
||||
&& echo "export PATH=/home/frappe/.local/bin:\$PATH" >>/home/frappe/.bashrc \
|
||||
&& 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 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 \
|
||||
&& . ${NVM_DIR}/nvm.sh \
|
||||
&& nvm install ${NODE_VERSION_14} \
|
||||
&& nvm use v${NODE_VERSION_14} \
|
||||
&& npm install -g yarn \
|
||||
&& 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
|
||||
|
||||
|
||||
EXPOSE 8000-8005 9000-9005 6787
|
||||
|
||||
FROM bench as bench-test
|
||||
|
||||
# Print version and verify bashrc is properly sourced so that everything works
|
||||
# in the interactive shell and Dockerfile
|
||||
|
||||
RUN node --version \
|
||||
&& npm --version \
|
||||
&& yarn --version \
|
||||
&& bench --help
|
||||
|
||||
RUN bash -c "node --version" \
|
||||
&& bash -c "npm --version" \
|
||||
&& bash -c "yarn --version" \
|
||||
&& bash -c "bench --help"
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user