Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions catalog/files-demo/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ name: Files Demo
version: "1.10"
description:
short: "whoami wired to a use-case folder — proves the folder-enforcement override path."

# Authoring-only (APP_MANIFEST.md # A2). This is a molma test fixture, not a
# tracked third-party app — it borrows the whoami image deliberately and should
# not auto-bump, so it opts out of the version-watcher.
upstream:
source: manual
note: "molma demo fixture; pinned to the whoami image on purpose, not upstream-tracked."

compose_file: compose.yml
main_service: files-demo
main_port: 8080
Expand Down
9 changes: 9 additions & 0 deletions catalog/hermes-agent/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ links:
homepage: https://hermes-agent.nousresearch.com/docs/
source: https://github.com/NousResearch/hermes-agent

# Authoring-only: catalog version-watcher metadata (APP_MANIFEST.md # A2).
# Calendar versioning (year.month.day.build); both compose services run the same
# image off this one tag, so a bump rewrites both. The brain ignores this block.
upstream:
source: dockerhub
repo: nousresearch/hermes-agent
tracks: [nousresearch/hermes-agent]
version_pattern: '^v(\d{4}\.\d+\.\d+\.\d+)$'

compose_file: compose.yml
main_service: dashboard
main_port: 9119
Expand Down
9 changes: 9 additions & 0 deletions catalog/whoami/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ name: Whoami
version: "1.10"
description:
short: "Tiny HTTP server that echoes request info — a routing smoke test."

# Authoring-only: tells the catalog version-watcher where the upstream version
# lives and how to read it (APP_MANIFEST.md # A2). The brain ignores this block.
upstream:
source: dockerhub
repo: traefik/whoami
tracks: [traefik/whoami]
version_pattern: '^v(\d+\.\d+\.\d+)$'

compose_file: compose.yml
main_service: whoami
main_port: 80
Expand Down
11 changes: 9 additions & 2 deletions docs/dev/authoring-apps-with-an-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ STEPS

