# Customizing a Single-Node Deployment

This guide explains **which files to edit** and **which `auplc-installer` command to re-run** after you change a single-node install—for example adding a course, changing auth, or rebuilding an image.

The behavior described here matches the installer on the repository **`develop`** branch (`auplc_installer/`).

:::{seealso}
For the initial install path, see [Quick Start](quick-start.md) and [Single-Node Deployment](single-node.md).
:::

## Two Configuration Layers

Helm deploys JupyterHub with two values files merged together:

| File | Who owns it | Purpose |
|------|-------------|---------|
| `runtime/values.yaml` | **You** (checked into git) | Site configuration: auth, ingress, teams, resource catalog, quotas, storage, and any new courses you add. |
| `runtime/values.local.yaml` | **Installer** (auto-generated) | Machine-specific overlay: detected GPU SKU, gfx-tagged image references for GPU resources, and optional course-selection filtering. |

The installer writes `runtime/values.local.yaml` during install and runtime commands. Its header marks it as auto-generated:

```yaml
# Auto-generated by auplc-installer.
# Detected SKU keys : strix
# Product names    : AMD_Radeon_890M_Graphics
# Primary gfx tag  : gfx1150
# Course selection : all (default)
# Regenerated on install/upgrade.
```

**Do not treat `values.local.yaml` as the place to add a new course or change auth.** Edit `runtime/values.yaml` for intentional site changes. The installer **regenerates** the local overlay on `install`, `rt install`, `rt upgrade`, and `rt reinstall`—hand-edits there will be lost.

Helm merge order (later files win on conflicting keys):

```text
runtime/values.yaml  +  runtime/values.local.yaml  →  deployed release
```

## Quick Reference: What To Run After a Change

