erp-sandbox 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- erp_sandbox-0.2.0/LICENSE +21 -0
- erp_sandbox-0.2.0/PKG-INFO +134 -0
- erp_sandbox-0.2.0/README.md +120 -0
- erp_sandbox-0.2.0/erp_sandbox/__init__.py +10 -0
- erp_sandbox-0.2.0/erp_sandbox/__main__.py +5 -0
- erp_sandbox-0.2.0/erp_sandbox/assets/conf/mariadb/erpnext.cnf +30 -0
- erp_sandbox-0.2.0/erp_sandbox/assets/conf/nginx-00-resolver.sh +32 -0
- erp_sandbox-0.2.0/erp_sandbox/assets/conf/redis/redis.conf +22 -0
- erp_sandbox-0.2.0/erp_sandbox/assets/config.sample.toml +273 -0
- erp_sandbox-0.2.0/erp_sandbox/assets/docker-compose.yml +135 -0
- erp_sandbox-0.2.0/erp_sandbox/assets/images/bench/Dockerfile +232 -0
- erp_sandbox-0.2.0/erp_sandbox/assets/images/bench/entrypoint.sh +104 -0
- erp_sandbox-0.2.0/erp_sandbox/assets/secrets.sample.env +45 -0
- erp_sandbox-0.2.0/erp_sandbox/build_bench.py +364 -0
- erp_sandbox-0.2.0/erp_sandbox/cli.py +727 -0
- erp_sandbox-0.2.0/erp_sandbox/doctor.py +957 -0
- erp_sandbox-0.2.0/erp_sandbox/fetch_apps.py +441 -0
- erp_sandbox-0.2.0/erp_sandbox/fixtures/README.md +22 -0
- erp_sandbox-0.2.0/erp_sandbox/fixtures/empty.toml +3 -0
- erp_sandbox-0.2.0/erp_sandbox/fixtures/malformed.toml +4 -0
- erp_sandbox-0.2.0/erp_sandbox/fixtures/missing_database.toml +44 -0
- erp_sandbox-0.2.0/erp_sandbox/guard.py +121 -0
- erp_sandbox-0.2.0/erp_sandbox/known_versions.py +123 -0
- erp_sandbox-0.2.0/erp_sandbox/preflight.py +1502 -0
- erp_sandbox-0.2.0/erp_sandbox/project.py +168 -0
- erp_sandbox-0.2.0/erp_sandbox/render.py +659 -0
- erp_sandbox-0.2.0/erp_sandbox/seed_db.py +633 -0
- erp_sandbox-0.2.0/erp_sandbox/selftest.py +2150 -0
- erp_sandbox-0.2.0/erp_sandbox/templates/Procfile.tmpl +17 -0
- erp_sandbox-0.2.0/erp_sandbox/templates/apps.txt.tmpl +1 -0
- erp_sandbox-0.2.0/erp_sandbox/templates/claude_rule.md.tmpl +64 -0
- erp_sandbox-0.2.0/erp_sandbox/templates/claude_settings.json.tmpl +15 -0
- erp_sandbox-0.2.0/erp_sandbox/templates/code_workspace.tmpl +45 -0
- erp_sandbox-0.2.0/erp_sandbox/templates/common_site_config.json.tmpl +16 -0
- erp_sandbox-0.2.0/erp_sandbox/templates/compose.docker.tmpl +18 -0
- erp_sandbox-0.2.0/erp_sandbox/templates/compose.podman.tmpl +53 -0
- erp_sandbox-0.2.0/erp_sandbox/templates/env.tmpl +48 -0
- erp_sandbox-0.2.0/erp_sandbox/templates/nginx.site.conf.tmpl +90 -0
- erp_sandbox-0.2.0/erp_sandbox.egg-info/PKG-INFO +134 -0
- erp_sandbox-0.2.0/erp_sandbox.egg-info/SOURCES.txt +43 -0
- erp_sandbox-0.2.0/erp_sandbox.egg-info/dependency_links.txt +1 -0
- erp_sandbox-0.2.0/erp_sandbox.egg-info/entry_points.txt +3 -0
- erp_sandbox-0.2.0/erp_sandbox.egg-info/top_level.txt +1 -0
- erp_sandbox-0.2.0/pyproject.toml +94 -0
- erp_sandbox-0.2.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Datahenge LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: erp-sandbox
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Build a containerized ERPNext development environment from one config.toml
|
|
5
|
+
Author-email: Brian Pond <brian@datahenge.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Datahenge/erp-sandbox
|
|
8
|
+
Project-URL: Issues, https://github.com/Datahenge/erp-sandbox/issues
|
|
9
|
+
Keywords: erpnext,frappe,podman,docker,development
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# erp-sandbox
|
|
16
|
+
|
|
17
|
+
Build a containerized ERPNext development environment from a single
|
|
18
|
+
`config.toml`. Install the tool once, run `erp-sandbox init`, edit one file, run
|
|
19
|
+
one command, and get a working bench with **all source editable from the host**.
|
|
20
|
+
|
|
21
|
+
Linux only, podman or Docker, no root required. The command is `erp-sandbox`,
|
|
22
|
+
or `sandbox` for short.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Start here
|
|
27
|
+
|
|
28
|
+
| | |
|
|
29
|
+
|---|---|
|
|
30
|
+
| **Setting one up for the first time** | [docs/setup.md](docs/setup.md) — ~30 min, 11 of it unattended |
|
|
31
|
+
| Working in one day to day | [docs/using.md](docs/using.md) |
|
|
32
|
+
| Something is broken | [docs/troubleshooting.md](docs/troubleshooting.md) |
|
|
33
|
+
| Running environments on a shared VPS | [docs/vps.md](docs/vps.md) |
|
|
34
|
+
| Working on `erp-sandbox` itself | [docs/contributing.md](docs/contributing.md) |
|
|
35
|
+
|
|
36
|
+
The short version, if you have done this before:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
uv tool install git+https://github.com/Datahenge/erp-sandbox.git
|
|
40
|
+
erp-sandbox init acme-dev && cd ~/erp-sandboxes/acme-dev
|
|
41
|
+
$EDITOR config.toml .erp-sandbox/secrets.env
|
|
42
|
+
erp-sandbox build
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then open `http://localhost:<ports.http>` and log in as `Administrator` with
|
|
46
|
+
the `ADMIN_PASSWORD` you set.
|
|
47
|
+
|
|
48
|
+
Already installed? `uv tool upgrade erp-sandbox` — one tool serves every
|
|
49
|
+
environment on the machine ([upgrading](docs/using.md#upgrading-erp-sandbox)).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## What you get
|
|
54
|
+
|
|
55
|
+
Four containers, one HTTP port:
|
|
56
|
+
|
|
57
|
+
| Service | What it does | Reachable from the host |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `nginx` | the only entry point — `/` to web, `/socket.io` to realtime | **yes**, `ports.http` |
|
|
60
|
+
| `bench` | honcho running the Procfile: web, socketio, scheduler, workers | no |
|
|
61
|
+
| `mariadb` | the site database | **yes**, `ports.mariadb`, on `127.0.0.1` |
|
|
62
|
+
| `redis` | cache, queue and socketio as logical DBs 0/1/2 | no |
|
|
63
|
+
|
|
64
|
+
Everything is **bind-mounted from your project directory** — `bench/apps/` the
|
|
65
|
+
cloned source, `bench/env/` the virtualenv, `data/mariadb/` the database. So:
|
|
66
|
+
|
|
67
|
+
- Edit a `.py` on the host and the reloader picks it up.
|
|
68
|
+
- `git status` works in every app.
|
|
69
|
+
- No `docker cp`, no rebuild to change a line.
|
|
70
|
+
|
|
71
|
+
You can run several client environments side by side, each with its own ports.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## What you need
|
|
76
|
+
|
|
77
|
+
Linux, Python 3.11+ on the host, podman (rootless) or Docker Engine with its
|
|
78
|
+
compose plugin, `git`, and ~20 GB free. The container images bring their own
|
|
79
|
+
Python, Node and wkhtmltopdf.
|
|
80
|
+
|
|
81
|
+
The full list, with a command to check each one, is
|
|
82
|
+
[step 1 of setup.md](docs/setup.md#1-check-your-machine--2-min).
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Two roots — understand this first
|
|
87
|
+
|
|
88
|
+
Almost every confusing moment with this tool comes from mixing these up.
|
|
89
|
+
|
|
90
|
+
| | **The package** | **A project** |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| What | `erp-sandbox` itself | one client's environment |
|
|
93
|
+
| Holds | templates, compose file, bench `Dockerfile` | `config.toml`, `.erp-sandbox/`, `bench/`, `data/` |
|
|
94
|
+
| Installed | once per machine, shared | created by you, as many as you like |
|
|
95
|
+
| Upgrades | replaced wholesale | never touched |
|
|
96
|
+
|
|
97
|
+
Every verb except `init` acts on **the environment containing your working
|
|
98
|
+
directory**, found by walking up for `.erp-sandbox/` — the same way `bench` finds
|
|
99
|
+
its bench. So `erp-sandbox bench console` works anywhere inside a project. Use
|
|
100
|
+
`--project DIR` to act on one from outside.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Documentation
|
|
105
|
+
|
|
106
|
+
**Using the tool**
|
|
107
|
+
|
|
108
|
+
| | |
|
|
109
|
+
|---|---|
|
|
110
|
+
| First run, end to end | [docs/setup.md](docs/setup.md) |
|
|
111
|
+
| Day-to-day work, editor, debugging, teardown | [docs/using.md](docs/using.md) |
|
|
112
|
+
| Symptom → cause → fix | [docs/troubleshooting.md](docs/troubleshooting.md) |
|
|
113
|
+
|
|
114
|
+
**Working on the tool**
|
|
115
|
+
|
|
116
|
+
| | |
|
|
117
|
+
|---|---|
|
|
118
|
+
| Setup, commands, rules | [docs/contributing.md](docs/contributing.md) |
|
|
119
|
+
| What is decided, and why | [docs/decisions.md](docs/decisions.md) |
|
|
120
|
+
| What the tool does, from the developer's side | [docs/functional.md](docs/functional.md) |
|
|
121
|
+
| Architecture, `config.toml` schema, credentials, seeding | [docs/technical.md](docs/technical.md) |
|
|
122
|
+
| Requirements and non-goals | [docs/requirements.md](docs/requirements.md) |
|
|
123
|
+
| Acceptance checklist and the test suite | [docs/testing.md](docs/testing.md) |
|
|
124
|
+
| What changed and when | [CHANGELOG.md](CHANGELOG.md) |
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT — see [LICENSE](LICENSE). Copyright Datahenge LLC.
|
|
131
|
+
|
|
132
|
+
erp-sandbox is an independent project, built for ERPNext. It is not affiliated
|
|
133
|
+
with or endorsed by Frappe Technologies. ERPNext and Frappe are trademarks of
|
|
134
|
+
Frappe Technologies Pvt. Ltd.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# erp-sandbox
|
|
2
|
+
|
|
3
|
+
Build a containerized ERPNext development environment from a single
|
|
4
|
+
`config.toml`. Install the tool once, run `erp-sandbox init`, edit one file, run
|
|
5
|
+
one command, and get a working bench with **all source editable from the host**.
|
|
6
|
+
|
|
7
|
+
Linux only, podman or Docker, no root required. The command is `erp-sandbox`,
|
|
8
|
+
or `sandbox` for short.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Start here
|
|
13
|
+
|
|
14
|
+
| | |
|
|
15
|
+
|---|---|
|
|
16
|
+
| **Setting one up for the first time** | [docs/setup.md](docs/setup.md) — ~30 min, 11 of it unattended |
|
|
17
|
+
| Working in one day to day | [docs/using.md](docs/using.md) |
|
|
18
|
+
| Something is broken | [docs/troubleshooting.md](docs/troubleshooting.md) |
|
|
19
|
+
| Running environments on a shared VPS | [docs/vps.md](docs/vps.md) |
|
|
20
|
+
| Working on `erp-sandbox` itself | [docs/contributing.md](docs/contributing.md) |
|
|
21
|
+
|
|
22
|
+
The short version, if you have done this before:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
uv tool install git+https://github.com/Datahenge/erp-sandbox.git
|
|
26
|
+
erp-sandbox init acme-dev && cd ~/erp-sandboxes/acme-dev
|
|
27
|
+
$EDITOR config.toml .erp-sandbox/secrets.env
|
|
28
|
+
erp-sandbox build
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Then open `http://localhost:<ports.http>` and log in as `Administrator` with
|
|
32
|
+
the `ADMIN_PASSWORD` you set.
|
|
33
|
+
|
|
34
|
+
Already installed? `uv tool upgrade erp-sandbox` — one tool serves every
|
|
35
|
+
environment on the machine ([upgrading](docs/using.md#upgrading-erp-sandbox)).
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## What you get
|
|
40
|
+
|
|
41
|
+
Four containers, one HTTP port:
|
|
42
|
+
|
|
43
|
+
| Service | What it does | Reachable from the host |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `nginx` | the only entry point — `/` to web, `/socket.io` to realtime | **yes**, `ports.http` |
|
|
46
|
+
| `bench` | honcho running the Procfile: web, socketio, scheduler, workers | no |
|
|
47
|
+
| `mariadb` | the site database | **yes**, `ports.mariadb`, on `127.0.0.1` |
|
|
48
|
+
| `redis` | cache, queue and socketio as logical DBs 0/1/2 | no |
|
|
49
|
+
|
|
50
|
+
Everything is **bind-mounted from your project directory** — `bench/apps/` the
|
|
51
|
+
cloned source, `bench/env/` the virtualenv, `data/mariadb/` the database. So:
|
|
52
|
+
|
|
53
|
+
- Edit a `.py` on the host and the reloader picks it up.
|
|
54
|
+
- `git status` works in every app.
|
|
55
|
+
- No `docker cp`, no rebuild to change a line.
|
|
56
|
+
|
|
57
|
+
You can run several client environments side by side, each with its own ports.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## What you need
|
|
62
|
+
|
|
63
|
+
Linux, Python 3.11+ on the host, podman (rootless) or Docker Engine with its
|
|
64
|
+
compose plugin, `git`, and ~20 GB free. The container images bring their own
|
|
65
|
+
Python, Node and wkhtmltopdf.
|
|
66
|
+
|
|
67
|
+
The full list, with a command to check each one, is
|
|
68
|
+
[step 1 of setup.md](docs/setup.md#1-check-your-machine--2-min).
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Two roots — understand this first
|
|
73
|
+
|
|
74
|
+
Almost every confusing moment with this tool comes from mixing these up.
|
|
75
|
+
|
|
76
|
+
| | **The package** | **A project** |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| What | `erp-sandbox` itself | one client's environment |
|
|
79
|
+
| Holds | templates, compose file, bench `Dockerfile` | `config.toml`, `.erp-sandbox/`, `bench/`, `data/` |
|
|
80
|
+
| Installed | once per machine, shared | created by you, as many as you like |
|
|
81
|
+
| Upgrades | replaced wholesale | never touched |
|
|
82
|
+
|
|
83
|
+
Every verb except `init` acts on **the environment containing your working
|
|
84
|
+
directory**, found by walking up for `.erp-sandbox/` — the same way `bench` finds
|
|
85
|
+
its bench. So `erp-sandbox bench console` works anywhere inside a project. Use
|
|
86
|
+
`--project DIR` to act on one from outside.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Documentation
|
|
91
|
+
|
|
92
|
+
**Using the tool**
|
|
93
|
+
|
|
94
|
+
| | |
|
|
95
|
+
|---|---|
|
|
96
|
+
| First run, end to end | [docs/setup.md](docs/setup.md) |
|
|
97
|
+
| Day-to-day work, editor, debugging, teardown | [docs/using.md](docs/using.md) |
|
|
98
|
+
| Symptom → cause → fix | [docs/troubleshooting.md](docs/troubleshooting.md) |
|
|
99
|
+
|
|
100
|
+
**Working on the tool**
|
|
101
|
+
|
|
102
|
+
| | |
|
|
103
|
+
|---|---|
|
|
104
|
+
| Setup, commands, rules | [docs/contributing.md](docs/contributing.md) |
|
|
105
|
+
| What is decided, and why | [docs/decisions.md](docs/decisions.md) |
|
|
106
|
+
| What the tool does, from the developer's side | [docs/functional.md](docs/functional.md) |
|
|
107
|
+
| Architecture, `config.toml` schema, credentials, seeding | [docs/technical.md](docs/technical.md) |
|
|
108
|
+
| Requirements and non-goals | [docs/requirements.md](docs/requirements.md) |
|
|
109
|
+
| Acceptance checklist and the test suite | [docs/testing.md](docs/testing.md) |
|
|
110
|
+
| What changed and when | [CHANGELOG.md](CHANGELOG.md) |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT — see [LICENSE](LICENSE). Copyright Datahenge LLC.
|
|
117
|
+
|
|
118
|
+
erp-sandbox is an independent project, built for ERPNext. It is not affiliated
|
|
119
|
+
with or endorsed by Frappe Technologies. ERPNext and Frappe are trademarks of
|
|
120
|
+
Frappe Technologies Pvt. Ltd.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
erp-sandbox — build a containerized ERPNext development environment from one
|
|
3
|
+
config.toml.
|
|
4
|
+
|
|
5
|
+
Installed with `pipx install` or `uv tool install`, never `pip install`:
|
|
6
|
+
modern Debian marks the system interpreter externally managed (PEP 668).
|
|
7
|
+
See decision #27.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# MariaDB settings Frappe requires, plus a few that make a dev database
|
|
2
|
+
# pleasant. Committed and generic: nothing here is client-specific, and the
|
|
3
|
+
# port is passed on the command line from config.toml rather than set here.
|
|
4
|
+
|
|
5
|
+
[mysqld]
|
|
6
|
+
# Frappe will not install on a database that is not utf8mb4. The handshake
|
|
7
|
+
# line is the one people miss: without it the client negotiates latin1 and
|
|
8
|
+
# emoji or accented characters arrive mangled rather than rejected.
|
|
9
|
+
character-set-client-handshake = FALSE
|
|
10
|
+
character-set-server = utf8mb4
|
|
11
|
+
collation-server = utf8mb4_unicode_ci
|
|
12
|
+
|
|
13
|
+
# v12-era Frappe assumes this. Leaving the 10.4 default of
|
|
14
|
+
# STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,... breaks patches that insert
|
|
15
|
+
# zero dates.
|
|
16
|
+
sql_mode = ""
|
|
17
|
+
|
|
18
|
+
# A development box is not a production server: prefer fast restarts and low
|
|
19
|
+
# memory over durability. A dev database that loses the last second of writes
|
|
20
|
+
# on a hard kill is not a problem; one that takes two minutes to start is.
|
|
21
|
+
innodb_flush_log_at_trx_commit = 2
|
|
22
|
+
innodb_file_per_table = 1
|
|
23
|
+
|
|
24
|
+
max_allowed_packet = 256M
|
|
25
|
+
|
|
26
|
+
[mysql]
|
|
27
|
+
default-character-set = utf8mb4
|
|
28
|
+
|
|
29
|
+
[client]
|
|
30
|
+
default-character-set = utf8mb4
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Teach nginx to resolve upstream names at request time instead of at startup.
|
|
3
|
+
#
|
|
4
|
+
# Without this, nginx refuses to start when the bench container is not running:
|
|
5
|
+
# a name in an `upstream` block is resolved once, at config load, and a failure
|
|
6
|
+
# there is fatal. That is the wrong behaviour for a development environment,
|
|
7
|
+
# where the bench is stopped and restarted constantly and the developer needs
|
|
8
|
+
# the proxy to stay up and say 502 rather than vanish with it.
|
|
9
|
+
#
|
|
10
|
+
# The nameserver address cannot be hardcoded: Docker publishes an embedded DNS
|
|
11
|
+
# server at 127.0.0.11, podman runs aardvark-dns on the network's gateway. Both
|
|
12
|
+
# write it into the container's own /etc/resolv.conf, so read it from there.
|
|
13
|
+
#
|
|
14
|
+
# The official nginx image runs every executable in /docker-entrypoint.d before
|
|
15
|
+
# starting nginx; this is mounted in as one of them.
|
|
16
|
+
|
|
17
|
+
set -eu
|
|
18
|
+
|
|
19
|
+
target=/etc/nginx/conf.d/resolver.conf
|
|
20
|
+
|
|
21
|
+
servers="$(awk '/^nameserver/ { printf "%s ", $2 }' /etc/resolv.conf)"
|
|
22
|
+
|
|
23
|
+
if [ -z "$servers" ]; then
|
|
24
|
+
echo "00-resolver.sh: no nameserver in /etc/resolv.conf; leaving nginx to resolve at startup" >&2
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# ipv6=off because an AAAA lookup for a container name that only has an A
|
|
29
|
+
# record produces a slow, confusing failure on some engines.
|
|
30
|
+
printf 'resolver %s valid=10s ipv6=off;\nresolver_timeout 5s;\n' "$servers" > "$target"
|
|
31
|
+
|
|
32
|
+
echo "00-resolver.sh: nginx will resolve upstreams via: $servers"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# One Redis container, three logical databases (decision #6): 0 cache,
|
|
2
|
+
# 1 queue, 2 socketio. Production uses three separate containers; this
|
|
3
|
+
# deliberately does not.
|
|
4
|
+
#
|
|
5
|
+
# The port is supplied on the command line from config.toml, so it is not set
|
|
6
|
+
# here -- there is no second place for it to disagree.
|
|
7
|
+
|
|
8
|
+
# Nothing in here is worth keeping across a restart. The cache rebuilds, the
|
|
9
|
+
# queues refill, and socketio state is per-connection. Persistence would buy
|
|
10
|
+
# nothing and cost fork pauses.
|
|
11
|
+
save ""
|
|
12
|
+
appendonly no
|
|
13
|
+
|
|
14
|
+
# Bound to every interface *inside the container*. The container network is the
|
|
15
|
+
# boundary, and the port is never published to the host (decision #16).
|
|
16
|
+
bind 0.0.0.0
|
|
17
|
+
protected-mode no
|
|
18
|
+
|
|
19
|
+
# Evict cache entries rather than returning errors when memory runs short.
|
|
20
|
+
maxmemory-policy allkeys-lru
|
|
21
|
+
|
|
22
|
+
loglevel notice
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# ═══ config.toml ══════════════════════════════════════════════════════════
|
|
2
|
+
#
|
|
3
|
+
# The only file you edit. Everything else is generated from it by
|
|
4
|
+
# `erp-sandbox render`, and generated files are overwritten on every render.
|
|
5
|
+
#
|
|
6
|
+
# erp-sandbox init acme-dev && cd acme-dev
|
|
7
|
+
# $EDITOR config.toml
|
|
8
|
+
# erp-sandbox build
|
|
9
|
+
#
|
|
10
|
+
# config.toml is gitignored. This sample is committed, so every value in it is
|
|
11
|
+
# a placeholder -- acme, example.com. Nothing real belongs here.
|
|
12
|
+
|
|
13
|
+
[project]
|
|
14
|
+
name = "acme" # compose project + container name prefix
|
|
15
|
+
# lowercase, [a-z0-9_-]
|
|
16
|
+
site = "site_acme1" # Frappe site name; becomes bench/sites/<name>
|
|
17
|
+
timezone = "America/Los_Angeles"
|
|
18
|
+
|
|
19
|
+
# ─── Host-published ports ────────────────────────────────────────────────
|
|
20
|
+
# Only these two reach the host. Change them if something already listens;
|
|
21
|
+
# preflight checks both before the build does any work.
|
|
22
|
+
[ports]
|
|
23
|
+
http = 8101 # nginx: the URL you open in a browser
|
|
24
|
+
mariadb = 3316 # for DBeaver / DataGrip / the mysql client
|
|
25
|
+
# bind = "127.0.0.1" # where http is published; this is the default.
|
|
26
|
+
# "0.0.0.0" reaches every interface -- under
|
|
27
|
+
# Docker that bypasses ufw. mariadb is always
|
|
28
|
+
# loopback.
|
|
29
|
+
|
|
30
|
+
# ─── Internal ports ──────────────────────────────────────────────────────
|
|
31
|
+
# Container network only -- nothing here is published. You would change these
|
|
32
|
+
# only to avoid a collision inside the bench container. socketio and the file
|
|
33
|
+
# watcher stay internal deliberately: nginx multiplexes /socket.io onto
|
|
34
|
+
# ports.http, which is what keeps realtime working through one port.
|
|
35
|
+
[internal_ports]
|
|
36
|
+
web = 8000
|
|
37
|
+
socketio = 9000
|
|
38
|
+
file_watcher = 6787
|
|
39
|
+
mariadb = 3306
|
|
40
|
+
redis = 6379
|
|
41
|
+
|
|
42
|
+
# ─── The stack ───────────────────────────────────────────────────────────
|
|
43
|
+
# These pin the toolchain. The defaults below are correct for ERPNext v12.
|
|
44
|
+
# A v13/v14 environment changes them.
|
|
45
|
+
[stack]
|
|
46
|
+
frappe_version = 12 # which Frappe series; must be one this
|
|
47
|
+
# tool knows how to build (decision #28)
|
|
48
|
+
base_image = "docker.io/library/debian:bullseye-slim" # bullseye = python 3.9
|
|
49
|
+
python = "3.9"
|
|
50
|
+
node = "12.22.12"
|
|
51
|
+
yarn = "1.22.22" # yarn 1.x; v12 asset builds predate berry
|
|
52
|
+
wkhtmltopdf = "0.12.6.1-2" # packaging release, NOT the wkhtmltopdf version;
|
|
53
|
+
# must be a patched-qt build, and the release
|
|
54
|
+
# that ships a bullseye .deb is 0.12.6.1-2
|
|
55
|
+
bench = "5.24.1"
|
|
56
|
+
mariadb_image = "docker.io/library/mariadb:10.4.6"
|
|
57
|
+
redis_image = "docker.io/library/redis:6.2-bullseye"
|
|
58
|
+
nginx_image = "docker.io/library/nginx:1.24-bullseye"
|
|
59
|
+
|
|
60
|
+
# System packages an app needs in order to *build*. Empty for a stock
|
|
61
|
+
# Frappe/ERPNext bench. An app with a compiled dependency will say so in its own
|
|
62
|
+
# README -- e.g. one wrapping a system service may need that service's -dev
|
|
63
|
+
# package. Debian package names, installed into the bench image.
|
|
64
|
+
extra_packages = []
|
|
65
|
+
|
|
66
|
+
# Ceilings pip may not resolve past while installing the apps, as a pip
|
|
67
|
+
# constraints file (`-c`). NOT a requirements file: nothing here is installed,
|
|
68
|
+
# and a package is only capped if some app already asks for it. It applies to
|
|
69
|
+
# every app's install, not one of them.
|
|
70
|
+
#
|
|
71
|
+
# `erp-sandbox init` seeds this from the Frappe series template. It is yours to
|
|
72
|
+
# edit, and whatever it says is what the build uses -- an empty list means no
|
|
73
|
+
# constraints at all. erp-sandbox never adds one back.
|
|
74
|
+
#
|
|
75
|
+
# Why the v12 seed exists: vanilla v12 pins openpyxl==2.6.4, whose compat layer
|
|
76
|
+
# reads numpy.float, removed in numpy 1.24. Nothing in a 2019 requirements.txt
|
|
77
|
+
# caps numpy, so pip is free to install 2.x and the site build dies importing
|
|
78
|
+
# openpyxl.
|
|
79
|
+
#
|
|
80
|
+
# Empty it if your fork has modernised pins. A fork pinning numpy==1.26.4 has
|
|
81
|
+
# no candidate left under this cap, and pip says only "No matching distribution
|
|
82
|
+
# found for numpy==1.26.4" -- see docs/troubleshooting.md.
|
|
83
|
+
#
|
|
84
|
+
# One entry per package, no spaces inside an entry: "numpy<1.24", not
|
|
85
|
+
# "numpy < 1.24".
|
|
86
|
+
pip_constraints = ["numpy<1.24"]
|
|
87
|
+
|
|
88
|
+
# ─── The tool itself ─────────────────────────────────────────────────────
|
|
89
|
+
# The oldest erp-sandbox that can build this environment.
|
|
90
|
+
#
|
|
91
|
+
# One installed erp-sandbox now serves every environment on the machine, so
|
|
92
|
+
# upgrading it changes all of them at once. This is how an environment says
|
|
93
|
+
# what it needs: preflight refuses to build with an older tool rather than
|
|
94
|
+
# failing somewhere in the middle on a feature that did not exist yet.
|
|
95
|
+
#
|
|
96
|
+
# `erp-sandbox init` writes the version that created the environment. Raise it by
|
|
97
|
+
# hand if you come to depend on something newer.
|
|
98
|
+
|
|
99
|
+
[erp-sandbox]
|
|
100
|
+
min_version = "0.1.0"
|
|
101
|
+
|
|
102
|
+
# ─── Applications ────────────────────────────────────────────────────────
|
|
103
|
+
# Order matters: it is the clone order and the install order. frappe must be
|
|
104
|
+
# first. Frappe and ERPNext are not special-cased -- they are entries like any
|
|
105
|
+
# other, which is what makes this tool generic.
|
|
106
|
+
#
|
|
107
|
+
# name — the Frappe app name; must match the app's own module name
|
|
108
|
+
# repo — a clone URL: https://host/path, ssh://user@host/path, or the
|
|
109
|
+
# scp-style user@host:path. Both schemes are supported.
|
|
110
|
+
# ref — branch or tag; whatever `git checkout` accepts
|
|
111
|
+
# install — false clones the app and leaves it off the site
|
|
112
|
+
#
|
|
113
|
+
# requires_credentials — true means this clone needs the [credentials.<host>]
|
|
114
|
+
# entry for its host. It is not the same question as "is the
|
|
115
|
+
# repository private": an ssh remote to a private repository
|
|
116
|
+
# authenticates with your own key, which this tool does not manage,
|
|
117
|
+
# so it takes false. Set true only for an https clone that needs a
|
|
118
|
+
# token, and preflight will refuse the combination of true and ssh.
|
|
119
|
+
#
|
|
120
|
+
# blobless — optional, false when absent; set true below for frappe and
|
|
121
|
+
# erpnext, the two clones that cost the disk. true clones with
|
|
122
|
+
# --filter=blob:none: every commit and branch arrives, but the
|
|
123
|
+
# contents of files at old commits are fetched only when a command
|
|
124
|
+
# asks for one. Measured on 2026-08-23, frappe and erpnext at
|
|
125
|
+
# version-12 carry 1.4 GiB of objects between them and about 70 MiB
|
|
126
|
+
# of that is commits, trees and the current files.
|
|
127
|
+
# The price is that `git log -p`, `git blame` on an old file, and
|
|
128
|
+
# `git bisect` need the network, and are slower the first time. Leave
|
|
129
|
+
# it false for an app you dig through history in; it is refused
|
|
130
|
+
# outright for an app with requires_credentials = true, because those
|
|
131
|
+
# later fetches happen long after the token is gone. Applies when the
|
|
132
|
+
# app is cloned -- setting it later does not shrink a clone you
|
|
133
|
+
# already have, and clearing it does not fetch the history back.
|
|
134
|
+
|
|
135
|
+
[[apps]]
|
|
136
|
+
name = "frappe"
|
|
137
|
+
repo = "https://github.com/frappe/frappe.git"
|
|
138
|
+
ref = "version-12"
|
|
139
|
+
requires_credentials = false
|
|
140
|
+
install = true
|
|
141
|
+
blobless = true # history arrives; old file contents on demand
|
|
142
|
+
|
|
143
|
+
[[apps]]
|
|
144
|
+
name = "erpnext"
|
|
145
|
+
repo = "https://github.com/frappe/erpnext.git"
|
|
146
|
+
ref = "version-12"
|
|
147
|
+
requires_credentials = false
|
|
148
|
+
install = true
|
|
149
|
+
blobless = true # most of this clone is history you may never read
|
|
150
|
+
|
|
151
|
+
# Your own apps go here. They are commented out so that `erp-sandbox build` works
|
|
152
|
+
# immediately after `erp-sandbox init` -- frappe and erpnext above are public, so
|
|
153
|
+
# an unedited config produces a stock ERPNext and proves the machine works.
|
|
154
|
+
# Uncomment and edit when you have real repositories to add.
|
|
155
|
+
#
|
|
156
|
+
# [[apps]]
|
|
157
|
+
# name = "acme_app"
|
|
158
|
+
# repo = "https://git.example.com/acme/acme_app.git"
|
|
159
|
+
# ref = "master"
|
|
160
|
+
# requires_credentials = true # needs the [credentials."git.example.com"] entry below
|
|
161
|
+
# install = true
|
|
162
|
+
#
|
|
163
|
+
# [[apps]]
|
|
164
|
+
# name = "acme_tools"
|
|
165
|
+
# repo = "git@git.example.com:acme/acme_tools.git"
|
|
166
|
+
# ref = "version-12"
|
|
167
|
+
# requires_credentials = false # ssh: your own key authenticates, no token needed
|
|
168
|
+
# install = false # cloned and importable, but not installed on the site
|
|
169
|
+
|
|
170
|
+
# ─── Credentials ─────────────────────────────────────────────────────────
|
|
171
|
+
# Keyed by git host, so GitHub and GitLab repos can coexist in one config.
|
|
172
|
+
# A host needs an entry here only if some app on it has
|
|
173
|
+
# requires_credentials = true.
|
|
174
|
+
#
|
|
175
|
+
# Prefer "env:NAME". The token then lives in your shell and never touches a
|
|
176
|
+
# file. A literal is accepted -- config.toml is gitignored -- but the renderer
|
|
177
|
+
# warns, because the value is now on disk.
|
|
178
|
+
#
|
|
179
|
+
# The token is used host-side only, by erp-sandbox fetch, for the
|
|
180
|
+
# duration of one clone. It never enters a container, an image layer, or a
|
|
181
|
+
# build argument.
|
|
182
|
+
# Commented out along with the apps that needed them. A [credentials] entry is
|
|
183
|
+
# required only for an app with requires_credentials = true.
|
|
184
|
+
#
|
|
185
|
+
# [credentials."git.example.com"]
|
|
186
|
+
# username = "your-username"
|
|
187
|
+
# token = "env:GIT_PAT"
|
|
188
|
+
#
|
|
189
|
+
# [credentials."gitlab.com"]
|
|
190
|
+
# username = "your-username"
|
|
191
|
+
# token = "env:GITLAB_PAT"
|
|
192
|
+
|
|
193
|
+
# ─── Where secrets come from ─────────────────────────────────────────────
|
|
194
|
+
# Every "env:NAME" above is looked up in your shell first, then in this file.
|
|
195
|
+
# The shell wins, so `export NAME=...` overrides the file for one run.
|
|
196
|
+
# preflight prints which source answered for each secret.
|
|
197
|
+
#
|
|
198
|
+
# Omit this section to use the default, secrets.env, next to config.toml.
|
|
199
|
+
# Set file = "" to use the shell only. The path may be absolute or start with
|
|
200
|
+
# ~, which is how you keep secrets outside the repository entirely.
|
|
201
|
+
#
|
|
202
|
+
# It cannot be ".env" -- render.py generates that file for compose and
|
|
203
|
+
# overwrites it on every render.
|
|
204
|
+
# [secrets]
|
|
205
|
+
# file = "secrets.env"
|
|
206
|
+
|
|
207
|
+
# ─── Database ────────────────────────────────────────────────────────────
|
|
208
|
+
[database]
|
|
209
|
+
root_password = "env:DB_ROOT_PASSWORD"
|
|
210
|
+
|
|
211
|
+
# seed = how the site's database gets its contents.
|
|
212
|
+
# "new_site" — bench new-site, then install each install = true app.
|
|
213
|
+
# Empty but working. The only mode that needs no client
|
|
214
|
+
# data, and therefore the default.
|
|
215
|
+
# "sql_dump" — restore a raw SQL dump you supply.
|
|
216
|
+
# "frappe_backup" — bench restore a frappe-native backup tarball.
|
|
217
|
+
#
|
|
218
|
+
# The tool never distributes client data. Dump files are yours, at a path on
|
|
219
|
+
# your own disk.
|
|
220
|
+
seed = "new_site"
|
|
221
|
+
dump_path = "" # required unless seed = "new_site"
|
|
222
|
+
|
|
223
|
+
# REQUIRED with a restored dump, and there is no way to recover from getting
|
|
224
|
+
# it wrong quietly: without the encryption_key that matches the dump, every
|
|
225
|
+
# stored password in the restored site is unreadable, and you find out weeks
|
|
226
|
+
# later when an integration cannot authenticate. Prefer "env:NAME".
|
|
227
|
+
encryption_key = ""
|
|
228
|
+
|
|
229
|
+
[site]
|
|
230
|
+
admin_password = "env:ADMIN_PASSWORD"
|
|
231
|
+
|
|
232
|
+
# Shared VPS only: the https address Caddy serves this site on, one subdomain
|
|
233
|
+
# per environment. It becomes Frappe's host_name, so emails, webhook payloads
|
|
234
|
+
# and OAuth callbacks carry it, and `erp-sandbox proxy-snippet` prints the Caddy
|
|
235
|
+
# block for it. Leave it unset on a laptop. See docs/vps.md.
|
|
236
|
+
# public_url = "https://acme.dev.example.com"
|
|
237
|
+
|
|
238
|
+
# ─── Debugging ───────────────────────────────────────────────────────────
|
|
239
|
+
# Opt-in remote debugging with debugpy: set a breakpoint in VSCode (or any
|
|
240
|
+
# DAP-speaking editor) and step through the framework and your own apps.
|
|
241
|
+
#
|
|
242
|
+
# Off by default, because turning it on publishes a THIRD host port -- and a
|
|
243
|
+
# debug port runs arbitrary code by design, so it is bound to 127.0.0.1 only.
|
|
244
|
+
#
|
|
245
|
+
# Turning this on and re-rendering writes the attach configuration into
|
|
246
|
+
# <name>.code-workspace. Open that workspace -- File > Open Workspace from
|
|
247
|
+
# File, not the folder -- and pick it from the debug dropdown. Opening the
|
|
248
|
+
# folder instead leaves the path mapping unresolved, and the editor sees host
|
|
249
|
+
# paths while the interpreter reports container ones, so breakpoints bind to
|
|
250
|
+
# nothing and never fire without reporting anything.
|
|
251
|
+
#
|
|
252
|
+
# target = "web" is the only value. The other long-running frappe processes
|
|
253
|
+
# reach your code through a fork that debugpy does not follow, so a breakpoint
|
|
254
|
+
# in them never fires -- see docs/technical.md section 10 before assuming a
|
|
255
|
+
# worker can be debugged this way.
|
|
256
|
+
#
|
|
257
|
+
# Enabling this turns OFF the web server's auto-reload. That is deliberate and
|
|
258
|
+
# unavoidable: the reloader re-executes the server, which drops the debugger.
|
|
259
|
+
# While debugging, restart the web process to pick up code changes:
|
|
260
|
+
#
|
|
261
|
+
# erp-sandbox restart bench
|
|
262
|
+
#
|
|
263
|
+
# Omit this section entirely to leave debugging off.
|
|
264
|
+
[debug]
|
|
265
|
+
enabled = false
|
|
266
|
+
target = "web"
|
|
267
|
+
port = 5678
|
|
268
|
+
|
|
269
|
+
# ─── Frappe runtime settings → common_site_config.json ───────────────────
|
|
270
|
+
[frappe]
|
|
271
|
+
developer_mode = 1
|
|
272
|
+
background_workers = 1 # worker processes per queue (short/long/default)
|
|
273
|
+
gunicorn_workers = 2
|