Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ jobs:
--set controller.storage.size=4Gi \
--set controller.storage.maxBytes=1073741824 \
--set 'nfs.trustedNodeCIDRs[0]=192.0.2.10/32'
helm lint charts/magpie --strict
5 changes: 3 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ jobs:
--set controller.storage.size=4Gi \
--set controller.storage.maxBytes=1073741824 \
--set 'nfs.trustedNodeCIDRs[0]=192.0.2.10/32'
helm lint charts/magpie --strict
- name: Configure Git
run: |
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
- name: Package chart
run: helm package charts/multica-runtime-controller --destination .cr-release-packages
- name: Package charts
run: helm package charts/multica-runtime-controller charts/magpie --destination .cr-release-packages
- name: Install chart-releaser
shell: bash
run: |
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Korio Helm Charts

Helm charts for running Korio services on Kubernetes.
Helm charts maintained by Korio for running services on Kubernetes.

## Install

Expand All @@ -13,6 +13,7 @@ helm repo update

| Chart | Description |
| --- | --- |
| [magpie](https://github.com/korioinc/helm/tree/main/charts/magpie) | Magpie model gateway with a local management UI and persistent credentials. Defaults to `latest`; set `image.tag` in `values.yaml` (e.g. `"0.1.1275"`) to pin the application version. |
| [multica-runtime-controller](https://github.com/korioinc/helm/tree/main/charts/multica-runtime-controller) | Run Multica agent tasks in dedicated Kubernetes Pods with shared workspace storage. |

Follow the chart's README for prerequisites, installation, provider configuration, and operations. Each chart includes its defaults in `values.yaml` and validation rules in `values.schema.json`.
Expand Down
2 changes: 2 additions & 0 deletions charts/magpie/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.DS_Store
*.tgz
20 changes: 20 additions & 0 deletions charts/magpie/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: v2
name: magpie
description: >-
Magpie model gateway with a local management UI and persistent credentials.
Set the application version directly with image.tag in values.yaml (for example, 0.1.1275); defaults to latest.
type: application
version: 0.1.0
home: https://github.com/yetone/magpie
sources:
- https://github.com/yetone/magpie
- https://github.com/korioinc/helm
maintainers:
- name: jskorlol
email: jskorlol@gmail.com
keywords:
- magpie
- ai
- gateway
annotations:
artifacthub.io/category: ai-machine-learning
237 changes: 237 additions & 0 deletions charts/magpie/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
# Magpie Helm chart

This chart runs a persistent Magpie gateway with a local management UI.
It uses the official `ghcr.io/yetone/magpie` image and defaults to `image.tag: "latest"`.
Magpie releases frequently. Set the application version directly in your `values.yaml` with `image.tag`
(for example, `"0.1.1275"`), independently of the chart version.
The supported architectures are Linux amd64 and arm64.

```mermaid
flowchart LR
Admin[LAN administrator] -->|HTTPS admin hostname| Gate[LAN IP allowlist]
Gate --> UI[Web UI Service]
Client[API client] -->|HTTPS and gateway key| Ingress[API Ingress]
Ingress --> Service[ClusterIP API Service]
Service --> API[Magpie gateway]
UI --> PVC[PVC mounted at /config]
API --> PVC
API -->|Explicit provider/model| Upstream[Registered upstream]
```

The default Web UI binds to `127.0.0.1:3430` inside the Pod and uses a local tunnel.
Enabling `webui.ingress` binds it to `0.0.0.0:3430` behind a separate Service and protected HTTPS hostname.
The diagram shows that optional LAN Ingress mode.
The API uses port `3425` and requires enabled gateway keys for remote requests.
The chart uses Magpie's standard listener ports throughout its templates and first-run settings.

## Install

The chart creates a Web UI Secret with a random 48-character key during installation.
If the default Secret already exists, the chart reuses its key instead of generating a new one.

```sh
helm repo add korioinc https://korioinc.github.io/helm
helm repo update
helm upgrade --install magpie korioinc/magpie --namespace tools-magpie --create-namespace --wait
```

Repository installation requires a published chart release.
Before publication, install from the repository root with `./charts/magpie` instead of `korioinc/magpie`.

To pin the application version, create your own `values.yaml`:

```yaml
image:
tag: "0.1.1275"
```

Pass it with `helm upgrade --install magpie korioinc/magpie --namespace tools-magpie -f values.yaml --wait`.
Only change `image.tag` for application updates; no chart release is needed.
A configured `image.digest` overrides the tag, so clear or replace it when changing versions.

| Setting | Purpose |
| --- | --- |
| `auth.existingSecret` | Empty by default: create or reuse `<fullname>-webui`. Set a name to reference an externally managed Secret without changing it |
| `auth.webKeySecretKey` | Secret field containing the Web UI key |
| `image.repository`, `image.tag`, `image.digest` | Image selection; defaults to `latest`. Set `image.tag` in `values.yaml` (e.g. `"0.1.1275"`) to pin a version; digest takes precedence |
| `persistence.storageClass` | Storage class; empty uses the cluster default, `-` disables dynamic provisioning |
| `persistence.size` | Requested volume size |
| `persistence.existingClaim` | Reuse an existing writable claim instead of creating one |
| `ingress.enabled` | Enable HTTPS access to the model API |
| `ingress.host` | Public hostname; also sets Magpie's advertised API URL |
| `ingress.className`, `ingress.annotations` | Ingress controller configuration |
| `ingress.tlsSecretName` | Existing TLS Secret in the release namespace |
| `webui.ingress.enabled` | Enable LAN administration through Traefik |
| `webui.ingress.host` | Admin hostname, separate from the API hostname |
| `webui.ingress.className`, `webui.ingress.annotations` | Admin Ingress configuration |
| `webui.ingress.tlsSecretName` | Existing TLS Secret for the admin hostname |

The chart defaults to the `latest` image tag and no Ingress. An image tag or digest must remain set.
An enabled Ingress requires a hostname and TLS Secret.
Ingress routes all paths to the API listener, including `/v1`, `/models`, `/muse-code/models`, and `/v1beta`.
Management endpoints stay on the separate Web UI listener.
The optional admin Ingress requires a distinct hostname and an existing Traefik access middleware.
Set `webui.ingress.annotations.traefik.ingress.kubernetes.io/router.middlewares` to the appropriate private-access rule.
The middleware must restrict access to trusted networks; the chart cannot verify its policy.
The chart preserves this annotation and creates no additional middleware or Traefik CRD.
Use LAN DNS pointing to the private Ingress entrypoint, so LAN browsers reach it directly.
API Ingress annotations and paths remain independent of administration.

## Web UI Secret lifecycle

By default, the Secret is `<fullname>-webui` in the release namespace (`magpie-webui` for the commands above).
The key is stored in `auth.webKeySecretKey`, which defaults to `web-key`.
A generated key has 48 random alphanumeric characters. Existing keys must have at least 16 characters
and use only letters, digits, or `-._~`. A missing or invalid key fails installation or upgrade; it is never silently replaced.

The chart reads the existing Secret through Helm `lookup` and preserves its data during upgrades.
A Secret managed by this release is retained on uninstall with `helm.sh/resource-policy: keep`.
Reinstalling with the same release name, namespace, and fullname reuses that key.
A pre-existing unmanaged Secret is only referenced, not adopted or changed.
A Secret owned by another Helm release requires explicit `auth.existingSecret` to avoid accidental sharing.

To use an external Secret, set:

```yaml
auth:
existingSecret: my-magpie-webui
webKeySecretKey: web-key
```

This path does not create, read, validate, or modify the Secret; provide a valid key before the Pod starts.
For older installations that used the fixed `magpie-webui` default with a different release/fullname,
set `auth.existingSecret: magpie-webui` before upgrading to preserve the existing login key.

Automatic reuse requires Helm to access the cluster API and read Secrets in the release namespace.
`helm template` and client-side dry runs cannot look up existing keys and generate a fresh preview key each time.
For offline-rendered GitOps deployments, manage the Secret externally and set `auth.existingSecret`.
Use `--dry-run=server` for a cluster-aware preview; rendered manifests contain credentials, so keep output private.
Helm release history also contains generated credentials. Restrict access to Secrets and release records.
Helm rollback restores stored manifests without `lookup` and can restore an older key after manual rotation.
Use an externally managed Secret when key rotation must remain independent of Helm history.

## Configure providers and clients

With LAN Ingress enabled, open `https://<webui.ingress.host>/?k=<web-key>` from an allowed source network.
With the default loopback mode, start a tunnel on the administrator's computer:

```sh
kubectl --namespace tools-magpie port-forward deployment/magpie 3430:3430 --address 127.0.0.1
```

Retrieve the Web UI key (use the Secret name and field shown in Helm NOTES if overridden):

```sh
kubectl --namespace tools-magpie get secret magpie-webui \
-o jsonpath='{.data.web-key}' | base64 --decode
```

For loopback mode, open `http://127.0.0.1:3430/?k=<web-key>`.
Use the release's actual deployment name when it differs.

1. Add providers or supported subscription accounts in the Web UI.
2. Register vLLM or CLIProxyAPI as custom OpenAI-compatible providers when needed.
3. Use each upstream's reachable URL, such as its Kubernetes Service DNS name.
4. Refresh the model list and select the models to expose.
5. Create a named gateway key for each API client under **Gateway → Gateway keys**.
6. Request the full `provider/model` ID returned by `/v1/models`.

The chart creates no gateway keys. A new installation rejects all remote API requests until a key exists.
Web UI keys and API gateway keys are independent.

New installations disable automatic routing groups and automatic image description or image-model selection.
The chart seeds `settings.json` and `providers.json` only when each file is absent.
It preserves subsequent Web UI changes, account credentials, provider configuration, and named gateway keys.
Do not enable provider fallback lists, routing groups, model remapping, or vendor `Auto` models for fixed model selection.
An administrator can change these policies in the Web UI; the chart does not lock application settings.
An existing or restored volume keeps its policies. Review them before reconnecting clients.

For Codex OAuth, copy the final `http://localhost:1455/auth/callback?...` URL into the Web UI's callback field.
The browser's failed localhost redirect is expected when the callback listener runs inside Kubernetes.
No public OAuth callback route is required. Other provider login flows require their own verification.

The official image does not contain Claude Code. Claude subscription forwarding requires an image that includes it.
An Anthropic API-key provider or a suitable external upstream does not require that local executable.
When using a custom image, set its repository and tag and clear or replace `image.digest`.
The image must retain `/magpie`, `/bin/sh`, and writable HOME/XDG directories under `/config`.

## Verify after deployment

Use the public endpoint for authentication checks. API port-forwarding reaches loopback and bypasses remote authentication.
With sharing enabled, the first request below should return `401`:

```sh
curl -i https://magpie.example.com/v1/models
curl --fail-with-body https://magpie.example.com/v1/models \
-H "Authorization: Bearer $MAGPIE_GATEWAY_KEY"
curl --fail-with-body https://magpie.example.com/v1/chat/completions \
-H "Authorization: Bearer $MAGPIE_GATEWAY_KEY" \
-H 'Content-Type: application/json' \
-d '{"model":"<provider>/<model>","messages":[{"role":"user","content":"Hello"}],"stream":true}'
```

Use `https://<host>/v1` for OpenAI-compatible clients, including Codex Responses requests.
Use `https://<host>` for Anthropic-compatible clients, including Claude Code.
Client model catalogs and protocol support still need verification with the actual client and upstream.
Test provider selection, upstream errors without model substitution, streaming, tool calls, and restart persistence.

## Storage, updates, and access

The Pod runs as UID/GID `65532`, with `fsGroup: 65532` for the claim.
`/config` holds accounts, API credentials, plugins, catalogs, and application state.
Back up that volume and restrict access to it. Claim annotations retain newly created storage on Helm uninstall.
An existing claim must be writable by the container user. Local storage remains tied to its Kubernetes node.

The deployment uses one replica and `Recreate` updates to avoid concurrent file writers.
Updates cause downtime and can interrupt active streams; Magpie's Web mode does not guarantee stream draining.
Do not run two releases against the same claim. Retained claims require explicit adoption or `existingClaim` on reinstall.
First-run settings disable application auto-update, and the read-only root filesystem protects the installed binary.
Change `image.tag` in your `values.yaml` when updating the application (for example, `"0.1.1275"`).
A configured digest takes precedence over the tag.
`Chart.yaml` `version` identifies the chart package; it does not select the application image.
The chart omits `appVersion` because application versions are selected independently.

The default `image.pullPolicy: Always` resolves `latest` whenever a Pod starts.
It does not update running Pods, and a Helm upgrade with unchanged values does not trigger a restart.
Use `kubectl --namespace tools-magpie rollout restart deployment/magpie` to fetch a newer `latest` image.
A mutable tag can change on any Pod restart; pin a release tag or digest for reproducible deployments.
Restart the deployment after rotating the Web UI Secret.

The chart explicitly clears the image's `MAGPIE_ADDR` override.
Sharing then controls the API listener and its remote authentication.
Disabling sharing closes external API access.
With admin Ingress enabled, readiness checks the gateway's loopback health, keeping the admin Service available.
With loopback-only administration, API TCP readiness still removes unreachable public endpoints.
Keep the Gateway port in Settings at its default `3425`; changing it in the Web UI disconnects the Service.
For a restored volume with another Gateway port, set it back through the Web UI.

Ingress must forward to the Pod IP through the API Service.
A proxy in the same Pod that forwards through loopback would bypass native gateway authentication.
Magpie prints a Web UI login link during startup, so treat application logs as sensitive.
Its printed network link may use `MAGPIE_PUBLIC_URL`, which belongs to the API host.
Use the admin hostname from Helm NOTES or your `values.yaml` for browser login.
The chart does not enable request-body logging or telemetry.

Configure streaming timeouts in the chosen Ingress controller. Avoid response-buffering middleware.
This chart does not change shared controller settings.

## Verification commands

```sh
# Run from the korioinc/helm repository root.
helm lint ./charts/magpie --strict
helm template magpie ./charts/magpie --namespace tools-magpie
helm package ./charts/magpie --destination /tmp
```

Add `-f values.yaml` to lint or render your installation overrides.

## Upstream evidence

The original chart implementation was checked against Magpie `0.1.1024`, commit `528d6c12dc11558f55aa00a39c7ba988c83520eb`.
The installed image is selected by installation values. The links below record the original runtime review.

- [Docker image and filesystem layout](https://github.com/yetone/magpie/blob/528d6c12dc11558f55aa00a39c7ba988c83520eb/Dockerfile)
- [Listener selection and remote authentication](https://github.com/yetone/magpie/blob/528d6c12dc11558f55aa00a39c7ba988c83520eb/internal/gateway/lan.go)
- [Automatic routing groups](https://github.com/yetone/magpie/blob/528d6c12dc11558f55aa00a39c7ba988c83520eb/internal/provider/group.go)
- [Headless Web UI and OAuth callback instructions](https://github.com/yetone/magpie/blob/528d6c12dc11558f55aa00a39c7ba988c83520eb/docs/reference.md#docker)
17 changes: 17 additions & 0 deletions charts/magpie/scripts/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/sh
set -eu
umask 077

mkdir -p /config/magpie
# Seed new installations only. Keep Web UI changes and restored credentials intact.
for name in settings.json providers.json; do
target="/config/magpie/$name"
if [ ! -e "$target" ]; then
temporary=$(mktemp "$target.XXXXXX")
cat "/bootstrap/$name" > "$temporary"
chmod 600 "$temporary"
mv "$temporary" "$target"
fi
done

exec /magpie "$@"
24 changes: 24 additions & 0 deletions charts/magpie/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{- if .Values.webui.ingress.enabled }}
LAN Web UI: https://{{ .Values.webui.ingress.host }}/?k=<web-key>
Access middleware: {{ index .Values.webui.ingress.annotations "traefik.ingress.kubernetes.io/router.middlewares" }}
LAN DNS must resolve the admin hostname to the private Ingress entrypoint.
{{- else }}
The Web UI listens on Pod loopback only. Open a local tunnel:

kubectl --namespace {{ .Release.Namespace }} port-forward deployment/{{ include "magpie.fullname" . }} {{ include "magpie.webuiPort" . }}:{{ include "magpie.webuiPort" . }} --address 127.0.0.1

Open http://127.0.0.1:{{ include "magpie.webuiPort" . }}/?k=<web-key>.
{{- end }}

Retrieve the Web UI key from Secret {{ include "magpie.webuiSecretName" . }}, field {{ .Values.auth.webKeySecretKey }}:

kubectl --namespace {{ .Release.Namespace }} get secret {{ include "magpie.webuiSecretName" . }} -o go-template='{{ "{{ index .data " }}{{ .Values.auth.webKeySecretKey | quote }}{{ " | base64decode }}" }}'

Keep this key private. It is separate from API gateway keys.

Create named gateway keys in the Web UI before connecting external API clients.
Remote API requests without an enabled gateway key are denied.
{{- if .Values.ingress.enabled }}
OpenAI-compatible API base URL: https://{{ .Values.ingress.host }}/v1
Anthropic-compatible API base URL: https://{{ .Values.ingress.host }}
{{- end }}
46 changes: 46 additions & 0 deletions charts/magpie/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{{- define "magpie.apiPort" -}}3425{{- end -}}
{{- define "magpie.webuiPort" -}}3430{{- end -}}

{{- define "magpie.webuiName" -}}
{{- printf "%s-webui" (include "magpie.fullname" . | trunc 57 | trimSuffix "-") -}}
{{- end -}}

{{- define "magpie.webuiSecretName" -}}
{{- default (include "magpie.webuiName" .) .Values.auth.existingSecret -}}
{{- end -}}

{{- define "magpie.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{- define "magpie.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else if contains (include "magpie.name" .) .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name (include "magpie.name" .) | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}

{{- define "magpie.selectorLabels" -}}
app.kubernetes.io/name: {{ include "magpie.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}

{{- define "magpie.labels" -}}
{{ include "magpie.selectorLabels" . }}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | quote }}
{{- if .Values.image.tag }}
app.kubernetes.io/version: {{ .Values.image.tag | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}

{{- define "magpie.image" -}}
{{- if .Values.image.digest -}}
{{- printf "%s@%s" .Values.image.repository .Values.image.digest -}}
{{- else -}}
{{- printf "%s:%s" .Values.image.repository (required "Set image.tag or image.digest in the installation values" .Values.image.tag) -}}
{{- end -}}
{{- end -}}
Loading
Loading