feat(codespace): replace broken odoo-image blueprint with python:3.12-slim pattern
The previous blueprint (upstream odoo/docker:19.0 + COPY + deploy.command)
systematically broke inside Easypanel's inline-dockerfile build context:
- COPY files not available
- wkhtmltopdf/libldap-2.5-0 package names absent from Debian bookworm
- Easypanel wrapped deploy.command with 'python -c …' → exit 100
- postgres password / app env set separately → mismatch auth failure
- cat > /etc/odoo/odoo.conf without parent dir → ENOENT
The new blueprint is what actually ran in production for 34h+ stable:
- FROM python:3.12-slim with an inline ENTRYPOINT (no CMD wrapping)
- init-odoo.sh: apt install build deps then pip install /mnt/os-dev
(idempotent — skipped if import odoo works, restarts < 1 s)
- --dev=reload,qweb,assets,xml,werkzeug baked into exec odoo
- mkdir -p /etc/odoo /var/lib/odoo /var/log/odoo before writing odoo.conf
- Single createFromSchema call → shared password for postgres + app
- Bind-mount os-dev → /mnt/os-dev → hot-reload on any host edit
deploy.py prepare writes easypanel.schema.json + init-odoo.sh +
inline.Dockerfile + .db_password, all ready for TRAE Easypanel MCP
createFromSchema + deployAppService. Verified end-to-end (HTTP 200 on
/web/database/manager, hot-reload on release.py product_name edit,
CREATE DATABASE via psycopg2 + odoo UI Create Database).
Docs:
- README.md rewritten with new layout table, troubleshooting
playbook, daily workflow, verified-state reference table,
old-vs-new blueprint comparison table.
This commit is contained in:
1 parent
fff19a9e17
commit
6e7be43dc8
4 files changed
+467
-336
No files matched your search
+14
@@ -0,0 +1,14 @@
|
||||
# Sensitive artifacts generated by `deploy.py prepare`
|
||||
.db_password
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Generated files (re-run `deploy.py prepare` to regenerate)
|
||||
easypanel.schema.json
|
||||
init-odoo.sh
|
||||
inline.Dockerfile
|
||||
|
||||
# Editor
|
||||
.idea/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
@@ -1,153 +1,192 @@
|
||||
# stateless-container-for-onebspace-development
|
||||
|
||||
Stateless hot-reload **Odoo 19** development workspace for the
|
||||
`1b-agent/os-dev` repo, deployed on the **Easypanel** project
|
||||
`onebspace`.
|
||||
Blueprint (and reusable scripts) for spinning up a **stateless hot-reload
|
||||
`os-dev` codespace** for the `1b-agent/os-dev` Odoo 19 fork, deployed on
|
||||
the Easypanel project `onebspace`.
|
||||
|
||||
This repo is the **blueprint** — the live state is on the
|
||||
[Easypanel onebspace project](http://45.137.70.123:3000)
|
||||
(`odoo-dev` + `odoo-dev-db` services) and the bind-mounted workspace
|
||||
at `/root/Documents/trae_projects/one_b_os/os-dev/`.
|
||||
|
||||
---
|
||||
This blueprint was **battle-tested Sep 2026** on a real deployment: the
|
||||
container stayed **Up 34 hours+** stably running Odoo 19 from a bind-mount
|
||||
of the live source tree with all five `--dev=*` flags active, and we
|
||||
proved end-to-end hot-reload by editing `release.py product_name` and
|
||||
watching the change propagate without a restart.
|
||||
|
||||
## 1. What this gives you
|
||||
|
||||
- **No image rebuilds for code edits.** Edit any `.py` in
|
||||
`os-dev/odoo/addons/...` and `--dev=reload` (watchdog) restarts
|
||||
Odoo in milliseconds.
|
||||
- **Full hot-reload flags** baked into `odoo.conf`:
|
||||
```ini
|
||||
dev_mode = reload,qweb,assets,xml,werkzeug
|
||||
```
|
||||
- `reload` → restart on `.py` changes
|
||||
- `qweb` → recompile QWeb per request
|
||||
- `assets` → unbundle JS/CSS
|
||||
- `xml` → re-read XML views from disk
|
||||
- `werkzeug` → in-browser tracebacks
|
||||
- **Image built by Easypanel from an inline Dockerfile** on every
|
||||
deploy (`source.type=dockerfile`), no Gitea Container Registry
|
||||
needed.
|
||||
- **Bind-mount** of the entire `1b-agent/os-dev` workspace at
|
||||
`/mnt/os-dev` so the upstream addons, your custom addons, and
|
||||
the core Odoo code are all editable in place.
|
||||
* **Zero image rebuilds on code edits.** Edit any `.py` / `.xml` / `.js` in
|
||||
`os-dev/` and the change is live within milliseconds because Odoo is
|
||||
launched with `--dev=reload,qweb,assets,xml,werkzeug`.
|
||||
* **Bind-mounted source.** The whole os-dev repository is mounted from
|
||||
the host into the container at `/mnt/os-dev`. First boot takes ~5 min
|
||||
while `pip install /mnt/os-dev` builds native extensions; every
|
||||
subsequent boot is < 1 s (idempotent install).
|
||||
* **Tiny base image.** `python:3.12-slim` (Debian bookworm, ~45 MB)
|
||||
instead of the bloated upstream `ubuntu:noble` Odoo image.
|
||||
* **Trivial inline Dockerfile** (80 lines, 2 `RUN` instructions total,
|
||||
zero `COPY` instructions — every file is a heredoc). This is crucial
|
||||
because Easypanel's `source.type=dockerfile` build context contains
|
||||
only the Dockerfile; no repo files are available to `COPY`.
|
||||
* **Single schema call.** Passwords for postgres AND the app env are
|
||||
generated from the same `createFromSchema` payload, so the "FATAL:
|
||||
password authentication failed for user odoo" mismatch bug cannot
|
||||
happen again.
|
||||
* **2 UI extras on postgres:** `pgWeb` + `dbGate` web panels are
|
||||
enabled out of the box on the companion db service.
|
||||
|
||||
---
|
||||
## 2. What changed from the old blueprint
|
||||
|
||||
## 2. Components
|
||||
The previous blueprint tried (and failed) to replicate the upstream
|
||||
`odoo/docker:19.0` Dockerfile with COPY'd files. Every single step of
|
||||
that approach broke inside Easypanel's inline-dockerfile build context:
|
||||
|
||||
| Old approach (broken) | New approach (works) |
|
||||
|---|---|
|
||||
| Upstream `odoo:19.0` base (1 GB, ubuntu) | `python:3.12-slim` (45 MB, bookworm) |
|
||||
| COPY entrypoint.sh / COPY odoo.conf in Dockerfile | Inlined as `RUN cat > /file <<'MARKER'` heredocs |
|
||||
| `wkhtmltopdf` and `libldap-2.5-0` apt packages (names don't exist in debian bookworm) | Omitted (we don't use PDFs in dev) / renamed to `libldap2-dev` |
|
||||
| `deploy.command="bash entrypoint.sh"` (Easypanel wraps it as `python -c "bash…"` → exit 100) | Explicit `ENTRYPOINT ["/usr/local/bin/init-odoo.sh"]` in inline Dockerfile |
|
||||
| Updated app env password AFTER postgres service was already running → password mismatch | Shared password in single `createFromSchema` payload |
|
||||
| `cat > /etc/odoo/odoo.conf` without creating the parent dir → ENOENT → `odoo.conf doesn't exist or is not readable` | `mkdir -p /etc/odoo /var/lib/odoo /var/log/odoo` first |
|
||||
| Manual tRPC calls (`services.app.createService` etc.) bypassing TRAE MCP | Easypanel MCP `createFromSchema` (handles ordering + postgres) |
|
||||
|
||||
## 3. Repo layout
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `Dockerfile` | Verbatim copy of [`github.com/odoo/docker/19.0/Dockerfile`](https://github.com/odoo/docker/blob/master/19.0/Dockerfile) + `pip install -r requirements.txt --break-system-packages`. SHA-pinned release: `ODOO_RELEASE=20260817`, `ODOO_SHA=7e6c102efc7e2ad56801432fef7e2e0d2f703e5a` |
|
||||
| `entrypoint.sh` | Postgres env-var → `--db_host/--db_user/...` propagation (copied from upstream) |
|
||||
| `wait-for-psql.py` | Waits for Postgres before starting Odoo (copied from upstream) |
|
||||
| `odoo.conf` | Bake-time Odoo config; full hot-reload; `addons_path` includes our `/mnt/os-dev/{addons,odoo/addons}` |
|
||||
| `requirements.txt` | Python deps from the upstream `odoo/19.0` requirements.txt |
|
||||
| `easypanel.schema.json` | Easypanel service payload using **`source.type=dockerfile`** + inline Dockerfile |
|
||||
| `deploy.py` | Calls Easypanel\'s native tRPC API directly (bypasses the MCP wrapper that only supports `source.type=image`) to push the inline Dockerfile, set env, and deploy |
|
||||
| `.gitea/workflows/build.yml` | Optional: auto-build & push `os-dev:19.0` to a Gitea Container Registry on every push to `1b-agent/os-dev`'s `19.0` branch |
|
||||
|---|---|
|
||||
| `deploy.py` | **One-stop script.** `python3 deploy.py prepare` writes all artifacts + a fresh DB password. `dockerfile` subcommand prints the generated inline Dockerfile for inspection. |
|
||||
| `init-odoo.sh` | Runtime entrypoint. Written by `deploy.py prepare`. Installs apt deps, then pip-installs the bind-mounted `/mnt/os-dev` if odoo is not yet importable, writes odoo.conf from env vars, waits for postgres, then `exec`s odoo with all 5 dev flags. |
|
||||
| `inline.Dockerfile` | The 80-line inline Dockerfile (written by `prepare`). Uses heredocs to drop init-odoo.sh and sets `ENTRYPOINT` explicitly. |
|
||||
| `easypanel.schema.json` | Ready-to-use `createFromSchema` payload (written by `prepare`). Contains both services, mounts, ports, env vars, and a fresh shared password. |
|
||||
| `.db_password` | Plaintext copy of the shared password (written by `prepare`). Git-ignored. |
|
||||
| `wait-for-psql.py` | Legacy artifact, no longer used (init-odoo.sh's socket-poll loop replaced it). Kept for reference. |
|
||||
| `entrypoint.sh` | Legacy artifact, no longer used (replaced by init-odoo.sh). Kept for reference. |
|
||||
|
||||
---
|
||||
## 4. Step-by-step: deploy a new codespace
|
||||
|
||||
## 3. How it was built (research summary)
|
||||
### 4.1 Prerequisites on the host
|
||||
|
||||
### 3.1 The official `odoo/docker` image
|
||||
- Base: `ubuntu:noble` (not Debian)
|
||||
- Pre-installs `python3-watchdog` (what makes `--dev=reload` work)
|
||||
- Entrypoint waits for Postgres (`wait-for-psql.py --timeout=30`)
|
||||
- Default `odoo.conf` has `addons_path = /mnt/extra-addons`,
|
||||
`data_dir = /var/lib/odoo` — both declared as `VOLUME`
|
||||
* Easypanel project `onebspace` exists.
|
||||
* A source checkout of `os-dev` at
|
||||
`/root/Documents/trae_projects/one_b_os/os-dev/` (this is what gets
|
||||
bind-mounted — live edits inside it take effect immediately).
|
||||
* TRAE MCP Easypanel server is authenticated against
|
||||
`http://45.137.70.123:3000` with a bearer token from
|
||||
`/root/.trae-server/data/Machine/mcp.json`.
|
||||
* Docker swarm mode (Easypanel runs swarm).
|
||||
|
||||
### 3.2 dev_mode flags (Odoo 19 CLI reference)
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `reload` | restart `odoo-bin` on `.py` change (needs `python3-watchdog`) |
|
||||
| `qweb` | recompile QWeb templates per request |
|
||||
| `assets` | serve JS/CSS unbundled → edit-and-refresh |
|
||||
| `xml` | re-read XML view files on update |
|
||||
| `werkzeug` | show full Python tracebacks in browser |
|
||||
### 4.2 Run the prepare command
|
||||
|
||||
### 3.3 Why `source.type=dockerfile` (not image pull)
|
||||
Easypanel\'s `services.app.createFromSchema` (the MCP wrapper) only
|
||||
supports `source.type=image`, and Easypanel couldn\'t pull from
|
||||
the Gitea Container Registry due to TLS cert mismatch + missing
|
||||
package pre-creation.
|
||||
|
||||
Easypanel itself supports `source.type=dockerfile` (inline
|
||||
Dockerfile build) — but the MCP wrapper doesn\'t surface it. We
|
||||
**bypassed the MCP** and called the native tRPC API directly:
|
||||
|
||||
```
|
||||
POST /api/trpc/services.app.createService
|
||||
POST /api/trpc/services.app.updateSourceDockerfile
|
||||
POST /api/trpc/services.app.updateEnv
|
||||
POST /api/trpc/services.app.deployService
|
||||
```
|
||||
|
||||
The Dockerfile is stored on Easypanel\'s filesystem at
|
||||
`/etc/easypanel/projects/<project>/<service>/code/Dockerfile` and
|
||||
rebuilt on every deploy.
|
||||
|
||||
### 3.4 Self-contained inline Dockerfile
|
||||
Easypanel\'s `source.type=dockerfile` has **no `COPY` access**
|
||||
(per docs: *"Files from a repository or upload are not
|
||||
automatically available to this source type"*), so all `COPY`-ed
|
||||
files are inlined as heredocs (`RUN cat > /file <<\'MARKER\'`).
|
||||
This produces a single ~17 KB Dockerfile with zero `COPY`
|
||||
instructions.
|
||||
|
||||
---
|
||||
|
||||
## 4. Reproducing this setup on a fresh Easypanel
|
||||
|
||||
### 4.1 Prereqs
|
||||
- An Easypanel instance with a project (we used `onebspace`)
|
||||
- A clone of `1b-agent/os-dev` at `/root/Documents/trae_projects/one_b_os/os-dev/`
|
||||
- An Easypanel bearer token (Settings → API → Generate Token,
|
||||
or grab the MCP token from `/root/.trae-server/data/Machine/mcp.json`)
|
||||
|
||||
### 4.2 Run the deploy script
|
||||
```bash
|
||||
cd stateless-container-for-onebspace-development
|
||||
python3 deploy.py deploy
|
||||
cd /root/Documents/trae_projects/one_b_os/stateless-container-for-onebspace-development
|
||||
python3 deploy.py prepare
|
||||
# → writes easypanel.schema.json, inline.Dockerfile, init-odoo.sh, .db_password
|
||||
# → prints "Password: <fresh 24 chars>"
|
||||
```
|
||||
This will:
|
||||
1. Generate the inline Dockerfile from `../os-dev/*` files
|
||||
2. Generate a fresh DB password and save to `.db_password`
|
||||
3. Patch `CHANGE_ME_DB_PASSWORD` in the env block
|
||||
4. Call the four Easypanel tRPC procedures in order
|
||||
5. Build the image in-place and start the container
|
||||
|
||||
Verify:
|
||||
Optional sanity checks:
|
||||
```bash
|
||||
curl -sS -o /dev/null -w "HTTP %{http_code} bytes=%{size_download}\\n" \
|
||||
http://127.0.0.1:18069/web/database/manager
|
||||
# expected: HTTP 200 bytes=70194
|
||||
python3 deploy.py dockerfile | head -10 # confirm FROM python:3.12-slim
|
||||
jq '.schema.services[0].data.password' easypanel.schema.json # same password
|
||||
jq '.schema.services[1].data.env' easypanel.schema.json # PASSWORD=same value
|
||||
```
|
||||
|
||||
---
|
||||
### 4.3 Apply via TRAE Easypanel MCP
|
||||
|
||||
Inside a TRAE agent chat, do:
|
||||
```
|
||||
1. call mcp_Easypanel : execute_mutation / createFromSchema
|
||||
payload = the json.read(easypanel.schema.json)
|
||||
2. call mcp_Easypanel : execute_destructive / deployAppService
|
||||
{projectName: "onebspace", serviceName: "os-dev-codespace"}
|
||||
3. wait 5-7 min. First boot runs pip install /mnt/os-dev → ~5 min
|
||||
native build (lxml, pillow, python-ldap, psycopg2-c).
|
||||
4. curl -sS -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:18069/web/database/manager
|
||||
expect: HTTP 200 (70,194 bytes)
|
||||
5. Visit http://127.0.0.1:18069/web/database/manager
|
||||
Master password = admin. Click "Create Database".
|
||||
```
|
||||
|
||||
### 4.4 Troubleshooting the container
|
||||
|
||||
If the service flips to **Desired: Running, Current: exit 100 / exit 2**,
|
||||
catch it in its 10-second window:
|
||||
```bash
|
||||
CID=$(docker ps --filter "name=onebspace_os-dev-codespace" --format '{{.ID}}' | head -1)
|
||||
docker exec $CID cat /tmp/init.log 2>&1 | tail -50
|
||||
```
|
||||
The init script redirects everything to `/tmp/init.log` with `set -x`.
|
||||
Common fixes:
|
||||
|
||||
* `exit 100` on boot and no log written → the Dockerfile ENTRYPOINT was
|
||||
stripped / Easypanel wrapped it with `python`. Re-apply
|
||||
`inline.Dockerfile` via `updateAppSourceDockerfile`.
|
||||
* `apt-get install had errors` → check for package name drift (e.g.
|
||||
`libldap-2.5-0` vs `libldap2-dev`). The script continues on apt
|
||||
errors; only a failed `pip install` matters.
|
||||
* `config file '/etc/odoo/odoo.conf' doesn't exist or is not readable` →
|
||||
you forgot `mkdir -p /etc/odoo` before the `cat >` redirect. It's in
|
||||
the current init script; if you see this, the script was not
|
||||
updated.
|
||||
* `FATAL: password authentication failed for user odoo` → you updated
|
||||
the app env but not the live postgres role. Don't do that. Always
|
||||
deploy both sides from the same `createFromSchema` payload, or if you
|
||||
must update the password live:
|
||||
```
|
||||
docker exec -it $PG_CID psql -U odoo -d postgres \
|
||||
-c "ALTER USER odoo WITH PASSWORD '$NEW_PASS';"
|
||||
```
|
||||
* Odoo installs but you can't create a database → verify your
|
||||
postgres user was CREATED with CREATEDB. The schema uses the
|
||||
`postgres` service type in Easypanel, which does this for you.
|
||||
|
||||
## 5. Daily workflow
|
||||
|
||||
```bash
|
||||
# edit code in place — auto-reload picks it up
|
||||
vim /root/Documents/trae_projects/one_b_os/os-dev/odoo/addons/web/controllers/main.py
|
||||
# 1. Just edit code.
|
||||
vim /root/Documents/trae_projects/one_b_os/os-dev/odoo/addons/...
|
||||
|
||||
# restart Odoo manually if needed
|
||||
docker restart $(docker ps --filter "name=onebspace_odoo-dev" --format "{{.ID}}" | head -1)
|
||||
# 2. Refresh the page. --dev=reload re-imports modified modules on
|
||||
# next request; --dev=qweb re-renders templates; --dev=assets serves
|
||||
# unbundled JS.
|
||||
|
||||
# rebuild the image (only when requirements.txt or Dockerfile changes)
|
||||
python3 deploy.py redeploy
|
||||
# 3. Only if you edit requirements.txt / setup.py's install_requires AND
|
||||
# want to test the new deps WITHOUT waiting for a fresh image:
|
||||
CID=$(docker ps --filter name=onebspace_os-dev-codespace --format '{{.ID}}' | head -1)
|
||||
docker exec -it $CID bash -lc 'pip install --break-system-packages /mnt/os-dev'
|
||||
|
||||
# 4. Restart (NOT needed for .py edits, only for stale workers):
|
||||
docker restart $CID
|
||||
|
||||
# 5. Destroy the whole stack when done:
|
||||
# (via TRAE MCP) destroyAppService + destroyPostgresService
|
||||
```
|
||||
|
||||
---
|
||||
## 6. Verified working state (reference)
|
||||
|
||||
## 6. Verified working state
|
||||
| Item | Value (from the Sep 14 2026 live deployment) |
|
||||
|---|---|
|
||||
| Container image | `python:3.12-slim@sha256:6fb6b091a…` (bookworm) |
|
||||
| Container uptime | Up 34 h (stable, no flapping) |
|
||||
| Odoo PID 1 cmdline | `/usr/local/bin/python3 /usr/local/bin/odoo -c /etc/odoo/odoo.conf --dev=reload,qweb,assets,xml,werkzeug` |
|
||||
| Bind mount | `/root/Documents/trae_projects/one_b_os/os-dev` → `/mnt/os-dev` (rw) |
|
||||
| Filestore volume | `onebspace_os-dev-codespace-filestore` → `/var/lib/odoo` |
|
||||
| HTTP /web/database/manager | **200 OK** (70 194 bytes) |
|
||||
| CREATE DATABASE test | psycopg2 `CREATE DATABASE test_codespace_db OWNER odoo` — OK |
|
||||
| Hot-reload test | Edit `release.py product_name='Odoo'` → `PRODUCT_NAME=1BOSDEV_HOTRELOAD_TEST` inside running container within 10 s → revert → back to `Odoo`. No container restart. |
|
||||
| Postgres | `postgres:17`, user `odoo`, port 5432 (swarm DNS `onebspace_os-dev-codespace-db`) |
|
||||
| Host port | 18069 → container 8069 |
|
||||
| First-boot install time | ~5 min (native lxml, pillow, python-ldap builds) |
|
||||
|
||||
- `Odoo version 19.0-20260817` (matches `ODOO_SHA=7e6c102e…`)
|
||||
- HTTP 200 (70,194 bytes) on `/web/database/manager`
|
||||
- Addons loaded from `/mnt/os-dev/addons`, `/mnt/os-dev/odoo/addons`,
|
||||
plus upstream `/usr/lib/python3/dist-packages/odoo/addons`
|
||||
- `AutoReload watcher running with watchdog` + `HTTP service (werkzeug) running`
|
||||
- Bind-mount `/root/Documents/trae_projects/one_b_os/os-dev/` → `/mnt/os-dev/`
|
||||
verified (24 addons in both views)
|
||||
## 7. Customizing
|
||||
|
||||
Open `deploy.py` and edit the block at the top:
|
||||
|
||||
```python
|
||||
EASYPANEL_PROJECT = "onebspace"
|
||||
APP_SERVICE = "os-dev-codespace"
|
||||
HOST_PORT = 18069
|
||||
OS_DEV_HOST_PATH = "/root/Documents/trae_projects/one_b_os/os-dev"
|
||||
BASE_IMAGE = "python:3.12-slim"
|
||||
PG_IMAGE = "postgres:17"
|
||||
```
|
||||
|
||||
Then run `prepare` again to regenerate all artifacts.
|
||||
@@ -1,156 +1,317 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Deploy (or destroy / status) the Odoo 19 stateless dev stack to Easypanel.
|
||||
Deploy the Odoo 19 **os-dev codespace** (stateless hot-reload workspace)
|
||||
to Easypanel on a given project.
|
||||
|
||||
This script talks to Easypanel's NATIVE tRPC REST API directly — bypassing
|
||||
the MCP wrapper, which only supports source.type=image. The full pipeline
|
||||
that worked end-to-end is:
|
||||
This is the PROVEN-WORKING pipeline (tested Sep 14, 2026 on the `onebspace`
|
||||
project, service `os-dev-codespace`, container stable 34h+):
|
||||
|
||||
1. services.app.createService (projectName, serviceName)
|
||||
2. services.app.updateSourceDockerfile (inline Dockerfile with all COPY files
|
||||
inlined as heredocs)
|
||||
3. services.app.updateEnv (HOST/USER/PASSWORD/ADMIN_PASSWD)
|
||||
4. services.app.deployService (triggers docker buildx build + run)
|
||||
1. Start a tiny `python:3.12-slim` image (Debian 12 bookworm).
|
||||
2. On first container boot, the init script installs build deps via apt
|
||||
then runs `pip install /mnt/os-dev` + `pip install -r requirements.txt`
|
||||
from the BIND-MOUNTED source tree (so live edits in /mnt/os-dev are
|
||||
picked up by `--dev=reload,qweb,assets,xml,werkzeug`).
|
||||
3. A companion `postgres:17` service is created alongside, with a single
|
||||
fresh password shared between both services (no mismatch bug).
|
||||
4. Easypanel is told to build an INLINE Dockerfile that writes the init
|
||||
script via heredoc and sets `ENTRYPOINT` explicitly (critical: because
|
||||
`python:3.12-slim` ships with `CMD=[python]`, Easypanel's default
|
||||
`deploy.command` wrapping would otherwise produce `exit 100`).
|
||||
|
||||
Usage:
|
||||
python3 deploy.py deploy
|
||||
python3 deploy.py destroy
|
||||
python3 deploy.py status
|
||||
python3 deploy.py redeploy (just re-trigger deployService)
|
||||
What this replaces:
|
||||
- The old official odoo/docker base image (huge, ubuntu:noble based,
|
||||
forces re-bakes on every source edit, requires COPYs that do not
|
||||
work in Easypanel inline-dockerfile build contexts that have no
|
||||
source files available).
|
||||
- Manual tRPC calls — we now use the TRAE MCP
|
||||
(`mcp_Easypanel` / `createFromSchema` + friends) because those
|
||||
procedures are the supported surface and handle the
|
||||
postgres/app-service ordering correctly.
|
||||
|
||||
Reads:
|
||||
easypanel.schema.json (only for the CHANGE_ME_DB_PASSWORD placeholder)
|
||||
os-dev/Dockerfile (verbatim upstream + pip install)
|
||||
os-dev/entrypoint.sh, wait-for-psql.py, odoo.conf, requirements.txt
|
||||
Easypanel token: from /root/.trae-server/data/Machine/mcp.json (Bearer).
|
||||
Usage (run from this repo's directory, or anywhere):
|
||||
python3 deploy.py deploy # full createFromSchema → deploy
|
||||
python3 deploy.py destroy # destroy app + postgres + filestore vol
|
||||
python3 deploy.py status # inspect both services
|
||||
|
||||
Dependencies:
|
||||
- TRAE MCP Easypanel server reachable (handled by the agent).
|
||||
- os-dev source tree bind-mounted at /mnt/os-dev inside the
|
||||
container (defaults to /root/Documents/trae_projects/one_b_os/os-dev
|
||||
on the host).
|
||||
- `odoo/release.py` MIN_PY_VERSION = (3, 10) — we use 3.12 safely.
|
||||
|
||||
Key runtime guarantees of the deployed service:
|
||||
* Idempotent install: `pip install /mnt/os-dev` ONLY runs if
|
||||
`python3 -c "import odoo"` fails (i.e. on first ever container
|
||||
boot, ~5 min native build). Subsequent restarts start Odoo in <1s.
|
||||
* Hot-reload: `--dev=reload,qweb,assets,xml,werkzeug` on the cmdline.
|
||||
Edit `release.py product_name` or any addon `.py` and the change is
|
||||
loaded on next HTTP request (verified end-to-end).
|
||||
* No image rebuild needed on code changes (the whole point).
|
||||
* Postgres password is created ONCE per schema deploy and set on
|
||||
BOTH the postgres service AND the app env SIMULTANEOUSLY via the
|
||||
same `createFromSchema` call, which eliminates the mismatch bug
|
||||
("FATAL: password authentication failed for user odoo") we had when
|
||||
updating app env without ALTER USER on the live pg.
|
||||
* `mkdir -p /etc/odoo /var/lib/odoo /var/log/odoo` before `cat >
|
||||
/etc/odoo/odoo.conf` — otherwise the redirect fails with ENOENT on
|
||||
the non-existent directory (another bug this blueprint fixes).
|
||||
"""
|
||||
import json, os, pathlib, re, secrets, string, sys, urllib.request, urllib.error
|
||||
from __future__ import annotations
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parent
|
||||
SCHEMA = ROOT / "easypanel.schema.json"
|
||||
OSDEV = ROOT.parent / "os-dev" if (ROOT.parent / "os-dev").exists() else pathlib.Path("/root/Documents/trae_projects/one_b_os/os-dev")
|
||||
MCP_JSON = pathlib.Path("/root/.trae-server/data/Machine/mcp.json")
|
||||
import json, pathlib, secrets, string, sys, subprocess, textwrap
|
||||
|
||||
EASYPANEL_URL = "http://45.137.70.123:3000"
|
||||
PROJECT = "onebspace"
|
||||
SERVICE = "odoo-dev"
|
||||
ROOT = pathlib.Path(__file__).resolve().parent
|
||||
|
||||
# ---------- helpers ----------
|
||||
# ---------- CONFIGURATION (edit these per project) --------------------------
|
||||
|
||||
def gen_password(n=24):
|
||||
return "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(n))
|
||||
EASYPANEL_PROJECT = "onebspace" # project name in Easypanel
|
||||
APP_SERVICE = "os-dev-codespace" # app service name
|
||||
DB_SERVICE = f"{APP_SERVICE}-db" # companion postgres service
|
||||
HOST_PORT = 18069 # host port → 8069 inside container
|
||||
FILESTORE_VOLUME = f"{APP_SERVICE}-filestore"
|
||||
OS_DEV_HOST_PATH = "/root/Documents/trae_projects/one_b_os/os-dev"
|
||||
BASE_IMAGE = "python:3.12-slim" # Debian 12 bookworm, slim, ~45MB
|
||||
PG_IMAGE = "postgres:17"
|
||||
|
||||
def load_token():
|
||||
cfg = json.loads(MCP_JSON.read_text())
|
||||
return cfg["mcpServers"]["Easypanel"]["headers"]["Authorization"].split(" ", 1)[1]
|
||||
# Resource budgets. The 5-min first-boot pip install is memory-hungry
|
||||
# (lxml builds ~2 GB). Allocate headroom.
|
||||
APP_RESOURCES = {"memoryReservation": 512, "memoryLimit": 2048,
|
||||
"cpuReservation": 0.5, "cpuLimit": 2}
|
||||
PG_RESOURCES = {"memoryReservation": 256, "memoryLimit": 1024,
|
||||
"cpuReservation": 0.25, "cpuLimit": 1}
|
||||
|
||||
TOKEN = load_token()
|
||||
# ---------- helpers ----------------------------------------------------------
|
||||
|
||||
def call(proc, payload, timeout=600):
|
||||
body = json.dumps(payload).encode()
|
||||
req = urllib.request.Request(
|
||||
f"{EASYPANEL_URL}/api/trpc/{proc}", data=body, method="POST",
|
||||
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"})
|
||||
def gen_password(n: int = 24) -> str:
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
return "".join(secrets.choice(alphabet) for _ in range(n))
|
||||
|
||||
|
||||
def build_init_script() -> str:
|
||||
"""The bash entrypoint that runs inside each container.
|
||||
|
||||
It is written into the built image via heredoc in the inline
|
||||
Dockerfile. The script MUST be short, idempotent, and tolerant of
|
||||
missing optional packages.
|
||||
|
||||
* CRITICAL FIX #1: no `wkhtmltopdf` + no `libldap-2.5-0`. Those
|
||||
package names do NOT exist in Debian bookworm/trixie repos that
|
||||
ship with `python:3.12-slim` (it's `libldap2` in bookworm, and
|
||||
wkhtmltopdf was removed upstream). Referencing them causes
|
||||
`apt-get install` to fail and the container dies with `exit 100`.
|
||||
* CRITICAL FIX #2: `mkdir -p /etc/odoo /var/lib/odoo /var/log/odoo`
|
||||
BEFORE `cat > /etc/odoo/odoo.conf <<EOF` — otherwise the bash
|
||||
redirect fails with `line 37: /etc/odoo/odoo.conf: No such file
|
||||
or directory`, then odoo is exec'd with -c pointing to nothing
|
||||
→ `odoo: error: the config file '/etc/odoo/odoo.conf' doesn't
|
||||
exist or is not readable` → exit 2 → service flapping.
|
||||
* CRITICAL FIX #3: DO NOT `set -e`. Pip's `--quiet --break-system-
|
||||
packages` + `--no-cache-dir` can exit non-zero on transient
|
||||
warnings (e.g. "Running as root") that should NOT kill the
|
||||
container. We instead rely on explicit early-exit points.
|
||||
"""
|
||||
return textwrap.dedent(r"""
|
||||
#!/bin/bash
|
||||
exec > /tmp/init.log 2>&1
|
||||
set -x
|
||||
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
apt-get update -qq
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
build-essential pkg-config \
|
||||
libpq-dev libpq5 \
|
||||
libxml2-dev libxslt1-dev \
|
||||
libldap2-dev libsasl2-dev \
|
||||
libjpeg-dev zlib1g-dev \
|
||||
libcurl4-openssl-dev libssl-dev libffi-dev \
|
||||
libxml2 libxslt1.1 libldap2 libsasl2-2 \
|
||||
libjpeg62-turbo zlib1g libcurl4 libssl3 libffi8 \
|
||||
fonts-noto-cjk node-less \
|
||||
>>/tmp/init.log 2>&1 || echo "[init] WARN: apt-get install had errors (continuing)"
|
||||
fi
|
||||
|
||||
ODOO_INSTALLED=0
|
||||
python3 -c "import odoo" 2>/dev/null && ODOO_INSTALLED=1
|
||||
|
||||
if [ "$ODOO_INSTALLED" = "0" ]; then
|
||||
echo "[init] Installing Odoo from /mnt/os-dev (first boot — this takes ~5 min)..."
|
||||
pip install --quiet --no-cache-dir --break-system-packages --upgrade pip wheel
|
||||
pip install --quiet --no-cache-dir --break-system-packages psycopg2-binary
|
||||
pip install --quiet --no-cache-dir --break-system-packages /mnt/os-dev
|
||||
if [ -s /mnt/os-dev/requirements.txt ]; then
|
||||
pip install --quiet --no-cache-dir --break-system-packages \
|
||||
-r /mnt/os-dev/requirements.txt
|
||||
fi
|
||||
echo "[init] Odoo install complete."
|
||||
else
|
||||
echo "[init] Odoo already installed; skipping pip install."
|
||||
fi
|
||||
|
||||
mkdir -p /etc/odoo /var/lib/odoo /var/log/odoo
|
||||
cat > /etc/odoo/odoo.conf <<EOF
|
||||
[options]
|
||||
admin_passwd = ${ADMIN_PASSWD:-admin}
|
||||
db_host = ${HOST}
|
||||
db_port = ${PORT:-5432}
|
||||
db_user = ${USER:-odoo}
|
||||
db_password = ${PASSWORD}
|
||||
dbfilter = .*
|
||||
list_db = True
|
||||
addons_path = /mnt/os-dev/addons,/mnt/os-dev/odoo/addons
|
||||
data_dir = /var/lib/odoo
|
||||
dev_mode = reload,qweb,assets,xml,werkzeug
|
||||
xmlrpc_port = 8069
|
||||
workers = 0
|
||||
log_level = info
|
||||
EOF
|
||||
|
||||
echo "[init] Waiting for postgres at ${HOST}:${PORT}..."
|
||||
for i in $(seq 1 30); do
|
||||
python3 -c "
|
||||
import socket, sys
|
||||
s = socket.socket()
|
||||
s.settimeout(2)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as r:
|
||||
return r.status, r.read().decode()
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, e.read().decode()
|
||||
s.connect(('${HOST}', int('${PORT:-5432}')))
|
||||
sys.exit(0)
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
" && break || sleep 2
|
||||
done
|
||||
|
||||
# ---------- build the inline Dockerfile (no COPY, all heredocs) ----------
|
||||
|
||||
def build_inline_dockerfile():
|
||||
df = (OSDEV / "Dockerfile").read_text()
|
||||
entrypoint = (OSDEV / "entrypoint.sh").read_text()
|
||||
wait_psql = (OSDEV / "wait-for-psql.py").read_text()
|
||||
odoo_conf = (OSDEV / "odoo.conf").read_text()
|
||||
require = (OSDEV / "requirements.txt").read_text()
|
||||
|
||||
replacements = [
|
||||
("COPY ./entrypoint.sh /",
|
||||
"RUN cat > /entrypoint.sh <<'__ENTRYPOINT_EOF__'\n"
|
||||
+ entrypoint + "\n__ENTRYPOINT_EOF__\nRUN chmod +x /entrypoint.sh"),
|
||||
("COPY ./odoo.conf /etc/odoo/",
|
||||
"RUN cat > /etc/odoo/odoo.conf <<'__ODOOCONF_EOF__'\n"
|
||||
+ odoo_conf + "\n__ODOOCONF_EOF__"),
|
||||
("COPY wait-for-psql.py /usr/local/bin/wait-for-psql.py",
|
||||
"RUN cat > /usr/local/bin/wait-for-psql.py <<'__WAITFORPSQL_EOF__'\n"
|
||||
+ wait_psql + "\n__WAITFORPSQL_EOF__\nRUN chmod +x /usr/local/bin/wait-for-psql.py"),
|
||||
("COPY requirements.txt /tmp/requirements.txt\n"
|
||||
"RUN pip3 install --no-cache-dir --break-system-packages -r /tmp/requirements.txt \\\n"
|
||||
" && rm /tmp/requirements.txt",
|
||||
"RUN cat > /tmp/requirements.txt <<'__REQUIREMENTS_EOF__'\n"
|
||||
+ require + "\n__REQUIREMENTS_EOF__\n"
|
||||
"RUN pip3 install --no-cache-dir --break-system-packages -r /tmp/requirements.txt \\\n"
|
||||
" && rm /tmp/requirements.txt"),
|
||||
]
|
||||
out = df
|
||||
for src, dst in replacements:
|
||||
out = out.replace(src, dst)
|
||||
return out
|
||||
echo "[init] Starting Odoo with --dev=reload (live edits auto-apply)..."
|
||||
exec odoo -c /etc/odoo/odoo.conf \
|
||||
--dev=reload,qweb,assets,xml,werkzeug \
|
||||
--db-filter='.*' \
|
||||
--limit-time-real=120 \
|
||||
--log-level=info
|
||||
""").strip() + "\n"
|
||||
|
||||
|
||||
# ---------- commands ----------
|
||||
def build_inline_dockerfile() -> str:
|
||||
"""Return the inline Dockerfile that Easypanel will build.
|
||||
|
||||
def cmd_deploy():
|
||||
schema = json.loads(SCHEMA.read_text())
|
||||
Why an inline Dockerfile at all, rather than `source.type=image`
|
||||
with `deploy.command`? Because `python:3.12-slim` ships with
|
||||
`CMD=["python"]` and Easypanel then REWRITES `deploy.command` as
|
||||
args to that python entrypoint, resulting in the container running
|
||||
`python -c "bash /usr/local/bin/init-odoo.sh"` which is a Python
|
||||
syntax error → exit 100.
|
||||
|
||||
By building a trivial 2-layer Dockerfile we set ENTRYPOINT to our
|
||||
init script explicitly, bypassing Easypanel's wrapping entirely.
|
||||
"""
|
||||
init = build_init_script()
|
||||
# Use single-quote heredoc delimiter so NO shell expansion happens
|
||||
# during Dockerfile RUN — env vars like $HOST are expanded at
|
||||
# container runtime by bash, not at build time.
|
||||
return (
|
||||
f"FROM {BASE_IMAGE}\n"
|
||||
f"RUN cat > /usr/local/bin/init-odoo.sh <<'__INIT_EOF__'\n"
|
||||
f"{init}"
|
||||
f"__INIT_EOF__\n"
|
||||
f"RUN chmod +x /usr/local/bin/init-odoo.sh\n"
|
||||
f'ENTRYPOINT ["/usr/local/bin/init-odoo.sh"]\n'
|
||||
f"CMD []\n"
|
||||
)
|
||||
|
||||
|
||||
def build_schema(password: str) -> dict:
|
||||
dockerfile = build_inline_dockerfile()
|
||||
env_block = (
|
||||
f"HOST=$(PROJECT_NAME)_{DB_SERVICE}\n"
|
||||
f"PORT=5432\n"
|
||||
f"USER=odoo\n"
|
||||
f"PASSWORD={password}\n"
|
||||
f"ADMIN_PASSWD=admin\n"
|
||||
f"PYTHONUNBUFFERED=1\n"
|
||||
f"PIP_DISABLE_PIP_VERSION_CHECK=1\n"
|
||||
)
|
||||
return {
|
||||
"name": f"{APP_SERVICE} (stateless hot-reload workspace)",
|
||||
"projectName": EASYPANEL_PROJECT,
|
||||
"schema": {
|
||||
"services": [
|
||||
{"type": "postgres", "data": {
|
||||
"serviceName": DB_SERVICE, "image": PG_IMAGE,
|
||||
"databaseName": "postgres", "user": "odoo",
|
||||
"password": password, "exposedPort": 0,
|
||||
"pgWeb": {"enabled": True},
|
||||
"dbGate": {"enabled": True},
|
||||
"resources": PG_RESOURCES}},
|
||||
{"type": "app", "data": {
|
||||
"serviceName": APP_SERVICE,
|
||||
"source": {"type": "dockerfile", "dockerfile": dockerfile},
|
||||
"env": env_block,
|
||||
"ports": [{"protocol": "tcp", "published": HOST_PORT, "target": 8069}],
|
||||
"domains": [{"host": "$(EASYPANEL_DOMAIN)", "port": 8069,
|
||||
"https": True, "path": "/"}],
|
||||
"mounts": [
|
||||
{"type": "bind", "hostPath": OS_DEV_HOST_PATH,
|
||||
"mountPath": "/mnt/os-dev"},
|
||||
{"type": "volume", "name": FILESTORE_VOLUME,
|
||||
"mountPath": "/var/lib/odoo"}],
|
||||
"resources": APP_RESOURCES}},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# ---------- commands (via MCP) ---------------------------------------------
|
||||
|
||||
def mcp_call(kind: str, procedure: str, payload: dict) -> dict:
|
||||
"""Invoke an Easypanel MCP procedure via `run_mcp`.
|
||||
|
||||
We shell out to `trae mcp run` equivalent — but because we are
|
||||
already executing inside an agent context with the MCP available,
|
||||
callers that are the agent use the MCP tool directly. This
|
||||
function is a helper for unit testing and returns a JSON payload
|
||||
representation of a schema/result.
|
||||
"""
|
||||
return {"kind": kind, "procedure": procedure, "payload": payload}
|
||||
|
||||
|
||||
def save_schema_and_password(schema: dict, password: str) -> None:
|
||||
(ROOT / "easypanel.schema.json").write_text(
|
||||
json.dumps(schema, indent=2))
|
||||
(ROOT / ".db_password").write_text(password + "\n")
|
||||
(ROOT / "inline.Dockerfile").write_text(schema["schema"]["services"][1]
|
||||
["data"]["source"]["dockerfile"])
|
||||
(ROOT / "init-odoo.sh").write_text(build_init_script())
|
||||
print(f"[deploy] wrote easypanel.schema.json, inline.Dockerfile, "
|
||||
f"init-odoo.sh, .db_password")
|
||||
|
||||
|
||||
def cmd_prepare() -> tuple[dict, str]:
|
||||
"""Build schema/artifacts — used by `deploy` and also dry-run."""
|
||||
pwd = gen_password()
|
||||
schema_str = json.dumps(schema).replace("CHANGE_ME_DB_PASSWORD", pwd)
|
||||
schema = json.loads(schema_str)
|
||||
# Inline Dockerfile from current os-dev workspace
|
||||
inline = build_inline_dockerfile()
|
||||
env = schema["schema"]["services"][1]["data"]["env"].replace(
|
||||
"CHANGE_ME_DB_PASSWORD", pwd)
|
||||
|
||||
print(f"--- 1. services.app.createService ({SERVICE}) ---")
|
||||
s, b = call("services.app.createService",
|
||||
{"json": {"projectName": PROJECT, "serviceName": SERVICE}})
|
||||
print(f"status={s} body={b[:200]}")
|
||||
|
||||
print(f"\n--- 2. services.app.updateSourceDockerfile ({len(inline)} bytes) ---")
|
||||
s, b = call("services.app.updateSourceDockerfile",
|
||||
{"json": {"projectName": PROJECT, "serviceName": SERVICE,
|
||||
"dockerfile": inline}})
|
||||
print(f"status={s} body={b[:200]}")
|
||||
|
||||
print(f"\n--- 3. services.app.updateEnv ---")
|
||||
s, b = call("services.app.updateEnv",
|
||||
{"json": {"projectName": PROJECT, "serviceName": SERVICE, "env": env}})
|
||||
print(f"status={s} body={b[:200]}")
|
||||
|
||||
print(f"\n--- 4. services.app.deployService (build + run) ---")
|
||||
s, b = call("services.app.deployService",
|
||||
{"json": {"projectName": PROJECT, "serviceName": SERVICE}})
|
||||
print(f"status={s} body={b[:200]}")
|
||||
|
||||
(ROOT / ".db_password").write_text(pwd + "\n")
|
||||
print(f"\nDB password saved to {ROOT / '.db_password'}")
|
||||
print("Verify: curl -sS -o /dev/null -w 'HTTP %{http_code}\\n' "
|
||||
"http://127.0.0.1:18069/web/database/manager")
|
||||
schema = build_schema(pwd)
|
||||
save_schema_and_password(schema, pwd)
|
||||
return schema, pwd
|
||||
|
||||
|
||||
def cmd_destroy():
|
||||
for svc in (SERVICE, f"{SERVICE}-db"):
|
||||
for proc in ("destroyAppService", "destroyPostgresService"):
|
||||
s, b = call(proc, {"projectName": PROJECT, "serviceName": svc}, timeout=30)
|
||||
print(f"[{proc}({svc})] status={s} body={b[:120]}")
|
||||
|
||||
|
||||
def cmd_status():
|
||||
s, b = call("services.app.inspectService",
|
||||
{"json": {"projectName": PROJECT, "serviceName": SERVICE}}, timeout=30)
|
||||
print(b[:1500])
|
||||
|
||||
|
||||
def cmd_redeploy():
|
||||
print(f"--- services.app.deployService ({SERVICE}) ---")
|
||||
s, b = call("services.app.deployService",
|
||||
{"json": {"projectName": PROJECT, "serviceName": SERVICE}})
|
||||
print(f"status={s} body={b[:200]}")
|
||||
def main() -> int:
|
||||
cmds = {"prepare", "dockerfile", "help"}
|
||||
if len(sys.argv) < 2 or sys.argv[1] == "help":
|
||||
print(__doc__)
|
||||
print("Subcommands: prepare | dockerfile | help")
|
||||
print(" prepare -> write easypanel.schema.json + inline.Dockerfile + init-odoo.sh + .db_password")
|
||||
print(" (then use TRAE Easypanel MCP: createFromSchema + deployAppService)")
|
||||
print(" dockerfile -> just print the generated inline Dockerfile to stdout")
|
||||
return 0
|
||||
cmd = sys.argv[1]
|
||||
if cmd == "dockerfile":
|
||||
print(build_inline_dockerfile())
|
||||
return 0
|
||||
if cmd == "prepare":
|
||||
schema, pwd = cmd_prepare()
|
||||
print(f"Password: {pwd}")
|
||||
print(f"Schema project: {schema['projectName']} app={APP_SERVICE} db={DB_SERVICE}")
|
||||
return 0
|
||||
print(__doc__)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2 or sys.argv[1] not in ("deploy", "destroy", "status", "redeploy"):
|
||||
print(__doc__); sys.exit(1)
|
||||
{"deploy": cmd_deploy, "destroy": cmd_destroy,
|
||||
"status": cmd_status, "redeploy": cmd_redeploy}[sys.argv[1]]()
|
||||
sys.exit(main())
|
||||
@@ -1,83 +0,0 @@
|
||||
{
|
||||
"_comment": "Stateless Odoo 19 hot-reload dev stack \u2014 onebspace project. Uses source.type=dockerfile (inline Dockerfile build) so Easypanel builds the image in-place on every deploy. The Dockerfile.inline file is auto-generated from os-dev/Dockerfile + COPY files. Deploy: python3 deploy.py deploy",
|
||||
"name": "Odoo 19 Dev (live hot-reload)",
|
||||
"projectName": "onebspace",
|
||||
"schema": {
|
||||
"services": [
|
||||
{
|
||||
"type": "postgres",
|
||||
"data": {
|
||||
"serviceName": "odoo-dev-db",
|
||||
"image": "postgres:17",
|
||||
"databaseName": "postgres",
|
||||
"user": "odoo",
|
||||
"password": "CHANGE_ME_DB_PASSWORD",
|
||||
"exposedPort": 0,
|
||||
"pgWeb": {
|
||||
"enabled": true
|
||||
},
|
||||
"resources": {
|
||||
"memoryReservation": 256,
|
||||
"memoryLimit": 1024,
|
||||
"cpuReservation": 0.25,
|
||||
"cpuLimit": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "app",
|
||||
"data": {
|
||||
"serviceName": "odoo-dev",
|
||||
"source": {
|
||||
"type": "dockerfile",
|
||||
"dockerfile": "<see deploy.py \u2014 generated from os-dev/* files>"
|
||||
},
|
||||
"env": "HOST=$(PROJECT_NAME)_odoo-dev-db\nPORT=5432\nUSER=odoo\nPASSWORD=CHANGE_ME_DB_PASSWORD\nADMIN_PASSWD=admin",
|
||||
"ports": [
|
||||
{
|
||||
"protocol": "tcp",
|
||||
"published": 18069,
|
||||
"target": 8069
|
||||
}
|
||||
],
|
||||
"domains": [
|
||||
{
|
||||
"host": "$(EASYPANEL_DOMAIN)",
|
||||
"port": 8069,
|
||||
"https": true,
|
||||
"path": "/"
|
||||
}
|
||||
],
|
||||
"mounts": [
|
||||
{
|
||||
"type": "bind",
|
||||
"hostPath": "/root/Documents/trae_projects/one_b_os/os-dev",
|
||||
"mountPath": "/mnt/os-dev"
|
||||
},
|
||||
{
|
||||
"type": "volume",
|
||||
"name": "odoo-filestore",
|
||||
"mountPath": "/var/lib/odoo"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"content": "[options]\naddons_path = /mnt/os-dev/addons,/mnt/os-dev/odoo/addons,/usr/lib/python3/dist-packages/odoo/addons\ndata_dir = /var/lib/odoo\ndev_mode = reload,qweb,assets,xml,werkzeug\nxmlrpc_port = 8069\nworkers = 0\nadmin_passwd = admin\nlog_level = info\ndbfilter = .*\nlist_db = True\n",
|
||||
"mountPath": "/etc/odoo/odoo.conf"
|
||||
}
|
||||
],
|
||||
"deploy": {
|
||||
"command": "odoo --addons-path=/mnt/os-dev/addons,/mnt/os-dev/odoo/addons --dev=reload,qweb,assets,xml,werkzeug --db-filter=.* --limit-time-real=120 --log-level=info",
|
||||
"replicas": 1,
|
||||
"zeroDowntime": false
|
||||
},
|
||||
"resources": {
|
||||
"memoryReservation": 512,
|
||||
"memoryLimit": 2048,
|
||||
"cpuReservation": 0.5,
|
||||
"cpuLimit": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user