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.
Files changed (45) hide show
  1. erp_sandbox-0.2.0/LICENSE +21 -0
  2. erp_sandbox-0.2.0/PKG-INFO +134 -0
  3. erp_sandbox-0.2.0/README.md +120 -0
  4. erp_sandbox-0.2.0/erp_sandbox/__init__.py +10 -0
  5. erp_sandbox-0.2.0/erp_sandbox/__main__.py +5 -0
  6. erp_sandbox-0.2.0/erp_sandbox/assets/conf/mariadb/erpnext.cnf +30 -0
  7. erp_sandbox-0.2.0/erp_sandbox/assets/conf/nginx-00-resolver.sh +32 -0
  8. erp_sandbox-0.2.0/erp_sandbox/assets/conf/redis/redis.conf +22 -0
  9. erp_sandbox-0.2.0/erp_sandbox/assets/config.sample.toml +273 -0
  10. erp_sandbox-0.2.0/erp_sandbox/assets/docker-compose.yml +135 -0
  11. erp_sandbox-0.2.0/erp_sandbox/assets/images/bench/Dockerfile +232 -0
  12. erp_sandbox-0.2.0/erp_sandbox/assets/images/bench/entrypoint.sh +104 -0
  13. erp_sandbox-0.2.0/erp_sandbox/assets/secrets.sample.env +45 -0
  14. erp_sandbox-0.2.0/erp_sandbox/build_bench.py +364 -0
  15. erp_sandbox-0.2.0/erp_sandbox/cli.py +727 -0
  16. erp_sandbox-0.2.0/erp_sandbox/doctor.py +957 -0
  17. erp_sandbox-0.2.0/erp_sandbox/fetch_apps.py +441 -0
  18. erp_sandbox-0.2.0/erp_sandbox/fixtures/README.md +22 -0
  19. erp_sandbox-0.2.0/erp_sandbox/fixtures/empty.toml +3 -0
  20. erp_sandbox-0.2.0/erp_sandbox/fixtures/malformed.toml +4 -0
  21. erp_sandbox-0.2.0/erp_sandbox/fixtures/missing_database.toml +44 -0
  22. erp_sandbox-0.2.0/erp_sandbox/guard.py +121 -0
  23. erp_sandbox-0.2.0/erp_sandbox/known_versions.py +123 -0
  24. erp_sandbox-0.2.0/erp_sandbox/preflight.py +1502 -0
  25. erp_sandbox-0.2.0/erp_sandbox/project.py +168 -0
  26. erp_sandbox-0.2.0/erp_sandbox/render.py +659 -0
  27. erp_sandbox-0.2.0/erp_sandbox/seed_db.py +633 -0
  28. erp_sandbox-0.2.0/erp_sandbox/selftest.py +2150 -0
  29. erp_sandbox-0.2.0/erp_sandbox/templates/Procfile.tmpl +17 -0
  30. erp_sandbox-0.2.0/erp_sandbox/templates/apps.txt.tmpl +1 -0
  31. erp_sandbox-0.2.0/erp_sandbox/templates/claude_rule.md.tmpl +64 -0
  32. erp_sandbox-0.2.0/erp_sandbox/templates/claude_settings.json.tmpl +15 -0
  33. erp_sandbox-0.2.0/erp_sandbox/templates/code_workspace.tmpl +45 -0
  34. erp_sandbox-0.2.0/erp_sandbox/templates/common_site_config.json.tmpl +16 -0
  35. erp_sandbox-0.2.0/erp_sandbox/templates/compose.docker.tmpl +18 -0
  36. erp_sandbox-0.2.0/erp_sandbox/templates/compose.podman.tmpl +53 -0
  37. erp_sandbox-0.2.0/erp_sandbox/templates/env.tmpl +48 -0
  38. erp_sandbox-0.2.0/erp_sandbox/templates/nginx.site.conf.tmpl +90 -0
  39. erp_sandbox-0.2.0/erp_sandbox.egg-info/PKG-INFO +134 -0
  40. erp_sandbox-0.2.0/erp_sandbox.egg-info/SOURCES.txt +43 -0
  41. erp_sandbox-0.2.0/erp_sandbox.egg-info/dependency_links.txt +1 -0
  42. erp_sandbox-0.2.0/erp_sandbox.egg-info/entry_points.txt +3 -0
  43. erp_sandbox-0.2.0/erp_sandbox.egg-info/top_level.txt +1 -0
  44. erp_sandbox-0.2.0/pyproject.toml +94 -0
  45. 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,5 @@
1
+ """`python -m erp_sandbox`, equivalent to the `erp-sandbox` console script."""
2
+
3
+ from erp_sandbox.cli import main
4
+
5
+ raise SystemExit(main())
@@ -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