# Agent Guidelines — TSYS Cloudron Packaging

**Top-level files:** [`README.md`](README.md) (project overview + app inventory),
[`STATUS.md`](STATUS.md) (living status, agent-maintained),
[`JOURNAL.md`](JOURNAL.md) (append-only ADR/pattern journal),
[`GitUrlList.txt`](GitUrlList.txt) (upstream repo list — source of truth for
the app set). Read [`STATUS.md`](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

1. **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.**
2. **Atomic commits.** One coherent change per commit (e.g. a single package,
   or a single doc-correction pass — not both mixed).
3. **Conventional format**: `feat: add <app> Cloudron package (<Category>)`,
   `fix(scope): desc`, `docs: desc`, `refactor(scope): desc`. Include a body
   for anything non-obvious.
4. **Shell scripts are committed executable.** Run `chmod +x start.sh` on the
   host before committing — Cloudron builds hit permission errors on
   `RUN 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:

1. **Update [`STATUS.md`](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).
2. **Keep counts consistent across all docs.** When a package completes, the
   same total must appear in `STATUS.md`, `README.md` ("Current Progress"),
   and `JOURNAL.md` ("Current Status"). A count in one place but not the
   others is a protocol violation.
3. **Update the app inventory** in [`README.md`](README.md) — add the
   ✅ Packaged marker on the right row, and the app to the "Completed
   Packages" table.
4. **Append to [`JOURNAL.md`](JOURNAL.md)** — one section per package
   (pattern used, build process, challenges, files created, commit hash).
   Never delete or reorder existing entries.
5. **Grep for stale paths after any rename/restructure** —
   `grep -rn 'old/path' --include='*.md'` and fix references in the same
   commit.
6. **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).
7. **Treat `GitUrlList.txt` as 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-repos.sh) | Clone all upstream repos into `Package-Workspace/<Category>/<app>/repo/` |

## Key Docs

| Doc | Purpose |
|-----|---------|
| [`STATUS.md`](STATUS.md) | Current state, completed packages, known issues, next priorities |
| [`JOURNAL.md`](JOURNAL.md) | Per-package write-ups, the 5 packaging patterns, challenges & solutions |
| [`README.md`](README.md) | Project overview + full app inventory + category breakdown |
| [`GitUrlList.txt`](GitUrlList.txt) | Master upstream repo list (source of truth) |
| [`Package-Templates/`](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](STATUS.md#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:**
1. `mkdir -p Package-Workspace/<Category>/<app>/` and clone upstream into `repo/`.
2. Write `Dockerfile` + `CloudronManifest.json` (+ `start.sh` if runtime setup needed).
3. Verify with the grind lifecycle (below): `scripts/grind-stack.sh up <pkg-dir>`
   → `curl` / `logs` → `down`.
4. Generate the SBOM ([#834](https://projects.knownelement.com/issues/834)):
   `scripts/grind-stack.sh sbom <pkg-dir>` → commits
   `sbom.cyclonedx.json` beside the package (syft via pinned container).
5. GLPI software catalog entry ([#835](https://projects.knownelement.com/issues/835)):
   staged on the ticket until #801 grants agent write access, then
   `mglpi software create` per package.
6. Write `README.md` + `CHANGELOG.md` + `logo.png` (+ `.env.example`).
7. Commit as `feat: add <app> Cloudron package (<Category>)`, push.
8. Run the gardening protocol above (update STATUS / README / JOURNAL).
9. `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`](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):**
- `chmod` in `RUN` fails on Cloudron base → make scripts executable on the host.
- Don't `apk`/`apt-get` in 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.sh` before running migrations.
- Prefer `npm install` over `npm ci` when 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](../TSYSDevStack-SupportStack-LocalWorkstation)
is a sibling project. Solo operator, conventional-commit discipline, build
locally then push to `origin/main` on `git.knownelement.com`.