| You changed… | Edit | Re-run |
|--------------|------|--------|
| Hub settings, auth, ingress, storage class, teams, quotas, spawn options | `runtime/values.yaml` | `./auplc-installer rt upgrade` |
| Hub Python code or Hub Dockerfile | `runtime/hub/…`, `dockerfiles/Hub/…` | `./auplc-installer img build hub` then `./auplc-installer rt reinstall` |
| An existing course image (Dockerfile / course content) | `dockerfiles/Courses/…` | `./auplc-installer img build <target>` then `./auplc-installer rt reinstall` |
| Which **existing** courses are visible / pulled (subset) | — (use CLI flag) | `./auplc-installer rt upgrade --courses=…` (or full `./auplc-installer install --courses=…` if you also need images/K3s) |
| Image registry or tag coordinates | CLI flags | `./auplc-installer rt upgrade --image-registry=… --image-tag=…` |
| Add a **brand-new** course (new spawn option) | See [Adding a new course](#adding-a-new-course) below | `./auplc-installer img build …` then `./auplc-installer rt reinstall` |
| K3s, device plugin, or full stack from scratch | — | `./auplc-installer install` |

Preview a full install plan without touching the cluster:

```bash
./auplc-installer install --dry-run
```

(`--dry-run` applies to `install` only, not to `rt upgrade` or `rt reinstall`.)

## What Each Runtime Command Does

Understanding the split between **values changes** and **image changes** avoids unnecessary full reinstalls.

### `rt upgrade` — config / overlay refresh

Use when you changed `runtime/values.yaml`, want to refresh GPU detection, change `--courses=`, or update `--image-registry` / `--image-tag`.

On every run it:

1. Re-detects GPU SKUs on the node
2. **Regenerates** `runtime/values.local.yaml`
3. Runs `helm upgrade` with `-f runtime/values.yaml -f runtime/values.local.yaml`

It does **not** rebuild or pull container images.

If you run `rt upgrade` **without** `--courses=`, the installer reads the `# Course selection : …` line from the existing overlay and **preserves** your previous course subset (for example `basic`). Pass `--courses=` explicitly to change it.

### `rt reinstall` — pick up new images

Use after `./auplc-installer img build …` or when pods must restart against freshly built local images.

It runs `helm uninstall`, then the same path as `rt install` (regenerate overlay + `helm install` + wait for deployments).

### `install` — full stack

Use for first-time deployment or when you need K3s, the ROCm device plugin, image pull/build stages, and Hub deploy together. Re-running `install` on an existing node is heavier than `rt upgrade` or `rt reinstall`.

## Changing Existing Settings (No New Course)

### Example: switch auth mode or edit team mappings

1. Edit `runtime/values.yaml` (for example `custom.authMode`, `custom.teams.mapping`).
2. Apply:

   ```bash
   ./auplc-installer rt upgrade
   ```

### Example: rebuild after a Dockerfile change

If you changed the Hub or a course image but not `values.yaml`:

```bash
./auplc-installer img build cv          # Makefile targets: hub, cv, dl, llm, physim, base-rocm, base-cpu, …
./auplc-installer rt reinstall
```

`img build` alone does not redeploy; `rt reinstall` removes and redeploys the Helm release so pods pick up the new local images.

### Example: install only base CPU + GPU environments

```bash
./auplc-installer rt upgrade --courses=basic
```

Valid course keys (from `auplc_installer/catalog.py`): `cpu`, `gpu`, `Course-CV`, `Course-DL`, `Course-LLM`, `Course-PhySim`. Presets: `all`, `basic` (`cpu` + `gpu`), `none` (Hub only).

Do not only edit `values.local.yaml` to hide courses—the overlay is regenerated from the installer catalog and your `--courses=` selection.

## Adding a New Course

Adding a spawn-time environment (for example `Course-MyLab`) touches several layers. This is a **developer / site operator** task; the installer does not discover new courses automatically.

`runtime/values.yaml` includes an inline **Course Management Guide** at the top of the file with the same three-step pattern (images → requirements → teams).

### 1. Course image

- Add a Dockerfile under `dockerfiles/Courses/` (follow an existing course such as `DL` or `CV`).
- Add a `make` target in `dockerfiles/Makefile` (for example `my-lab`).

Build and verify:

```bash
./auplc-installer img build my-lab
# or: make -C dockerfiles my-lab GPU_TARGET=gfx1150
```

### 2. Installer catalog (pull / build / TUI course picker)

Add an entry to `COURSE_CATALOG` in `auplc_installer/catalog.py` and extend `BASE_TEAM_MAPPING` if new groups should see the course.

Without this step, `./auplc-installer install --courses=…`, `img build`, and the TUI course picker will not know your course key.

### 3. Overlay mapping (GPU courses only)

For **GPU-tagged** courses, also add the resource key and image basename to `_RESOURCE_IMAGE_BASE` in `auplc_installer/overlay.py`. The overlay emits gfx-suffixed image lines only for keys listed there (`gpu`, `Course-CV`, …).

CPU-only resources (like the built-in `cpu` course) keep plain image tags in `values.yaml` and are not rewritten by the overlay.

### 4. Hub resource catalog

Edit `runtime/values.yaml` under `custom.resources`:

- `images` — image reference (placeholder tag is fine; the overlay rewrites GPU course tags locally)
- `metadata` — display name, description, `acceleratorKeys`, optional `launchMode`
- `requirements` — CPU/RAM/GPU spawn requirements if needed

Add the course key to `custom.teams.mapping` for every group that should see it in the spawn UI.

See [Configuration Reference](../jupyterhub/configuration-reference.md) for field details.

### 5. Deploy

After images exist locally (or in your registry):

```bash
./auplc-installer rt reinstall
```

Run a full `./auplc-installer install` only if you also changed K3s-level settings or need to pull/build images again.

### 6. Verify

```bash
kubectl get pods -n jupyterhub
```

Open the Hub spawn page and confirm the new resource appears for a user in the right group.

## Understanding `values.local.yaml`

The local overlay typically sets:

- `custom.accelerators.<sku>.nodeSelector` (and optional `HSA_OVERRIDE_GFX_VERSION`) from detected GPU product names
- `custom.resources.images` for GPU resources only, with registry tags including `-<gfxNNNN>` suffixes
- `custom.resources.metadata.<resource>.acceleratorKeys` aligned with detected SKUs
- Filtered `custom.teams.mapping` when install or upgrade used `--courses=` other than `all`

If you need different image tags on this machine, prefer CLI flags:

```bash
./auplc-installer rt upgrade --image-tag=develop --image-registry=ghcr.io/myfork
```

rather than hand-editing the overlay.

## Common Mistakes

| Mistake | Better approach |
|---------|-----------------|
| Adding a course only in `values.local.yaml` | Add to `values.yaml`, `catalog.py`, Dockerfile, and (for GPU courses) `overlay.py`; redeploy with `rt reinstall` |
| Running `rt upgrade` after an image rebuild | Use `rt reinstall` so pods restart with new images |
| Running full `install` for every `values.yaml` tweak | Use `rt upgrade` unless you also need K3s, image pull/build, or device-plugin deploy |
| Editing `values.local.yaml` and expecting it to persist | Change `values.yaml` or re-run `rt upgrade` / `install` with the flags you need |
| Expecting `rt upgrade` to preserve a hand-edited course list in the overlay | Pass `--courses=` explicitly, or rely on the preserved `# Course selection` header from the previous install |

## Related Docs

- [Single-Node Deployment](single-node.md) — installer commands and TUI reference
- [JupyterHub Configuration Reference](../jupyterhub/configuration-reference.md) — `custom.resources`, teams, auth
- [Contributing](../contributing/contributing.md) — development workflow and image builds
