From 1bc2b417c3368d9c7113cbcada3b1b1f77bc9c1f Mon Sep 17 00:00:00 2001 From: argocd-image-updater Date: Sat, 15 Aug 2026 02:40:32 +0000 Subject: [PATCH 1/9] chore(deploy): sdk-example -> sha256:db6f5e0364e86c14731d3937e37031dff7f23391fed5fe34eab10450ef97976e [skip ci] Written by argocd-image-updater for application jukebox-dev. --- argo/overlays/dev/kustomization.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/argo/overlays/dev/kustomization.yaml b/argo/overlays/dev/kustomization.yaml index 3b67ec1..7c42dba 100644 --- a/argo/overlays/dev/kustomization.yaml +++ b/argo/overlays/dev/kustomization.yaml @@ -27,3 +27,4 @@ images: - name: jukebox0 newName: 368076259134.dkr.ecr.us-east-1.amazonaws.com/sdk-example digest: sha256:db6f5e0364e86c14731d3937e37031dff7f23391fed5fe34eab10450ef97976e + newTag: jukebox From 42954614b4ce25aa56929845ceed45991291adbc Mon Sep 17 00:00:00 2001 From: jplorier Date: Sun, 16 Aug 2026 14:59:59 -0300 Subject: [PATCH 2/9] chore: expose COMMIT_HASH and BUILD_TIME on the health endpoint CI already passed COMMIT_HASH as a docker build-arg, but the Dockerfile never declared the ARG, so the value was discarded and the health check could not report which commit is running. Declare ARG COMMIT_HASH and ARG BUILD_TIME in the final stage and promote both to ENV so they reach the process regardless of the start script, stamp BUILD_TIME in CI, and surface both on /api/system/health. BUILD_TIME identifies the image rather than the container, so unlike serverStartDate it survives restarts and is usable as a release reference. --- .github/workflows/aws_dev_release_gitops.yml | 4 ++++ .github/workflows/aws_prod_release.yml | 4 ++++ Dockerfile | 4 ++++ server/router/routes.ts | 2 ++ 4 files changed, 14 insertions(+) diff --git a/.github/workflows/aws_dev_release_gitops.yml b/.github/workflows/aws_dev_release_gitops.yml index 88968d2..fe62124 100644 --- a/.github/workflows/aws_dev_release_gitops.yml +++ b/.github/workflows/aws_dev_release_gitops.yml @@ -88,6 +88,9 @@ jobs: images: ${{ steps.login-ecr.outputs.registry }}/${{ env.REPOSITORY }} tags: | type=raw,value=${{ github.event.repository.name }} + - name: Compute image build timestamp + id: buildtime + run: echo "value=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT - name: Build and push multi-platform images to ECR id: build uses: docker/build-push-action@v5 @@ -99,6 +102,7 @@ jobs: build-args: | REF=${{ github.ref }} COMMIT_HASH=${{ github.sha }} + BUILD_TIME=${{ steps.buildtime.outputs.value }} cache-from: type=gha cache-to: type=gha,mode=max provenance: false diff --git a/.github/workflows/aws_prod_release.yml b/.github/workflows/aws_prod_release.yml index 42011fb..5e842f9 100644 --- a/.github/workflows/aws_prod_release.yml +++ b/.github/workflows/aws_prod_release.yml @@ -102,6 +102,9 @@ jobs: type=raw,value=${{ github.event.repository.name }}-${{ github.event.release.tag_name }} type=raw,value=${{ github.event.repository.name }}-latest + - name: Compute image build timestamp + id: buildtime + run: echo "value=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT - name: Build and push multi-platform images to ECR uses: docker/build-push-action@v5 with: @@ -112,6 +115,7 @@ jobs: build-args: | REF=${{ github.ref }} COMMIT_HASH=${{ github.sha }} + BUILD_TIME=${{ steps.buildtime.outputs.value }} cache-from: type=gha cache-to: type=gha,mode=max provenance: false diff --git a/Dockerfile b/Dockerfile index 9fe06d0..953d36b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,9 @@ FROM node:22-alpine WORKDIR /app +ARG COMMIT_HASH +ENV COMMIT_HASH=$COMMIT_HASH +ARG BUILD_TIME +ENV BUILD_TIME=$BUILD_TIME ADD . ./ EXPOSE 3000 ENTRYPOINT [ "npm", "start" ] diff --git a/server/router/routes.ts b/server/router/routes.ts index 0ad5861..c482c21 100644 --- a/server/router/routes.ts +++ b/server/router/routes.ts @@ -25,6 +25,8 @@ router.get("/system/health", (req, res) => { status: "OK", serverStartDate: SERVER_START_DATE, envs: { + COMMIT_HASH: process.env.COMMIT_HASH ?? "NOT SET", + BUILD_TIME: process.env.BUILD_TIME ?? "NOT SET", API_KEY: process.env.API_KEY ? "SET" : "NOT SET", PORT: process.env.PORT ? process.env.PORT : "NOT SET", NODE_ENV: process.env.NODE_ENV ? process.env.NODE_ENV : "NOT SET", From adb411ac2f026d81b2d00f2c24ae97989d0c59ed Mon Sep 17 00:00:00 2001 From: Terraform Date: Sun, 16 Aug 2026 19:11:08 +0000 Subject: [PATCH 3/9] Add GitOps release CICD --- .github/workflows/aws_dev_release_gitops.yml | 132 ++++++------------- 1 file changed, 37 insertions(+), 95 deletions(-) diff --git a/.github/workflows/aws_dev_release_gitops.yml b/.github/workflows/aws_dev_release_gitops.yml index 88968d2..7f9aa45 100644 --- a/.github/workflows/aws_dev_release_gitops.yml +++ b/.github/workflows/aws_dev_release_gitops.yml @@ -3,21 +3,37 @@ on: push: branches: - dev + # Image Updater's write-back lands on this same branch. paths-ignore is + # what stops build -> push -> digest write-back -> build looping: the + # write-back only ever touches argo/ (its writeBackTarget is pinned to + # kustomization:/argo/overlays/dev), so it can never match this trigger. + # A push touching both argo/ and app code still builds, which is correct. + # + # This replaces the old [skip ci] guard. [skip ci] worked, but GitHub + # applies it to pull_request events too, keyed on the HEAD commit of the + # PR - so once a write-back commit was the tip of dev, every dev -> main + # release PR was silently skipped and Auto Release never fired. Guarding + # on paths instead of on a magic string keeps that blast radius out of PRs. + paths-ignore: + - 'argo/**' workflow_dispatch: -# GitOps deploy, git as the source of truth. Build pushes to the shared dev ECR -# repo under the mutable tag sdk-example: (kept mutable so the ECR -# lifecycle policy manages it; the commit sha is baked into the image via -# COMMIT_HASH, not the tag). The sdk-apps ApplicationSet (in -# metaversecloud-com/sdk-gitops) creates one Application per env from this repo's -# argo/envs//config.json, named "-", tracking the deploy branch -# (automated + selfHeal). The deploy job does NOT touch the cluster: it pins the -# env overlay's images[].digest to the digest just built (immutable, changes -# every build even though the tag is reused) and commits it to the deploy branch -# - that manifest change is what rolls the workload - then triggers `argocd app -# sync` via the Argo CD API with a token. No kubectl, no EKS/AWS auth, no -# config/secret seeding (kustomize ConfigMap + SealedSecret are Argo-managed). -# Adding an env = committing argo/envs//config.json on the inventory ref. +# GitOps deploy, git as the source of truth. This workflow only BUILDS: it +# pushes to the shared dev ECR repo under the mutable tag sdk-example: +# (kept mutable so the ECR lifecycle policy manages it; the commit sha is baked +# into the image via COMMIT_HASH, not the tag). Nothing here deploys. +# +# Argo CD Image Updater owns the deploy half. It watches that tag, and when the +# digest behind it changes it commits images[].digest into this repo's +# argo/overlays//kustomization.yaml on the deploy branch. That manifest +# change is what rolls the workload - the sdk-apps ApplicationSet's Applications +# are automated + selfHeal, so no explicit `argocd app sync` is needed. Config +# lives in metaversecloud-com/sdk-gitops (bootstrap/imageupdater-sdk-apps.yaml). +# +# The sdk-apps ApplicationSet creates one Application per env from this repo's +# argo/envs//config.json, named "-". Adding an env = committing +# argo/envs//config.json on the inventory ref. No kubectl, no EKS/AWS auth, +# no config/secret seeding (kustomize ConfigMap + SealedSecret are Argo-managed). env: REPOSITORY: 'sdk-example' ENV: 'dev' @@ -29,13 +45,11 @@ concurrency: permissions: id-token: write # Build: OIDC -> AWS role for ECR push - contents: write # deploy: commit the image-tag bump to the deploy branch + contents: read # checkout only; the deploy-branch commit is Image Updater's jobs: Build: runs-on: ubuntu-latest - outputs: - digest: ${{ steps.build.outputs.digest }} steps: - name: Checkout uses: actions/checkout@v4 @@ -56,6 +70,13 @@ jobs: echo "::error::Build failed" exit 1 fi + # Emulation for the arm64 target. The images differ only by their + # node/alpine base (npm/node binaries); node_modules is ADDed from + # the runner and is arch-neutral (these apps have no native runtime + # deps), so a single buildx multi-arch push is correct. NOTE: if any + # app adds a native RUNTIME dependency, this pattern would ship the + # runner's arch into both variants - switch that app to a per-arch + # native build or a Dockerfile `RUN npm ci`. - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: @@ -103,82 +124,3 @@ jobs: cache-to: type=gha,mode=max provenance: false sbom: false - - deploy: - # In-VPC self-hosted runner (reaches the internal Argo CD API). This job - # never talks to the cluster: it commits the new image tag to the deploy - # branch (git = source of truth) and asks Argo CD to sync. Auth is an Argo CD - # API token (secret ARGOCD_AUTH_TOKEN) whose account needs applications - # get+sync on sdk-apps/* in argocd-rbac-cm - widen that policy to cover all - # apps. No EKS access entry / k8s RBAC / AWS creds needed here. - runs-on: topia-dev-sdk-apps-metaversecloud-com - needs: Build - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install argocd + yq - run: | - set -euo pipefail - # Arch-aware; gate on executing the binary so a stale wrong-arch binary - # from a prior run is replaced (avoids "Exec format error"). - case "$(uname -m)" in - x86_64) ARCH=amd64 ;; - aarch64|arm64) ARCH=arm64 ;; - *) echo "::error::Unsupported arch $(uname -m)"; exit 1 ;; - esac - if ! yq --version &>/dev/null; then - curl -sSLo /tmp/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_$ARCH - sudo install -m0755 /tmp/yq /usr/local/bin/yq - fi - # Pinned to the Argo CD server version (argo.topia.io reports v3.4.2); - # bump this when the server is upgraded. - ARGOCD_VERSION=v3.4.2 - if [ "$(argocd version --client --short 2>/dev/null | grep -o 'v[0-9.]*' | head -1)" != "$ARGOCD_VERSION" ]; then - curl -sSLo /tmp/argocd https://github.com/argoproj/argo-cd/releases/download/$ARGOCD_VERSION/argocd-linux-$ARCH - sudo install -m0755 /tmp/argocd /usr/local/bin/argocd - fi - - name: Pin image digest in the env overlay and commit - env: - DIGEST: ${{ needs.Build.outputs.digest }} - run: | - set -euo pipefail - [ -n "$DIGEST" ] || { echo "::error::empty image digest from Build"; exit 1; } - k="argo/overlays/$ENV/kustomization.yaml" - # Pin every image that tracks the shared dev ECR repo (newName .../sdk-example) - # to the digest just built - immutable and unique per build even though the - # tag is reused - and drop any mutable newTag. Images pinned elsewhere are - # left untouched. This manifest change is what makes Argo roll the workload. - DIGEST="$DIGEST" yq -i \ - '(.images[] | select(.newName | test("/sdk-example$")) | .digest) = strenv(DIGEST)' "$k" - yq -i 'del(.images[] | select(.newName | test("/sdk-example$")) | .newTag)' "$k" - git config user.email devops@topia.io - git config user.name Devops - if git diff --quiet -- "$k"; then - echo "No image-digest change in $k" - else - git add "$k" - # [skip ci] + the GITHUB_TOKEN push both keep this commit from - # retriggering the workflow. - git commit -m "chore(deploy): ${GITHUB_REPOSITORY##*/} image $DIGEST [skip ci]" - git pull --rebase origin "$GITHUB_REF_NAME" - git push origin HEAD:"$GITHUB_REF_NAME" - fi - - name: Trigger Argo CD sync - env: - ARGOCD_SERVER: ${{ vars.ARGOCD_SERVER }} - ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_AUTH_TOKEN }} - run: | - set -euo pipefail - app="${GITHUB_REPOSITORY##*/}-$ENV" - export ARGOCD_SERVER ARGOCD_AUTH_TOKEN - # argocd-server runs with server.insecure=true (plaintext HTTP/2; TLS - # is terminated upstream, not on the pod), so the CLI must use - # --plaintext (no TLS) rather than --insecure (which still does a TLS - # handshake and gets reset by the plaintext port). --grpc-web over - # cleartext. Refresh so Argo sees the commit just pushed, then sync and - # wait for health. - argocd app get "$app" --refresh --grpc-web --plaintext >/dev/null - argocd app sync "$app" --grpc-web --plaintext --timeout 300 - argocd app wait "$app" --health --grpc-web --plaintext --timeout 300 From cf16b233a08b796e51f7463be94fb5a1c5c634ee Mon Sep 17 00:00:00 2001 From: argocd-image-updater Date: Sun, 16 Aug 2026 19:13:29 +0000 Subject: [PATCH 4/9] chore(deploy): sdk-example -> sha256:2c48bb495f8f53a6b144227bf6c7be916dba6c8fbdf8550f0a833d36742b2ea1 [skip ci] Written by argocd-image-updater for application jukebox-dev. --- argo/overlays/dev/kustomization.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/argo/overlays/dev/kustomization.yaml b/argo/overlays/dev/kustomization.yaml index 7c42dba..9e6e726 100644 --- a/argo/overlays/dev/kustomization.yaml +++ b/argo/overlays/dev/kustomization.yaml @@ -26,5 +26,5 @@ patches: images: - name: jukebox0 newName: 368076259134.dkr.ecr.us-east-1.amazonaws.com/sdk-example - digest: sha256:db6f5e0364e86c14731d3937e37031dff7f23391fed5fe34eab10450ef97976e + digest: sha256:2c48bb495f8f53a6b144227bf6c7be916dba6c8fbdf8550f0a833d36742b2ea1 newTag: jukebox From 8ffa8556c60f633e9ef45e9ce6447da9c315e814 Mon Sep 17 00:00:00 2001 From: Terraform Date: Sun, 16 Aug 2026 20:24:15 +0000 Subject: [PATCH 5/9] Add production release CICD --- .github/workflows/aws_prod_release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/aws_prod_release.yml b/.github/workflows/aws_prod_release.yml index 42011fb..5e842f9 100644 --- a/.github/workflows/aws_prod_release.yml +++ b/.github/workflows/aws_prod_release.yml @@ -102,6 +102,9 @@ jobs: type=raw,value=${{ github.event.repository.name }}-${{ github.event.release.tag_name }} type=raw,value=${{ github.event.repository.name }}-latest + - name: Compute image build timestamp + id: buildtime + run: echo "value=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT - name: Build and push multi-platform images to ECR uses: docker/build-push-action@v5 with: @@ -112,6 +115,7 @@ jobs: build-args: | REF=${{ github.ref }} COMMIT_HASH=${{ github.sha }} + BUILD_TIME=${{ steps.buildtime.outputs.value }} cache-from: type=gha cache-to: type=gha,mode=max provenance: false From ff1d026d939641cbd17bc76a62504877a20921de Mon Sep 17 00:00:00 2001 From: argocd-image-updater Date: Sun, 16 Aug 2026 20:25:50 +0000 Subject: [PATCH 6/9] chore(deploy): sdk-example -> sha256:dae30d8970b3ea51f4c1652da7502a3e7b5cb62321efec92227e909d02d984d4 Written by argocd-image-updater for application jukebox-dev. --- argo/overlays/dev/kustomization.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/argo/overlays/dev/kustomization.yaml b/argo/overlays/dev/kustomization.yaml index 9e6e726..53d486f 100644 --- a/argo/overlays/dev/kustomization.yaml +++ b/argo/overlays/dev/kustomization.yaml @@ -26,5 +26,5 @@ patches: images: - name: jukebox0 newName: 368076259134.dkr.ecr.us-east-1.amazonaws.com/sdk-example - digest: sha256:2c48bb495f8f53a6b144227bf6c7be916dba6c8fbdf8550f0a833d36742b2ea1 + digest: sha256:dae30d8970b3ea51f4c1652da7502a3e7b5cb62321efec92227e909d02d984d4 newTag: jukebox From daebac637aea7d8db386750610a4592a734a308b Mon Sep 17 00:00:00 2001 From: Terraform Date: Sun, 16 Aug 2026 20:26:04 +0000 Subject: [PATCH 7/9] Add GitOps release CICD --- .github/workflows/aws_dev_release_gitops.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/aws_dev_release_gitops.yml b/.github/workflows/aws_dev_release_gitops.yml index 7f9aa45..a4bc72b 100644 --- a/.github/workflows/aws_dev_release_gitops.yml +++ b/.github/workflows/aws_dev_release_gitops.yml @@ -109,6 +109,9 @@ jobs: images: ${{ steps.login-ecr.outputs.registry }}/${{ env.REPOSITORY }} tags: | type=raw,value=${{ github.event.repository.name }} + - name: Compute image build timestamp + id: buildtime + run: echo "value=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT - name: Build and push multi-platform images to ECR id: build uses: docker/build-push-action@v5 @@ -120,6 +123,7 @@ jobs: build-args: | REF=${{ github.ref }} COMMIT_HASH=${{ github.sha }} + BUILD_TIME=${{ steps.buildtime.outputs.value }} cache-from: type=gha cache-to: type=gha,mode=max provenance: false From dec3ab58ef035b4d1ca41beb60ee8761869c0770 Mon Sep 17 00:00:00 2001 From: argocd-image-updater Date: Sun, 16 Aug 2026 20:27:59 +0000 Subject: [PATCH 8/9] chore(deploy): sdk-example -> sha256:29959e5c1ed10fbe1e013e285d6f213f56e3b8a32fcee1eb42a63e474dce3be2 Written by argocd-image-updater for application jukebox-dev. --- argo/overlays/dev/kustomization.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/argo/overlays/dev/kustomization.yaml b/argo/overlays/dev/kustomization.yaml index 53d486f..4f2f46b 100644 --- a/argo/overlays/dev/kustomization.yaml +++ b/argo/overlays/dev/kustomization.yaml @@ -26,5 +26,5 @@ patches: images: - name: jukebox0 newName: 368076259134.dkr.ecr.us-east-1.amazonaws.com/sdk-example - digest: sha256:dae30d8970b3ea51f4c1652da7502a3e7b5cb62321efec92227e909d02d984d4 + digest: sha256:29959e5c1ed10fbe1e013e285d6f213f56e3b8a32fcee1eb42a63e474dce3be2 newTag: jukebox From bf66888ffb08a73104cb749f483b84c39cbe2c93 Mon Sep 17 00:00:00 2001 From: argocd-image-updater Date: Sun, 16 Aug 2026 22:56:24 +0000 Subject: [PATCH 9/9] chore(deploy): sdk-example -> sha256:9250689f0d0f8417f30bf80bfdecee35841165160a79edccf94fea5ac993d5df Written by argocd-image-updater for application jukebox-dev. --- argo/overlays/dev/kustomization.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/argo/overlays/dev/kustomization.yaml b/argo/overlays/dev/kustomization.yaml index 4f2f46b..d96c004 100644 --- a/argo/overlays/dev/kustomization.yaml +++ b/argo/overlays/dev/kustomization.yaml @@ -26,5 +26,5 @@ patches: images: - name: jukebox0 newName: 368076259134.dkr.ecr.us-east-1.amazonaws.com/sdk-example - digest: sha256:29959e5c1ed10fbe1e013e285d6f213f56e3b8a32fcee1eb42a63e474dce3be2 + digest: sha256:9250689f0d0f8417f30bf80bfdecee35841165160a79edccf94fea5ac993d5df newTag: jukebox