Stateless Odoo 20 codespace blueprint for one-os-dev (port 18070, bind /mnt/one-os-dev)
This commit is contained in:
commit
6e28aba906
6 files changed
+588
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
.db_password
|
||||||
|
__pycache__/
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# stateless-container-for-one-os-dev
|
||||||
|
|
||||||
|
Stateless **hot-reload Odoo 20 codespace** for [`1b-agent/one-os-dev`](../one-os-dev),
|
||||||
|
deployed on the Easypanel project `onebspace`. Adapted from the proven
|
||||||
|
[`stateless-container-for-onebspace-development`](../stateless-container-for-onebspace-development)
|
||||||
|
blueprint (battle-tested Sep 2026 on Odoo 19) — same architecture, retargeted:
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
|---|---|
|
||||||
|
| App service | `one-os-dev-codespace` |
|
||||||
|
| DB service | `one-os-dev-codespace-db` (postgres:17 + pgWeb + dbGate) |
|
||||||
|
| Host port | **18070** → container 8069 (18069 stays with the Odoo 19 os-dev codespace) |
|
||||||
|
| Bind mount | `/root/Documents/trae_projects/one_b_os/one-os-dev` → `/mnt/one-os-dev` (rw) |
|
||||||
|
| Base image | `python:3.12-slim` (inline Dockerfile, heredoc ENTRYPOINT) |
|
||||||
|
| Hot reload | `--dev=reload,qweb,assets,xml,werkzeug` |
|
||||||
|
| Filestore | named volume `one-os-dev-codespace-filestore` → `/var/lib/odoo` |
|
||||||
|
|
||||||
|
## Deploy (one command + two MCP calls)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 deploy.py prepare # regenerates easypanel.schema.json, inline.Dockerfile, init-odoo.sh, .db_password
|
||||||
|
```
|
||||||
|
|
||||||
|
Then (TRAE / agent with the Easypanel MCP):
|
||||||
|
|
||||||
|
1. `createFromSchema` with the JSON from `easypanel.schema.json`
|
||||||
|
2. `deployAppService` `{projectName: "onebspace", serviceName: "one-os-dev-codespace"}`
|
||||||
|
3. First boot ≈ 5 min (pip install /mnt/one-os-dev builds lxml/pillow/python-ldap)
|
||||||
|
4. `curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:18070/web/database/manager` → 200
|
||||||
|
5. DB manager master password: `admin` (in `.db_password` is the postgres password)
|
||||||
|
|
||||||
|
## Before first boot — put the source where the bind mount expects it
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://agents-apps-and-tools-gitea.nxldoy.easypanel.host/1b-agent/one-os-dev.git \
|
||||||
|
/root/Documents/trae_projects/one_b_os/one-os-dev -b 20.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Live workflow: edit any `.py`/`.xml`/`.js` under that tree — changes go live on next request,
|
||||||
|
no restart, no image rebuild (see the upstream blueprint README §5 for details/troubleshooting).
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Deploy the Odoo 20 **one-one-os-dev codespace** (stateless hot-reload workspace)
|
||||||
|
to Easypanel on a given project.
|
||||||
|
|
||||||
|
This is the PROVEN-WORKING pipeline (tested Sep 14, 2026 on the `onebspace`
|
||||||
|
project, service `os-dev-codespace`, container stable 34h+):
|
||||||
|
|
||||||
|
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/one-os-dev` + `pip install -r requirements.txt`
|
||||||
|
from the BIND-MOUNTED source tree (so live edits in /mnt/one-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`).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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/one-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/one-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).
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json, pathlib, secrets, string, sys, subprocess, textwrap
|
||||||
|
|
||||||
|
ROOT = pathlib.Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
# ---------- CONFIGURATION (edit these per project) --------------------------
|
||||||
|
|
||||||
|
EASYPANEL_PROJECT = "onebspace" # project name in Easypanel
|
||||||
|
APP_SERVICE = "one-os-dev-codespace" # app service name
|
||||||
|
DB_SERVICE = f"{APP_SERVICE}-db" # companion postgres service
|
||||||
|
HOST_PORT = 18070 # host port → 8069 inside container
|
||||||
|
FILESTORE_VOLUME = f"{APP_SERVICE}-filestore"
|
||||||
|
OS_DEV_HOST_PATH = "/root/Documents/trae_projects/one_b_os/one-os-dev"
|
||||||
|
BASE_IMAGE = "python:3.12-slim" # Debian 12 bookworm, slim, ~45MB
|
||||||
|
PG_IMAGE = "postgres:17"
|
||||||
|
|
||||||
|
# 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}
|
||||||
|
|
||||||
|
# ---------- helpers ----------------------------------------------------------
|
||||||
|
|
||||||
|
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/one-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/one-os-dev
|
||||||
|
if [ -s /mnt/one-os-dev/requirements.txt ]; then
|
||||||
|
pip install --quiet --no-cache-dir --break-system-packages \
|
||||||
|
-r /mnt/one-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/one-os-dev/addons,/mnt/one-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:
|
||||||
|
s.connect(('${HOST}', int('${PORT:-5432}')))
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
" && break || sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
|
||||||
|
def build_inline_dockerfile() -> str:
|
||||||
|
"""Return the inline Dockerfile that Easypanel will build.
|
||||||
|
|
||||||
|
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/one-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 = build_schema(pwd)
|
||||||
|
save_schema_and_password(schema, pwd)
|
||||||
|
return schema, pwd
|
||||||
|
|
||||||
|
|
||||||
|
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__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"name": "one-os-dev-codespace (stateless hot-reload workspace)",
|
||||||
|
"projectName": "onebspace",
|
||||||
|
"schema": {
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"type": "postgres",
|
||||||
|
"data": {
|
||||||
|
"serviceName": "one-os-dev-codespace-db",
|
||||||
|
"image": "postgres:17",
|
||||||
|
"databaseName": "postgres",
|
||||||
|
"user": "odoo",
|
||||||
|
"password": "3eqZETeQmn9otDLnLfILYlpR",
|
||||||
|
"exposedPort": 0,
|
||||||
|
"pgWeb": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"dbGate": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"memoryReservation": 256,
|
||||||
|
"memoryLimit": 1024,
|
||||||
|
"cpuReservation": 0.25,
|
||||||
|
"cpuLimit": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "app",
|
||||||
|
"data": {
|
||||||
|
"serviceName": "one-os-dev-codespace",
|
||||||
|
"source": {
|
||||||
|
"type": "dockerfile",
|
||||||
|
"dockerfile": "FROM python:3.12-slim\nRUN cat > /usr/local/bin/init-odoo.sh <<'__INIT_EOF__'\n#!/bin/bash\nexec > /tmp/init.log 2>&1\nset -x\n\nif command -v apt-get >/dev/null 2>&1; then\n apt-get update -qq\n DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \\\n build-essential pkg-config \\\n libpq-dev libpq5 \\\n libxml2-dev libxslt1-dev \\\n libldap2-dev libsasl2-dev \\\n libjpeg-dev zlib1g-dev \\\n libcurl4-openssl-dev libssl-dev libffi-dev \\\n libxml2 libxslt1.1 libldap2 libsasl2-2 \\\n libjpeg62-turbo zlib1g libcurl4 libssl3 libffi8 \\\n fonts-noto-cjk node-less \\\n >>/tmp/init.log 2>&1 || echo \"[init] WARN: apt-get install had errors (continuing)\"\nfi\n\nODOO_INSTALLED=0\npython3 -c \"import odoo\" 2>/dev/null && ODOO_INSTALLED=1\n\nif [ \"$ODOO_INSTALLED\" = \"0\" ]; then\n echo \"[init] Installing Odoo from /mnt/one-os-dev (first boot \u2014 this takes ~5 min)...\"\n pip install --quiet --no-cache-dir --break-system-packages --upgrade pip wheel\n pip install --quiet --no-cache-dir --break-system-packages psycopg2-binary\n pip install --quiet --no-cache-dir --break-system-packages /mnt/one-os-dev\n if [ -s /mnt/one-os-dev/requirements.txt ]; then\n pip install --quiet --no-cache-dir --break-system-packages \\\n -r /mnt/one-os-dev/requirements.txt\n fi\n echo \"[init] Odoo install complete.\"\nelse\n echo \"[init] Odoo already installed; skipping pip install.\"\nfi\n\nmkdir -p /etc/odoo /var/lib/odoo /var/log/odoo\ncat > /etc/odoo/odoo.conf <<EOF\n[options]\nadmin_passwd = ${ADMIN_PASSWD:-admin}\ndb_host = ${HOST}\ndb_port = ${PORT:-5432}\ndb_user = ${USER:-odoo}\ndb_password = ${PASSWORD}\ndbfilter = .*\nlist_db = True\naddons_path = /mnt/one-os-dev/addons,/mnt/one-os-dev/odoo/addons\ndata_dir = /var/lib/odoo\ndev_mode = reload,qweb,assets,xml,werkzeug\nxmlrpc_port = 8069\nworkers = 0\nlog_level = info\nEOF\n\necho \"[init] Waiting for postgres at ${HOST}:${PORT}...\"\nfor i in $(seq 1 30); do\n python3 -c \"\nimport socket, sys\ns = socket.socket()\ns.settimeout(2)\ntry:\n s.connect(('${HOST}', int('${PORT:-5432}')))\n sys.exit(0)\nexcept Exception:\n sys.exit(1)\n\" && break || sleep 2\ndone\n\necho \"[init] Starting Odoo with --dev=reload (live edits auto-apply)...\"\nexec odoo -c /etc/odoo/odoo.conf \\\n --dev=reload,qweb,assets,xml,werkzeug \\\n --db-filter='.*' \\\n --limit-time-real=120 \\\n --log-level=info\n__INIT_EOF__\nRUN chmod +x /usr/local/bin/init-odoo.sh\nENTRYPOINT [\"/usr/local/bin/init-odoo.sh\"]\nCMD []\n"
|
||||||
|
},
|
||||||
|
"env": "HOST=$(PROJECT_NAME)_one-os-dev-codespace-db\nPORT=5432\nUSER=odoo\nPASSWORD=3eqZETeQmn9otDLnLfILYlpR\nADMIN_PASSWD=admin\nPYTHONUNBUFFERED=1\nPIP_DISABLE_PIP_VERSION_CHECK=1\n",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"protocol": "tcp",
|
||||||
|
"published": 18070,
|
||||||
|
"target": 8069
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
{
|
||||||
|
"host": "$(EASYPANEL_DOMAIN)",
|
||||||
|
"port": 8069,
|
||||||
|
"https": true,
|
||||||
|
"path": "/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mounts": [
|
||||||
|
{
|
||||||
|
"type": "bind",
|
||||||
|
"hostPath": "/root/Documents/trae_projects/one_b_os/one-os-dev",
|
||||||
|
"mountPath": "/mnt/one-os-dev"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "volume",
|
||||||
|
"name": "one-os-dev-codespace-filestore",
|
||||||
|
"mountPath": "/var/lib/odoo"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resources": {
|
||||||
|
"memoryReservation": 512,
|
||||||
|
"memoryLimit": 2048,
|
||||||
|
"cpuReservation": 0.5,
|
||||||
|
"cpuLimit": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#!/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/one-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/one-os-dev
|
||||||
|
if [ -s /mnt/one-os-dev/requirements.txt ]; then
|
||||||
|
pip install --quiet --no-cache-dir --break-system-packages \
|
||||||
|
-r /mnt/one-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/one-os-dev/addons,/mnt/one-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:
|
||||||
|
s.connect(('${HOST}', int('${PORT:-5432}')))
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
" && break || sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
RUN cat > /usr/local/bin/init-odoo.sh <<'__INIT_EOF__'
|
||||||
|
#!/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/one-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/one-os-dev
|
||||||
|
if [ -s /mnt/one-os-dev/requirements.txt ]; then
|
||||||
|
pip install --quiet --no-cache-dir --break-system-packages \
|
||||||
|
-r /mnt/one-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/one-os-dev/addons,/mnt/one-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:
|
||||||
|
s.connect(('${HOST}', int('${PORT:-5432}')))
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
" && break || sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
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
|
||||||
|
__INIT_EOF__
|
||||||
|
RUN chmod +x /usr/local/bin/init-odoo.sh
|
||||||
|
ENTRYPOINT ["/usr/local/bin/init-odoo.sh"]
|
||||||
|
CMD []
|
||||||
Reference in new issue
Block a user