From 1cc0063e03ef5997489573df72e017356f3eaf04 Mon Sep 17 00:00:00 2001 From: reachableceo Date: Wed, 2 Sep 2026 19:58:59 -0500 Subject: [PATCH] feat: add eLabFTW Cloudron package (Business-Apps) [#669] Official-image wrapper of elabftw/elabimg 5.6.12 (tag+digest pinned); localstorage + mysql + ldap addons; httpPort 443 via DISABLE_HTTPS. No OIDC upstream -> native LDAP flagged auth-risk. First mysql-addon and first Business-Apps package; 15/~57. Build verified green under ukrrs-batch.slice. Docs synced (JOURNAL s15, README, STATUS). Detail + verification: https://projects.knownelement.com/issues/669#note-3938 --- JOURNAL.md | 78 ++++++++++++++- .../Business-Apps/elabftw/.dockerignore | 1 + .../Business-Apps/elabftw/.env.example | 19 ++++ .../Business-Apps/elabftw/CHANGELOG.md | 21 ++++ .../elabftw/CloudronManifest.json | 23 +++++ .../Business-Apps/elabftw/Dockerfile | 40 ++++++++ .../Business-Apps/elabftw/README.md | 90 ++++++++++++++++++ .../Business-Apps/elabftw/logo.png | Bin 0 -> 6744 bytes .../Business-Apps/elabftw/start.sh | 80 ++++++++++++++++ README.md | 5 +- STATUS.md | 20 ++-- 11 files changed, 366 insertions(+), 11 deletions(-) create mode 100644 Package-Workspace/Business-Apps/elabftw/.dockerignore create mode 100644 Package-Workspace/Business-Apps/elabftw/.env.example create mode 100644 Package-Workspace/Business-Apps/elabftw/CHANGELOG.md create mode 100644 Package-Workspace/Business-Apps/elabftw/CloudronManifest.json create mode 100644 Package-Workspace/Business-Apps/elabftw/Dockerfile create mode 100644 Package-Workspace/Business-Apps/elabftw/README.md create mode 100644 Package-Workspace/Business-Apps/elabftw/logo.png create mode 100755 Package-Workspace/Business-Apps/elabftw/start.sh diff --git a/JOURNAL.md b/JOURNAL.md index 2d60deb..50617df 100644 --- a/JOURNAL.md +++ b/JOURNAL.md @@ -4,7 +4,7 @@ **Project**: TSYSDevStack-SupportStack-Cloudron **Goal**: Package ~57 applications for Cloudron PaaS platform **Start Date**: 2025-01-24 -**Current Status**: 14/~57 packages completed (~25%) +**Current Status**: 15/~57 packages completed (~26%) ## Completed Packages @@ -1135,6 +1135,82 @@ client_secret, redirect_url, scopes; PKCE + nonce state stored in Redis). --- +### 15. eLabFTW (Business-Apps) ✅ +**Date**: 2026-09-01 +**Application**: eLabFTW — open-source electronic lab notebook (ELN) and +lab inventory manager (experiments, resources database, scheduling, +digital signatures/timestamps, PDF/export pipelines). Upstream: +https://github.com/elabftw/elabftw (AGPL-3.0), v5.6.12. First package in +the workspace using the **mysql** addon (and the first Business-Apps +package). + +**Ticket**: [#669](https://projects.knownelement.com/issues/669) + +**Pattern**: official-image wrapper. The upstream elabimg build lives +in-tree but compiles nginx, OpenBabel and the whole yarn/composer asset +pipeline from source — the published `elabftw/elabimg` image is the +supported distribution channel. Wrapper is a digest-pinned FROM + a +start.sh ENTRYPOINT; final image 906MB (~209MB compressed on the Hub). + +**Auth gate verdict**: ⚠️ LDAP acceptable-with-risk. eLabFTW 5.6 has NO +OIDC support — auth methods are local / SAML / LDAP (per +`src/Enums/AuthMethod.php`). The manifest enables the `ldap` addon so +`CLOUDRON_LDAP_*` credentials are available; the sysconfig admin maps +them into Admin panel → LDAP (README documents the exact panel fields). +Flagged auth-risk: LDAP in STATUS.md/README — must be validated on the +live Cloudron before production. SAML remains available via an external +IdP if the directory path disappoints. + +**Key decisions**: + +- **Port 443 for plain HTTP**: elabimg with `DISABLE_HTTPS=true` serves + plain HTTP on port 443 (TLS terminates at the Cloudron proxy) — the + manifest's `httpPort` is 443, which reads odd but is upstream's + contract. +- **Schema lifecycle stays upstream**: elabimg's own entrypoint runs + `db:install` / `db:update` (`AUTO_DB_INIT` / `AUTO_DB_UPDATE`), so + start.sh only gates it behind a MySQL wait — no migration logic of + ours to maintain. +- **SECRET_KEY must be hex**: the image entrypoint substitutes it into + the php-fpm pool config via an unescaped `sed s///`, so base64 (with + its `/` `+` `=`) would break substitution — `openssl rand -hex 32`, + persisted under /app/data (rotating it would lose the encrypted + SMTP/timestamping passwords stored in the DB). +- **Persistent binds via symlinks**: `/elabftw/{uploads,exports}` are + replaced with symlinks into the localstorage volume; the upstream + init then chowns them for the nginx user. +- **Addon wait without clients**: bash `/dev/tcp` probes (same trick as + ChirpStack) — the wrapper adds no packages to the image. +- **Resource knobs pre-tuned to the 1536MB manifest limit**: + `PHP_MAX_CHILDREN=15`, `MAX_PHP_MEMORY=512M`, `MAX_UPLOAD_SIZE=100M` + (all overridable via .env). + +**Verification**: + +- `docker build --cgroup-parent ukrrs-batch.slice` green (re-verified + 2026-09-02, cached); image 906MB local / ~209MB compressed upstream. +- start.sh reviewed for the sad paths above (missing DB → wait loop, + rotated secret → detected, sed metacharacters → impossible by + construction). Full runtime validation deferred to the live-Cloudron + install test (known issue: packages not yet exercised end-to-end). + +**Files Created**: + +- Dockerfile (official-image wrapper, tag+digest pinned) +- CloudronManifest.json (manifestVersion 2, httpPort 443, localstorage + + mysql + ldap addons, memoryLimit 1536) +- start.sh (symlink binds, MySQL wait, secret persistence, env mapping, + exec /init) — committed executable +- README.md (auth story + LDAP panel wiring, config layout, knobs) +- CHANGELOG.md +- .env.example (ELABFTW_TZ / AUTO_DB / PHP knobs) +- .dockerignore (excludes the cloned repo/ from the build context) +- logo.png + +**Commit**: `feat: add eLabFTW Cloudron package (Business-Apps) [#669]` + +--- + ## Packaging Pattern: Download Pre-Compiled Binaries ### When to Use diff --git a/Package-Workspace/Business-Apps/elabftw/.dockerignore b/Package-Workspace/Business-Apps/elabftw/.dockerignore new file mode 100644 index 0000000..7d37f1a --- /dev/null +++ b/Package-Workspace/Business-Apps/elabftw/.dockerignore @@ -0,0 +1 @@ +repo/ diff --git a/Package-Workspace/Business-Apps/elabftw/.env.example b/Package-Workspace/Business-Apps/elabftw/.env.example new file mode 100644 index 0000000..d4e01dd --- /dev/null +++ b/Package-Workspace/Business-Apps/elabftw/.env.example @@ -0,0 +1,19 @@ +# eLabFTW Cloudron package — optional runtime knobs. +# Set with: cloudron env set ELABFTW_TZ=America/Chicago --app +# (then restart the app). Everything has a sane default; empty = unset. + +# PHP / container timezone (IANA name) +ELABFTW_TZ=UTC + +# nginx + PHP upload limit (also raises client_max_body_size) +ELABFTW_MAX_UPLOAD_SIZE=100M + +# php-fpm worker count (defaults tuned for the 1536 MB memory limit) +ELABFTW_PHP_MAX_CHILDREN=15 + +# per-worker PHP memory limit +ELABFTW_MAX_PHP_MEMORY=512M + +# run `bin/init db:install` / `bin/console db:update` at boot +ELABFTW_AUTO_DB_INIT=true +ELABFTW_AUTO_DB_UPDATE=true diff --git a/Package-Workspace/Business-Apps/elabftw/CHANGELOG.md b/Package-Workspace/Business-Apps/elabftw/CHANGELOG.md new file mode 100644 index 0000000..d4e4c0b --- /dev/null +++ b/Package-Workspace/Business-Apps/elabftw/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + +## 1.0.0 — 2026-09-01 + +Initial Cloudron package (eLabFTW 5.6.12, ticket +[#669](https://projects.knownelement.com/issues/669)). + +- Official-image wrapper around `elabftw/elabimg:5.6.12` (digest + pinned); no additions to the runtime image. +- `start.sh`: MySQL addon wait loop, `CLOUDRON_MYSQL_*` → `DB_*` + mapping, `SITE_URL` from `CLOUDRON_APP_ORIGIN`, plain-HTTP mode + (`DISABLE_HTTPS=true`, internal port 443), persistent `SECRET_KEY` + under `/app/data`, uploads/exports bound into the localstorage volume. +- Schema lifecycle via upstream `AUTO_DB_INIT` / `AUTO_DB_UPDATE` + (runs after the DB wait in `start.sh`). +- Auth: no OIDC upstream — `ldap` addon enabled and documented mapping + table for the sysconfig LDAP panel (**auth-risk: LDAP**); SAML + available for an external IdP. +- Env knobs: `ELABFTW_TZ`, `ELABFTW_MAX_UPLOAD_SIZE`, + `ELABFTW_PHP_MAX_CHILDREN`, `ELABFTW_MAX_PHP_MEMORY`, + `ELABFTW_AUTO_DB_INIT`, `ELABFTW_AUTO_DB_UPDATE`. diff --git a/Package-Workspace/Business-Apps/elabftw/CloudronManifest.json b/Package-Workspace/Business-Apps/elabftw/CloudronManifest.json new file mode 100644 index 0000000..080c531 --- /dev/null +++ b/Package-Workspace/Business-Apps/elabftw/CloudronManifest.json @@ -0,0 +1,23 @@ +{ + "manifestVersion": 2, + "type": "app", + "id": "io.cloudron.elabftw", + "title": "eLabFTW", + "description": "Open-source electronic lab notebook (ELN) and lab inventory manager for research teams: experiments, resources database, scheduling, digital signatures and timestamps, PDF/export pipelines. MySQL stores all state; uploads and exports persist in Cloudron localstorage. Auth note: eLabFTW has no OIDC support; single sign-on is provided via the Cloudron LDAP directory configured in the admin panel (auth-risk: LDAP).", + "author": "Nicolas CARPi / Deltablot", + "website": "https://www.elabftw.net/", + "contactEmail": "cloudron@tsys.dev", + "tagline": "Electronic lab notebook and inventory for research teams", + "version": "5.6.12", + "healthCheckPath": "/", + "httpPort": 443, + "memoryLimit": 1536, + "addons": { + "localstorage": true, + "mysql": {}, + "ldap": {} + }, + "mediaLinks": [], + "changelog": "Initial Cloudron package for eLabFTW 5.6.12 (official-image wrapper of elabftw, digest pinned). start.sh waits for the MySQL addon, maps CLOUDRON_MYSQL_* into elabimg DB_* variables, persists the SECRET_KEY under /app/data and binds uploads/exports into the localstorage volume; the schema is installed and updated automatically (AUTO_DB_INIT/AUTO_DB_UPDATE). Plain HTTP on the internal port via DISABLE_HTTPS (TLS terminates at the Cloudron proxy). No OIDC upstream: the ldap addon is enabled and the admin wires CLOUDRON_LDAP_* into Admin panel - LDAP (auth-risk flag); SAML also available for an external IdP.", + "icon": "file://logo.png" +} diff --git a/Package-Workspace/Business-Apps/elabftw/Dockerfile b/Package-Workspace/Business-Apps/elabftw/Dockerfile new file mode 100644 index 0000000..e5010ed --- /dev/null +++ b/Package-Workspace/Business-Apps/elabftw/Dockerfile @@ -0,0 +1,40 @@ +# eLabFTW Cloudron Package +# +# eLabFTW is an open-source electronic lab notebook (ELN) for research +# teams: experiments, inventory, scheduling, timestamps and PDF export, +# backed by MySQL. Packaged as an official-image wrapper around the +# upstream elabimg image (nginx + php-fpm + s6-overlay in one container). +# +# Upstream: https://github.com/elabftw/elabftw +# - The elabimg build lives in-tree (containers/elabimg/) but compiles +# nginx, OpenBabel and the whole yarn/composer asset pipeline from +# source; the published image is the supported distribution channel. +# - elabimg 5.6.12 with DISABLE_HTTPS=true serves plain HTTP on port +# 443 (Cloudron terminates TLS at the platform proxy). +# - DB schema: AUTO_DB_INIT / AUTO_DB_UPDATE run `bin/init db:install` +# / `bin/console db:update` from the image entrypoint; start.sh +# gates them behind a MySQL wait loop. +# +# Authentication: NO OIDC upstream (v5.6 auth methods: local, SAML, LDAP). +# Verdict per AGENTS.md: native LDAP = acceptable with auth-risk flag. +# The manifest enables the `ldap` addon so CLOUDRON_LDAP_* credentials are +# available; the sysconfig admin wires them into Admin panel -> LDAP (see +# README). SAML is also available for an external IdP. +# +# Pattern: official-image wrapper. Image pinned by tag AND digest (amd64 +# manifest digest of the 5.6.12 tag, from the Docker Hub registry API). +FROM elabftw/elabimg:5.6.12@sha256:e91fcc92f0c9a3190ac4f8db7fad03703f1662b33856a53baba9b5a06d71a51d + +# start.sh waits for the mysql addon, persists the SECRET_KEY, maps the +# Cloudron env into elabimg's DB_*/SITE_URL/DISABLE_HTTPS variables, +# binds uploads + exports under /app/data and execs the s6 init. +# Made executable on the host, not at build time (Cloudron builds hit +# permission errors on RUN chmod). bash ships with elabimg. +COPY start.sh /app/start.sh + +# elabimg HTTP mode (DISABLE_HTTPS=true) listens here; no other listener. +EXPOSE 443 + +# s6-overlay must run as root: the upstream entrypoint creates the +# unprivileged nginx/php user and drops privileges for the services. +ENTRYPOINT ["/bin/bash", "/app/start.sh"] diff --git a/Package-Workspace/Business-Apps/elabftw/README.md b/Package-Workspace/Business-Apps/elabftw/README.md new file mode 100644 index 0000000..271336a --- /dev/null +++ b/Package-Workspace/Business-Apps/elabftw/README.md @@ -0,0 +1,90 @@ +# eLabFTW — Cloudron Package + +[eLabFTW](https://www.elabftw.net/) is an open-source electronic lab +notebook (ELN) and inventory manager for research teams: experiments with +revisions, resources database, scheduler, MFA / API keys, timestamping and +PDF export pipelines. Packaged as an **official-image wrapper** around the +upstream `elabftw/elabimg:5.6.12` image (nginx + php-fpm + s6-overlay, +digest pinned). + +- **Upstream:** https://github.com/elabftw/elabftw (AGPL-3.0) +- **Ticket:** [#669](https://projects.knownelement.com/issues/669) +- **Category:** Business-Apps · **Pattern:** official-image wrapper + + `start.sh` env mapping + +## Authentication (auth gate verdict: ⚠️ LDAP — auth-risk flag) + +eLabFTW 5.x auth methods are **local, SAML and LDAP — there is no OIDC +support** (verified in `src/Enums/AuthMethod.php`; no OIDC client anywhere +upstream). Per the AGENTS.md policy this package ships with the +**LDAP path** and carries the auth-risk flag: + +1. The manifest enables the Cloudron **`ldap`** addon, so the container + holds `CLOUDRON_LDAP_*` credentials (inspect them with + `cloudron exec env | grep LDAP` in the app terminal). +2. Log in once as the sysadmin account created during first-run setup. +3. Open **Sysconfig → Server → LDAP** and map the panel to the Cloudron + directory: + +| eLabFTW LDAP panel | Cloudron value | +|--------------------|----------------| +| Toggle LDAP login | on | +| Scheme | `ldap` | +| Host | `CLOUDRON_LDAP_HOST` | +| Port | `CLOUDRON_LDAP_PORT` (3002) | +| Use TLS | off (plain LDAP on the box) | +| Base DN | `CLOUDRON_LDAP_USERS_BASE_DN` | +| Username (bind DN) | `CLOUDRON_LDAP_BIND_DN` | +| Password | `CLOUDRON_LDAP_BIND_PASSWORD` | +| Search attr | `mail` (or `username`) | +| Email attribute | `mail` | +| Firstname / Lastname | `givenname` / `sn` | + +**auth-risk: LDAP** — this must be validated on the live Cloudron before +production use (see STATUS.md). SAML remains available as an alternative +against an external IdP, but the Cloudron platform provider is OIDC-only, +so LDAP is the only platform-integrated SSO path today. + +## Addons & ports + +| Concern | Cloudron wiring | +|---------|-----------------| +| Database | `mysql` addon → `DB_*` (schema auto-installed / auto-updated) | +| Files | `localstorage`: `/app/data/uploads`, `/app/data/exports` | +| SSO directory | `ldap` addon (`CLOUDRON_LDAP_*`, see above) | +| Web | single HTTP port `443` (`DISABLE_HTTPS=true`; TLS at the proxy) | + +## Runtime layout + +- `start.sh` waits for MySQL, maps `CLOUDRON_MYSQL_*` → elabimg `DB_*`, + sets `SITE_URL` from `CLOUDRON_APP_ORIGIN`, and hands over to the + upstream s6 `/init`, which applies config and runs + `bin/init db:install` / `bin/console db:update` (`AUTO_DB_INIT` / + `AUTO_DB_UPDATE`). +- The `SECRET_KEY` (encrypts SMTP / timestamping passwords) is generated + once and persisted at `/app/data/.secret_key` — hex, because the image + entrypoint injects it via unescaped `sed`. +- First boot: open the app URL and complete the initial setup (create the + sysadmin + first team); afterwards do the LDAP wiring above. + +## Environment knobs (.env.example) + +| Variable | Default | Purpose | +|----------|---------|---------| +| `ELABFTW_TZ` | `UTC` | PHP / container timezone | +| `ELABFTW_MAX_UPLOAD_SIZE` | `100M` | nginx + PHP upload limit | +| `ELABFTW_PHP_MAX_CHILDREN` | `15` | php-fpm workers (tuned for 1536 MB) | +| `ELABFTW_MAX_PHP_MEMORY` | `512M` | per-worker PHP memory limit | +| `ELABFTW_AUTO_DB_INIT` | `true` | run `db:install` at boot | +| `ELABFTW_AUTO_DB_UPDATE` | `true` | run `db:update` at boot | + +## Build & install + +```bash +docker build --cgroup-parent ukrrs-batch.slice -t elabftw-cloudron:test \ + Package-Workspace/Business-Apps/elabftw/ +``` + +`cloudron build && cloudron install` on the Cloudron VPS for real +deployment. First start waits for MySQL, installs the schema and serves +the setup wizard on the app domain. diff --git a/Package-Workspace/Business-Apps/elabftw/logo.png b/Package-Workspace/Business-Apps/elabftw/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c4a4ca4802d93d18a0fc06d747786a68731a1f29 GIT binary patch literal 6744 zcmXY$1z1$i_wbj6rE5h>!tc`EEiAY!xd@1KxinHLAuY8aQc8z}BHi7wNVkAUBPB|A zNxhff`+xR1bMKwGGiP?@oH=tp^Ilt1g^c(CF$e@AQ&UCifIv7Ez$C1P7FsP6uQXxT`%;B3L4NKn@b>vhpSef#`p$ zAs_2{&F*Aq*|SY$^tMY_O_Pn}4DZ4Qdm1>nnn^n}w$Mp(g*gk3VmdD;^075BM$`Ve z&&Z`~-1j>;^>!@WbhmggKfb!rB?{oIh78bIB|$TZUt{0V%~Gw>IqfrV*5>6+>g}}_ zZT_3MzQzg-sV+~xMnWA>zQ_hF!^lNMn@80JTs0fT^P z5^EPEkOZVZQtpUD2WoJ|Os{m5J>Oe*0Zd=t~T(5P)%HCxJA?)PKsQ^-ikOYep z$g`@$55iRmR|%hwU?pcl5Pk_{Cz@g#VOOtf7_Zo8f!D42nD@bugs@3%6m52tn;9&^ zZBqEgn$?w;SqQF72)kdQmc9jknm*{Da4W6T$4JhOAmmOqZlJVDcMRmR$bL}+Vw6G< zB3+cSIwL3%Nmy|FjK2Z?ir$+<(uFM}kN_iWBDzL5i6)6^%5+uc2 zS;!1}_|IjSA7m0kd5JdS0bXJx8G>f>ubSH7uZZJSZ2BykLulwsM~`eWy3IniC@8yo z|1sKPD96z_ZWw!M1S5+p?QGgZ5>QEVra@$a$JCK2=nT}5_V5Lclsl#&4)Tr&hPjXA z7C@%v+Qr?ISFW+}GOQ<$7w9lk31>wLXfs(jutt1{Uh@xS^ubW-SmAtPbOTo>oGW+5 zSjYbI=rmlU0nI_b;jUT%rSdrOMqcl}ds2fHSWBsl^c~o$xjl-)G-8~+*9a@0tMJBC zBGMGT2n*5wkx|g)H#cbEcwM%`(frdoxL*!Q#3^GKzG6ii^#O`d4W&sLs(Tb@lw_>{caqbYZ6O|7^uLCFuSdQ8|qb8#M!Oew!F7MZ$N*I6aDw6 zX%K{|?U1B{6sm_P-R^~xmM=rHKT3;6txO1E*5+WEN9$c{$TdooF{gGD-dRqhcKzbl zP}<(Q?SUv2z(kB7d{*g$@kHdP=o%SM%gf%`2M?=pOoi`eynZYq=n0csHm|uN@^zWq zn zLlUDTD-*`M|JWx&Y}jL^>G$JrN<8Gg2=g4!^#?T5qJKGGV8T}fwlr8$n!}^bM&}X{ zd2>_oRwgN>s)RDf_ddATI$73W^;KxEvuQ3P`>LLhlms ziedf!Dc{ID58dp|$P@bk&5kZm{%UrZds)RJ&$|s6J-?9E!EA_8VR3dPl!>pg7p6!E ztHHWsiXls2pM2Wn4lHy92HK5g#ZVF(W_#7fcs{a~b8`EXA2Ouf;s;Xkb`)#Mtmhv^P-!>wn1n~H?^a1z|2F*Y z#yJ^k1T5HBLDF zXs^B>euj${$WRddx+?UTYE5yib>Dap0z@&@mdoeyJvs$)sJ9D%iP z(mSy!jsR7L*hBZ@&bW+IMmjrGu@DuXvbX)IO{*7oyxa!&w8{OcJnCCZc4S3YVxp72 zZ5k&Mz(<(8nG(e%UURzYIDHJSB$<{+`HZGQl#Nw*R$2DIV+H;mRoD@+E?&=-u7QjA zQ$Si1k2tRZ8Hh=mGEs3a(MSu=2%<#LO!$aGlQmelLq}Dw9AM}}yycotr@3Cgi51Gy z<)szwr!#@1@Ltur-sF_SEUl)F>SCGGo!qkMU^(k+M4qzg)v6EbOAq;w8$JwNeC0df zu0i~zp^9{J{gJHmsZR`F!anZwopQY2!9p#&j{oT#o z3hRn8Z*zM*X{80iKsO&(jbVXb6SCSN)yU}Ulvf3rj?EK0wHnpXM+@eI3h1gBex_xH z!JFHAfzRKG{ljHzChuh76I9CA)bV_m_NzMjczBZFSv(|_)s^DIL*Fs*_l!piDyxO? z0nz@U>I_(5YvKE6?C9it&R+FVm7rA?DLLuIr>2fi*wIB2gL1E(hu_Xc`tPQOY0s%r zykDbcFP_*jqOY_3Yih;WMwX{To6U-ewtxOO+b|^}IqJ2XiFCkW>s9l2(#JEIOl)?X zvxjY};!!gY>zGwb)1y=77gw4|(qljP?ZpT-jlCv(rHW&Ma!bojMVnU0t#~PU>o$q0 zT?nb5Bp@&Py+}&LdgzF061#G(&G{t-SwMTn??oOeAtgR;ce$5>s|H%07zhX$SjC|? zRJ(t>+HJ$9%_F$RiCJ^r(xhY^a^46KaJL)i`#pNbV4K*@gO#a5Gt1Zf*}tr}%+Tw> zBGXP1-5{2uIy+m#(HVd}s8nWsD3g>tB<*8D9#j~~iqvk?#7-~O+qHaN7Wv5$bu!%L zEHdaX5_9s@uv0>7mg%LVwjPrbwQG8*zO`In8~B zhmRH(>7FWF^eu%t`23Ph@V9#LHt!G~rvxlzWz4C3%Q$qCdP7NpD6+8^3_~FlqII1T z6^4l{(vI5(Ea>spB(X|w@3u=HH!D7+5H6Piaa5{&DRp z{FDfc6!}*$vBR|Hl}8g;ty%BMwGflTCPpjiJS_ZO-|MNecL`6(uTPCrwPF5m+74J6Ze%HNv-Am^O~V zEtYqDrYldEr&p7-N$({B+e#y9h-brF`Bpqc4?LP$<^MYAlu94X>nsBr2c;V}j3MnW zsp?lGRE_?2bvPBsnZGd4Svp>m`pB?4Q3kb-`tZYBQVkE~3FC=jR%;qVZf)HxOdb5X z^`p4GS#2Mk&ucDU=R`D2_JANDT9l5ZkuwRNpk{8^_gbOKkdJ5d=%0u@YL+I}r?U>A^jU7brK)Hthu(owf;E}+L@)*Sft`=_Y<1JiA3Qv+`EXOXcUWRLB? zFw=Jr29`I1Qp7WLyFL6v97}Cl!VZ~t#ZLOKHnv_0T^)-a2mFiwbnAN?Ss8mt34(jy zP%qwE%1%T}T^)b$gA6SYw{38kGzODUe`|nTM<>@l?x{X}`;KSA(R@42P#uT@tD5!6 z`sV!burR;Gb@(aRs5{q&N8mwZRIlibGlQ{_43SHrU2E&?8o}{yuU4h^1 z4wduYd*v?IBMVczOOo=pGbsA7iOTBRtEI2Wm_4oM0U^)*_s2@kr*v2iG>U>UYU3h! zsINNWJOj!C{gNhqlN=r%iazdlW}hjosQXfZ6KgWs1$JcDovS#iyVab?8sQvm{|1Qq z)y2!VH`ONeVSBZEs~7dZmOZm>=@r(LuRQjN-ZJ@RZ|NK9yX$K>504Aw{c`4?v9yr6 zlt4C`h{U-1Nffpi`yr}nWP`E=3Rci+YBY843+xSjO+?oKr4c0+exWPMj~FAGkDUv$Y(@t&5SoW4GQZfzfQ1otBLt-Yf(wX zm1Y_AWN{1aV44@**~@K}_YyreXUyv9~ED_1vVB67s zA?plBUg`?%%6ndF9_5mf!9eV?NW#flH@-rtpMEI>@sgLD=IGfnuob_W8JDSL_&)#n z^Iiptvf?X*OHx0Kx-K`xQ}5^auwT;3zZ1PZ1->`7j&+X`$K$0;M@gRP!yToEmP}-C zPal!WUpuYe-j3eN4c1sTFV54YrhJRwVX~I)+5$bb%^Y6*kNR_!bxEN!&x=*}1Y2)M zIxZQO!g~Xocp5NOwPMVW9j5j2;91M%!32Zj_T5yqUtQ}Jc_b|_%kk-W_0N|CSA`a{ zOTMVa|F3Ws{A?`7o7h;oONig4R>HqE-MNg9M3`H&#}#9*Wx_um{nyT{gB<8m9(z*I z^~UWhOaqV-$W5C+h>>!#fYERPTyF8*bQa*?$D>RQNP z0^}3F()c*tps|~s9#{h5O6?p}@O_K#pn{i?pcB{y2z_Za(r10LrpUEPqwlfiPgYtE zKjzWI8}D4oZ`6=kSTueQwoTq=V&wd7VqwBVLb_4b8oTZ>zDSZ7Y9|Kgc?}zoGF%zs zj-jk~+4phlRPjip>#61=nHhOI?%;#7`uxkvpVBB_EDbcnC-xEGZ^eMfDA}NfzYQHR zO5;29TMa|rW%TmI%@hZ%$P^twH+P{WRrh+I4n(F|2k(AOGwd6QQsjfp&4{$L=asMM z5*j}@7Krm-Aihi!Uyb0cpHwqoIwogHi9cM+RgQc2J*67L1W}FQoVIMrY~KF)H#A!F<%X-_XPJ*pRJ^)Gh76#!}}HX zsM7)-xakK)o^>-94Z;~yx~kXqHp6#58tV`xOZVh%WOt5E_h56BZF9kS7NKNuMW(7y z6QzUHx3IuTVAd^yy=r6A9}|HtMbvDbnnA6eK(|o!pZcwx)s~w-3dWZ%CubE0T3WA(J_enVrEdJ?3uT?&(zT=BaE3vTGbyQ zH*nssT0LEL1|wP1eb4jkNU?w?O{4xnxXo!J-Vd4u(^@=ydYbD{RfQuP&q@4nn0UT7 z#@;&xJ<06eB-f6m^21rGnAl0}^5Rp(ArYIa$P`OO4GwmBOefog`O{Mow+FU4;52Ny z`wDE*U=y#5TgKZw?SibH3mREuxV&W83?X^RF*S|HuoOCHs6xkwoXPZs z{0funy&)3gAVv{)YPZ$I!&3=^H`qjZeWwKX>li^XE#*Q#_2Z9kpw$$P7NI9(@<(2p zdkQYg#oa+kIw^1T%1Bkhbv?&V*24l?8!O#f8xQca>1N7I4=yU@6)ytjK#L8F^MLVw>Xob7^I05kNq1$L?cX1hFvoNCAUOLRM8qUB$HkHD#?dTh49 zhEGYYdcnHmx@(g?%tfzF(GDAv>k24<9;t*Aa2JQruz0QjxMg7Syo+bBbvx>wnr>No ztmY>g?XIwKh?meGRCMlsDA^`6ZFgconp2J-Z=sUj8?M2#Qrc~Nj^E+x1v0uiu%v70 zx>9Bu!ziJ1G6iJ>H{;vJtJ^9+jIY zFYvdOYoh5)+ca+p+0t-U%)IAle;%_&@0ph+UPtc8juv@aQwFniMhbLy8Zl(L5hwp9 zd>zikkw4Hz3@Q;+oJZNbhYb+&yT&oG7U@?yh+Op3)@=8WYuTfqzBHbexp~3&vNp&M zIoTlh%X>^L0Q>*Ak>cC}v1PIRxV|B(5-zIer=i$T2e3Pk?1Y#vu)hd70d_ZL@Mc*A z4NJt+#lhFw7O9iQ;2i6p{r#8b_>ml-3K=~YhkOl^Bj82anNt&6W518%LeSinxdit= z!T&`@UT8A;8bo|6s=>H-S(7}4E>&(c%uHT@PlQ zR}TKG)V*K}&dci0&G8qNZAmV|G_I$#nGs#l|Gb1H6Er9c^{64bO1!jSUs ztpW>Hl(Nk9-@mH;omnn=*PWIMtm!;l#h!*A^e%=H48S_OvthFaj>N?$G~_rD^0{f0 z2!~$Yc8Tu&&LagwhwP>tNDN3~ zi@yt>8-jXZK$*~Qyie?K3A_&sW6*N=5YNts-{20LJt%?;Z5v;z23rsYePD2~JI&<} z9b7BnF^Hh0gHxHU zP`O|utHsN4g&XoARHwm@{273}Rk4;-)nsl4)0C+D4+rV~WN#$`=z$w8&AAb(sr+#T z4vK$H2+L^$b8tE}^(&n50CT2^&Ewe5Md_l0Nq{F)poJ!~qd3)Q4eC+j;KukM-0L-n8jl;`MI$#43Dv&K?)=}_8`4@|w<~;ygI=w|Cz0jg2 z4i^^}mQcB?<@4UKP>=urDn`(Vkhqob_Wg^#J6HHr|8a(^q3RLuUw~jjN;F_wC|ETq z@=tDB2>qy#qjj|M9sZm|byYQ8zrm3W_D+xNO}~SqCEBoRbf%XDo0sf5&=42;=)MTU zxVXFwn|B~4@*0v-JS0Jb%-DF6Tf literal 0 HcmV?d00001 diff --git a/Package-Workspace/Business-Apps/elabftw/start.sh b/Package-Workspace/Business-Apps/elabftw/start.sh new file mode 100755 index 0000000..2a0e329 --- /dev/null +++ b/Package-Workspace/Business-Apps/elabftw/start.sh @@ -0,0 +1,80 @@ +#!/bin/bash +set -euo pipefail + +# eLabFTW runtime setup for Cloudron: +# 1. bind persistent storage: /elabftw/{uploads,exports} -> /app/data +# 2. wait for the Cloudron mysql addon (the image entrypoint runs +# db:install / db:update immediately, without any DB wait) +# 3. persist the SECRET_KEY (rotating it would lose the encrypted +# SMTP / timestamping passwords stored in the DB) +# 4. map the Cloudron env into elabimg's runtime variables +# 5. exec the upstream s6-overlay init (nginx + php-fpm + invoker + +# chronos), which applies the config and starts the web server +# +# Authentication: eLabFTW has NO OIDC support (auth methods: local, SAML, +# LDAP). Cloudron SSO therefore goes through the platform LDAP directory: +# the manifest enables the `ldap` addon and the sysconfig admin maps +# CLOUDRON_LDAP_* into Admin panel -> LDAP (see package README). This is +# flagged auth-risk: LDAP in STATUS.md / README.md. + +DATA_DIR="/app/data" +SECRET_FILE="${DATA_DIR}/.secret_key" + +# --- 1. persistent uploads / exports ----------------------------------------- +mkdir -p "${DATA_DIR}/uploads" "${DATA_DIR}/exports" +# replace the in-image directories with symlinks into the Cloudron volume; +# the upstream entrypoint will chown/chmod them for the nginx user +rm -rf /elabftw/uploads /elabftw/exports +ln -sfn "${DATA_DIR}/uploads" /elabftw/uploads +ln -sfn "${DATA_DIR}/exports" /elabftw/exports + +# --- 2. wait for the mysql addon ---------------------------------------------- +wait_tcp() { + local host="$1" port="$2" name="$3" + echo "Waiting for ${name} at ${host}:${port} ..." + until (exec 3<>"/dev/tcp/${host}/${port}") 2>/dev/null; do + echo "${name} is unavailable - sleeping" + sleep 2 + done + echo "${name} is up" +} + +wait_tcp "${CLOUDRON_MYSQL_HOST:-127.0.0.1}" "${CLOUDRON_MYSQL_PORT:-3306}" "MySQL" + +# --- 3. persistent SECRET_KEY -------------------------------------------------- +# hex on purpose: the image entrypoint substitutes it into the php-fpm pool +# config with an unescaped `sed s/.../.../`, so the value must not contain +# `/`, `&` or other sed metacharacters (base64 would break it). +if [[ ! -s "${SECRET_FILE}" ]]; then + ( umask 077; openssl rand -hex 32 > "${SECRET_FILE}" ) + echo "Generated new eLabFTW secret key" +fi +export SECRET_KEY="$(cat "${SECRET_FILE}")" + +# --- 4. Cloudron -> elabimg environment ---------------------------------------- +export DB_HOST="${CLOUDRON_MYSQL_HOST:-127.0.0.1}" +export DB_PORT="${CLOUDRON_MYSQL_PORT:-3306}" +export DB_NAME="${CLOUDRON_MYSQL_DATABASE:-elabftw}" +export DB_USER="${CLOUDRON_MYSQL_USERNAME:-elabftw}" +export DB_PASSWORD="${CLOUDRON_MYSQL_PASSWORD:-}" + +# Cloudron terminates TLS at the platform proxy; elabimg serves plain HTTP +export DISABLE_HTTPS=true +export SITE_URL="${CLOUDRON_APP_ORIGIN:-http://localhost}" +export SERVER_NAME="${CLOUDRON_APP_DOMAIN:-localhost}" + +export TZ="${ELABFTW_TZ:-UTC}" +export PHP_TIMEZONE="${ELABFTW_TZ:-UTC}" + +# schema lifecycle handled by the image entrypoint (runs after this script +# hands over): db:install on first boot, db:update on every boot +export AUTO_DB_INIT="${ELABFTW_AUTO_DB_INIT:-true}" +export AUTO_DB_UPDATE="${ELABFTW_AUTO_DB_UPDATE:-true}" + +# resource knobs (defaults tuned for the 1536 MB manifest memory limit) +export MAX_UPLOAD_SIZE="${ELABFTW_MAX_UPLOAD_SIZE:-100M}" +export PHP_MAX_CHILDREN="${ELABFTW_PHP_MAX_CHILDREN:-15}" +export MAX_PHP_MEMORY="${ELABFTW_MAX_PHP_MEMORY:-512M}" + +# --- 5. hand over to the upstream init ------------------------------------------ +exec /init diff --git a/README.md b/README.md index 9a41d2f..ea73819 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ The Cloudron component focuses on packaging upstream free/libre/open application ### 📊 Current Progress - **Total Applications**: ~57 (see [GitUrlList.txt](GitUrlList.txt)) -- **Completed Packages**: 14/~57 (~25%) +- **Completed Packages**: 15/~57 (~26%) - **Packaging Templates**: Created ✅ -- **Packages Committed & Pushed**: 14 ✅ +- **Packages Committed & Pushed**: 15 ✅ - **Build Tickets**: 46 filed (#633-#678, umbrella [#632](https://projects.knownelement.com/issues/632), Redmine project 55); grist-core excluded (packaged upstream) @@ -36,6 +36,7 @@ The Cloudron component focuses on packaging upstream free/libre/open application | 12 | Database Gateway | Infrastructure | 93.7MB | 8080 | localstorage, postgresql | ✅ Committed | | 13 | FX | DevOps-Tools | 3.55GB | 8000 | localstorage (auth proxy) | ✅ Committed | | 14 | ChirpStack | Infrastructure | 83.4MB | 8080 | localstorage, postgresql, redis | ✅ Committed | +| 15 | eLabFTW | Business-Apps | ~209MB (compressed) | 443 | localstorage, mysql, ldap | ✅ Committed | ### 📦 Packages in Development diff --git a/STATUS.md b/STATUS.md index d7a1d37..7ec302c 100644 --- a/STATUS.md +++ b/STATUS.md @@ -3,21 +3,23 @@ > **Human read-only. Agents maintain this file automatically after each work > session.** Do not edit by hand — the next agent run will overwrite it. > -> **Last updated:** 2026-09-01 by Crush (GLM-5.2) — ChirpStack packaged -> (#668, Infrastructure, 14th package); auth gate verdict: native OIDC -> (`user_authentication.openid_connect`) wired to the platform provider; -> postgresql + redis addons, official-image wrapper of chirpstack 4.19.1. +> **Last updated:** 2026-09-02 by ZCode (GLM-5.3) — eLabFTW commit landed +> (#669, Business-Apps, 15th package; packaged 2026-09-01 by Crush +> GLM-5.2, gardening finished + committed 2026-09-02 after the prior +> session ended before committing). Auth gate verdict: no OIDC upstream +> (local/SAML/LDAP only) → native LDAP wired to the platform directory +> with an auth-risk flag; official-image wrapper of elabimg 5.6.12. ## Current State: STABLE (packaging phase, ongoing) -Cloudron packaging pipeline is operational. 14 of ~57 upstream applications are +Cloudron packaging pipeline is operational. 15 of ~57 upstream applications are packaged, committed, and pushed. Packaging templates exist for the core patterns. The gardening protocol (this file + AGENTS.md) keeps docs in sync. All remaining apps now carry build tickets (#633-#678) under umbrella [#632](https://projects.knownelement.com/issues/632) in Redmine project 55 — ready for the sequential grind-driver pattern. -## Completed Packages (14) +## Completed Packages (15) | # | Application | Category | Pattern | Port(s) | Addons | |---|-------------|----------|---------|---------|--------| @@ -35,6 +37,7 @@ ready for the sequential grind-driver pattern. | 12 | Database Gateway | Infrastructure | Multi-stage (Go, CGO) | 8080 | localstorage, postgresql | | 13 | FX | DevOps-Tools | Pre-compiled binaries + auth proxy | 8000 | localstorage | | 14 | ChirpStack | Infrastructure | Official-image wrapper | 8080 | localstorage, postgresql, redis | +| 15 | eLabFTW | Business-Apps | Official-image wrapper | 443 | localstorage, mysql, ldap | Each package lives in `Package-Workspace///` and contains a `Dockerfile`, `CloudronManifest.json`, `README.md`, `CHANGELOG.md`, `logo.png`, @@ -116,7 +119,7 @@ Full write-ups of each pattern + challenges are in [`JOURNAL.md`](JOURNAL.md). | Low-Code | 3 | 1/3 | corteza done | | Monitoring | 6 | 1/6 | healthchecks done | | Automation | 4 | 1/4 | windmill done | -| Business-Apps | 8 | 0/8 | | +| Business-Apps | 8 | 1/8 | elabftw done | | Collaboration | 2 | 0/2 | | | Communication | 1 | 0/1 | | | Data-Management | 2 | 0/2 | | @@ -137,7 +140,7 @@ Auth capability is a hard gate before packaging (see LDAP acceptable (risk flag), 🔄 = auth-proxy (no users), ❌ = local-only (unacceptable / blocked-on-auth). -### Completed packages (14) +### Completed packages (15) | App | OIDC | LDAP | Verdict | Note | |-----|------|------|---------|------| @@ -155,6 +158,7 @@ LDAP acceptable (risk flag), 🔄 = auth-proxy (no users), ❌ = local-only | Database Gateway | yes | no | ✅ preferred | **Packaged**; native OIDC-only app — platform provider env (`CLOUDRON_OIDC_*`) seeded into config.json; roles from the `groups` claim | | FX | n/a | n/a | 🔄 proxy | **Packaged**; CLI-only FaaS tool with no user concept — pinned binary + workspace driven from the Cloudron terminal; landing page gated by `httpAuth.type=proxy` | | ChirpStack | yes | no | ✅ preferred | **Packaged**; native `[user_authentication.openid_connect]` wired to `CLOUDRON_OIDC_*`; OIDC-registered users are non-admin — one-time `CHIRPSTACK_AUTH_MODE=internal` bootstrap links the seeded `admin` to your SSO email (README) | +| eLabFTW | no | yes | ⚠️ risk (LDAP) | **Packaged**; no OIDC upstream (auth = local/SAML/LDAP per `src/Enums/AuthMethod.php`); `ldap` addon enabled, README maps `CLOUDRON_LDAP_*` → sysconfig LDAP panel — must be validated on the live Cloudron before production; SAML possible via external IdP | ### Candidates researched