From 1d064dc871f9d5762f9a7d9dc76613c01f71769f Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sun, 6 Sep 2026 04:02:05 -0700 Subject: [PATCH 01/11] feat(helm): publish the chart to GHCR as a signed OCI artifact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chart has only ever existed inside the repo, so self-hosters whose security process requires an officially published artifact could not use it at all — a chart pulled from a git checkout is not something they can pin, mirror, or admit. Publishes helm/sim to oci://ghcr.io/simstudioai/charts/sim on push to main, signed with Sigstore keyless signing and carrying a SLSA build provenance attestation, both stored in the registry so they survive a mirror into an internal registry. The publish job lives in helm.yml rather than its own publish-*.yml so it can gate on the existing jobs: nothing ships unless the chart linted, unit-tested, rendered clean under kubeconform, and installed on kind. Publishing is idempotent — a version already in the registry is skipped rather than overwritten, since a published chart version is immutable. Also pushes helm/artifacthub-repo.yml to the reserved artifacthub.io tag for the Artifact Hub listing, and switches the chart README and the Kubernetes docs page off the clone-and-install path. --- .github/workflows/helm.yml | 149 ++++++++++++++++++ .../docs/platform/self-hosting/kubernetes.mdx | 35 ++-- helm/artifacthub-repo.yml | 22 +++ helm/sim/Chart.yaml | 2 +- helm/sim/README.md | 62 +++++++- 5 files changed, 257 insertions(+), 13 deletions(-) create mode 100644 helm/artifacthub-repo.yml diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index ac6298ed8d6..1e99a2b0804 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -5,6 +5,8 @@ on: branches: [main, staging, dev] paths: - 'helm/sim/**' + # Repository-level Artifact Hub metadata, republished by the publish job. + - 'helm/artifacthub-repo.yml' - '.github/workflows/helm.yml' # The image inventory is generated from the chart and checked here, so a # change to its generator has to run this workflow too. @@ -14,6 +16,8 @@ on: branches: [main, staging, dev] paths: - 'helm/sim/**' + # Repository-level Artifact Hub metadata, republished by the publish job. + - 'helm/artifacthub-repo.yml' - '.github/workflows/helm.yml' # The image inventory is generated from the chart and checked here, so a # change to its generator has to run this workflow too. @@ -181,3 +185,148 @@ jobs: - name: Run helm test run: helm test sim --namespace sim --timeout 5m + + # Publishes the chart to GHCR as an OCI artifact. Self-hosters cannot admit a + # chart pulled from a git checkout — they need an immutable, versioned artifact + # they can pin by digest and mirror into an internal registry — so shipping the + # chart in-repo only is the same as not shipping it. + # + # Lives here rather than in a `publish-*.yml` of its own so it can gate on the + # jobs above: nothing is published unless the chart linted, unit-tested, + # rendered clean under kubeconform, and actually installed on a kind cluster. + # A separate workflow would race those instead of waiting for them. + publish: + name: Publish chart to GHCR + needs: [chart, install] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'simstudioai/sim' + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} + timeout-minutes: 15 + permissions: + contents: read + packages: write + # Sigstore signs against the runner's OIDC identity; no key material is stored. + id-token: write + attestations: write + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + + - name: Set up Helm + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 + with: + version: v3.16.4 + + # oras also reads ~/.docker/config.json, so this one login covers both the + # chart push and the Artifact Hub metadata push below. + - name: Login to GHCR + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Package chart + id: package + run: | + set -euo pipefail + name=$(helm show chart helm/sim | awk '/^name:/ {print $2}') + version=$(helm show chart helm/sim | awk '/^version:/ {print $2}') + helm package helm/sim --destination dist + echo "name=${name}" >> "$GITHUB_OUTPUT" + echo "version=${version}" >> "$GITHUB_OUTPUT" + echo "path=dist/${name}-${version}.tgz" >> "$GITHUB_OUTPUT" + echo "repository=ghcr.io/${GITHUB_REPOSITORY_OWNER}/charts/${name}" >> "$GITHUB_OUTPUT" + + # Chart versions are immutable once published: whoever pinned a version + # must keep resolving the same bytes forever. The PR gate above + # already forces a version bump on every chart change, so a version that + # is already in the registry means this commit changed something outside + # `helm/sim/` — republishing would either fail or silently move a tag. + - name: Skip if this version is already published + id: exists + run: | + set -euo pipefail + if helm show chart "oci://${{ steps.package.outputs.repository }}" \ + --version "${{ steps.package.outputs.version }}" > /dev/null 2>&1; then + echo "already=true" >> "$GITHUB_OUTPUT" + echo "::notice::${{ steps.package.outputs.name }} ${{ steps.package.outputs.version }} is already published; skipping." + else + echo "already=false" >> "$GITHUB_OUTPUT" + fi + + # `helm push` takes the namespace only — it derives the repository + # basename from the chart's name and the tag from its version, so the + # result is ghcr.io//charts/sim:. + - name: Push chart + id: push + if: steps.exists.outputs.already == 'false' + run: | + set -euo pipefail + output=$(helm push "${{ steps.package.outputs.path }}" \ + "oci://ghcr.io/${GITHUB_REPOSITORY_OWNER}/charts" 2>&1) + echo "$output" + digest=$(echo "$output" | grep -oE 'sha256:[a-f0-9]{64}' | head -1 || true) + if [ -z "$digest" ]; then + echo "::error::helm push did not report a digest; refusing to sign an unidentified artifact" + exit 1 + fi + echo "digest=${digest}" >> "$GITHUB_OUTPUT" + + - name: Install Cosign + if: steps.exists.outputs.already == 'false' + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + + # Signed by digest, never by tag: a tag is a mutable pointer, so signing + # one would attest to whatever it happens to reference later. The verify + # is not ceremony — it fails the run if the signature we just wrote cannot + # be read back with the identity we expect, which is the whole point of + # publishing a signature at all. + - name: Sign and verify chart + if: steps.exists.outputs.already == 'false' + run: | + set -euo pipefail + ref="${{ steps.package.outputs.repository }}@${{ steps.push.outputs.digest }}" + cosign sign --yes "$ref" + cosign verify "$ref" \ + --certificate-identity-regexp "^https://github.com/${GITHUB_REPOSITORY}/" \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com + + # Stored alongside the chart so a mirrored registry carries the + # attestation with it, rather than only being retrievable from GitHub. + - name: Attest build provenance + if: steps.exists.outputs.already == 'false' + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-name: ${{ steps.package.outputs.repository }} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true + + - name: Set up ORAS + uses: oras-project/setup-oras@1d808f7d7f6995cc68b7bf507bfe5c5446e1dc9d # v2.0.1 + + # Artifact Hub reads repository metadata from the reserved `artifacthub.io` + # tag on the chart's own OCI repository. Pushed on every run, including + # version-skip runs, so an edit to the metadata file alone still lands. + - name: Publish Artifact Hub metadata + run: | + set -euo pipefail + oras push "${{ steps.package.outputs.repository }}:artifacthub.io" \ + --config /dev/null:application/vnd.cncf.artifacthub.config.v1+yaml \ + helm/artifacthub-repo.yml:application/vnd.cncf.artifacthub.repository-metadata.layer.v1.yaml + + - name: Summary + run: | + { + if [ "${{ steps.exists.outputs.already }}" = "true" ]; then + echo "### Chart ${{ steps.package.outputs.version }} was already published — nothing to do" + else + echo "### Published chart ${{ steps.package.outputs.version }}" + echo + echo "Digest: \`${{ steps.push.outputs.digest }}\`" + fi + echo + echo '```bash' + echo "helm install sim oci://${{ steps.package.outputs.repository }} --version ${{ steps.package.outputs.version }}" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx index 29772c1cacc..b714eabe3b8 100644 --- a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx +++ b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx @@ -21,10 +21,11 @@ import { FAQ } from '@/components/ui/faq' ## Installation -```bash -# Clone repo -git clone https://github.com/simstudioai/sim.git && cd sim +The chart is published to GitHub Container Registry as an OCI artifact at +`oci://ghcr.io/simstudioai/charts/sim`. Install it directly — no clone, and no +`helm repo add`. +```bash # Generate secrets BETTER_AUTH_SECRET=$(openssl rand -hex 32) ENCRYPTION_KEY=$(openssl rand -hex 32) @@ -34,7 +35,8 @@ CRON_SECRET=$(openssl rand -hex 32) POSTGRES_PASSWORD=$(openssl rand -hex 24) # Install -helm install sim ./helm/sim \ +helm install sim oci://ghcr.io/simstudioai/charts/sim \ + --version 1.9.1 \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ @@ -56,13 +58,28 @@ helm install sim ./helm/sim \ This installs the chart's default image tag. For production, **pin `app`, `realtime`, and `migrations` to the same explicit release tag** — see [Upgrades](/platform/self-hosting/upgrades). +## Verifying the chart + +Every published version is signed with Sigstore keyless signing and carries a SLSA build-provenance attestation. Both live in the registry alongside the chart, so they survive a mirror into an internal registry. + +```bash +cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.1 \ + --certificate-identity-regexp '^https://github.com/simstudioai/sim/' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com + +gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.1 --repo simstudioai/sim +``` + +Signing is Sigstore-only — there is no GPG `.prov` file, so `helm install --verify` does not apply. + ## Cloud-Specific Values These are cloud-tuned **alternatives** to the generic install above — pick one path, don't run both. The commands reuse the `$BETTER_AUTH_SECRET`, `$ENCRYPTION_KEY`, `$INTERNAL_API_SECRET`, `$API_ENCRYPTION_KEY`, `$CRON_SECRET`, and `$POSTGRES_PASSWORD` variables generated in [Installation](#installation) above, so run that block's `openssl` lines first in the same shell. They use `helm upgrade --install`, so they work whether or not a release exists yet. Two caveats when converting an existing generic install rather than starting fresh: (1) **reuse the original secret values** — recover them with `helm get values sim -n simstudio` if your shell no longer has them; supplying a newly generated `ENCRYPTION_KEY` makes every previously encrypted value (workspace environment variables, stored provider keys, MCP OAuth credentials) undecryptable. (2) The cloud values rename the bundled PostgreSQL database to `simstudio`, but Postgres only applies that setting on first initialization — add `--set postgresql.auth.database=sim` to keep your existing database. If you'd rather start clean, `helm uninstall sim -n simstudio`, delete its PVCs, and run the cloud command fresh. ```bash -helm upgrade --install sim ./helm/sim \ - --values ./helm/sim/examples/values-aws.yaml \ +helm upgrade --install sim oci://ghcr.io/simstudioai/charts/sim \ + --version 1.9.1 \ + --values https://raw.githubusercontent.com/simstudioai/sim/main/helm/sim/examples/values-aws.yaml \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ @@ -80,7 +97,7 @@ helm upgrade --install sim ./helm/sim \ Every one of those overrides is required. The cloud values files hardcode a placeholder domain in all six places, and overriding only `NEXT_PUBLIC_APP_URL` leaves sign-in pointed at the placeholder, realtime rejecting every socket upgrade, and the Ingress serving the wrong host. -Swap the `--values` file for your cloud: `values-aws.yaml` (EKS), `values-azure.yaml` (AKS), or `values-gcp.yaml` (GKE). Everything else is identical. +Swap the `--values` file for your cloud: `values-aws.yaml` (EKS), `values-azure.yaml` (AKS), or `values-gcp.yaml` (GKE). Everything else is identical. Helm reads `--values` over HTTPS, so this works without a checkout; the example files are not part of the packaged chart. Download the file and edit it locally if you'd rather not fetch it at install time. ## Key Configuration @@ -181,8 +198,8 @@ kubectl port-forward deployment/sim-app 3000:3000 -n simstudio # View logs kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100 -# Upgrade -helm upgrade sim ./helm/sim --namespace simstudio +# Upgrade (always pin the target chart version) +helm upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.1 --namespace simstudio # Uninstall helm uninstall sim --namespace simstudio diff --git a/helm/artifacthub-repo.yml b/helm/artifacthub-repo.yml new file mode 100644 index 00000000000..47a578b80a0 --- /dev/null +++ b/helm/artifacthub-repo.yml @@ -0,0 +1,22 @@ +# Artifact Hub repository metadata for the Sim chart. +# +# Lives outside `helm/sim/` on purpose: it describes the *repository* rather +# than the chart, so it must not be packaged into the `.tgz`, and a change to it +# must not trip the chart's mandatory Chart.yaml version bump. +# +# The publish job pushes this file to the OCI registry under the reserved +# `artifacthub.io` tag, which is where Artifact Hub looks for it: +# ghcr.io/simstudioai/charts/sim:artifacthub.io +# +# `owners` is what backs an ownership claim — Artifact Hub matches the email of +# the requesting account against this list, and processes claims immediately +# rather than waiting for the next repository scan. +owners: + - name: Sim Team + email: help@sim.ai + +# Set this to the repository's Artifact Hub ID once the repository has been +# registered at https://artifacthub.io/control-panel/repositories. It is what +# turns on the "Verified Publisher" badge; until then the listing still works, +# just unverified. +# repositoryID: "" diff --git a/helm/sim/Chart.yaml b/helm/sim/Chart.yaml index b3d1464925c..80914ca33f9 100644 --- a/helm/sim/Chart.yaml +++ b/helm/sim/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: sim description: A Helm chart for Sim - the open-source AI workspace where teams build, deploy, and manage AI agents type: application -version: 1.9.0 +version: 1.9.1 appVersion: "v0.8.18" kubeVersion: ">=1.25.0-0" home: https://sim.ai diff --git a/helm/sim/README.md b/helm/sim/README.md index 54ded4b28af..c3b06bc1dc8 100644 --- a/helm/sim/README.md +++ b/helm/sim/README.md @@ -2,6 +2,7 @@ Deploy [Sim](https://sim.ai) — the open-source AI workspace where teams build, deploy, and manage AI agents — on Kubernetes. +* **Registry:** `oci://ghcr.io/simstudioai/charts/sim` * **Chart version:** see `Chart.yaml` * **App version:** tracks the upstream Sim release * **Kubernetes:** 1.25+ @@ -19,8 +20,8 @@ export INTERNAL_API_SECRET=$(openssl rand -hex 32) export CRON_SECRET=$(openssl rand -hex 32) export POSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=') -# Install from this repository -helm install sim ./helm/sim \ +# Install from the registry +helm install sim oci://ghcr.io/simstudioai/charts/sim \ --namespace sim --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -96,7 +97,42 @@ If you set `app.secrets.existingSecret.enabled=true` and point at a pre-created ## Installing the chart -### From this repository +### From the registry + +The chart is published to GitHub Container Registry as an OCI artifact. This is +the supported install path — no clone, no `helm repo add`, and every version is +immutable once published. + +```bash +# List the published versions +helm show chart oci://ghcr.io/simstudioai/charts/sim --version 1.9.1 + +helm install sim oci://ghcr.io/simstudioai/charts/sim \ + --version 1.9.1 \ + --namespace sim --create-namespace \ + --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ + --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ + --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ + --set app.env.CRON_SECRET="$CRON_SECRET" \ + --set postgresql.auth.password="$POSTGRES_PASSWORD" +``` + +Always pass `--version`. Without it Helm resolves to the newest published +version at install time, which makes the same command produce different +deployments on different days. + +To mirror the chart into an internal registry — the usual requirement for an +air-gapped or internal-only cluster: + +```bash +helm pull oci://ghcr.io/simstudioai/charts/sim --version 1.9.1 +helm push sim-1.9.1.tgz oci://registry.internal.example.com/charts +``` + +The container images the chart references are listed in +[`images.yaml`](./images.yaml); mirror those alongside it. + +### From a checkout ```bash helm install sim ./helm/sim \ @@ -132,6 +168,26 @@ helm install sim ./helm/sim --dry-run --debug \ --- +## Verifying the chart + +Every published version is signed with [Sigstore](https://www.sigstore.dev/) +keyless signing and carries a SLSA build-provenance attestation, both stored in +the registry next to the chart so they survive a mirror. + +```bash +# The signature: proves this chart was signed by a GitHub Actions run in this repo +cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.1 \ + --certificate-identity-regexp '^https://github.com/simstudioai/sim/' \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com + +# The provenance: proves which workflow, commit, and runner produced it +gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.1 --repo simstudioai/sim +``` + +There is no GPG `.prov` file — signing is Sigstore-only, so there is no +long-lived private key to hold or rotate. `helm install --verify` expects the +GPG provenance format and will not work; use `cosign verify` above. + ## Upgrading ```bash From 41b71eb6839d1bb164f15e1ccf262bbbd319b1f7 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sun, 6 Sep 2026 04:13:19 -0700 Subject: [PATCH 02/11] fix(helm): fail closed on registry lookup errors and pin documented installs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The publish guard treated every non-zero `helm show chart` exit as proof the version was unpublished, so a transient 5xx, an expired token, or a DNS blip would have enabled a push that moves an already-published version tag. Runs where the version already exists are routine — the path filter also fires on package.json and workflow edits — so this was reachable. Now only an explicit `: not found` counts as absent; anything else stops the job. Verified against the pinned Helm v3.16.4 that an absent version and an absent repository both report `: not found`, so a first publish still proceeds, while denied/unauthorized/dial-tcp failures abort instead. Also pins the two documented install paths that were still reproducible only by accident: the cloud-specific command paired a pinned chart with values fetched from the moving main branch, and the README quick start omitted --version while the same README warns against exactly that. --- .github/workflows/helm.yml | 29 ++++++++++++++----- .../docs/platform/self-hosting/kubernetes.mdx | 10 +++++-- helm/sim/README.md | 1 + 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index 1e99a2b0804..6cb0d11435a 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -239,20 +239,35 @@ jobs: echo "repository=ghcr.io/${GITHUB_REPOSITORY_OWNER}/charts/${name}" >> "$GITHUB_OUTPUT" # Chart versions are immutable once published: whoever pinned a version - # must keep resolving the same bytes forever. The PR gate above - # already forces a version bump on every chart change, so a version that - # is already in the registry means this commit changed something outside - # `helm/sim/` — republishing would either fail or silently move a tag. + # must keep resolving the same bytes forever. The PR gate above already + # forces a version bump on every chart change, so a version that is + # already in the registry means this commit changed something outside + # `helm/sim/`. + # + # The lookup must fail CLOSED. Treating every non-zero exit as "absent" + # would let a transient 5xx, an expired token, or a DNS blip re-push an + # existing version and move a tag consumers have already pinned — and + # same-version runs are routine, since the path filter also fires on + # `package.json` and workflow edits. + # + # Verified against the pinned Helm (v3.16.4): an absent version AND an + # absent repository both report `: not found`, so a first publish + # still proceeds, while `denied`, `unauthorized`, and `dial tcp` failures + # do not match and stop the job instead. - name: Skip if this version is already published id: exists run: | set -euo pipefail - if helm show chart "oci://${{ steps.package.outputs.repository }}" \ - --version "${{ steps.package.outputs.version }}" > /dev/null 2>&1; then + if err=$(helm show chart "oci://${{ steps.package.outputs.repository }}" \ + --version "${{ steps.package.outputs.version }}" 2>&1 >/dev/null); then echo "already=true" >> "$GITHUB_OUTPUT" echo "::notice::${{ steps.package.outputs.name }} ${{ steps.package.outputs.version }} is already published; skipping." - else + elif printf '%s\n' "$err" | grep -q ': not found'; then echo "already=false" >> "$GITHUB_OUTPUT" + else + printf '%s\n' "$err" + echo "::error::Could not determine whether ${{ steps.package.outputs.name }} ${{ steps.package.outputs.version }} is already published. Refusing to push, because an unchecked push can overwrite a published version." + exit 1 fi # `helm push` takes the namespace only — it derives the repository diff --git a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx index b714eabe3b8..50422a45b15 100644 --- a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx +++ b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx @@ -77,9 +77,15 @@ Signing is Sigstore-only — there is no GPG `.prov` file, so `helm install --ve These are cloud-tuned **alternatives** to the generic install above — pick one path, don't run both. The commands reuse the `$BETTER_AUTH_SECRET`, `$ENCRYPTION_KEY`, `$INTERNAL_API_SECRET`, `$API_ENCRYPTION_KEY`, `$CRON_SECRET`, and `$POSTGRES_PASSWORD` variables generated in [Installation](#installation) above, so run that block's `openssl` lines first in the same shell. They use `helm upgrade --install`, so they work whether or not a release exists yet. Two caveats when converting an existing generic install rather than starting fresh: (1) **reuse the original secret values** — recover them with `helm get values sim -n simstudio` if your shell no longer has them; supplying a newly generated `ENCRYPTION_KEY` makes every previously encrypted value (workspace environment variables, stored provider keys, MCP OAuth credentials) undecryptable. (2) The cloud values rename the bundled PostgreSQL database to `simstudio`, but Postgres only applies that setting on first initialization — add `--set postgresql.auth.database=sim` to keep your existing database. If you'd rather start clean, `helm uninstall sim -n simstudio`, delete its PVCs, and run the cloud command fresh. ```bash +# The example values files are not part of the packaged chart, so fetch the one +# you want at a release tag — pinning the chart but reading values off a moving +# branch would still make this command produce different deployments over time. +SIM_RELEASE=v0.8.24 +curl -fsSLO "https://raw.githubusercontent.com/simstudioai/sim/$SIM_RELEASE/helm/sim/examples/values-aws.yaml" + helm upgrade --install sim oci://ghcr.io/simstudioai/charts/sim \ --version 1.9.1 \ - --values https://raw.githubusercontent.com/simstudioai/sim/main/helm/sim/examples/values-aws.yaml \ + --values values-aws.yaml \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ @@ -97,7 +103,7 @@ helm upgrade --install sim oci://ghcr.io/simstudioai/charts/sim \ Every one of those overrides is required. The cloud values files hardcode a placeholder domain in all six places, and overriding only `NEXT_PUBLIC_APP_URL` leaves sign-in pointed at the placeholder, realtime rejecting every socket upgrade, and the Ingress serving the wrong host. -Swap the `--values` file for your cloud: `values-aws.yaml` (EKS), `values-azure.yaml` (AKS), or `values-gcp.yaml` (GKE). Everything else is identical. Helm reads `--values` over HTTPS, so this works without a checkout; the example files are not part of the packaged chart. Download the file and edit it locally if you'd rather not fetch it at install time. +Swap the `--values` file for your cloud: `values-aws.yaml` (EKS), `values-azure.yaml` (AKS), or `values-gcp.yaml` (GKE). Everything else is identical. Keep the downloaded file in your own config repo — the `--set` overrides above cover the six placeholder domains, but anything else you tune belongs in the file. ## Key Configuration diff --git a/helm/sim/README.md b/helm/sim/README.md index c3b06bc1dc8..91e252c0e1c 100644 --- a/helm/sim/README.md +++ b/helm/sim/README.md @@ -22,6 +22,7 @@ export POSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=') # Install from the registry helm install sim oci://ghcr.io/simstudioai/charts/sim \ + --version 1.9.1 \ --namespace sim --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ From 163c917ab4a92cdd57f477e6e705f5e6dc2e14be Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sun, 6 Sep 2026 23:06:29 -0700 Subject: [PATCH 03/11] chore(helm): harden the chart workflow against zizmor and document cosign v3 Ran the two standard workflow linters over helm.yml. actionlint reported one shellcheck style issue; zizmor reported 22 findings, including two High. Both High findings predate this PR: the version gate expanded github.base_ref straight into a run block. The three Medium findings were checkouts leaving the token in .git/config. Fixed all of them here rather than leaving known findings in a file this PR already rewrites -- the gate only reads history and fetches a public branch, so it never needed the credential. The 16 informational findings were mine: every ${{ }} in a run block is template expansion before the shell sees it, so the documented fix is to pass values through env and reference them as shell variables. Done for the whole publish job, which also removes the shellcheck nit. zizmor now reports zero findings on the file in pedantic mode, with no suppressions, and actionlint is clean. Separately, cosign v3 writes the Sigstore protobuf bundle format by default and cosign v2 cannot read it, so the verification instructions now state the required version. The in-workflow verify uses the same binary it signs with, so it could never have caught this. Re-verified the existence guard's four branches after the env refactor, and confirmed it also fails closed when the registry login has not happened. --- .github/workflows/helm.yml | 81 ++++++++++++------- .../docs/platform/self-hosting/kubernetes.mdx | 4 + helm/sim/README.md | 2 + 3 files changed, 59 insertions(+), 28 deletions(-) diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index 6cb0d11435a..b27a11745d3 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -38,6 +38,8 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false - name: Set up Helm uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 @@ -129,11 +131,16 @@ jobs: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 0 + # The version gate only reads history and fetches a public branch, so + # it never needs the token left behind in .git/config. + persist-credentials: false - name: Require a Chart.yaml version bump when chart content changes + env: + BASE_REF: ${{ github.base_ref }} run: | set -euo pipefail - base="origin/${{ github.base_ref }}" - git fetch origin "${{ github.base_ref }}" + base="origin/${BASE_REF}" + git fetch origin "${BASE_REF}" merge_base=$(git merge-base "$base" HEAD) changed=$(git diff --name-only "$merge_base" HEAD) if echo "$changed" | grep -q '^helm/sim/'; then @@ -155,6 +162,8 @@ jobs: timeout-minutes: 25 steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false - name: Set up Helm uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 @@ -202,11 +211,10 @@ jobs: runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 15 permissions: - contents: read - packages: write - # Sigstore signs against the runner's OIDC identity; no key material is stored. - id-token: write - attestations: write + contents: read # Read the chart source. + packages: write # Push the chart, its signature, and its attestations to GHCR. + id-token: write # Sigstore signs against the runner's OIDC identity; no key material is stored. + attestations: write # Let actions/attest-build-provenance record the SLSA provenance. steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: @@ -230,13 +238,16 @@ jobs: id: package run: | set -euo pipefail - name=$(helm show chart helm/sim | awk '/^name:/ {print $2}') - version=$(helm show chart helm/sim | awk '/^version:/ {print $2}') + chart=$(helm show chart helm/sim) + name=$(printf '%s\n' "$chart" | awk '/^name:/ {print $2}') + version=$(printf '%s\n' "$chart" | awk '/^version:/ {print $2}') helm package helm/sim --destination dist - echo "name=${name}" >> "$GITHUB_OUTPUT" - echo "version=${version}" >> "$GITHUB_OUTPUT" - echo "path=dist/${name}-${version}.tgz" >> "$GITHUB_OUTPUT" - echo "repository=ghcr.io/${GITHUB_REPOSITORY_OWNER}/charts/${name}" >> "$GITHUB_OUTPUT" + { + echo "name=${name}" + echo "version=${version}" + echo "path=dist/${name}-${version}.tgz" + echo "repository=ghcr.io/${GITHUB_REPOSITORY_OWNER}/charts/${name}" + } >> "$GITHUB_OUTPUT" # Chart versions are immutable once published: whoever pinned a version # must keep resolving the same bytes forever. The PR gate above already @@ -256,17 +267,20 @@ jobs: # do not match and stop the job instead. - name: Skip if this version is already published id: exists + env: + REPOSITORY: ${{ steps.package.outputs.repository }} + NAME: ${{ steps.package.outputs.name }} + VERSION: ${{ steps.package.outputs.version }} run: | set -euo pipefail - if err=$(helm show chart "oci://${{ steps.package.outputs.repository }}" \ - --version "${{ steps.package.outputs.version }}" 2>&1 >/dev/null); then + if err=$(helm show chart "oci://${REPOSITORY}" --version "${VERSION}" 2>&1 >/dev/null); then echo "already=true" >> "$GITHUB_OUTPUT" - echo "::notice::${{ steps.package.outputs.name }} ${{ steps.package.outputs.version }} is already published; skipping." + echo "::notice::${NAME} ${VERSION} is already published; skipping." elif printf '%s\n' "$err" | grep -q ': not found'; then echo "already=false" >> "$GITHUB_OUTPUT" else printf '%s\n' "$err" - echo "::error::Could not determine whether ${{ steps.package.outputs.name }} ${{ steps.package.outputs.version }} is already published. Refusing to push, because an unchecked push can overwrite a published version." + echo "::error::Could not determine whether ${NAME} ${VERSION} is already published. Refusing to push, because an unchecked push can overwrite a published version." exit 1 fi @@ -276,12 +290,13 @@ jobs: - name: Push chart id: push if: steps.exists.outputs.already == 'false' + env: + CHART_PATH: ${{ steps.package.outputs.path }} run: | set -euo pipefail - output=$(helm push "${{ steps.package.outputs.path }}" \ - "oci://ghcr.io/${GITHUB_REPOSITORY_OWNER}/charts" 2>&1) - echo "$output" - digest=$(echo "$output" | grep -oE 'sha256:[a-f0-9]{64}' | head -1 || true) + output=$(helm push "${CHART_PATH}" "oci://ghcr.io/${GITHUB_REPOSITORY_OWNER}/charts" 2>&1) + printf '%s\n' "$output" + digest=$(printf '%s\n' "$output" | grep -oE 'sha256:[a-f0-9]{64}' | head -1 || true) if [ -z "$digest" ]; then echo "::error::helm push did not report a digest; refusing to sign an unidentified artifact" exit 1 @@ -299,9 +314,12 @@ jobs: # publishing a signature at all. - name: Sign and verify chart if: steps.exists.outputs.already == 'false' + env: + REPOSITORY: ${{ steps.package.outputs.repository }} + DIGEST: ${{ steps.push.outputs.digest }} run: | set -euo pipefail - ref="${{ steps.package.outputs.repository }}@${{ steps.push.outputs.digest }}" + ref="${REPOSITORY}@${DIGEST}" cosign sign --yes "$ref" cosign verify "$ref" \ --certificate-identity-regexp "^https://github.com/${GITHUB_REPOSITORY}/" \ @@ -324,24 +342,31 @@ jobs: # tag on the chart's own OCI repository. Pushed on every run, including # version-skip runs, so an edit to the metadata file alone still lands. - name: Publish Artifact Hub metadata + env: + REPOSITORY: ${{ steps.package.outputs.repository }} run: | set -euo pipefail - oras push "${{ steps.package.outputs.repository }}:artifacthub.io" \ + oras push "${REPOSITORY}:artifacthub.io" \ --config /dev/null:application/vnd.cncf.artifacthub.config.v1+yaml \ helm/artifacthub-repo.yml:application/vnd.cncf.artifacthub.repository-metadata.layer.v1.yaml - name: Summary + env: + ALREADY: ${{ steps.exists.outputs.already }} + REPOSITORY: ${{ steps.package.outputs.repository }} + VERSION: ${{ steps.package.outputs.version }} + DIGEST: ${{ steps.push.outputs.digest }} run: | { - if [ "${{ steps.exists.outputs.already }}" = "true" ]; then - echo "### Chart ${{ steps.package.outputs.version }} was already published — nothing to do" + if [ "${ALREADY}" = "true" ]; then + echo "### Chart ${VERSION} was already published — nothing to do" else - echo "### Published chart ${{ steps.package.outputs.version }}" + echo "### Published chart ${VERSION}" echo - echo "Digest: \`${{ steps.push.outputs.digest }}\`" + echo "Digest: \`${DIGEST}\`" fi echo echo '```bash' - echo "helm install sim oci://${{ steps.package.outputs.repository }} --version ${{ steps.package.outputs.version }}" + echo "helm install sim oci://${REPOSITORY} --version ${VERSION}" echo '```' } >> "$GITHUB_STEP_SUMMARY" diff --git a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx index 50422a45b15..2217039f86c 100644 --- a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx +++ b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx @@ -72,6 +72,10 @@ gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.1 --repo simstudi Signing is Sigstore-only — there is no GPG `.prov` file, so `helm install --verify` does not apply. + + Verification requires **cosign v3.0 or newer**. Signatures use the Sigstore protobuf bundle format, which cosign v3 writes by default and cosign v2 cannot read. cosign v3.1+ auto-detects both formats. + + ## Cloud-Specific Values These are cloud-tuned **alternatives** to the generic install above — pick one path, don't run both. The commands reuse the `$BETTER_AUTH_SECRET`, `$ENCRYPTION_KEY`, `$INTERNAL_API_SECRET`, `$API_ENCRYPTION_KEY`, `$CRON_SECRET`, and `$POSTGRES_PASSWORD` variables generated in [Installation](#installation) above, so run that block's `openssl` lines first in the same shell. They use `helm upgrade --install`, so they work whether or not a release exists yet. Two caveats when converting an existing generic install rather than starting fresh: (1) **reuse the original secret values** — recover them with `helm get values sim -n simstudio` if your shell no longer has them; supplying a newly generated `ENCRYPTION_KEY` makes every previously encrypted value (workspace environment variables, stored provider keys, MCP OAuth credentials) undecryptable. (2) The cloud values rename the bundled PostgreSQL database to `simstudio`, but Postgres only applies that setting on first initialization — add `--set postgresql.auth.database=sim` to keep your existing database. If you'd rather start clean, `helm uninstall sim -n simstudio`, delete its PVCs, and run the cloud command fresh. diff --git a/helm/sim/README.md b/helm/sim/README.md index 91e252c0e1c..5b5a0876dad 100644 --- a/helm/sim/README.md +++ b/helm/sim/README.md @@ -189,6 +189,8 @@ There is no GPG `.prov` file — signing is Sigstore-only, so there is no long-lived private key to hold or rotate. `helm install --verify` expects the GPG provenance format and will not work; use `cosign verify` above. +> **Requires cosign v3.0 or newer.** Signatures use the Sigstore protobuf bundle format, which cosign v3 writes by default and cosign v2 cannot read. cosign v3.1+ auto-detects both formats. + ## Upgrading ```bash From 3c0d190c54035133b60bea7c37a8775095224695 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 7 Sep 2026 10:09:15 -0700 Subject: [PATCH 04/11] fix(helm): push Artifact Hub metadata from helm/ so the layer title matches Dry-ran the publish sequence against a local OCI registry, which surfaced one divergence from Artifact Hub's documented command: passing a path-qualified argument to oras records the layer's title annotation as `helm/artifacthub-repo.yml` rather than the bare `artifacthub-repo.yml` the docs produce. Artifact Hub selects the layer by media type, so this was probably harmless, but there is no reason to diverge from the documented form. Running the step from helm/ reproduces it exactly. Everything else in the sequence behaved as intended against a real registry: helm push derives charts/sim:1.9.1 from the chart name and version, the digest grep extracts it, the metadata lands with both documented media types under the artifacthub.io tag, the chart and metadata tags coexist without disturbing each other, and helm pull round-trips to an identical sha256. Also confirmed the overwrite hazard behind the fail-closed guard is real rather than theoretical: re-pushing modified content under an existing version moved the tag, so a pinned consumer would have silently received different bytes. --- .github/workflows/helm.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index b27a11745d3..ccc84a78b97 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -344,11 +344,15 @@ jobs: - name: Publish Artifact Hub metadata env: REPOSITORY: ${{ steps.package.outputs.repository }} + # Run from `helm/` so the layer's title annotation is the bare + # `artifacthub-repo.yml`, matching Artifact Hub's documented command. A + # path-qualified argument records `helm/artifacthub-repo.yml` instead. + working-directory: helm run: | set -euo pipefail oras push "${REPOSITORY}:artifacthub.io" \ --config /dev/null:application/vnd.cncf.artifacthub.config.v1+yaml \ - helm/artifacthub-repo.yml:application/vnd.cncf.artifacthub.repository-metadata.layer.v1.yaml + artifacthub-repo.yml:application/vnd.cncf.artifacthub.repository-metadata.layer.v1.yaml - name: Summary env: From 2b3312024adf6edd10f220311c971cb3efef83a3 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 7 Sep 2026 10:14:03 -0700 Subject: [PATCH 05/11] fix(helm): categorise the chart for Artifact Hub The chart carried `category: developer-tools`, which is a different convention entirely -- Artifact Hub reads `artifacthub.io/category` and accepts only one value from a fixed list that has no developer-tools entry. The listing this PR sets up would therefore have published uncategorised. Adds `artifacthub.io/category: ai-machine-learning`, the closest fit in that list, and keeps the bare annotation for whatever else consumes it. Bumps to 1.9.2 per the chart's own gate and moves the documented versions with it. Deliberately not adding `artifacthub.io/images`: Artifact Hub already extracts images from a dry-run render, and a hand-listed copy would be a second source of truth next to the generated images.yaml. --- .../docs/platform/self-hosting/kubernetes.mdx | 10 +++++----- helm/sim/Chart.yaml | 5 ++++- helm/sim/README.md | 14 +++++++------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx index 2217039f86c..8e714e145c8 100644 --- a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx +++ b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx @@ -36,7 +36,7 @@ POSTGRES_PASSWORD=$(openssl rand -hex 24) # Install helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.1 \ + --version 1.9.2 \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ @@ -63,11 +63,11 @@ helm install sim oci://ghcr.io/simstudioai/charts/sim \ Every published version is signed with Sigstore keyless signing and carries a SLSA build-provenance attestation. Both live in the registry alongside the chart, so they survive a mirror into an internal registry. ```bash -cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.1 \ +cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.2 \ --certificate-identity-regexp '^https://github.com/simstudioai/sim/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com -gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.1 --repo simstudioai/sim +gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.2 --repo simstudioai/sim ``` Signing is Sigstore-only — there is no GPG `.prov` file, so `helm install --verify` does not apply. @@ -88,7 +88,7 @@ SIM_RELEASE=v0.8.24 curl -fsSLO "https://raw.githubusercontent.com/simstudioai/sim/$SIM_RELEASE/helm/sim/examples/values-aws.yaml" helm upgrade --install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.1 \ + --version 1.9.2 \ --values values-aws.yaml \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -209,7 +209,7 @@ kubectl port-forward deployment/sim-app 3000:3000 -n simstudio kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100 # Upgrade (always pin the target chart version) -helm upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.1 --namespace simstudio +helm upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.2 --namespace simstudio # Uninstall helm uninstall sim --namespace simstudio diff --git a/helm/sim/Chart.yaml b/helm/sim/Chart.yaml index 80914ca33f9..49714fb94a8 100644 --- a/helm/sim/Chart.yaml +++ b/helm/sim/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: sim description: A Helm chart for Sim - the open-source AI workspace where teams build, deploy, and manage AI agents type: application -version: 1.9.1 +version: 1.9.2 appVersion: "v0.8.18" kubeVersion: ">=1.25.0-0" home: https://sim.ai @@ -21,6 +21,9 @@ keywords: - nextjs annotations: category: developer-tools + # Artifact Hub only accepts one value from its fixed list; the bare `category` + # above is a different convention and does not categorise the listing. + artifacthub.io/category: ai-machine-learning artifacthub.io/license: Apache-2.0 artifacthub.io/links: | - name: Homepage diff --git a/helm/sim/README.md b/helm/sim/README.md index 5b5a0876dad..c1c197d1b86 100644 --- a/helm/sim/README.md +++ b/helm/sim/README.md @@ -22,7 +22,7 @@ export POSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=') # Install from the registry helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.1 \ + --version 1.9.2 \ --namespace sim --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -106,10 +106,10 @@ immutable once published. ```bash # List the published versions -helm show chart oci://ghcr.io/simstudioai/charts/sim --version 1.9.1 +helm show chart oci://ghcr.io/simstudioai/charts/sim --version 1.9.2 helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.1 \ + --version 1.9.2 \ --namespace sim --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -126,8 +126,8 @@ To mirror the chart into an internal registry — the usual requirement for an air-gapped or internal-only cluster: ```bash -helm pull oci://ghcr.io/simstudioai/charts/sim --version 1.9.1 -helm push sim-1.9.1.tgz oci://registry.internal.example.com/charts +helm pull oci://ghcr.io/simstudioai/charts/sim --version 1.9.2 +helm push sim-1.9.2.tgz oci://registry.internal.example.com/charts ``` The container images the chart references are listed in @@ -177,12 +177,12 @@ the registry next to the chart so they survive a mirror. ```bash # The signature: proves this chart was signed by a GitHub Actions run in this repo -cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.1 \ +cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.2 \ --certificate-identity-regexp '^https://github.com/simstudioai/sim/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com # The provenance: proves which workflow, commit, and runner produced it -gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.1 --repo simstudioai/sim +gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.2 --repo simstudioai/sim ``` There is no GPG `.prov` file — signing is Sigstore-only, so there is no From 4352ce7579a53ef7a7549c32c25e627cc1fc02ab Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 7 Sep 2026 10:26:20 -0700 Subject: [PATCH 06/11] feat(helm): also publish the chart to a classic HTTP repo I claimed earlier that OCI-only was defensible in 2026. That was wrong, and checking rather than reasoning settled it: Bitnami, cert-manager, ingress-nginx, prometheus-community, Grafana, Argo and external-secrets all still serve a live index.yaml. Dual-publish is the actual convention, and external-secrets -- the workflow this one is modelled on -- does both. Adds a chart-releaser job that maintains index.yaml on a pages branch and attaches each packaged chart to a GitHub release. It is a separate job from the OCI publish because it needs contents: write to cut that release, and that permission has no business sitting in the job that holds the signing identity. Three details worth stating: - mark_as_latest is false. A chart release must never take the "Latest" badge from the application release it packages. - Releases are named helm-chart- so they stay distinguishable from the vX.Y.Z app releases in the same list. - skip_existing mirrors the OCI job: re-running on a published version is a no-op rather than an overwrite. The job no-ops with a warning until a gh-pages branch exists, because creating that branch and enabling Pages are one-time manual steps no workflow can perform for itself, and main's CI must not fail on their absence. No documentation yet. index.yaml is only reachable once the branch, Pages, and DNS are in place, and pointing users at a hostname that does not resolve is the exact failure this whole PR exists to remove. --- .github/workflows/helm.yml | 70 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index ccc84a78b97..e5b293dedd3 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -374,3 +374,73 @@ jobs: echo "helm install sim oci://${REPOSITORY} --version ${VERSION}" echo '```' } >> "$GITHUB_STEP_SUMMARY" + + # The classic HTTP repo, published alongside the OCI artifact above. Both is + # what the ecosystem actually does: Bitnami, cert-manager, ingress-nginx, + # prometheus-community, Grafana, Argo and external-secrets all still serve an + # index.yaml, because plenty of clusters, GitOps configs and mirroring tools + # only speak `helm repo add`. OCI is the modern path, not yet the only one. + # + # Separate from the OCI job on purpose: chart-releaser needs `contents: write` + # to cut a release and push the index, and there is no reason to hand that to + # the job holding the signing identity. + publish-http: + name: Publish chart to the Helm repo + needs: [chart, install] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'simstudioai/sim' + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} + timeout-minutes: 15 + permissions: + contents: write # Cut the chart release and push index.yaml to the pages branch. + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + # chart-releaser diffs against the previous tag to decide which charts + # changed, so it needs the full history rather than a shallow clone. + fetch-depth: 0 + # chart-releaser authenticates with CR_TOKEN, not the checkout credential. + persist-credentials: false + + # Creating the pages branch and turning on GitHub Pages are one-time + # manual steps that no workflow can do for itself. Skip loudly rather than + # failing main when they have not happened yet -- the OCI publish is + # independent and must not be held hostage to this. + - name: Check the pages branch exists + id: pages + run: | + set -euo pipefail + if git ls-remote --exit-code --heads origin gh-pages >/dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + echo "::warning::No gh-pages branch, so the HTTP chart repo was not updated. Create it and point GitHub Pages at it to activate this job. The OCI publish is unaffected." + fi + + - name: Configure Git + if: steps.pages.outputs.exists == 'true' + env: + ACTOR: ${{ github.actor }} + run: | + set -euo pipefail + git config user.name "${ACTOR}" + git config user.email "${ACTOR}@users.noreply.github.com" + + # chart-releaser writes index.yaml to the pages branch and attaches the + # .tgz to a GitHub release, which is where index.yaml points -- so the + # packages stay reachable no matter which domain serves the index. + - name: Run chart-releaser + if: steps.pages.outputs.exists == 'true' + uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f # v1.7.0 + with: + charts_dir: helm + # Re-running on an already-released version must be a no-op, the same + # way the OCI publish above refuses to move a published version. + skip_existing: true + # A chart release must never take the "Latest" badge from the + # application release it packages. + mark_as_latest: false + env: + CR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Keeps chart releases visually distinct from the vX.Y.Z app releases + # they share the list with. + CR_RELEASE_NAME_TEMPLATE: "helm-chart-{{ .Version }}" From 52eaa9a4f60334a48cccad9de04c6922b6f52f31 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 7 Sep 2026 10:39:10 -0700 Subject: [PATCH 07/11] docs(helm): document the Helm repository now that charts.sim.ai serves Held these back until the hostname actually resolved, because shipping an install command that points at a dead domain is the exact failure this work exists to remove. It resolves now: DNS is in place, GitHub Pages serves the branch, and Let's Encrypt has issued the certificate. Verified end to end rather than assumed: helm repo add sim https://charts.sim.ai helm repo update # Successfully got an update Presents OCI as the preferred path in both places and the repository as the compatibility path, and says plainly that the signature and provenance are attached to the OCI artifact only -- `helm repo add` has no equivalent, and leaving that implicit would let someone believe they had verified something they had not. Bumps to 1.9.3 per the chart's own gate, since this touches helm/sim/. --- .../docs/platform/self-hosting/kubernetes.mdx | 38 +++++++++++++++---- helm/sim/Chart.yaml | 2 +- helm/sim/README.md | 38 +++++++++++++++---- 3 files changed, 63 insertions(+), 15 deletions(-) diff --git a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx index 8e714e145c8..c8cb5ed50ee 100644 --- a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx +++ b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx @@ -22,8 +22,12 @@ import { FAQ } from '@/components/ui/faq' ## Installation The chart is published to GitHub Container Registry as an OCI artifact at -`oci://ghcr.io/simstudioai/charts/sim`. Install it directly — no clone, and no -`helm repo add`. +`oci://ghcr.io/simstudioai/charts/sim`. Install it directly — no clone required. + +It is also available from a classic Helm repository at `https://charts.sim.ai` +for tooling that expects one; see [Helm repository](#helm-repository) below. +Prefer OCI where you can, since the signature and provenance are attached to the +OCI artifact. ```bash # Generate secrets @@ -36,7 +40,7 @@ POSTGRES_PASSWORD=$(openssl rand -hex 24) # Install helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.2 \ + --version 1.9.3 \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ @@ -58,16 +62,36 @@ helm install sim oci://ghcr.io/simstudioai/charts/sim \ This installs the chart's default image tag. For production, **pin `app`, `realtime`, and `migrations` to the same explicit release tag** — see [Upgrades](/platform/self-hosting/upgrades). +## Helm repository + +For clusters or GitOps configs that consume `helm repo add` rather than OCI: + +```bash +helm repo add sim https://charts.sim.ai +helm repo update + +helm install sim sim/sim --version 1.9.3 --namespace simstudio --create-namespace \ + --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ + --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ + --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ + --set app.env.API_ENCRYPTION_KEY="$API_ENCRYPTION_KEY" \ + --set app.env.CRON_SECRET="$CRON_SECRET" \ + --set postgresql.auth.password="$POSTGRES_PASSWORD" +``` + +It serves the same chart as the OCI registry. The signature and provenance below +apply to the OCI artifact only. + ## Verifying the chart Every published version is signed with Sigstore keyless signing and carries a SLSA build-provenance attestation. Both live in the registry alongside the chart, so they survive a mirror into an internal registry. ```bash -cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.2 \ +cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.3 \ --certificate-identity-regexp '^https://github.com/simstudioai/sim/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com -gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.2 --repo simstudioai/sim +gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.3 --repo simstudioai/sim ``` Signing is Sigstore-only — there is no GPG `.prov` file, so `helm install --verify` does not apply. @@ -88,7 +112,7 @@ SIM_RELEASE=v0.8.24 curl -fsSLO "https://raw.githubusercontent.com/simstudioai/sim/$SIM_RELEASE/helm/sim/examples/values-aws.yaml" helm upgrade --install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.2 \ + --version 1.9.3 \ --values values-aws.yaml \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -209,7 +233,7 @@ kubectl port-forward deployment/sim-app 3000:3000 -n simstudio kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100 # Upgrade (always pin the target chart version) -helm upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.2 --namespace simstudio +helm upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.3 --namespace simstudio # Uninstall helm uninstall sim --namespace simstudio diff --git a/helm/sim/Chart.yaml b/helm/sim/Chart.yaml index 49714fb94a8..c1f0e67b93f 100644 --- a/helm/sim/Chart.yaml +++ b/helm/sim/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: sim description: A Helm chart for Sim - the open-source AI workspace where teams build, deploy, and manage AI agents type: application -version: 1.9.2 +version: 1.9.3 appVersion: "v0.8.18" kubeVersion: ">=1.25.0-0" home: https://sim.ai diff --git a/helm/sim/README.md b/helm/sim/README.md index c1c197d1b86..0a552c0db3d 100644 --- a/helm/sim/README.md +++ b/helm/sim/README.md @@ -3,6 +3,7 @@ Deploy [Sim](https://sim.ai) — the open-source AI workspace where teams build, deploy, and manage AI agents — on Kubernetes. * **Registry:** `oci://ghcr.io/simstudioai/charts/sim` +* **Helm repository:** `https://charts.sim.ai` * **Chart version:** see `Chart.yaml` * **App version:** tracks the upstream Sim release * **Kubernetes:** 1.25+ @@ -22,7 +23,7 @@ export POSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=') # Install from the registry helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.2 \ + --version 1.9.3 \ --namespace sim --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -106,10 +107,10 @@ immutable once published. ```bash # List the published versions -helm show chart oci://ghcr.io/simstudioai/charts/sim --version 1.9.2 +helm show chart oci://ghcr.io/simstudioai/charts/sim --version 1.9.3 helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.2 \ + --version 1.9.3 \ --namespace sim --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -126,13 +127,36 @@ To mirror the chart into an internal registry — the usual requirement for an air-gapped or internal-only cluster: ```bash -helm pull oci://ghcr.io/simstudioai/charts/sim --version 1.9.2 -helm push sim-1.9.2.tgz oci://registry.internal.example.com/charts +helm pull oci://ghcr.io/simstudioai/charts/sim --version 1.9.3 +helm push sim-1.9.3.tgz oci://registry.internal.example.com/charts ``` The container images the chart references are listed in [`images.yaml`](./images.yaml); mirror those alongside it. +### From the Helm repository + +The chart is also published to a classic Helm repository, for clusters and +tooling that consume `helm repo add` rather than OCI. + +```bash +helm repo add sim https://charts.sim.ai +helm repo update + +helm install sim sim/sim \ + --version 1.9.3 \ + --namespace sim --create-namespace \ + --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ + --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ + --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ + --set app.env.CRON_SECRET="$CRON_SECRET" \ + --set postgresql.auth.password="$POSTGRES_PASSWORD" +``` + +Both paths serve the same chart. Prefer OCI where you can: the signature and +provenance described in [Verifying the chart](#verifying-the-chart) are attached +to the OCI artifact, and `helm repo add` has no equivalent. + ### From a checkout ```bash @@ -177,12 +201,12 @@ the registry next to the chart so they survive a mirror. ```bash # The signature: proves this chart was signed by a GitHub Actions run in this repo -cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.2 \ +cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.3 \ --certificate-identity-regexp '^https://github.com/simstudioai/sim/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com # The provenance: proves which workflow, commit, and runner produced it -gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.2 --repo simstudioai/sim +gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.3 --repo simstudioai/sim ``` There is no GPG `.prov` file — signing is Sigstore-only, so there is no From 3df1903c76022ea90ee5a6793084d6fa29b59dda Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 7 Sep 2026 10:48:04 -0700 Subject: [PATCH 08/11] docs(self-hosting): install from the published chart, not a checkout The Terraform page still told readers the chart "is not published to a Helm repository or an OCI registry, so there is no repository to point at", and had them vendor the repo as a submodule or git clone. That is now false, and it is the same claim that makes a self-hoster conclude Sim is not packaged for them, so it is the most important line in the docs to correct. It now points repository at the OCI registry with a pinned version, notes the classic repository as the alternative for tooling that does not speak OCI, and replaces the "because the chart is local, version does nothing" paragraph -- pinning the chart version is now both possible and the thing that stops an unplanned apply pulling new migrations. Syntax checked against the provider docs; the page's existing set_sensitive list form is already v3. Also switches the two upgrade commands and the env render check off ./helm/sim, which no longer exists for anyone following the install path. Deliberately unchanged, because these are not install references: - The chart README links on the Kubernetes page. The README lives in the repo; charts.sim.ai serves index.yaml, not documentation. - The cosign --certificate-identity-regexp. That is a Sigstore OIDC identity that happens to look like a URL, and rewriting it would break verification. - The releases page and issue tracker links, which point where they should. - The git clone in the quick start, which is the Docker Compose path and genuinely does need the repo. --- .../self-hosting/environment-variables.mdx | 2 +- .../self-hosting/reference-architectures.mdx | 15 +++++++++++---- .../docs/platform/self-hosting/upgrades.mdx | 5 +++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx b/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx index 8dbcf1ba31a..6caf10394b8 100644 --- a/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx +++ b/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx @@ -311,7 +311,7 @@ Setting the variable to an empty string does **not** remove it: the chart reads Null the variable in every layer that sets it. If it appears in both `app.env` and `app.envDefaults`, nulling only the `app.env` entry lets the `envDefaults` value apply again and the limit stays in force. With External Secrets, also drop the key from `externalSecrets.remoteRefs.app`, which keeps syncing it independently. Confirm what the pod will actually receive before rolling out: ```bash -helm template sim ./helm/sim -f values.yaml | grep -A1 FREE_TABLE # expect no output +helm template sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.3 -f values.yaml | grep -A1 FREE_TABLE # expect no output ``` `null` deletion has no effect under `helm upgrade --reuse-values` — pass your full values with `-f`, or use `--reset-then-reuse-values` (Helm 3.14+). If you deploy with Argo CD, put the `null` in `valueFiles` or the `values` string rather than `valuesObject`, which strips nulls. On Docker Compose, delete the line from your `.env` file. diff --git a/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx b/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx index 0f3242e5995..4c0c31eac92 100644 --- a/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx +++ b/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx @@ -139,7 +139,7 @@ See [Security](/platform/self-hosting/security) for the full secret inventory, w ## Calling the chart from Terraform -If you already run Terraform, the chart is the resource to wrap — not something to reimplement. It is not published to a Helm repository or an OCI registry, so there is no `repository` to point at: vendor this repo as a submodule, a release tarball, or a `git clone` in your pipeline, and give `chart` the local path. +If you already run Terraform, the chart is the resource to wrap — not something to reimplement. Point `repository` at the OCI registry and pin `version`; there is no need to vendor the repo. ```hcl resource "helm_release" "sim" { @@ -147,8 +147,15 @@ resource "helm_release" "sim" { namespace = "sim" create_namespace = true - # Local path, not a repository. Pin the git ref you vendor from. - chart = "${path.module}/sim/helm/sim" + # Always pin `version`. Without it Terraform resolves the newest published + # chart at apply time, which is how an unplanned apply moves Sim to a new + # release with new migrations. + repository = "oci://ghcr.io/simstudioai/charts" + chart = "sim" + version = "1.9.3" + + # Or the classic repository, if your tooling does not speak OCI: + # repository = "https://charts.sim.ai" # Your own values file. The examples under helm/sim/examples/ carry # placeholder secrets and are starting points, not deployable as-is. @@ -172,6 +179,6 @@ resource "helm_release" "sim" { The example values files ship literal placeholders such as `your-secure-production-auth-secret-here`. That includes `postgresql.auth.password`. The chart only rejects empty values and its own `CHANGE-ME` strings, so a deployment that inherits those placeholders installs cleanly with a publicly known session-signing secret and database password. Override every secret, or use External Secrets and set none of them inline. -Because the chart is local, `version` does nothing — what pins it is the git ref you vendor from, and `helm/sim/Chart.yaml` tells you which chart release that ref carries. Pin that ref, and pin the image tags separately, or an unplanned `terraform apply` can move Sim to a new release with new migrations. See [Upgrades](/platform/self-hosting/upgrades). +Pin `version` above, and pin the image tags separately — the chart version and the application version move independently, so pinning one does not pin the other. See [Upgrades](/platform/self-hosting/upgrades). Once the infrastructure exists, follow [Kubernetes](/platform/self-hosting/kubernetes) for the install itself, then the [pre-launch checklist](/platform/self-hosting/security) and the [verification checklist](/platform/self-hosting/verify). diff --git a/apps/docs/content/docs/platform/self-hosting/upgrades.mdx b/apps/docs/content/docs/platform/self-hosting/upgrades.mdx index 7fdf3f9bfe7..bac0c5b4f1b 100644 --- a/apps/docs/content/docs/platform/self-hosting/upgrades.mdx +++ b/apps/docs/content/docs/platform/self-hosting/upgrades.mdx @@ -156,7 +156,8 @@ Migration surprises are usually data-shaped rather than schema-shaped, so a stag ```bash -helm upgrade sim ./helm/sim \ +helm upgrade sim oci://ghcr.io/simstudioai/charts/sim \ + --version 1.9.3 \ --namespace simstudio \ --values my-values.yaml ``` @@ -164,7 +165,7 @@ helm upgrade sim ./helm/sim \ Preview first if the chart version changed: ```bash -helm diff upgrade sim ./helm/sim -n simstudio --values my-values.yaml +helm diff upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.3 -n simstudio --values my-values.yaml ``` Then watch the rollout: From cde982db713ea13532b5ccce83e59a69ade146a9 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 7 Sep 2026 10:55:37 -0700 Subject: [PATCH 09/11] docs(helm): audit the chart documentation against the chart Swept the remaining checkout-era references and audited the Helm docs by checking every values path they mention against values.yaml rather than reading them. That found a real error: the chart has no `copilot.env`. The map is `copilot.server.env`, so anyone following the env-validation note would have set a key that silently does nothing. Fixed. It also flagged `app.envDefaults.FREE_TABLES_LIMIT` and `app.secrets.existingSecret.keys`, which are NOT bugs -- both appear only in upgrade notes describing keys older releases shipped and this one removed. Left exactly as they are; "fixing" them would delete the history an upgrader needs. The chart README was the bigger problem. It led with registry install after the earlier commits, then used `./helm/sim` in eleven subsequent commands -- upgrade, dry-run, examples, parameters, secrets, troubleshooting. Someone following the documented install had no such path. They now use the OCI reference with a pinned version, with one note establishing that convention so the checkout form does not have to be repeated everywhere. Two related corrections while in there: the example values files are not part of the packaged chart, so the examples section now fetches one at a release tag instead of assuming it locally; and values.schema.json cannot be `cat`ed without a checkout, so it links to the repo or `helm pull --untar`. Left pointing at GitHub on purpose: the chart README deep links (that is where the README lives, and an extra hop through the repository root would be worse), the releases page, the issue tracker, the cosign identity regexp, and the Docker Compose quick start's git clone. Bumps to 1.9.4 per the chart's own gate. --- .../self-hosting/environment-variables.mdx | 2 +- .../docs/platform/self-hosting/kubernetes.mdx | 12 ++-- .../self-hosting/reference-architectures.mdx | 2 +- .../docs/platform/self-hosting/upgrades.mdx | 4 +- helm/sim/Chart.yaml | 2 +- helm/sim/README.md | 58 +++++++++++-------- 6 files changed, 46 insertions(+), 34 deletions(-) diff --git a/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx b/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx index 6caf10394b8..9dff32abb55 100644 --- a/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx +++ b/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx @@ -311,7 +311,7 @@ Setting the variable to an empty string does **not** remove it: the chart reads Null the variable in every layer that sets it. If it appears in both `app.env` and `app.envDefaults`, nulling only the `app.env` entry lets the `envDefaults` value apply again and the limit stays in force. With External Secrets, also drop the key from `externalSecrets.remoteRefs.app`, which keeps syncing it independently. Confirm what the pod will actually receive before rolling out: ```bash -helm template sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.3 -f values.yaml | grep -A1 FREE_TABLE # expect no output +helm template sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 -f values.yaml | grep -A1 FREE_TABLE # expect no output ``` `null` deletion has no effect under `helm upgrade --reuse-values` — pass your full values with `-f`, or use `--reset-then-reuse-values` (Helm 3.14+). If you deploy with Argo CD, put the `null` in `valueFiles` or the `values` string rather than `valuesObject`, which strips nulls. On Docker Compose, delete the line from your `.env` file. diff --git a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx index c8cb5ed50ee..ef384eac77c 100644 --- a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx +++ b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx @@ -40,7 +40,7 @@ POSTGRES_PASSWORD=$(openssl rand -hex 24) # Install helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.3 \ + --version 1.9.4 \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ @@ -70,7 +70,7 @@ For clusters or GitOps configs that consume `helm repo add` rather than OCI: helm repo add sim https://charts.sim.ai helm repo update -helm install sim sim/sim --version 1.9.3 --namespace simstudio --create-namespace \ +helm install sim sim/sim --version 1.9.4 --namespace simstudio --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ @@ -87,11 +87,11 @@ apply to the OCI artifact only. Every published version is signed with Sigstore keyless signing and carries a SLSA build-provenance attestation. Both live in the registry alongside the chart, so they survive a mirror into an internal registry. ```bash -cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.3 \ +cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.4 \ --certificate-identity-regexp '^https://github.com/simstudioai/sim/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com -gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.3 --repo simstudioai/sim +gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.4 --repo simstudioai/sim ``` Signing is Sigstore-only — there is no GPG `.prov` file, so `helm install --verify` does not apply. @@ -112,7 +112,7 @@ SIM_RELEASE=v0.8.24 curl -fsSLO "https://raw.githubusercontent.com/simstudioai/sim/$SIM_RELEASE/helm/sim/examples/values-aws.yaml" helm upgrade --install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.3 \ + --version 1.9.4 \ --values values-aws.yaml \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -233,7 +233,7 @@ kubectl port-forward deployment/sim-app 3000:3000 -n simstudio kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100 # Upgrade (always pin the target chart version) -helm upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.3 --namespace simstudio +helm upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 --namespace simstudio # Uninstall helm uninstall sim --namespace simstudio diff --git a/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx b/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx index 4c0c31eac92..1c6338d4670 100644 --- a/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx +++ b/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx @@ -152,7 +152,7 @@ resource "helm_release" "sim" { # release with new migrations. repository = "oci://ghcr.io/simstudioai/charts" chart = "sim" - version = "1.9.3" + version = "1.9.4" # Or the classic repository, if your tooling does not speak OCI: # repository = "https://charts.sim.ai" diff --git a/apps/docs/content/docs/platform/self-hosting/upgrades.mdx b/apps/docs/content/docs/platform/self-hosting/upgrades.mdx index bac0c5b4f1b..e5126334ee2 100644 --- a/apps/docs/content/docs/platform/self-hosting/upgrades.mdx +++ b/apps/docs/content/docs/platform/self-hosting/upgrades.mdx @@ -157,7 +157,7 @@ Migration surprises are usually data-shaped rather than schema-shaped, so a stag ```bash helm upgrade sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.3 \ + --version 1.9.4 \ --namespace simstudio \ --values my-values.yaml ``` @@ -165,7 +165,7 @@ helm upgrade sim oci://ghcr.io/simstudioai/charts/sim \ Preview first if the chart version changed: ```bash -helm diff upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.3 -n simstudio --values my-values.yaml +helm diff upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 -n simstudio --values my-values.yaml ``` Then watch the rollout: diff --git a/helm/sim/Chart.yaml b/helm/sim/Chart.yaml index c1f0e67b93f..3abe26fabc5 100644 --- a/helm/sim/Chart.yaml +++ b/helm/sim/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: sim description: A Helm chart for Sim - the open-source AI workspace where teams build, deploy, and manage AI agents type: application -version: 1.9.3 +version: 1.9.4 appVersion: "v0.8.18" kubeVersion: ">=1.25.0-0" home: https://sim.ai diff --git a/helm/sim/README.md b/helm/sim/README.md index 0a552c0db3d..d9952cd3739 100644 --- a/helm/sim/README.md +++ b/helm/sim/README.md @@ -23,7 +23,7 @@ export POSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=') # Install from the registry helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.3 \ + --version 1.9.4 \ --namespace sim --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -107,10 +107,10 @@ immutable once published. ```bash # List the published versions -helm show chart oci://ghcr.io/simstudioai/charts/sim --version 1.9.3 +helm show chart oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.3 \ + --version 1.9.4 \ --namespace sim --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -127,8 +127,8 @@ To mirror the chart into an internal registry — the usual requirement for an air-gapped or internal-only cluster: ```bash -helm pull oci://ghcr.io/simstudioai/charts/sim --version 1.9.3 -helm push sim-1.9.3.tgz oci://registry.internal.example.com/charts +helm pull oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 +helm push sim-1.9.4.tgz oci://registry.internal.example.com/charts ``` The container images the chart references are listed in @@ -144,7 +144,7 @@ helm repo add sim https://charts.sim.ai helm repo update helm install sim sim/sim \ - --version 1.9.3 \ + --version 1.9.4 \ --namespace sim --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -169,20 +169,26 @@ helm install sim ./helm/sim \ --set postgresql.auth.password="$POSTGRES_PASSWORD" ``` +The remaining examples in this README use the OCI reference. If you are working +from a checkout, substitute `./helm/sim` and drop `--version` — the commands are +otherwise identical. + ### With a values file ```bash -helm install sim ./helm/sim \ +helm install sim oci://ghcr.io/simstudioai/charts/sim \ + --version 1.9.4 \ --namespace sim --create-namespace \ --values my-values.yaml ``` -Run `helm template ./helm/sim --values my-values.yaml | less` first to see what will be applied. +Run `helm template oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 --values my-values.yaml | less` first to +see what will be applied. ### Validate the install ```bash -helm install sim ./helm/sim --dry-run --debug \ +helm install sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 --dry-run --debug \ --values my-values.yaml \ --set app.env.BETTER_AUTH_SECRET=$(openssl rand -hex 16) \ --set app.env.ENCRYPTION_KEY=$(openssl rand -hex 16) \ @@ -201,12 +207,12 @@ the registry next to the chart so they survive a mirror. ```bash # The signature: proves this chart was signed by a GitHub Actions run in this repo -cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.3 \ +cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.4 \ --certificate-identity-regexp '^https://github.com/simstudioai/sim/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com # The provenance: proves which workflow, commit, and runner produced it -gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.3 --repo simstudioai/sim +gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.4 --repo simstudioai/sim ``` There is no GPG `.prov` file — signing is Sigstore-only, so there is no @@ -218,7 +224,7 @@ GPG provenance format and will not work; use `cosign verify` above. ## Upgrading ```bash -helm upgrade sim ./helm/sim --namespace sim --values my-values.yaml +helm upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 --namespace sim --values my-values.yaml ``` --- @@ -248,7 +254,7 @@ kubectl delete namespace sim ## Examples -Pre-built values files for common scenarios live in `helm/sim/examples/`. Each file has a header explaining when to use it and any prerequisites. +Pre-built values files for common scenarios live in [`helm/sim/examples/`](https://github.com/simstudioai/sim/tree/main/helm/sim/examples). Each file has a header explaining when to use it and any prerequisites. They are **not** part of the packaged chart, so fetch the one you want at a release tag rather than expecting it locally. | File | When to use | |---|---| @@ -266,9 +272,13 @@ Pre-built values files for common scenarios live in `helm/sim/examples/`. Each f Use one with: ```bash -helm install sim ./helm/sim \ +SIM_RELEASE=v0.8.24 +curl -fsSLO "https://raw.githubusercontent.com/simstudioai/sim/$SIM_RELEASE/helm/sim/examples/values-production.yaml" + +helm install sim oci://ghcr.io/simstudioai/charts/sim \ + --version 1.9.4 \ --namespace sim --create-namespace \ - --values ./helm/sim/examples/values-production.yaml \ + --values values-production.yaml \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ @@ -283,12 +293,14 @@ This chart is intentionally configurable. Rather than maintain a hand-curated pa ```bash # Print all values with comments and defaults -helm show values ./helm/sim - -# Print the JSON Schema (used by `helm install` to validate your values) -cat ./helm/sim/values.schema.json +helm show values oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 ``` +The JSON Schema that `helm install` validates your values against ships inside +the chart as `values.schema.json`; read it in the +[repository](https://github.com/simstudioai/sim/blob/main/helm/sim/values.schema.json) +or extract it from a pulled chart with `helm pull --untar`. + `values.yaml` is heavily commented; each top-level section explains what it controls and which sub-keys are required vs optional. For per-cloud examples and idiomatic overrides, see `examples/`. --- @@ -329,7 +341,7 @@ Before installing in production, confirm each of the following: kubernetes.io/metadata.name: ingress-nginx ``` * **Namespace hardening** — label the install namespace with Pod Security Standards `restricted` enforcement (`pod-security.kubernetes.io/enforce=restricted`). All workloads set `runAsNonRoot`, drop all Linux capabilities, disable privilege escalation, and set `seccompProfile: RuntimeDefault` — the four controls the Restricted profile requires. `readOnlyRootFilesystem` is intentionally **not** defaulted anywhere (Postgres/Ollama genuinely need a writable root; the stateless services — `realtime`, `pii`, `copilot` — could tolerate it but aren't pre-wired with a `/tmp` `emptyDir`). If your policy requires it, set `.securityContext.readOnlyRootFilesystem: true` and mount an `emptyDir` at `/tmp` yourself via `extraVolumes`/`extraVolumeMounts`. -* **Env validation** — keys under `app.env`, `realtime.env`, and `copilot.env` are passed through to the application and validated at startup. The JSON Schema intentionally does not enforce `additionalProperties: false` (would break custom user envs), so typos like `OPENA_API_KEY` (instead of `OPENAI_API_KEY`) surface as missing-key errors at runtime, not at `helm install` time. Review your env block carefully. +* **Env validation** — keys under `app.env`, `realtime.env`, and `copilot.server.env` are passed through to the application and validated at startup. The JSON Schema intentionally does not enforce `additionalProperties: false` (would break custom user envs), so typos like `OPENA_API_KEY` (instead of `OPENAI_API_KEY`) surface as missing-key errors at runtime, not at `helm install` time. Review your env block carefully. * **Set public URLs** — `app.env.NEXT_PUBLIC_APP_URL` and `app.env.BETTER_AUTH_URL` must match your public origin (e.g. `https://sim.example.com`). Leaving them as `localhost` breaks sign-in. --- @@ -341,7 +353,7 @@ The chart supports three ways to provide secrets, in increasing order of product ### 1. Inline `--set` (dev / dry-run only) ```bash -helm install sim ./helm/sim --set app.env.BETTER_AUTH_SECRET=... +helm install sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 --set app.env.BETTER_AUTH_SECRET=... ``` Discouraged for production — values land in `helm get values` output. @@ -472,7 +484,7 @@ With the chart-managed Secret (the default), nulling a key the application canno The common case is a free-tier cap inherited from a chart release older than the one that stopped presetting them, which shipped `FREE_TABLES_LIMIT: "3"` and `FREE_TABLE_ROWS_LIMIT: "1000"` under `app.envDefaults`. With billing disabled, Sim reads an unset limit as unlimited, so nulling these lifts the cap. Verify before rolling out: ```bash -helm template sim ./helm/sim -f values.yaml | grep -A1 FREE_TABLE # expect no output +helm template sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 -f values.yaml | grep -A1 FREE_TABLE # expect no output ``` --- @@ -540,7 +552,7 @@ Without a cluster-reachable `INTERNAL_API_BASE_URL` (it falls back to `NEXT_PUBL You ran `helm install` without setting required secrets. Generate them and pass with `--set`: ```bash -helm install sim ./helm/sim \ +helm install sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 \ --set app.env.BETTER_AUTH_SECRET=$(openssl rand -hex 32) \ --set app.env.ENCRYPTION_KEY=$(openssl rand -hex 32) \ --set app.env.INTERNAL_API_SECRET=$(openssl rand -hex 32) \ From a04715aaa81d11491693b39ae94351b771ffb410 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 7 Sep 2026 11:06:26 -0700 Subject: [PATCH 10/11] fix(helm): track the app release in appVersion, and enforce it appVersion sat at v0.8.18 while the app shipped through v0.8.24 -- six releases. Because the image tags default to Chart.AppVersion, a default `helm install` deployed the older Sim, and regenerating images.yaml here moved eleven lines, so the mirror inventory an air-gapped operator works from named the wrong images too. Publishing is what makes this serious. Cloning main got you whatever was there; a published chart version is immutable, so every stale appVersion would be frozen and installable forever. It was bumped by hand and nothing checked it, which is why it drifted. The publish job now refuses to publish when appVersion does not match the latest GitHub release, comparing against the release API rather than a hardcoded value so the check cannot go stale itself. Confirmed it fires on exactly the drift that existed (v0.8.18 vs v0.8.24) and passes now that it is fixed. Bumps appVersion to v0.8.24, regenerates images.yaml, and takes the chart to 1.9.5. --- .github/workflows/helm.yml | 25 ++++++++++++++ .../self-hosting/environment-variables.mdx | 2 +- .../docs/platform/self-hosting/kubernetes.mdx | 12 +++---- .../self-hosting/reference-architectures.mdx | 2 +- .../docs/platform/self-hosting/upgrades.mdx | 4 +-- helm/sim/Chart.yaml | 4 +-- helm/sim/README.md | 34 +++++++++---------- helm/sim/images.yaml | 22 ++++++------ 8 files changed, 65 insertions(+), 40 deletions(-) diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index e5b293dedd3..dfbc2f5ff73 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -249,6 +249,31 @@ jobs: echo "repository=ghcr.io/${GITHUB_REPOSITORY_OWNER}/charts/${name}" } >> "$GITHUB_OUTPUT" + # `appVersion` is what the image tags default to, so a stale one publishes + # a chart that silently installs an old Sim -- and because published chart + # versions are immutable, every stale value is frozen forever. It sat six + # releases behind before this check existed, bumped only by hand. + # + # Compares against the latest GitHub release rather than a hardcoded value + # so it cannot go stale itself. Prereleases and drafts are excluded: the + # `/releases/latest` endpoint already returns neither. + - name: appVersion tracks the latest app release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + app_version=$(helm show chart helm/sim | awk '/^appVersion:/ {print $2}' | tr -d '"') + latest=$(gh api "repos/${GITHUB_REPOSITORY}/releases/latest" --jq .tag_name) + if [ -z "$latest" ]; then + echo "::error::Could not resolve the latest release; refusing to publish unverified." + exit 1 + fi + if [ "$app_version" != "$latest" ]; then + echo "::error::Chart.yaml appVersion is ${app_version} but the latest release is ${latest}. Bump appVersion (and the chart version) so the chart does not publish an install pinned to an older Sim." + exit 1 + fi + echo "appVersion ${app_version} matches the latest release." + # Chart versions are immutable once published: whoever pinned a version # must keep resolving the same bytes forever. The PR gate above already # forces a version bump on every chart change, so a version that is diff --git a/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx b/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx index 9dff32abb55..b41d95e48b1 100644 --- a/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx +++ b/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx @@ -311,7 +311,7 @@ Setting the variable to an empty string does **not** remove it: the chart reads Null the variable in every layer that sets it. If it appears in both `app.env` and `app.envDefaults`, nulling only the `app.env` entry lets the `envDefaults` value apply again and the limit stays in force. With External Secrets, also drop the key from `externalSecrets.remoteRefs.app`, which keeps syncing it independently. Confirm what the pod will actually receive before rolling out: ```bash -helm template sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 -f values.yaml | grep -A1 FREE_TABLE # expect no output +helm template sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 -f values.yaml | grep -A1 FREE_TABLE # expect no output ``` `null` deletion has no effect under `helm upgrade --reuse-values` — pass your full values with `-f`, or use `--reset-then-reuse-values` (Helm 3.14+). If you deploy with Argo CD, put the `null` in `valueFiles` or the `values` string rather than `valuesObject`, which strips nulls. On Docker Compose, delete the line from your `.env` file. diff --git a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx index ef384eac77c..303a0795366 100644 --- a/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx +++ b/apps/docs/content/docs/platform/self-hosting/kubernetes.mdx @@ -40,7 +40,7 @@ POSTGRES_PASSWORD=$(openssl rand -hex 24) # Install helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.4 \ + --version 1.9.5 \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ @@ -70,7 +70,7 @@ For clusters or GitOps configs that consume `helm repo add` rather than OCI: helm repo add sim https://charts.sim.ai helm repo update -helm install sim sim/sim --version 1.9.4 --namespace simstudio --create-namespace \ +helm install sim sim/sim --version 1.9.5 --namespace simstudio --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ --set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \ @@ -87,11 +87,11 @@ apply to the OCI artifact only. Every published version is signed with Sigstore keyless signing and carries a SLSA build-provenance attestation. Both live in the registry alongside the chart, so they survive a mirror into an internal registry. ```bash -cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.4 \ +cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.5 \ --certificate-identity-regexp '^https://github.com/simstudioai/sim/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com -gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.4 --repo simstudioai/sim +gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.5 --repo simstudioai/sim ``` Signing is Sigstore-only — there is no GPG `.prov` file, so `helm install --verify` does not apply. @@ -112,7 +112,7 @@ SIM_RELEASE=v0.8.24 curl -fsSLO "https://raw.githubusercontent.com/simstudioai/sim/$SIM_RELEASE/helm/sim/examples/values-aws.yaml" helm upgrade --install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.4 \ + --version 1.9.5 \ --values values-aws.yaml \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -233,7 +233,7 @@ kubectl port-forward deployment/sim-app 3000:3000 -n simstudio kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100 # Upgrade (always pin the target chart version) -helm upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 --namespace simstudio +helm upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 --namespace simstudio # Uninstall helm uninstall sim --namespace simstudio diff --git a/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx b/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx index 1c6338d4670..8df1ba75150 100644 --- a/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx +++ b/apps/docs/content/docs/platform/self-hosting/reference-architectures.mdx @@ -152,7 +152,7 @@ resource "helm_release" "sim" { # release with new migrations. repository = "oci://ghcr.io/simstudioai/charts" chart = "sim" - version = "1.9.4" + version = "1.9.5" # Or the classic repository, if your tooling does not speak OCI: # repository = "https://charts.sim.ai" diff --git a/apps/docs/content/docs/platform/self-hosting/upgrades.mdx b/apps/docs/content/docs/platform/self-hosting/upgrades.mdx index e5126334ee2..b5fde599612 100644 --- a/apps/docs/content/docs/platform/self-hosting/upgrades.mdx +++ b/apps/docs/content/docs/platform/self-hosting/upgrades.mdx @@ -157,7 +157,7 @@ Migration surprises are usually data-shaped rather than schema-shaped, so a stag ```bash helm upgrade sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.4 \ + --version 1.9.5 \ --namespace simstudio \ --values my-values.yaml ``` @@ -165,7 +165,7 @@ helm upgrade sim oci://ghcr.io/simstudioai/charts/sim \ Preview first if the chart version changed: ```bash -helm diff upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 -n simstudio --values my-values.yaml +helm diff upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 -n simstudio --values my-values.yaml ``` Then watch the rollout: diff --git a/helm/sim/Chart.yaml b/helm/sim/Chart.yaml index 3abe26fabc5..83ddb43abe3 100644 --- a/helm/sim/Chart.yaml +++ b/helm/sim/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: sim description: A Helm chart for Sim - the open-source AI workspace where teams build, deploy, and manage AI agents type: application -version: 1.9.4 -appVersion: "v0.8.18" +version: 1.9.5 +appVersion: "v0.8.24" kubeVersion: ">=1.25.0-0" home: https://sim.ai icon: https://raw.githubusercontent.com/simstudioai/sim/main/apps/sim/public/logo/primary/primary.svg diff --git a/helm/sim/README.md b/helm/sim/README.md index d9952cd3739..5c24b0ee3c5 100644 --- a/helm/sim/README.md +++ b/helm/sim/README.md @@ -23,7 +23,7 @@ export POSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=') # Install from the registry helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.4 \ + --version 1.9.5 \ --namespace sim --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -107,10 +107,10 @@ immutable once published. ```bash # List the published versions -helm show chart oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 +helm show chart oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.4 \ + --version 1.9.5 \ --namespace sim --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -127,8 +127,8 @@ To mirror the chart into an internal registry — the usual requirement for an air-gapped or internal-only cluster: ```bash -helm pull oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 -helm push sim-1.9.4.tgz oci://registry.internal.example.com/charts +helm pull oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 +helm push sim-1.9.5.tgz oci://registry.internal.example.com/charts ``` The container images the chart references are listed in @@ -144,7 +144,7 @@ helm repo add sim https://charts.sim.ai helm repo update helm install sim sim/sim \ - --version 1.9.4 \ + --version 1.9.5 \ --namespace sim --create-namespace \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ --set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \ @@ -177,18 +177,18 @@ otherwise identical. ```bash helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.4 \ + --version 1.9.5 \ --namespace sim --create-namespace \ --values my-values.yaml ``` -Run `helm template oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 --values my-values.yaml | less` first to +Run `helm template oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 --values my-values.yaml | less` first to see what will be applied. ### Validate the install ```bash -helm install sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 --dry-run --debug \ +helm install sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 --dry-run --debug \ --values my-values.yaml \ --set app.env.BETTER_AUTH_SECRET=$(openssl rand -hex 16) \ --set app.env.ENCRYPTION_KEY=$(openssl rand -hex 16) \ @@ -207,12 +207,12 @@ the registry next to the chart so they survive a mirror. ```bash # The signature: proves this chart was signed by a GitHub Actions run in this repo -cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.4 \ +cosign verify oci://ghcr.io/simstudioai/charts/sim:1.9.5 \ --certificate-identity-regexp '^https://github.com/simstudioai/sim/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com # The provenance: proves which workflow, commit, and runner produced it -gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.4 --repo simstudioai/sim +gh attestation verify oci://ghcr.io/simstudioai/charts/sim:1.9.5 --repo simstudioai/sim ``` There is no GPG `.prov` file — signing is Sigstore-only, so there is no @@ -224,7 +224,7 @@ GPG provenance format and will not work; use `cosign verify` above. ## Upgrading ```bash -helm upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 --namespace sim --values my-values.yaml +helm upgrade sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 --namespace sim --values my-values.yaml ``` --- @@ -276,7 +276,7 @@ SIM_RELEASE=v0.8.24 curl -fsSLO "https://raw.githubusercontent.com/simstudioai/sim/$SIM_RELEASE/helm/sim/examples/values-production.yaml" helm install sim oci://ghcr.io/simstudioai/charts/sim \ - --version 1.9.4 \ + --version 1.9.5 \ --namespace sim --create-namespace \ --values values-production.yaml \ --set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \ @@ -293,7 +293,7 @@ This chart is intentionally configurable. Rather than maintain a hand-curated pa ```bash # Print all values with comments and defaults -helm show values oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 +helm show values oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 ``` The JSON Schema that `helm install` validates your values against ships inside @@ -353,7 +353,7 @@ The chart supports three ways to provide secrets, in increasing order of product ### 1. Inline `--set` (dev / dry-run only) ```bash -helm install sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 --set app.env.BETTER_AUTH_SECRET=... +helm install sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 --set app.env.BETTER_AUTH_SECRET=... ``` Discouraged for production — values land in `helm get values` output. @@ -484,7 +484,7 @@ With the chart-managed Secret (the default), nulling a key the application canno The common case is a free-tier cap inherited from a chart release older than the one that stopped presetting them, which shipped `FREE_TABLES_LIMIT: "3"` and `FREE_TABLE_ROWS_LIMIT: "1000"` under `app.envDefaults`. With billing disabled, Sim reads an unset limit as unlimited, so nulling these lifts the cap. Verify before rolling out: ```bash -helm template sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 -f values.yaml | grep -A1 FREE_TABLE # expect no output +helm template sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 -f values.yaml | grep -A1 FREE_TABLE # expect no output ``` --- @@ -552,7 +552,7 @@ Without a cluster-reachable `INTERNAL_API_BASE_URL` (it falls back to `NEXT_PUBL You ran `helm install` without setting required secrets. Generate them and pass with `--set`: ```bash -helm install sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.4 \ +helm install sim oci://ghcr.io/simstudioai/charts/sim --version 1.9.5 \ --set app.env.BETTER_AUTH_SECRET=$(openssl rand -hex 32) \ --set app.env.ENCRYPTION_KEY=$(openssl rand -hex 32) \ --set app.env.INTERNAL_API_SECRET=$(openssl rand -hex 32) \ diff --git a/helm/sim/images.yaml b/helm/sim/images.yaml index 98c53e4074d..1e0efa1396c 100644 --- a/helm/sim/images.yaml +++ b/helm/sim/images.yaml @@ -22,22 +22,22 @@ # render it twice. That override also CHANGES where the chart pulls from, to # `/nvidia/k8s-device-plugin` — mirror the device plugin there # instead of to the `mirror` path listed below, or the pull fails. -appVersion: v0.8.18 +appVersion: v0.8.24 images: - source: busybox:1.36 mirror: busybox:1.36 - source: curlimages/curl:8.5.0 mirror: curlimages/curl:8.5.0 - - source: ghcr.io/simstudioai/copilot:v0.8.18 - mirror: simstudioai/copilot:v0.8.18 - - source: ghcr.io/simstudioai/migrations:v0.8.18 - mirror: simstudioai/migrations:v0.8.18 - - source: ghcr.io/simstudioai/pii:v0.8.18 - mirror: simstudioai/pii:v0.8.18 - - source: ghcr.io/simstudioai/realtime:v0.8.18 - mirror: simstudioai/realtime:v0.8.18 - - source: ghcr.io/simstudioai/simstudio:v0.8.18 - mirror: simstudioai/simstudio:v0.8.18 + - source: ghcr.io/simstudioai/copilot:v0.8.24 + mirror: simstudioai/copilot:v0.8.24 + - source: ghcr.io/simstudioai/migrations:v0.8.24 + mirror: simstudioai/migrations:v0.8.24 + - source: ghcr.io/simstudioai/pii:v0.8.24 + mirror: simstudioai/pii:v0.8.24 + - source: ghcr.io/simstudioai/realtime:v0.8.24 + mirror: simstudioai/realtime:v0.8.24 + - source: ghcr.io/simstudioai/simstudio:v0.8.24 + mirror: simstudioai/simstudio:v0.8.24 - source: nvcr.io/nvidia/k8s-device-plugin:v0.18.2 mirror: nvcr.io/nvidia/k8s-device-plugin:v0.18.2 - source: ollama/ollama:0.23.2 From 8dbe976a1c4cc3ac9202a20e975491fb78137c62 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 7 Sep 2026 11:13:07 -0700 Subject: [PATCH 11/11] fix(helm): fail the appVersion check only when it lags, not on any mismatch The check compared appVersion to /releases/latest for equality, which races the release it is meant to protect. Version tags are cut by the main-branch merge commit that releases them, so on that commit appVersion legitimately names a release that does not exist yet while ci.yml is still building it. Equality would have rejected the bump and blocked the very publish it was for. helm/sim/ci/kind-values.yaml already documents this circularity, and records it as the reason appVersion went unbumped from chart 1.2.0 to 1.6.3. Lagging is the failure; being ahead is a normal transient. The comparison is `sort -V`, so it orders versions rather than strings -- v0.9.9 against v0.10.0 is exactly where a string compare silently inverts. Verified all five cases: behind fails, equal passes, ahead passes with a notice, and both multi-digit orderings resolve correctly. --- .github/workflows/helm.yml | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index dfbc2f5ff73..6c2e29f79fe 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -254,10 +254,18 @@ jobs: # versions are immutable, every stale value is frozen forever. It sat six # releases behind before this check existed, bumped only by hand. # + # BEHIND is the failure. AHEAD is normal and must not be blocked: a + # version tag is cut by the main-branch merge commit that releases it + # (detect-version in ci.yml), so appVersion legitimately names a release + # that does not exist yet while that release is still being built. Failing + # on any mismatch would race that workflow and block the very publish the + # bump was for. `helm/sim/ci/kind-values.yaml` documents the same + # circularity, and it is why appVersion went unbumped for so long. + # # Compares against the latest GitHub release rather than a hardcoded value - # so it cannot go stale itself. Prereleases and drafts are excluded: the - # `/releases/latest` endpoint already returns neither. - - name: appVersion tracks the latest app release + # so the check cannot go stale itself. Prereleases and drafts are excluded: + # the `/releases/latest` endpoint already returns neither. + - name: appVersion does not lag the app release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | @@ -268,11 +276,16 @@ jobs: echo "::error::Could not resolve the latest release; refusing to publish unverified." exit 1 fi - if [ "$app_version" != "$latest" ]; then + if [ "$app_version" = "$latest" ]; then + echo "appVersion ${app_version} matches the latest release." + exit 0 + fi + oldest=$(printf '%s\n%s\n' "$app_version" "$latest" | sort -V | head -1) + if [ "$oldest" = "$app_version" ]; then echo "::error::Chart.yaml appVersion is ${app_version} but the latest release is ${latest}. Bump appVersion (and the chart version) so the chart does not publish an install pinned to an older Sim." exit 1 fi - echo "appVersion ${app_version} matches the latest release." + echo "::notice::appVersion ${app_version} is ahead of the latest release ${latest}, which is expected while that release is still being cut." # Chart versions are immutable once published: whoever pinned a version # must keep resolving the same bytes forever. The PR gate above already