Phase 5: scaffold Crucible (sow-tools) — dispatcher + builder shims, container, PR-first CI, fail-closed (D11, D19).
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# Build the Crucible image. PR-first (D7): PRs build only; push to main also
|
||||
# publishes registry.westgate.pw/sow/crucible:<git-sha>. Never a mutable tag.
|
||||
name: build-image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
REGISTRY: registry.westgate.pw
|
||||
IMAGE: sow/crucible
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
runs-on: docker
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { fetch-depth: 0 }
|
||||
|
||||
- name: Resolve tag
|
||||
id: tag
|
||||
run: echo "sha=$(git rev-parse --short=12 HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build image
|
||||
run: |
|
||||
docker build \
|
||||
--build-arg GIT_SHA=${{ steps.tag.outputs.sha }} \
|
||||
-f docker/Dockerfile \
|
||||
-t ${REGISTRY}/${IMAGE}:${{ steps.tag.outputs.sha }} .
|
||||
|
||||
# Publish only on main (post-merge). PRs verify the build but never push.
|
||||
- name: Publish image
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
env:
|
||||
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
echo "${REGISTRY_PASSWORD}" | docker login "${REGISTRY}" -u "${REGISTRY_USER}" --password-stdin
|
||||
docker push ${REGISTRY}/${IMAGE}:${{ steps.tag.outputs.sha }}
|
||||
@@ -1,179 +0,0 @@
|
||||
name: Release Tool
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: self-hosted
|
||||
env:
|
||||
CHECKOUT_PATH: ${{ vars.SOW_TOOLS_CHECKOUT_PATH }}
|
||||
|
||||
steps:
|
||||
- name: Sync tools checkout to tag
|
||||
env:
|
||||
TAG_NAME: ${{ github.ref_name }}
|
||||
EVENT_SHA: ${{ github.sha }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
CHECKOUT_PATH="${CHECKOUT_PATH:-/home/ubuntu/tools-checkout}"
|
||||
CLONE_URL="https://x-access-token:${GITHUB_TOKEN}@gitea.westgate.pw/ShadowsOverWestgate/sow-tools.git"
|
||||
export GIT_TERMINAL_PROMPT=0
|
||||
|
||||
if [[ ! -d "${CHECKOUT_PATH}/.git" ]]; then
|
||||
mkdir -p "$(dirname "${CHECKOUT_PATH}")"
|
||||
echo "Cloning tools checkout into ${CHECKOUT_PATH}"
|
||||
timeout 120 git clone --filter=blob:none "${CLONE_URL}" "${CHECKOUT_PATH}"
|
||||
fi
|
||||
|
||||
cd "${CHECKOUT_PATH}"
|
||||
git remote set-url origin "${CLONE_URL}"
|
||||
echo "Fetching tools refs"
|
||||
timeout 120 git fetch origin --prune --force --tags
|
||||
TAG_SHA="$(git rev-list -n1 "refs/tags/${TAG_NAME}")"
|
||||
if [[ -n "${EVENT_SHA}" && "${TAG_SHA}" != "${EVENT_SHA}" ]]; then
|
||||
echo "Fetched tag ${TAG_NAME} resolves to ${TAG_SHA}, but the workflow event SHA is ${EVENT_SHA}."
|
||||
echo "Refusing to build a release from a stale or mismatched local tag."
|
||||
exit 1
|
||||
fi
|
||||
git reset --hard "refs/tags/${TAG_NAME}"
|
||||
git clean -ffdx
|
||||
echo "Release ${TAG_NAME} will build commit $(git rev-parse HEAD)"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get update -qq
|
||||
apt-get install -y --no-install-recommends curl jq
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.26'
|
||||
cache: false
|
||||
|
||||
- name: Build sow-toolkit binaries
|
||||
run: |
|
||||
set -euo pipefail
|
||||
CHECKOUT_PATH="${CHECKOUT_PATH:-/home/ubuntu/tools-checkout}"
|
||||
cd "${CHECKOUT_PATH}"
|
||||
rm -f ./tools/sow-toolkit ./tools/sow-toolkit.exe
|
||||
mkdir -p tools .cache/go-build
|
||||
GOCACHE="${PWD}/.cache/go-build" go build -o ./tools/sow-toolkit ./cmd/nwn-tool
|
||||
GOOS=windows GOARCH=amd64 GOCACHE="${PWD}/.cache/go-build" go build -o ./tools/sow-toolkit.exe ./cmd/nwn-tool
|
||||
go version -m ./tools/sow-toolkit
|
||||
|
||||
- name: Pack release bundles
|
||||
id: pack
|
||||
env:
|
||||
TAG_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
CHECKOUT_PATH="${CHECKOUT_PATH:-/home/ubuntu/tools-checkout}"
|
||||
cd "${CHECKOUT_PATH}"
|
||||
linux_binary="tools/sow-toolkit"
|
||||
windows_binary="tools/sow-toolkit.exe"
|
||||
if [[ ! -x "${linux_binary}" ]]; then
|
||||
echo "Missing built tool: ${linux_binary}"
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "${windows_binary}" ]]; then
|
||||
echo "Missing built tool: ${windows_binary}"
|
||||
exit 1
|
||||
fi
|
||||
linux_archive="/tmp/sow-toolkit-linux-amd64-${TAG_NAME}.tar.gz"
|
||||
windows_archive="/tmp/sow-toolkit-windows-amd64-${TAG_NAME}.exe"
|
||||
tar -czf "${linux_archive}" -C tools sow-toolkit
|
||||
cp "${windows_binary}" "${windows_archive}"
|
||||
echo "linux_archive=${linux_archive}" >> "$GITHUB_OUTPUT"
|
||||
echo "linux_asset_name=sow-toolkit-linux-amd64.tar.gz" >> "$GITHUB_OUTPUT"
|
||||
echo "windows_archive=${windows_archive}" >> "$GITHUB_OUTPUT"
|
||||
echo "windows_asset_name=sow-toolkit-windows-amd64.exe" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG_NAME}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create release and upload artifacts
|
||||
env:
|
||||
GITEA_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITEA_SERVER_URL: ${{ github.server_url }}
|
||||
GITEA_REPO: ${{ github.repository }}
|
||||
LINUX_ARCHIVE: ${{ steps.pack.outputs.linux_archive }}
|
||||
LINUX_ASSET_NAME: ${{ steps.pack.outputs.linux_asset_name }}
|
||||
WINDOWS_ARCHIVE: ${{ steps.pack.outputs.windows_archive }}
|
||||
WINDOWS_ASSET_NAME: ${{ steps.pack.outputs.windows_asset_name }}
|
||||
TAG_NAME: ${{ steps.pack.outputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
API="${GITEA_SERVER_URL%/}/api/v1"
|
||||
OWNER="${GITEA_REPO%%/*}"
|
||||
NAME="${GITEA_REPO#*/}"
|
||||
AUTH=(-H "Authorization: token ${GITEA_API_TOKEN}" -H "Accept: application/json")
|
||||
RESP_FILE=/tmp/gitea-release.json
|
||||
|
||||
CODE=$(curl -sS -o "${RESP_FILE}" -w "%{http_code}" "${AUTH[@]}" \
|
||||
"${API}/repos/${OWNER}/${NAME}/releases/tags/${TAG_NAME}")
|
||||
|
||||
if [[ "${CODE}" == "200" ]]; then
|
||||
RELEASE_ID=$(jq -r '.id' "${RESP_FILE}")
|
||||
else
|
||||
BODY=$(jq -n --arg t "${TAG_NAME}" \
|
||||
'{tag_name: $t, name: $t, body: "sow-toolkit binary release built by Gitea Actions."}')
|
||||
CODE=$(curl -sS -o "${RESP_FILE}" -w "%{http_code}" "${AUTH[@]}" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d "${BODY}" \
|
||||
"${API}/repos/${OWNER}/${NAME}/releases")
|
||||
if [[ "${CODE}" != "201" && "${CODE}" != "200" ]]; then
|
||||
echo "Create release failed (HTTP ${CODE}):"
|
||||
cat "${RESP_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
RELEASE_ID=$(jq -r '.id' "${RESP_FILE}")
|
||||
fi
|
||||
|
||||
for name in "${LINUX_ASSET_NAME}" "${WINDOWS_ASSET_NAME}"; do
|
||||
ASSET_ID=$(jq -r --arg name "${name}" '.assets[]? | select(.name == $name) | .id' "${RESP_FILE}" | head -n1)
|
||||
if [[ -n "${ASSET_ID}" ]]; then
|
||||
curl -sS -f "${AUTH[@]}" -X DELETE \
|
||||
"${API}/repos/${OWNER}/${NAME}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
|
||||
fi
|
||||
done
|
||||
|
||||
curl -sS -f "${AUTH[@]}" -X POST \
|
||||
-F "attachment=@${LINUX_ARCHIVE}" \
|
||||
"${API}/repos/${OWNER}/${NAME}/releases/${RELEASE_ID}/assets?name=${LINUX_ASSET_NAME}"
|
||||
|
||||
curl -sS -f "${AUTH[@]}" -X POST \
|
||||
-F "attachment=@${WINDOWS_ARCHIVE}" \
|
||||
"${API}/repos/${OWNER}/${NAME}/releases/${RELEASE_ID}/assets?name=${WINDOWS_ASSET_NAME}"
|
||||
|
||||
- name: Cleanup temporary files
|
||||
if: always()
|
||||
env:
|
||||
TAG_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
rm -f \
|
||||
"/tmp/sow-toolkit-linux-amd64-${TAG_NAME}.tar.gz" \
|
||||
"/tmp/sow-toolkit-windows-amd64-${TAG_NAME}.exe" \
|
||||
/tmp/gitea-release.json
|
||||
|
||||
- name: Prune old releases (keep latest 1)
|
||||
env:
|
||||
GITEA_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITEA_SERVER_URL: ${{ github.server_url }}
|
||||
GITEA_REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
API="${GITEA_SERVER_URL%/}/api/v1"
|
||||
OWNER="${GITEA_REPO%%/*}"
|
||||
NAME="${GITEA_REPO#*/}"
|
||||
AUTH=(-H "Authorization: token ${GITEA_API_TOKEN}" -H "Accept: application/json")
|
||||
curl -sS "${AUTH[@]}" \
|
||||
"${API}/repos/${OWNER}/${NAME}/releases?limit=50&page=1" \
|
||||
-o /tmp/all-releases.json
|
||||
jq -r '.[1:][].id' /tmp/all-releases.json | while read -r rid; do
|
||||
[[ -n "${rid}" ]] || continue
|
||||
curl -sS -f "${AUTH[@]}" -X DELETE \
|
||||
"${API}/repos/${OWNER}/${NAME}/releases/${rid}"
|
||||
done
|
||||
rm -f /tmp/all-releases.json
|
||||
@@ -0,0 +1,40 @@
|
||||
# Tag a Crucible release: re-tag the already-built immutable image and attach
|
||||
# per-platform binary bundles. Tag-gated; never PR-triggered (deploy/release is
|
||||
# not a PR concern, D7). The :<sha> image from build-image is the source of truth.
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
env:
|
||||
REGISTRY: registry.westgate.pw
|
||||
IMAGE: sow/crucible
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: nixos/nix:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { fetch-depth: 0 }
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
nix develop --command bash -c '
|
||||
set -euo pipefail
|
||||
GOOS=linux GOARCH=amd64 scripts/build-binaries.sh
|
||||
mkdir -p dist
|
||||
tar -C bin -czf "dist/crucible-linux-amd64-${GITHUB_REF_NAME}.tar.gz" .
|
||||
'
|
||||
|
||||
- name: Upload release artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: crucible-${{ github.ref_name }}
|
||||
path: dist/*
|
||||
|
||||
# TODO(phase-6): re-tag registry.westgate.pw/sow/crucible:<sha> as :<tag>
|
||||
# and pin it from sow-platform releases/*.yml once the registry is live.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Lint + unit tests for the Crucible suite. PR-first: PRs + push to main (D7).
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: nixos/nix:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { fetch-depth: 0 }
|
||||
- name: vet + test + lint
|
||||
run: |
|
||||
nix develop --command bash -c '
|
||||
set -euo pipefail
|
||||
go vet ./...
|
||||
go test ./...
|
||||
shellcheck scripts/*.sh
|
||||
yamllint .gitea
|
||||
'
|
||||
- name: binary smoke (fail-closed contract)
|
||||
run: nix develop --command make smoke
|
||||
Reference in New Issue
Block a user