Commits now authored as VpEngOps (vault: creds/vpengops-vpentops); reachableceo is the human personal account. grind-stack gains `sbom` (CycloneDX via pinned syft container, run before sweep) per #834; GLPI catalog entries planned in #835 gated on #801 write access. AGENTS.md packaging steps extended; questions-v1.md opened for the founder (netbird scope, SigNoz reopen, SBOM format).
10 KiB
Agent Guidelines — TSYS Cloudron Packaging
Top-level files: README.md (project overview + app inventory),
STATUS.md (living status, agent-maintained),
JOURNAL.md (append-only ADR/pattern journal),
GitUrlList.txt (upstream repo list — source of truth for
the app set). Read STATUS.md first every session.
Active agent: Crush running GLM-5.2 (zai) for large tasks, Gemini for small. Permission mode:
yolo.
Repository Layout
README.md Project overview + full app inventory table
AGENTS.md THIS FILE — agent operating manual
STATUS.md Living status (agent-maintained, human read-only)
JOURNAL.md Append-only journal: per-package write-ups, patterns, ADRs
GitUrlList.txt Master list of upstream repos (source of truth, ~57 apps)
clone-repos.sh Automated upstream-repo cloning
Package-Templates/ Reusable Cloudron packaging templates
├── python-app/ (Dockerfile + CloudronManifest + start.sh templates)
├── django-app/ (Dockerfile + start.sh templates)
└── official-wrapper/ (Dockerfile template)
Package-Workspace/ One dir per app: <Category>/<app>/ (+ gitignored repo/)
├── API-Gateway/ webhook, apisix
├── Development/ puter, reviewboard
├── Documentation-Tools/ wireviz-web
├── Low-Code/ corteza
└── Monitoring/ healthchecks
Package-Workspace/**/repo/is gitignored — upstream source is cloned locally for reference but never committed. Only the package files (Dockerfile,CloudronManifest.json,start.sh,README.md, etc.) are tracked.- Each package =
Dockerfile+CloudronManifest.json+start.sh(if needed) +README.md+CHANGELOG.md+logo.png+.env.example(if config knobs exist). - No host build tooling required — packaging is
docker build(Cloudron base images handle runtimes). Keep the host clean.
Git Policy
- ALWAYS commit + push. Never wait. After each logical unit of work
(one package, one doc sync, one fix), immediately stage, commit
(conventional format), and push to
origin/main. Do not pause for review. This overrides any default "never commit unless asked" behavior. - Atomic commits. One coherent change per commit (e.g. a single package, or a single doc-correction pass — not both mixed).
- Conventional format:
feat: add <app> Cloudron package (<Category>),fix(scope): desc,docs: desc,refactor(scope): desc. Include a body for anything non-obvious. - Shell scripts are committed executable. Run
chmod +x start.shon the host before committing — Cloudron builds hit permission errors onRUN chmod, so make scripts executable at authoring time, not build time.
Automatic Gardening Protocol
Docs and code drift apart. After any work session, an agent MUST:
- Update
STATUS.md— reflect newly completed packages, changed counts, new issues, shifted priorities. This file is human read-only; agents own it. Update the "Last updated" header (date + agent). - Keep counts consistent across all docs. When a package completes, the
same total must appear in
STATUS.md,README.md("Current Progress"), andJOURNAL.md("Current Status"). A count in one place but not the others is a protocol violation. - Update the app inventory in
README.md— add the ✅ Packaged marker on the right row, and the app to the "Completed Packages" table. - Append to
JOURNAL.md— one section per package (pattern used, build process, challenges, files created, commit hash). Never delete or reorder existing entries. - Grep for stale paths after any rename/restructure —
grep -rn 'old/path' --include='*.md'and fix references in the same commit. - Self-audit before commit. For a new package, verify the package dir
appears in:
STATUS.md(Completed Packages table),README.md(inventory- completed table),
JOURNAL.md(new section).
- completed table),
- Treat
GitUrlList.txtas the source of truth for the app set. If the README inventory table disagrees, reconcile the README to the list, not the other way around.
Key Scripts
| Script | Purpose |
|---|---|
clone-repos.sh |
Clone all upstream repos into Package-Workspace/<Category>/<app>/repo/ |
Key Docs
| Doc | Purpose |
|---|---|
STATUS.md |
Current state, completed packages, known issues, next priorities |
JOURNAL.md |
Per-package write-ups, the 5 packaging patterns, challenges & solutions |
README.md |
Project overview + full app inventory + category breakdown |
GitUrlList.txt |
Master upstream repo list (source of truth) |
Package-Templates/ |
Reusable templates per packaging pattern |
Authentication Policy (MANDATORY)
Every app must have an auth story before packaging. Research it up front and record it in the Auth Status matrix.
| Auth type | Verdict | Action |
|---|---|---|
| OIDC client (native or plugin) | ✅ PREFERRED | Package; wire Cloudron OIDC provider env vars. |
| LDAP (native or plugin) | ⚠️ ACCEPTABLE w/ RISK | Package, but FLAG as "auth-risk: LDAP" in STATUS + README — must be fixed/validated before production. |
| Local-only (built-in user DB, no SSO) | ❌ UNACCEPTABLE | Do NOT package. Record in STATUS as blocked-on-auth. |
| No user concept (stateless/utility app) | ✅ via AUTH PROXY | Package with httpAuth: {"type":"proxy"} so Cloudron gates access at the proxy. Admin restricts who can reach it. |
OIDC env vars Cloudron exposes (when the app consumes the platform OIDC
provider): CLOUDRON_OIDC_ISSUER, CLOUDRON_OIDC_CLIENT_ID,
CLOUDRON_OIDC_CLIENT_SECRET, CLOUDRON_OIDC_TOKEN_SIGNATURE_ALGORITHM
(manifestVersion 2 / platform OIDC). LDAP addon env vars:
CLOUDRON_LDAP_*. See CloudronManifest.json addons (no extra addon needed
for proxy auth; use the httpAuth field).
Before writing a Dockerfile, determine which row applies and write the finding into STATUS.md's Auth Status table. Never silently ship a local-only- auth app.
Cloudron Packaging — Quick Reference
Pick a pattern (see JOURNAL.md for full templates and worked examples):
| Pattern | When | Examples |
|---|---|---|
| Official-image wrapper | App ships a usable Docker image | APISIX, Healthchecks, Review Board |
| Multi-stage build | App needs compiling | Webhook (Go), Puter (Node.js) |
| Python build | Python app with deps | WireViz Web |
| Django + PostgreSQL | Django web app | Healthchecks, Review Board |
| Pre-compiled binaries | Upstream ships release binaries | Corteza |
Standard package steps:
mkdir -p Package-Workspace/<Category>/<app>/and clone upstream intorepo/.- Write
Dockerfile+CloudronManifest.json(+start.shif runtime setup needed). - Verify with the grind lifecycle (below):
scripts/grind-stack.sh up <pkg-dir>→curl/logs→down. - Generate the SBOM (#834):
scripts/grind-stack.sh sbom <pkg-dir>→ commitssbom.cyclonedx.jsonbeside the package (syft via pinned container). - GLPI software catalog entry (#835):
staged on the ticket until #801 grants agent write access, then
mglpi software createper package. - Write
README.md+CHANGELOG.md+logo.png(+.env.example). - Commit as
feat: add <app> Cloudron package (<Category>), push. - Run the gardening protocol above (update STATUS / README / JOURNAL).
scripts/grind-stack.sh sweep <pkg-dir>— drop the test image + dangling build cache so disk usage stays flat across the grind.
Agent identity (MANDATORY, 2026-09-06): commits from this repo are authored
as VpEngOps <tsgstaff-coo-vpengops@turnsys.com> (repo-local git config;
identity fields vaulted at creds/vpengops-vpentops in the TSGCOO Bitwarden
vault via sm). NEVER commit as the reachableceo personal account — that
account is the human's. Global git config stays the human's; set repo-local
identity in every repo the agent works. Cloudron-side logins use the
vpentops platform account (same vault item).
Grind lifecycle (scripts/grind-stack.sh): serial
verify-stack harness. up builds the image under ukrrs-batch.slice and starts
ephemeral postgres/redis/mysql matching the manifest's addons, plus the app
container driven by the real CLOUDRON_* env contract — every resource labeled
cloudron.grind=1, so down/sweep can never touch fleet containers. One
stack at a time (serial lock; up refuses while one is live).
Host resource rules (day/night, disk): all builds + test stacks run under
ukrrs-batch.slice (cores 0-5, CPUWeight 25 — cores 6-7 stay interactive).
Night window 22:00-07:00 Central tolerates wider build parallelism; avoid
LLM-heavy sub-agent fan-outs 01:00-05:00 (z.ai peak ladder). Docker storage
lives on /data2 (SSD; moved off the NVMe root 2026-09-06 via ultix
15-docker-to-data2.sh — rollback: delete the docker.service.d data-root
drop-in, daemon-reload, restart).
Recurring gotchas (from JOURNAL.md):
chmodinRUNfails on Cloudron base → make scripts executable on the host.- Don't
apk/apt-getin base images that lack the package manager; use the image's built-in tooling or pick the right base. - Wait for DB/etcd addons in
start.shbefore running migrations. - Prefer
npm installovernpm ciwhen lockfiles don't match the build env. - Define every TCP port explicitly in
CloudronManifest.json.
Addons — Environment Variables
PostgreSQL: CLOUDRON_POSTGRESQL_{HOST,PORT,DATABASE,USERNAME,PASSWORD}.
etcd: CLOUDRON_ETCD_{HOST,PORT}. Localstorage mounts at /app/data.
Project Context
Packaging ~57 upstream FLOSS apps for Cloudron — TSYS Group's PaaS of choice.
This repo is the Cloudron half of the support stack; the
Docker-Compose local dev stack
is a sibling project. Solo operator, conventional-commit discipline, build
locally then push to origin/main on git.knownelement.com.