GroundSeg SLSA3 release #190
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: GroundSeg SLSA3 release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| release_channel: | |
| description: 'Release channel' | |
| required: true | |
| type: choice | |
| options: | |
| - nobuild | |
| - promote | |
| - edge | |
| - canary | |
| - latest | |
| - glob-only | |
| to_canary: | |
| description: 'Also push build to canary channel (if edge)' | |
| required: false | |
| type: boolean | |
| default: false | |
| version_server: | |
| description: 'Staging or production version server' | |
| required: true | |
| type: choice | |
| options: | |
| - staging.version.groundseg.app | |
| - version.groundseg.app | |
| permissions: read-all | |
| env: | |
| VERSION_AUTH: ${{ secrets.VERSION_AUTH }} | |
| RCLONE_CONFIG: ${{ secrets.RCLONE_CONFIG }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GLOB_AUTH: ${{ secrets.GLOB_AUTH}} | |
| jobs: | |
| args: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| commit-date: ${{ steps.ldflags.outputs.commit-date }} | |
| commit: ${{ steps.ldflags.outputs.commit }} | |
| version: ${{ steps.ldflags.outputs.version }} | |
| tree-state: ${{ steps.ldflags.outputs.tree-state }} | |
| channel: ${{ steps.channel.outputs.value }} | |
| bin-tag: ${{ steps.channel.outputs.bin-tag }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - id: ldflags | |
| run: | | |
| COMMIT_DATE=$(git log --date=iso8601-strict -1 --pretty=%ct) | |
| COMMIT=$GITHUB_SHA | |
| VERSION=$(git describe --tags --always --dirty | cut -c2-) | |
| TREE_STATE=$(if git diff --quiet; then echo "clean"; else echo "dirty"; fi) | |
| echo "commit-date=$COMMIT_DATE" >> "$GITHUB_OUTPUT" | |
| echo "commit=$COMMIT" >> "$GITHUB_OUTPUT" | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "tree-state=$TREE_STATE" >> "$GITHUB_OUTPUT" | |
| - id: channel | |
| run: | | |
| CHANNEL="${{ github.event.inputs.release_channel }}" | |
| if [ "$CHANNEL" = "latest" ]; then | |
| BIN_TAG=$(echo ${{ github.ref_name }} | cut -d'-' -f1 | sed 's@/@.@g') | |
| else | |
| BIN_TAG=$(echo ${{ github.ref_name }} | sed 's@/@.@g') | |
| fi | |
| echo "value=$CHANNEL" >> "$GITHUB_OUTPUT" | |
| echo "bin-tag=$BIN_TAG" >> "$GITHUB_OUTPUT" | |
| frontend-build: | |
| needs: args | |
| if: ${{ github.event.inputs.release_channel != 'nobuild' && github.event.inputs.release_channel != 'promote' }} | |
| runs-on: ubuntu-latest | |
| outputs: | |
| glob: ${{ steps.fe.outputs.glob }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build Frontend | |
| id: fe | |
| env: | |
| VERSION: ${{ needs.args.outputs.version }} | |
| run: | | |
| set -x | |
| cd ./ui | |
| docker build --build-arg GS_VERSION="${VERSION}" -t web-builder -f builder.Dockerfile . | |
| container_id=$(docker create web-builder) | |
| docker cp $container_id:/webui/build ./web | |
| rm -rf ../goseg/web | |
| mv web ../goseg/ | |
| docker build -t web-builder -f gallseg.Dockerfile . | |
| container_id=$(docker create web-builder) | |
| git clone https://github.com/Native-Planet/globber | |
| cd globber | |
| docker cp $container_id:/webui/build ./web | |
| ./glob.sh web | |
| hash=$(ls -1 -c . | head -1 | sed "s/glob-\\([a-z0-9\\.]*\\).glob/\\1/") | |
| mkdir -p /tmp/groundseg/version/glob | |
| mv glob-*.glob "/tmp/groundseg/version/glob/gallseg-${{ github.event.inputs.release_channel }}-${VERSION}-${hash}.glob" | |
| echo "$hash" > /tmp/groundseg/version/glob/hash.txt | |
| echo "glob=$hash" >> "$GITHUB_OUTPUT" | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: glob-output | |
| path: | | |
| /tmp/groundseg/version/glob | |
| - name: Upload web files | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: web-files | |
| path: goseg/web/ | |
| backend-build: | |
| if: ${{ github.event.inputs.release_channel != 'promote' && github.event.inputs.release_channel != 'glob-only' }} | |
| needs: [args, frontend-build] | |
| runs-on: ubuntu-latest | |
| outputs: | |
| hashes: ${{ steps.hash.outputs.hashes }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download web directory | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: web-files | |
| path: goseg/web | |
| - name: Setup Go | |
| uses: actions/setup-go@v4 | |
| with: | |
| go-version: '1.23.2' | |
| cache: false | |
| - name: Build binaries | |
| env: | |
| VERSION: ${{ needs.args.outputs.version }} | |
| COMMIT: ${{ needs.args.outputs.commit }} | |
| COMMIT_DATE: ${{ needs.args.outputs.commit-date }} | |
| TREE_STATE: ${{ needs.args.outputs.tree-state }} | |
| run: | | |
| cd goseg | |
| for arch in amd64 arm64; do | |
| GO111MODULE=on CGO_ENABLED=0 GOARCH=$arch go build -o ../groundseg_${arch}_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }} \ | |
| -trimpath \ | |
| -tags=netgo \ | |
| -ldflags="-X main.Version=${VERSION} -X main.Commit=${COMMIT} -X main.CommitDate=${COMMIT_DATE} -X main.TreeState=${TREE_STATE}" . | |
| done | |
| - name: Generate hashes | |
| id: hash | |
| run: | | |
| ls -la groundseg_*_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }} | |
| HASH_OUTPUT=$(sha256sum groundseg_*_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }} | tee /dev/stderr | base64 -w0) | |
| echo "Hash base64: $HASH_OUTPUT" >&2 | |
| echo "hashes=$HASH_OUTPUT" >> "$GITHUB_OUTPUT" | |
| - name: Upload binaries | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: binaries | |
| path: groundseg_*_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }} | |
| provenance: | |
| if: ${{ github.event.inputs.release_channel != 'promote' && github.event.inputs.release_channel != 'glob-only'}} | |
| needs: [backend-build] | |
| permissions: | |
| actions: read | |
| id-token: write | |
| contents: write | |
| uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected] | |
| with: | |
| base64-subjects: "${{ needs.backend-build.outputs.hashes }}" | |
| promote: | |
| needs: [args] | |
| if: ${{ github.event.inputs.release_channel == 'promote' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Promote version data | |
| run: | | |
| mkdir -p ~/.config/rclone/ | |
| echo "${{ env.RCLONE_CONFIG }}" > ~/.config/rclone/rclone.conf | |
| DEBIAN_FRONTEND=noninteractive sudo apt update | |
| DEBIAN_FRONTEND=noninteractive sudo apt install -y jq rclone | |
| VERSION_SERVER="${{ github.event.inputs.version_server }}" | |
| VERSION_DATA=$(curl -s -H "https://${VERSION_SERVER}/") | |
| EDGE_VALUES=$(echo "$VERSION_DATA" | jq -r '.groundseg.edge.groundseg') | |
| for key in amd64_url arm64_url; do | |
| VALUE=$(echo "$EDGE_VALUES" | jq -r ".$key") | |
| OLDFILE=${VALUE##*/} | |
| NEWFILE=$(echo "$OLDFILE" | sed 's/-rc[0-9]\+//g' | sed 's/edge/latest/g) | |
| NEWURL=$(echo "$VALUE" | sed 's/-rc[0-9]\+//g' | sed 's/edge/latest/g') | |
| rclone -vvv --config ~/.config/rclone/rclone.conf copy r2:groundseg/bin/${OLDFILE} r2:groundseg/bin/${NEWFILE} | |
| echo "Setting $key URL to $VALUE" | |
| curl -X PUT -H "X-Api-Key: ${VERSION_AUTH}" -H 'Content-Type: application/json' \ | |
| "https://${VERSION_SERVER}/modify/groundseg/latest/groundseg/${key}/payload" \ | |
| -d "{\"value\":\"$NEWURL\"}" | |
| done | |
| for key in slsa_url; do | |
| VALUE=$(echo "$EDGE_VALUES" | jq -r ".$key") | |
| echo "Setting $key URL to $VALUE" | |
| curl -X PUT -H "X-Api-Key: ${VERSION_AUTH}" -H 'Content-Type: application/json' \ | |
| "https://${VERSION_SERVER}/modify/groundseg/latest/groundseg/${key}/payload" \ | |
| -d "{\"value\":\"$VALUE\"}" | |
| done | |
| for key in amd64_sha256 arm64_sha256 major minor patch; do | |
| VALUE=$(echo "$EDGE_VALUES" | jq -r ".$key") | |
| echo "Setting $key to $VALUE" | |
| curl -X PUT -H "X-Api-Key: ${VERSION_AUTH}" -H 'Content-Type: application/json' \ | |
| "https://${VERSION_SERVER}/modify/groundseg/latest/groundseg/${key}/${VALUE}" \ | |
| -d "{\"value\":\"$VALUE\"}" | |
| done | |
| - name: Update GitHub Release | |
| if: ${{ github.event.inputs.version_server == 'version.groundseg.app' }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| TAG: ${{ needs.args.outputs.bin-tag }} | |
| run: | | |
| gh api \ | |
| --method PATCH \ | |
| "/repos/${{ github.repository }}/releases/tags/${TAG}-edge" \ | |
| -f prerelease=false | |
| deploy: | |
| needs: [args, backend-build, provenance] | |
| permissions: write-all | |
| if: ${{ github.event.inputs.release_channel != 'nobuild' && github.event.inputs.release_channel != 'promote' && github.event.inputs.release_channel != 'glob-only' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download SLSA provenance | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: multiple.intoto.jsonl | |
| - name: Download binaries | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: binaries | |
| - name: Download blobs | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: glob-output | |
| - name: Deploy Files | |
| run: | | |
| ### π² πππ‘π ππ ππ‘πππππ’ π² | |
| ### prepare environment | |
| mkdir -p ~/.config/rclone/ | |
| echo "${{ env.RCLONE_CONFIG }}" > ~/.config/rclone/rclone.conf | |
| curl -o rclone.zip https://downloads.rclone.org/rclone-current-linux-amd64.zip | |
| curl -L -o jq https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64 | |
| busybox unzip rclone.zip | |
| mv rclone-v*/rclone . | |
| chmod +x rclone | |
| chmod +x jq | |
| mv jq rclone /usr/local/bin/ | |
| # | |
| # copy files into bucket | |
| # | |
| ### copy binaries into bucket | |
| for arch in amd64 arm64; do | |
| rclone -vvv --config ~/.config/rclone/rclone.conf copy groundseg_${arch}_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }} r2:groundseg/bin | |
| done | |
| # | |
| ### copy glob into bucket | |
| ls | |
| GLOB_HASH=$(cat hash.txt | head -n 1) | |
| echo "Uploading desk glob gallseg-${{ needs.args.outputs.channel }}-${{ needs.args.outputs.bin-tag }}-${GLOB_HASH}.glob if present" | |
| globfile=$(ls *.glob) | |
| rclone -vvv --config ~/.config/rclone/rclone.conf copy ${globfile} r2:groundseg/glob | |
| curl -X POST -w "%{http_code}" \ | |
| -H "Content-Type: application/json" \ | |
| -H "X-Auth-Token: ${{ env.GLOB_AUTH }}" \ | |
| -d "{ | |
| \"branch\": \"${{ github.event.inputs.release_channel }}\", | |
| \"url\": \"https://files.native.computer/glob/${globfile}\" | |
| }" \ | |
| https://glob.native.computer/update-glob | |
| # | |
| ### copy provenance into bucket | |
| # | |
| mv multiple.intoto.jsonl groundseg_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }}.jsonl | |
| rclone -vvv --config ~/.config/rclone/rclone.conf copy groundseg_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }}.jsonl r2:groundseg/bin | |
| # | |
| # update version server | |
| # | |
| VERSION_SERVER="${{ github.event.inputs.version_server }}" | |
| # | |
| ### binaries and provenance | |
| for arch in amd64 arm64; do | |
| curl -X PUT -H "X-Api-Key: ${VERSION_AUTH}" -H 'Content-Type: application/json' \ | |
| "https://${VERSION_SERVER}/modify/groundseg/${{ needs.args.outputs.channel }}/groundseg/${arch}_url/payload" \ | |
| -d "{\"value\":\"https://files.native.computer/bin/groundseg_${arch}_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }}\"}" | |
| done | |
| curl -X PUT -H "X-Api-Key: ${VERSION_AUTH}" -H 'Content-Type: application/json' \ | |
| "https://${VERSION_SERVER}/modify/groundseg/${{ needs.args.outputs.channel }}/groundseg/slsa_url/payload" \ | |
| -d "{\"value\":\"https://files.native.computer/bin/groundseg_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }}.jsonl\"}" | |
| if [ "${{ github.event.inputs.to_canary }}" == "true" ]; then | |
| curl -X PUT -H "X-Api-Key: ${VERSION_AUTH}" -H 'Content-Type: application/json' \ | |
| "https://${VERSION_SERVER}/modify/groundseg/canary/groundseg/slsa_url/payload" \ | |
| -d "{\"value\":\"https://files.native.computer/bin/groundseg_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }}.jsonl\"}" | |
| fi | |
| # | |
| # get hashes | |
| AMD64_BIN="groundseg_amd64_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }}" | |
| ARM64_BIN="groundseg_arm64_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }}" | |
| AMDSHA=$(sha256sum "$AMD64_BIN" | awk '{print $1}') | |
| ARMSHA=$(sha256sum "$ARM64_BIN" | awk '{print $1}') | |
| curl -X PUT -H "X-Api-Key: ${{ env.VERSION_AUTH }}" \ | |
| "https://${{ github.event.inputs.version_server }}/modify/groundseg/${{ needs.args.outputs.channel }}/groundseg/amd64_sha256/$AMDSHA" | |
| curl -X PUT -H "X-Api-Key: ${{ env.VERSION_AUTH }}" \ | |
| "https://${{ github.event.inputs.version_server }}/modify/groundseg/${{ needs.args.outputs.channel }}/groundseg/arm64_sha256/$ARMSHA" | |
| # | |
| ### update canary if needed | |
| if [ "${{ github.event.inputs.to_canary }}" = "true" ] && [ "${{ github.event.inputs.release_channel }}" = "edge" ]; then | |
| for arch in amd64 arm64; do | |
| curl -X PUT -H "X-Api-Key: ${VERSION_AUTH}" -H 'Content-Type: application/json' \ | |
| "https://${VERSION_SERVER}/modify/groundseg/canary/groundseg/${arch}_url/payload" \ | |
| -d "{\"value\":\"https://files.native.computer/bin/groundseg_${arch}_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }}\"}" | |
| done | |
| curl -X PUT -H "X-Api-Key: ${{ env.VERSION_AUTH }}" \ | |
| "https://${{ github.event.inputs.version_server }}/modify/groundseg/canary/groundseg/amd64_sha256/$AMDSHA" | |
| curl -X PUT -H "X-Api-Key: ${{ env.VERSION_AUTH }}" \ | |
| "https://${{ github.event.inputs.version_server }}/modify/groundseg/canary/groundseg/arm64_sha256/$ARMSHA" | |
| fi | |
| # | |
| ### update semver | |
| VERSION=$(echo "${{ needs.args.outputs.bin-tag }}" | sed 's/-rc[0-9]*//') | |
| MAJOR=$(echo "$VERSION" | cut -d'.' -f1 | sed 's/v//') | |
| MINOR=$(echo "$VERSION" | cut -d'.' -f2) | |
| PATCH=$(echo "$VERSION" | cut -d'.' -f3) | |
| is_number() { | |
| [[ "$1" =~ ^[0-9]+$ ]] | |
| } | |
| if is_number "$MAJOR" && is_number "$MINOR" && is_number "$PATCH"; then | |
| echo "All version components are valid numbers. Sending to version server..." | |
| curl -X PUT -H "X-Api-Key: ${{ env.VERSION_AUTH }}" \ | |
| "https://${{ github.event.inputs.version_server }}/modify/groundseg/${{ needs.args.outputs.channel }}/groundseg/major/$MAJOR" | |
| curl -X PUT -H "X-Api-Key: ${{ env.VERSION_AUTH }}" \ | |
| "https://${{ github.event.inputs.version_server }}/modify/groundseg/${{ needs.args.outputs.channel }}/groundseg/minor/$MINOR" | |
| curl -X PUT -H "X-Api-Key: ${{ env.VERSION_AUTH }}" \ | |
| "https://${{ github.event.inputs.version_server }}/modify/groundseg/${{ needs.args.outputs.channel }}/groundseg/patch/$PATCH" | |
| if [ "${{ github.event.inputs.to_canary }}" = "true" ] && [ "${{ github.event.inputs.release_channel }}" = "edge" ]; then | |
| curl -X PUT -H "X-Api-Key: ${{ env.VERSION_AUTH }}" \ | |
| "https://${{ github.event.inputs.version_server }}/modify/groundseg/canary/groundseg/major/$MAJOR" | |
| curl -X PUT -H "X-Api-Key: ${{ env.VERSION_AUTH }}" \ | |
| "https://${{ github.event.inputs.version_server }}/modify/groundseg/canary/groundseg/minor/$MINOR" | |
| curl -X PUT -H "X-Api-Key: ${{ env.VERSION_AUTH }}" \ | |
| "https://${{ github.event.inputs.version_server }}/modify/groundseg/canary/groundseg/patch/$PATCH" | |
| fi | |
| else | |
| echo "Skipping version server semver update." | |
| echo "Major: $MAJOR, Minor: $MINOR, Patch: $PATCH" | |
| fi | |
| - name: Create release | |
| if: ${{ github.event.inputs.version_server == 'version.groundseg.app' && github.event.inputs.release_channel == 'latest' }} | |
| uses: softprops/action-gh-release@v2 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| tag_name: ${{ needs.args.outputs.bin-tag }} | |
| name: Release ${{ needs.args.outputs.bin-tag }} (${{ needs.args.outputs.channel }}) | |
| draft: false | |
| prerelease: ${{ contains(needs.args.outputs.channel, 'edge') || contains(needs.args.outputs.channel, 'canary') }} | |
| files: | | |
| ./groundseg_amd64_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }} | |
| ./groundseg_arm64_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }} | |
| ./groundseg_${{ needs.args.outputs.channel }}_${{ needs.args.outputs.bin-tag }}.jsonl | |
| glob-upload: | |
| if: ${{ github.event.inputs.release_channel == 'glob-only' }} | |
| needs: [frontend-build] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download blobs | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: glob-output | |
| - name: Upload glob to R2 | |
| run: | | |
| mkdir -p ~/.config/rclone/ | |
| echo "${{ env.RCLONE_CONFIG }}" > ~/.config/rclone/rclone.conf | |
| curl -o rclone.zip https://downloads.rclone.org/rclone-current-linux-amd64.zip | |
| busybox unzip rclone.zip | |
| mv rclone-v*/rclone /usr/local/bin/ | |
| chmod +x /usr/local/bin/rclone | |
| globfile=$(ls *.glob) | |
| rclone -vvv --config ~/.config/rclone/rclone.conf copy ${globfile} r2:groundseg/glob | |
| curl -X POST -w "%{http_code}" \ | |
| -H "Content-Type: application/json" \ | |
| -H "X-Auth-Token: ${{ env.GLOB_AUTH }}" \ | |
| -d "{ | |
| \"branch\": \"latest\", | |
| \"url\": \"https://files.native.computer/glob/${globfile}\" | |
| }" \ | |
| https://glob.native.computer/update-glob |