harness: release v0.28.5 with test sums key, digests on, console shipped and promoted (step 7)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 files changed,
+141
−0
harness/RUNLOG.md+44 −0harness/checks/check7.sh+7 −0harness/infra-copies/PROVENANCE.md+1 −0harness/infra-copies/a0b3f5a/scripts/ship-app.sh+13 −0harness/release/release-setup.sh+51 −0harness/release/step7-operator.sh+25 −0
diff --git a/harness/RUNLOG.md b/harness/RUNLOG.md| index e0dcc5782..6bb0b6c85 100644 |
| --- a/harness/RUNLOG.md |
| +++ b/harness/RUNLOG.md |
| @@ -370,3 +370,47 @@ runuser -l operator # the operator shell: harness carlos CLI, aws -> stan |
| Reset: `incus stop infra leo sophie dennis signer; for c in infra leo sophie dennis signer; do |
| incus snapshot restore $c boot; done; incus start infra; incus start leo sophie dennis signer`. |
| + |
| +## Step 7: test keys, the harness release, digests, the console shipped and promoted |
| + |
| +Release **v0.28.5** (a harness-only tag; nothing outside harness MinIO carries it), built on |
| +DOCS-T1 from branch head `1b635e61` (code = main `96db0efe` + the harness file), `-tags harness`, |
| +`-ldflags "-s -w -X .../internal/version.Build=v0.28.5"`, `GOFLAGS=-buildvcs=false`: |
| +`carlos-linux-arm64` `111a317c...5c31`, `console-linux-arm64` `b2b431f3...17e0` |
| +(`go version -m`: `-ldflags=... Build=v0.28.5`, `-tags=harness`). Console ship version CV = |
| +`1b635e61`. At `s3://carlos-harness-640012953448/artefacts/v0.28.5/`. |
| + |
| +`harness/release/release-setup.sh` (on host, root, 01:12Z): |
| +- **Test sums key**: `openssl genpkey -algorithm ed25519` -> `/run/harness/keys/sums.key` |
| + (root 600, tmpfs). Public half `mAnSlX23...` -> `/etc/carlos/harness-sums-pubkey` (root 644) |
| + on the host, leo, sophie, dennis and signer. `CARLOS_HARNESS_INFO=1` now reports it. |
| +- **SHA256SUMS.txt** (`b51b4663...`) lists `carlos-linux-arm64` and |
| + `carlos_0.28.5_{amd64,arm64}.deb`; signed with `openssl pkeyutl -sign -rawin` -> |
| + `SHA256SUMS.txt.sig` (base64); `openssl pkeyutl -verify`: Signature Verified Successfully. |
| + **Deviation:** no debs are built; the two `.deb` entries hash one-line placeholder files, |
| + because `publish-identity` requires both names in the list. |
| +- **Release key** private half -> fake Parameter Store `/carlos/release-key` (SecureString, |
| + version 1), via a 0600 tmpfs file and `--value file://`, never on a command line. |
| +- `/usr/local/bin/carlos` on the host is now the v0.28.5 release CLI (the runbook's `$CARLOS`). |
| + |
| +`harness/release/step7-operator.sh` (operator shell): runbook 5.0a first run item 3 and item 4. |
| +Command changes: profile `kass` -> `harness`, bucket `carlos-dev-640012953448` -> |
| +`harness-deployment`, CV and the console file are the harness release's; `ship-app.sh` is |
| +verbatim (`infra-copies/a0b3f5a/scripts/ship-app.sh`, `CARLOS_BIN=/usr/local/bin/carlos`). |
| + |
| +``` |
| +carlos v0.28.5 |
| +== 3: CARLOS_RELEASE_KEY=$(aws ssm get-parameter --name /carlos/release-key ...) carlos channels digests on --account-id bab |
| +pointer digests on for bab. Upgraded writers sign digests from now on |
| +pointer digests: on for bab since 2026-10-20T01:12:47Z |
| +== 4: CARLOS_BIN=$CARLOS ~/infra/scripts/ship-app.sh harness harness-deployment console 1b635e61 canary/console binary $CONSOLE |
| +shipped console 1b635e61 (1 artifacts) |
| +promoted console 1b635e61 -> canary/console |
| +canary/console 1b635e61 2026-10-20T01:12:48Z yes <- carlos channels (verifies under the test release pubkey) |
| +releases/bab/pointer-digests 231 bytes <- the digests marker |
| +manifest: console 1b635e61 binary github.com/carlosframework/platform/cmd/console carlos v0.28.5 {'linux-arm64': 'b2b431f3...17e0'} |
| +``` |
| + |
| +Check (`harness/checks/check7.sh`): under the test release public key the pointer verifies |
| +(`canary/console 1b635e61 ... yes`); under a freshly minted other key the same read prints |
| +`ERROR: channel pointer signature verification failed`. **Step 7: GREEN** (2026-10-20T01:14Z). |
diff --git a/harness/checks/check7.sh b/harness/checks/check7.sh| new file mode 100644 |
| index 000000000..9cc336fbd |
| --- /dev/null |
| +++ b/harness/checks/check7.sh |
| @@ -0,0 +1,7 @@ |
| +runuser -l operator -c ' |
| +export CARLOS_DEPLOYMENT_BUCKET=harness-deployment |
| +echo "-- under the test release key:"; CARLOS_RELEASE_PUBKEY="$(cat ~/release.pub)" carlos channels --app console --account-id bab 2>&1 | tail -1 |
| +OTHER=$(carlos release-keygen 2>/dev/null | sed -n "s/^CARLOS_RELEASE_PUBKEY=//p") |
| +echo "-- under another key:"; CARLOS_RELEASE_PUBKEY="$OTHER" carlos channels --app console --account-id bab 2>&1 | tail -2 |
| +aws s3 cp --quiet s3://harness-deployment/apps/bab/console/channels/canary/console.json - | python3 -c "import json,sys; d=json.load(sys.stdin); print(\"pointer keys:\", sorted(d)); c=d.get(\"pointer\") or d; print(json.dumps(c)[:400])" |
| +' |
diff --git a/harness/infra-copies/PROVENANCE.md b/harness/infra-copies/PROVENANCE.md| index 30db28a9f..f56e9ed36 100644 |
| --- a/harness/infra-copies/PROVENANCE.md |
| +++ b/harness/infra-copies/PROVENANCE.md |
| @@ -11,3 +11,4 @@ the infrastructure repo holds. |
| | `a0b3f5a/scripts/bootstrap-box.sh` | `scripts/bootstrap-box.sh` | `a0b3f5a5f2221429e8fb18e69eba504704be25b7` | origin/main | `46aa1642a24dde15...` | |
| | `a0b3f5a/scripts/update-platform-binary.sh` | `scripts/update-platform-binary.sh` | `a0b3f5a5f2221429e8fb18e69eba504704be25b7` | origin/main | `61bf3ef8f4e6f878...` | |
| | `747f699/dev/host-init.sh` | `dev/host-init.sh` | `747f699d8e48e14fd012a4ccd2f7ea830701dcee` | origin/host-root-owned-opt | `9ef0c3dbe9506571...` | |
| +| `a0b3f5a/scripts/ship-app.sh` | `scripts/ship-app.sh` | `a0b3f5a5f2221429e8fb18e69eba504704be25b7` | origin/main | `1274367d5f64fe2e...` | |
diff --git a/harness/infra-copies/a0b3f5a/scripts/ship-app.sh b/harness/infra-copies/a0b3f5a/scripts/ship-app.sh| new file mode 100755 |
| index 000000000..55781e2c2 |
| --- /dev/null |
| +++ b/harness/infra-copies/a0b3f5a/scripts/ship-app.sh |
| @@ -0,0 +1,13 @@ |
| +#!/bin/bash |
| +# ๐ค Ship and promote a release to a deployment. The signing key is read |
| +# from SSM inside this script and exported only to the promote call โ |
| +# it never appears in a terminal or transcript. |
| +# Usage: ship-app.sh <aws-profile> <deployment-bucket> <app> <version> <channel> <kind> <file-or-dir> |
| +set -euo pipefail |
| +profile=$1; bucket=$2; app=$3; ver=$4; channel=$5; kind=$6; artifact=$7 |
| +carlos=${CARLOS_BIN:-./carlos} |
| +export AWS_PROFILE=$profile AWS_REGION=eu-west-1 CARLOS_DEPLOYMENT_BUCKET=$bucket |
| +"$carlos" ship -app "$app" -kind "$kind" -version "$ver" "$artifact" |
| +CARLOS_RELEASE_KEY=$(aws ssm get-parameter --name /carlos/release-key \ |
| + --with-decryption --query Parameter.Value --output text) \ |
| + "$carlos" promote -app "$app" "$ver" "$channel" |
diff --git a/harness/release/release-setup.sh b/harness/release/release-setup.sh| new file mode 100755 |
| index 000000000..650b66b26 |
| --- /dev/null |
| +++ b/harness/release/release-setup.sh |
| @@ -0,0 +1,51 @@ |
| +#!/bin/bash |
| +# Step 7 (host side): the harness release v0.28.5 with test keys (H5). |
| +# REL_URL_CARLOS=... REL_URL_CONSOLE=... SUMS="<carlos sha> <console sha>" CV=<ship version> release-setup.sh |
| +# The sums key is generated here on tmpfs and never leaves /run; only its |
| +# public half is written to /etc/carlos/harness-sums-pubkey (host, boxes, signer). |
| +set -euo pipefail |
| +TAG=v0.28.5; V=${TAG#v} |
| +R=/root/harness/release/$TAG; K=/run/harness/keys |
| +mkdir -p $R |
| +curl -sf -o $R/carlos-linux-arm64 "$REL_URL_CARLOS"; curl -sf -o $R/console-linux-arm64 "$REL_URL_CONSOLE" |
| +set -- $SUMS |
| +[ "$(sha256sum < $R/carlos-linux-arm64 | cut -d' ' -f1)" = "$1" ] && [ "$(sha256sum < $R/console-linux-arm64 | cut -d' ' -f1)" = "$2" ] || { echo "artefact sha256 mismatch"; exit 1; } |
| +chmod 755 $R/carlos-linux-arm64 $R/console-linux-arm64 |
| + |
| +# The test sums key (ed25519), as the README's release recipe signs with openssl. |
| +if [ ! -s $K/sums.key ]; then (umask 077; openssl genpkey -algorithm ed25519 -out $K/sums.key); fi |
| +openssl pkey -in $K/sums.key -pubout -outform DER | tail -c 32 | base64 -w0 > $R/sums.pub |
| +# No debs are built for a harness release: placeholder files stand in, so the |
| +# signed list names carlos_<V>_{amd64,arm64}.deb as publish-identity requires. |
| +for a in amd64 arm64; do printf 'harness placeholder: no deb is built for %s %s\n' "$TAG" "$a" > $R/carlos_${V}_$a.deb; done |
| +(cd $R && sha256sum carlos-linux-arm64 carlos_${V}_amd64.deb carlos_${V}_arm64.deb > SHA256SUMS.txt) |
| +openssl pkeyutl -sign -inkey $K/sums.key -rawin -in $R/SHA256SUMS.txt | base64 -w0 > $R/SHA256SUMS.txt.sig |
| +echo >> $R/SHA256SUMS.txt.sig |
| + |
| +# The sums public key where the harness build reads it. |
| +install -m 0644 $R/sums.pub /etc/carlos/harness-sums-pubkey |
| +for c in leo sophie dennis signer; do incus file push $R/sums.pub $c/etc/carlos/harness-sums-pubkey --uid 0 --gid 0 --mode 0644 -q; done |
| + |
| +# The release key's private half into the fake Parameter Store (spec ยง3.11): |
| +# read from release.env into a 0600 tmpfs file, passed with file://, never on a command line. |
| +(umask 077; sed -n 's/^CARLOS_RELEASE_KEY=//p' $K/release.env | tr -d '\n' > $K/release-key.value) |
| +eval "$(python3 -c 'import json; u=json.load(open("/root/harness/minio-users.json"))["operator"]; print("export AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s" % (u["access_key"], u["secret_key"]))')" |
| +AWS_ENDPOINT_URL_SSM=http://203.0.113.1:8100 AWS_REGION=eu-west-1 aws ssm put-parameter --name /carlos/release-key --type SecureString \ |
| + --value file://$K/release-key.value --overwrite --query Version --output text |
| +rm -f $K/release-key.value |
| +unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY |
| + |
| +# The operator's CLI is the release's (the runbook's "$CARLOS"). |
| +install -m 0755 $R/carlos-linux-arm64 /usr/local/bin/carlos |
| + |
| +# The operator gets the release files (no key). |
| +O=/home/operator/release-$TAG; install -d -o operator -g operator -m 0755 $O |
| +for f in carlos-linux-arm64 console-linux-arm64 SHA256SUMS.txt SHA256SUMS.txt.sig carlos_${V}_amd64.deb carlos_${V}_arm64.deb sums.pub; do install -o operator -g operator -m 0644 $R/$f $O/$f; done |
| +chmod 755 $O/carlos-linux-arm64 $O/console-linux-arm64 |
| +echo "$CV" > $O/CV; chown operator:operator $O/CV |
| +install -o operator -g operator -m 0755 /root/harness/infra-copies/ship-app.sh /home/operator/infra/scripts/ship-app.sh |
| +echo "== release $TAG"; (cd $R && cat SHA256SUMS.txt && sha256sum SHA256SUMS.txt SHA256SUMS.txt.sig) |
| +echo "sums pubkey $(cut -c1-8 $R/sums.pub)... on: host $(stat -c '%U:%G %a' /etc/carlos/harness-sums-pubkey)"; for c in leo sophie dennis signer; do echo " $c $(incus exec $c -- stat -c '%U:%G %a' /etc/carlos/harness-sums-pubkey)"; done |
| +echo "sums.key $(stat -c '%U:%G %a' $K/sums.key) on $(findmnt -no FSTYPE --target $K)" |
| +echo "verify sums with openssl: $(openssl pkey -in $K/sums.key -pubout > /tmp/sums.pub.pem; base64 -d $R/SHA256SUMS.txt.sig > /tmp/sig.bin; openssl pkeyutl -verify -pubin -inkey /tmp/sums.pub.pem -rawin -in $R/SHA256SUMS.txt -sigfile /tmp/sig.bin; rm -f /tmp/sums.pub.pem /tmp/sig.bin)" |
| +echo "harness info now: $(CARLOS_HARNESS_INFO=1 $R/carlos-linux-arm64 | tr '\n' ' ')" |
diff --git a/harness/release/step7-operator.sh b/harness/release/step7-operator.sh| new file mode 100755 |
| index 000000000..35e183062 |
| --- /dev/null |
| +++ b/harness/release/step7-operator.sh |
| @@ -0,0 +1,25 @@ |
| +#!/bin/bash |
| +# Step 7 (operator shell): runbook 5.0a first run item 3 (digests on for bab) |
| +# and item 4 (ship and promote the console with ship-app.sh, unchanged). |
| +# Substitutions for the harness: profile kass -> harness, bucket |
| +# carlos-dev-640012953448 -> harness-deployment, CV and the console file are |
| +# the harness release's. |
| +set -euo pipefail |
| +cd ~/release-v0.28.5 |
| +CARLOS=/usr/local/bin/carlos |
| +CONSOLE=~/release-v0.28.5/console-linux-arm64 |
| +CV=$(cat CV) |
| +"$CARLOS" version |
| +export AWS_PROFILE=harness AWS_REGION=eu-west-1 CARLOS_DEPLOYMENT_BUCKET=harness-deployment CARLOS_RELEASE_PUBKEY="$(cat ~/release.pub)" |
| +echo "== 1a"; aws s3 ls --recursive s3://harness-deployment/apps/ | awk '{print $4}' | grep -E '^apps/[^/]+/console/channels/' || echo "(no console pointer yet)" |
| +echo "== 2"; sha256sum "$CONSOLE"; grep -c "$(sha256sum "$CONSOLE" | cut -d' ' -f1)" SHA256SUMS.txt || true |
| +echo "== 3: digests on" |
| +CARLOS_RELEASE_KEY=$(aws ssm get-parameter --name /carlos/release-key --with-decryption --query Parameter.Value --output text) \ |
| + "$CARLOS" channels digests on --account-id bab |
| +echo "== 4: ship and promote" |
| +CARLOS_BIN=$CARLOS ~/infra/scripts/ship-app.sh harness harness-deployment console "$CV" canary/console binary "$CONSOLE" |
| +echo "== checks" |
| +"$CARLOS" channels --app console --account-id bab |
| +"$CARLOS" channels digests --account-id bab |
| +aws s3 ls s3://harness-deployment/releases/bab/ |
| +aws s3 cp --quiet s3://harness-deployment/apps/bab/console/releases/$CV/manifest.json - | python3 -c 'import json,sys; m=json.load(sys.stdin); print("manifest:", m.get("app"), m.get("version"), m.get("kind"), m.get("program"), m.get("built_by"), {k:v.get("sha256") for k,v in (m.get("artifacts") or {}).items()})' |