# Eureka container contract

SIFs in this catalog are **the app**, not mini-Eureka. The host wrapper
(`omnibond/eureka` `image/apps/container/start.sh`) owns the session.

## How Eureka starts the SIF

If `container_args` is set:

```sh
apptainer exec --home "$HOME" \
  --bind /vault:/vault --bind /software:/software --bind "$OOD:$OOD" \
  --bind "$SCRATCH/var/lib/rstudio-server:/var/lib/rstudio-server" \
  --bind "$SCRATCH/var/run/rstudio-server:/var/run/rstudio-server" \
  [--nv] [--bind /tmp/.X11-unix] \
  "$SIF" $CONTAINER_ARGS
```

`--nv` is added when `/dev/nvidiactl` exists. `/tmp/.X11-unix` and
`DISPLAY`/`XAUTHORITY` are passed when the host already has an X server
(VNC today, Selkies later). The SIF does not contain either stack.

`$SCRATCH` is node-local `/tmp/eureka-container/$JOBID`. If `container_args`
is empty, Eureka uses `apptainer run`
(only for images whose %runscript *is* the session). Prefer `exec` for
Docker-imported images.

## Substitutions in `container_args`

| Token | Meaning |
| --- | --- |
| `$PORT` / `%PORT%` | Ephemeral listen port |
| `$HOST` / `%HOST%` | `hostname -f` of the compute VM |
| `$BASE_URL` / `%BASE_URL%` | `/node/$HOST/$PORT/` or `/rnode/...` from `connectUrl` |
| `$PASSWORD` / `%PASSWORD%` | Session password |
| `$CONFIG_FILE` / `%CONFIG_FILE%` | Host-written Jupyter-style config (hashed password, `base_url`) |
| `$HOME` / `%HOME%` | Session user home |
| `$JOBID` / `%JOBID%` | Eureka session id |
| `$USER` / `%USER%` | Session username |

Also exported into the container: `APPTAINERENV_PORT`, `PASSWORD`, `USER`,
`USERNAME`, `HOST`, `APPID`, `JOBID`, `RSTUDIO_PASSWORD`.

## Connect / OOD proxy (`connectUrl`)

No extra AFW field. The existing Admin Apps Connect path selects the proxy:

| `connectUrl` | Form action | App sees | Typical apps |
| --- | --- | --- | --- |
| `/login` | `/node/<host>/<port>/login` | full prefix; set `base_url` | Jupyter |
| `rnode:/login` | `/rnode/<host>/<port>/login` | prefix stripped; listen at `/` | code-server |
| `rnode:/auth-sign-in` | `/rnode/<host>/<port>/auth-do-sign-in` | prefix stripped | RStudio Server |
| *(empty)* | noVNC (`view: null`) | host Xvnc + XFCE; SIF on `DISPLAY=:1` | QGIS, ParaView, Spyder, Octave |
| `https://...` | used as-is | — | rare |

## Do not bake into the SIF

- OOD `/node/<host>/<port>/` paths (per-session)
- TurboVNC, websockify, Selkies, XFCE (host display stack)
- Pulling images at session start

## `container-program.json`

Admin Apps and catalog search fields. Optional `binds`, `env`, and `config` are passed through
the host wrapper. App-specific start logic belongs in `session.sh` next to
the SIF, not in a rebuilt image.

```json
{
  "display_name": "Jupyter Data Science",
  "name": "Jupyter Data Science",
  "kind": "interactive",
  "category": "notebook",
  "tags": ["notebook", "python", "r", "julia", "jupyter", "datascience"],
  "arch": "x86_64",
  "minCores": 2,
  "minMemory": 8,
  "minGpus": 0,
  "filePath": "/vault/containers/jupyter-datascience/2026.08/image.sif",
  "connectUrl": "/login",
  "container_args": "jupyter lab --core-mode --config=$CONFIG_FILE",
  "binds": [],
  "env": []
}
```

| JSON key | Meaning |
| --- | --- |
| `kind` | `interactive` (Launch / Admin Apps) or `flow` (editor `apptainer exec`). Recipe `kind: http`/`gui` is the session type; Eureka maps those to interactive. |
| `category` | One primary type for filters: `notebook`, `ide`, `visualization`, `gis`, `statistics`, `machine-learning`, `llm`, `bioinformatics`, `hpc`, `shell`, `web`. |
| `tags` | Extra search tokens (`python`, `gpu`, `blast`, …). |
| `arch` | `x86_64`, `arm64`, or `x86_64,arm64`. Default x86_64. |
| `appVersion` | Upstream software (BLAST 2.12, PyTorch 2.5.1). |
| `version` | Catalog/SIF packaging (2026.09). Pin this in `filePath` and flow specs. Multiple versions of the same app may be on vault at once; Download does not delete the others. |
| `minCores` / `minMemory` / `minGpus` | Launcher and flow instance-type floors (RAM in GB). |

Do not register `kind: flow` SIFs as Launch apps.

Same directory may also have `session.sh` (operator-trusted, run on the
host) and `overlay/` files. Eureka copies those onto `$SCRATCH` at session
start. Changing JSON or `session.sh` does **not** require rebuilding the SIF.

`$SCRATCH` is node-local `/tmp/eureka-container/$JOBID`. `$CS_CONFIG` is a
host-written code-server yaml (same pattern as `$CONFIG_FILE`).

`filePath` is the vault POSIX path. The public HTTPS object is in
`recipe.yaml` (`catalog.https` + `object_prefix`). Copy from catalog
to vault before launch; do not put the HTTPS URL in Admin Apps.
The operator cheat sheet is [LAUNCH.md](LAUNCH.md).