2. IDENTITY & RUNTIME. Set `id`, `name`, `version` (the app's real version — never `custom`, which is the Door-2 marker), `main_service` (the service that is "the app"), `main_port` (its *internal* listen port, NOT a host-side mapping), `preferred_slugs`. Write `description.short`: a single punchy sentence that captures what the app *does for the user* — written fresh, not lifted from the README. Optionally write `description.long`: a short markdown paragraph (3–5 sentences) that expands on the value proposition — what problems it solves, what makes it worth running. Read the README for facts, but write both fields in your own words; do not copy README prose, badges, install steps, or Docker-specific context. Do NOT add a `multi_user` field — household-vs-personal is the installer's runtime choice, not a manifest property (APP_MANIFEST.md # G).

Also write the `upstream:` block (APP_MANIFEST.md # A2) — this is the one chance to record HOW this project's version should be read, while you still have the repo and registry open. You are deciding the rule molma's nightly version-watcher will execute forever after; get it right now and the app auto-tracks, get it wrong (or skip it) and the app silently rots at this version.
- `source`: where the version TRUTH lives, which is not always where the image lives. If the project tags clean releases on GitHub (`vX.Y.Z` releases) but the registry only carries `:latest` + dated/`sha-` tags, use `source: github-releases` with the GitHub `repo` — the Releases API is the reliable signal. Otherwise use the registry the image is pulled from (`dockerhub` | `ghcr` | `quay`).
- `tracks`: the compose `image:` reference(s) whose tag a bump should rewrite. List every first-party image that moves on this same version (e.g. a gateway + worker on one tag). NEVER list a managed-service image (Postgres/Redis/MariaDB) — those are molma-owned and move separately.
- `version_pattern`: a regex that (1) FILTERS — matches ONLY real releases, so RC/nightly/`latest`/`sha-…`/dated-debug tags are excluded — and (2) CAPTURES — group 1 is the comparable version and the new manifest `version`, the full match is the new compose tag. Inspect the project's ACTUAL tag/release list before writing it; don't assume `vX.Y.Z`. The pattern must narrow to a SINGLE versioning scheme so a dotted-numeric "highest wins" compare is valid — that one rule covers both semver (`1.10.4`) and calendar versions (`2026.5.29.2`). Examples: `^v(\d+\.\d+\.\d+)$` (semver with `v` prefix), `^(\d+\.\d+\.\d+)$` (bare semver), `^v(\d{4}\.\d+\.\d+(?:\.\d+)?)$` (calendar). Sanity-check it against the real tag list: it must match the version you're shipping and reject the noise.
- If the project has NO machine-readable version discipline (only `:latest`, versions in blog posts, etc.), set `source: manual` plus a `note:` saying where to check by hand — and call this out in the report. A bad guess that lints green is worse than an honest `manual`.

3. REWRITE THE COMPOSE TO PASS ADMISSION (verify each against `admission.go`):
- Drop every `ports:` mapping. From a mapping like `8080:80`, mine the container side (`80`) for `main_port`.
- Convert named volumes to relative binds under `./data/` (e.g. `db_data:/var/lib/postgresql/data` -> `./data/db:/var/lib/postgresql/data`).
Expand Down Expand Up @@ -89,16 +95,17 @@ STEPS
(a) `go run ./cmd/molma manifest lint catalog/<id>/manifest.yml` — schema, slugs, permissions, health_probe shape, compose-exists/parses, `main_service` present. Ground truth for the schema; iterate on its messages. Remember it is non-strict, so it will NOT flag a malformed `storage:`/`services:` block — those you verify against the spec by eye.
(b) Admission + runtime (lint does NOT cover these; there is no admission CLI): run `docker compose -f catalog/<id>/compose.yml config -q`, then re-confirm against `admission.go` that none of step 3's rejections slipped in, that `main_port` is the internal port, that every `${MOLMA_SERVICE_*}` has a matching declared-and-used `services:` entry, and that every folder the app touches has a `permissions.folders` entry.

11. REPORT: what you changed and why; env vars rewired; permissions + reasoning; data-vs-cache split; digest status; health-probe choice; whether it's files-first-class or app_managed_user_content; icon found or skipped; screenshot count or skipped; anything that needed judgment or blocks Door-1 (e.g. needs a capability -> Tier 2). If you bailed under ADAPT, DON'T FORCE, this report (naming the blocker, the forbidding rule, and the tier verdict) IS the deliverable — there are no files.
11. REPORT: what you changed and why; env vars rewired; permissions + reasoning; data-vs-cache split; digest status; upstream-tracking choice (the `source` + `version_pattern` you picked and why, or `manual` + why it can't be auto-tracked); health-probe choice; whether it's files-first-class or app_managed_user_content; icon found or skipped; screenshot count or skipped; anything that needed judgment or blocks Door-1 (e.g. needs a capability -> Tier 2). If you bailed under ADAPT, DON'T FORCE, this report (naming the blocker, the forbidding rule, and the tier verdict) IS the deliverable — there are no files.

REFERENCE (verify against the on-disk sources — these are reminders, not the schema):
- Required fields: id, manifest_version, name, version, compose_file, main_service, main_port. Rest optional.
- Injection (MOLMA_ prefix): folders mount at `/molma/<folder>`, injected as `MOLMA_FOLDER_<NAME>`; managed services as `MOLMA_SERVICE_<NAME>_{HOST,USER,PASSWORD,NAME,DSN}`; app data dir as `MOLMA_DATA_DIR`.
- Folder taxonomy (only these): photos, documents, movies, music, notes, downloads.
- Slug rule: `^[a-z0-9]+(-[a-z0-9]+)*$` — single internal hyphens, no leading/trailing hyphen, no `--` run (which also rules out the reserved `xn--` prefix).
- `version: custom` is the Door-2 marker — never use it for a catalog app.
- `upstream` is authoring-only (the version-watcher reads it; the brain ignores it): `source` (dockerhub|ghcr|quay|github-releases|manual), `repo`, `tracks` (compose images, never managed-service images), `version_pattern` (filters to real releases + captures group 1 as the version). Inspect the real tag list before writing the pattern; use `source: manual` + `note` when there's no reliable version signal.

DO NOT: honor `ports:`; use named volumes; emit absolute host binds; set `version: custom`; add Linux capabilities; emit the MALMO_ prefix; add a `multi_user` field; set a Door-2 folder `target:`; auto-rewrite beyond the documented adaptations; fabricate digests; trust a green lint as proof admission passes; force an app through by stripping or faking something it genuinely needs — bail and explain instead (see ADAPT, DON'T FORCE).
DO NOT: honor `ports:`; use named volumes; emit absolute host binds; set `version: custom`; add Linux capabilities; emit the MALMO_ prefix; add a `multi_user` field; set a Door-2 folder `target:`; list a managed-service image in `upstream.tracks`; write a `version_pattern` you didn't check against the real tag list; auto-rewrite beyond the documented adaptations; fabricate digests; trust a green lint as proof admission passes; force an app through by stripping or faking something it genuinely needs — bail and explain instead (see ADAPT, DON'T FORCE).
```

## After the run
Expand Down
28 changes: 28 additions & 0 deletions docs/specs/APP_MANIFEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,33 @@ links:
changelog_url: https://github.com/photoprism/photoprism/releases # optional; used by the "What's new" panel after an update
```

### A2. Upstream version tracking (catalog-maintenance metadata)

**Authoring-only. Not consumed by the brain, never written into the signed catalog.** This block exists for one job: telling molma's catalog-repo version-watcher *where the app's upstream version lives* and *how to read it*, so the watcher can open a bump PR when upstream moves (`APP_STORE.md` # Upstream version tracking). It rides in the manifest because that's where it stays colocated, version-controlled, and authored once — by the import agent, at first import (`docs/dev/authoring-apps-with-an-agent.md`). The brain ignores it like any unknown field; catalog build drops it.

```yaml
upstream:
source: dockerhub # dockerhub | ghcr | quay | github-releases | manual
repo: traefik/whoami # namespace/name to query on that source
tracks: [traefik/whoami] # compose image(s) this version drives; a bump rewrites each one's tag
version_pattern: '^v(\d+\.\d+\.\d+)$' # filter + capture against candidate tags/release names
```

- **`source`** — where the version *truth* lives. Often the same registry the image is pulled from (`dockerhub`, `ghcr`, `quay`); use `github-releases` when the project tags releases on GitHub but publishes the image elsewhere (common — the semver discipline is in Releases, the registry only carries `:latest` + dated tags). `manual` opts the app out of automated tracking (see below).
- **`repo`** — the `owner/name` (or registry namespace) the watcher queries. For `github-releases` it's the GitHub repo; for the registries it's the image repository.
- **`tracks`** — the compose `image:` reference(s) whose tag a bump rewrites. All listed images share this one upstream version (e.g. a gateway + worker published on the same tag). **Managed-service images are never listed** — Postgres/Redis/MariaDB versions are molma-owned and move on their own track (`UPDATES.md` # 5), not with the app.
- **`version_pattern`** — a regex that does two things against each candidate (a registry tag, or a GitHub release `tag_name`): (1) **filters** — only matches count as releases, which is how RC/nightly/`sha-…`/`latest`/dated-debug tags get excluded; (2) **captures** — capture group 1 is the comparable version string and the new manifest `version`; the full match is the new compose tag. So `^v(\d+\.\d+\.\d+)$` on `v1.10.4` yields compose tag `v1.10.4`, manifest `version: 1.10.4`. The pattern must narrow candidates to **one** versioning scheme so a generic dotted-numeric "highest wins" comparison is valid — this is what lets the same machinery handle both semver (`1.10.4`) and calendar versions (`2026.5.29.2`) without a per-app comparator.

**`source: manual`** is the honest escape hatch for apps with no machine-readable version discipline (publishes only `:latest`, versions announced in a blog post, etc.). The watcher skips them and lists them in its "watch by hand" report. Pair it with a `note`:

```yaml
upstream:
source: manual
note: "Only publishes :latest; check the release notes at https://example.com/releases"
```

Omitting the `upstream` block entirely is treated as `manual` with no note — valid, but the import agent should always write at least the `source`/`repo`/`tracks`/`version_pattern` quartet when a real source exists, so the app is auto-tracked rather than silently falling off the watcher.

### B. Runtime

The minimum to actually launch the thing.
Expand Down Expand Up @@ -397,6 +424,7 @@ permissions:
- **No inter-app dependencies in v1.** Apps are self-contained. If they need multiple services, they go in the same compose. Cross-app sharing only via shared use-case folders (two of the same user's apps both binding the same `folders` entry; the installer points each at the same personal or shared source).
- **Manifest can live in-repo or in molma's catalog repo.** Both patterns supported indefinitely. Schema is identical in both cases. We bootstrap by writing manifests for popular apps; over time, upstreams ship their own.
- **Image references use version tags; the store catalog resolves digests.** Authors write `image: foo/bar:1.2.3`; molma's CI pins the bytes via a `sha256:` digest in the signed catalog (`APP_STORE.md`). Door-2 custom apps fall back to TOFU digest pinning in the brain.
- **`upstream` is authoring-only metadata for the catalog version-watcher.** Declares `source` / `repo` / `tracks` / `version_pattern` (or `source: manual` to opt out) so molma's catalog repo can detect a new upstream version and open a bump PR (`APP_STORE.md` # Upstream version tracking). Written once by the import agent at first import; the brain ignores it and catalog build drops it. The `version_pattern` both filters out non-release tags and captures the comparable version; it must narrow to one versioning scheme so a dotted-numeric "highest wins" comparison serves both semver and calendar versions.

## Open questions

Expand Down
Loading