GroundSeg SLSA3 release #171
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/generator_generic_slsa3.yml@v2.0.0 | |
| 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 |