Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
316 changes: 48 additions & 268 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ env:
YARN_VERSION: "4.18.0"

jobs:
build-console:
name: Build web console
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Enable yarn
run: corepack enable && corepack prepare yarn@${{ env.YARN_VERSION }} --activate
Expand All @@ -40,52 +42,16 @@ jobs:
- name: Build web console
run: ./scripts/build_web_console.sh

- name: Upload web console
uses: actions/upload-artifact@v6
with:
name: web-console
path: web-console/build
if-no-files-found: error

build-binaries:
name: Build binaries
needs: build-console
runs-on: ubuntu-latest
outputs:
version: ${{ steps.meta.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v7
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true

- name: Download web console
uses: actions/download-artifact@v6
with:
name: web-console
path: web-console/build

- name: Verify web console
- name: Build binaries
run: |
# upload-artifact stores the contents of web-console/build at the zip root
if [ -f web-console/index.html ]; then
mkdir -p web-console/build
find web-console -mindepth 1 -maxdepth 1 ! -name build -exec mv {} web-console/build/ \;
fi
if [ ! -f web-console/build/index.html ] && [ -f web-console/build/build/index.html ]; then
mv web-console/build/build/* web-console/build/
rmdir web-console/build/build
fi
test -f web-console/build/index.html

- name: Build binaries
run: ./scripts/generate_executables.sh
./scripts/generate_executables.sh

- name: Set version and checksums
id: meta
Expand All @@ -98,246 +64,60 @@ jobs:
sha256sum *.tar.gz > ./SHA256SUMS.txt
sha256sum *.zip >> ./SHA256SUMS.txt

- name: Upload archives
uses: actions/upload-artifact@v6
with:
name: archives
path: |
builds/*.tar.gz
builds/*.zip
builds/build_timestamp.txt
builds/SHA256SUMS.txt
if-no-files-found: error

- name: Upload container binaries
uses: actions/upload-artifact@v6
with:
name: container-binaries
path: builds/binary
if-no-files-found: error

github-release:
name: GitHub release
needs: build-binaries
if: >
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) &&
github.repository == 'mycontroller-org/server'
runs-on: ubuntu-latest
steps:
- name: Download archives
uses: actions/download-artifact@v6
with:
name: archives
path: .

- name: Verify archives
run: |
if [ -f SHA256SUMS.txt ]; then
mkdir -p builds
mv ./*.tar.gz ./*.zip ./build_timestamp.txt ./SHA256SUMS.txt builds/ 2>/dev/null || true
fi
if [ ! -f builds/SHA256SUMS.txt ] && [ -f builds/builds/SHA256SUMS.txt ]; then
mv builds/builds/* builds/
rmdir builds/builds
fi
test -f builds/SHA256SUMS.txt
ls builds/*.tar.gz builds/*.zip

- name: Release (tagged version)
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v3
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
with:
fail_on_unmatched_files: true
name: ${{ github.ref_name }}
files: |
builds/*.tar.gz
builds/*.zip
builds/build_timestamp.txt
builds/SHA256SUMS.txt

- name: Delete existing development release
if: github.ref == 'refs/heads/main'
- name: GitHub release (tagged version)
if: >
startsWith(github.ref, 'refs/tags/') &&
github.repository == 'mycontroller-org/server'
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
gh release delete development --yes || true
gh api --method DELETE "/repos/${GITHUB_REPOSITORY}/git/refs/tags/development" || true
gh release create "${GITHUB_REF_NAME}" \
--title "${GITHUB_REF_NAME}" \
--target "${GITHUB_SHA}" \
builds/*.tar.gz builds/*.zip builds/build_timestamp.txt builds/SHA256SUMS.txt

- name: Release (development rolling)
if: github.ref == 'refs/heads/main'
uses: softprops/action-gh-release@v3
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
with:
fail_on_unmatched_files: true
tag_name: development
target_commitish: ${{ github.sha }}
name: Development Build - Pre Release
prerelease: true
make_latest: false
body: |
Rolling build from `main` at ${{ github.sha }}.
Not guaranteed to be stable. Use a tagged release for production.
files: |
builds/*.tar.gz
builds/*.zip
builds/build_timestamp.txt
builds/SHA256SUMS.txt

- name: Point development tag at this commit
if: github.ref == 'refs/heads/main'
- name: GitHub release (development rolling)
if: github.ref == 'refs/heads/main' && github.repository == 'mycontroller-org/server'
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
# GitHub does not move an existing tag via target_commitish.
if gh api "/repos/${GITHUB_REPOSITORY}/git/refs/tags/development" >/dev/null 2>&1; then
gh api --method PATCH "/repos/${GITHUB_REPOSITORY}/git/refs/tags/development" \
-f sha="${GITHUB_SHA}" \
-F force=true
else
gh api --method POST "/repos/${GITHUB_REPOSITORY}/git/refs" \
-f ref="refs/tags/development" \
-f sha="${GITHUB_SHA}"
fi

container-images:
name: Container image
needs: build-binaries
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
component: [server, gateway, handler]
target:
- { platform: linux/amd64, binary_dir: linux-amd64 }
- { platform: linux/arm64, binary_dir: linux-arm64 }
- { platform: linux/arm/v7, binary_dir: linux-armv7 }
- { platform: linux/arm/v6, binary_dir: linux-armv6 }
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Download container binaries
uses: actions/download-artifact@v6
with:
name: container-binaries
path: builds

- name: Verify container binary
run: |
if [ ! -d builds/binary ] && [ -d builds/linux-amd64 ]; then
mkdir -p builds/binary
mv builds/linux-* builds/binary/
fi
test -f "builds/binary/${{ matrix.target.binary_dir }}/mycontroller-${{ matrix.component }}"
gh release delete development --yes || true
gh api --method DELETE "/repos/${GITHUB_REPOSITORY}/git/refs/tags/development" || true
gh release create development \
--prerelease \
--title "Development Build - Pre Release" \
--target "${GITHUB_SHA}" \
--notes "Rolling build from \`main\` at ${GITHUB_SHA}. Not guaranteed to be stable. Use a tagged release for production." \
builds/*.tar.gz builds/*.zip builds/build_timestamp.txt builds/SHA256SUMS.txt
gh api --method PATCH "/repos/${GITHUB_REPOSITORY}/git/refs/tags/development" \
-f sha="${GITHUB_SHA}" \
-F force=true

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Login to Docker Hub
if: github.repository == 'mycontroller-org/server'
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
run: docker buildx create --use --name mc-builder --driver docker-container

- name: Login to Quay
if: github.repository == 'mycontroller-org/server'
uses: docker/login-action@v4
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_TOKEN }}

- name: Login to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Image tags
id: tags
- name: Login to container registries
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
env:
VERSION: ${{ needs.build-binaries.outputs.version }}
COMPONENT: ${{ matrix.component }}
BINARY_DIR: ${{ matrix.target.binary_dir }}
OWNER: ${{ github.repository_owner }}
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }}
QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}
run: |
{
echo 'list<<EOF'
echo "ghcr.io/${OWNER}/${COMPONENT}:${VERSION}-${BINARY_DIR}"
if [ "${GITHUB_REPOSITORY}" = "mycontroller-org/server" ]; then
echo "quay.io/mycontroller/${COMPONENT}:${VERSION}-${BINARY_DIR}"
echo "docker.io/mycontroller/${COMPONENT}:${VERSION}-${BINARY_DIR}"
fi
echo 'EOF'
} >> "$GITHUB_OUTPUT"

- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
file: docker/${{ matrix.component }}.Dockerfile
platforms: ${{ matrix.target.platform }}
push: true
provenance: false
sbom: false
build-args: |
BINARY_PATH=builds/binary/${{ matrix.target.binary_dir }}/mycontroller-${{ matrix.component }}
tags: ${{ steps.tags.outputs.list }}

container-manifests:
name: Container manifests
needs: [build-binaries, container-images]
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
component: [server, gateway, handler]
registry:
- quay.io/mycontroller
- docker.io/mycontroller
- ghcr.io/${{ github.repository_owner }}
steps:
- name: Login to Docker Hub
if: github.repository == 'mycontroller-org/server' && startsWith(matrix.registry, 'docker.io/')
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}

- name: Login to Quay
if: github.repository == 'mycontroller-org/server' && startsWith(matrix.registry, 'quay.io/')
uses: docker/login-action@v4
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_TOKEN }}

- name: Login to GHCR
if: startsWith(matrix.registry, 'ghcr.io/')
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
echo "${GHCR_TOKEN}" | docker login ghcr.io -u "${GITHUB_ACTOR}" --password-stdin
if [ "${GITHUB_REPOSITORY}" = "mycontroller-org/server" ]; then
echo "${DOCKER_TOKEN}" | docker login docker.io -u "${DOCKER_USERNAME}" --password-stdin
echo "${QUAY_TOKEN}" | docker login quay.io -u "${QUAY_USERNAME}" --password-stdin
fi

- name: Create multi-arch manifest
if: github.repository == 'mycontroller-org/server' || startsWith(matrix.registry, 'ghcr.io/')
- name: Push container images
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
env:
IMAGE: ${{ matrix.registry }}/${{ matrix.component }}:${{ needs.build-binaries.outputs.version }}
PUSH_DOCKER_QUAY: ${{ github.repository == 'mycontroller-org/server' && '1' || '0' }}
run: |
docker buildx imagetools create \
--tag "${IMAGE}" \
"${IMAGE}-linux-amd64" \
"${IMAGE}-linux-arm64" \
"${IMAGE}-linux-armv7" \
"${IMAGE}-linux-armv6"
for TARGET_BUILD in server gateway handler; do
export TARGET_BUILD
./scripts/publish_container_images.sh
done
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Draft for **2.3.0** (`versions.txt`). Compared to [v2.2.0](https://github.com/my

### Changed

- Release workflow builds host binaries (Go 1.27.1 in Actions) and copies only those binaries into Alpine 3.24 images. Images are pushed to Docker Hub, Quay, and GHCR. `main` republishes the `development` pre-release by deleting and recreating it.
- Release workflow builds host binaries (Go 1.27.1 in Actions) and copies only those binaries into Alpine 3.24 images on the same runner (no artifact upload/download). Images are pushed to GHCR, Quay, and Docker Hub. `main` republishes the `development` pre-release by deleting and recreating it.
- Policy **Import** uses `storage.Upsert` so `createdOn` / `modifiedOn` come from the backup file (same as other resources). Built-in policies are still skipped.
- Built-in policies (admin / readwrite / readonly) are no longer rewritten on every start. `ModifiedOn` stays unless the code definition changed. Existing rows without `CreatedOn` are backfilled from `ModifiedOn` once.
- `make setup-release` now requires both versions: `make setup-release VERSION=x.y.z NEXT_VERSION=x.y.z`. The PR writes `NEXT_VERSION` into `versions.txt`.
Expand Down
11 changes: 10 additions & 1 deletion scripts/cmd/pack_web_console/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"archive/zip"
"bytes"
"fmt"
"io"
"io/fs"
Expand All @@ -17,9 +18,17 @@ func main() {
}

src := filepath.Join(root, "web-console", "build")
if _, err := os.Stat(filepath.Join(src, "index.html")); err != nil {
indexPath := filepath.Join(src, "index.html")
if _, err := os.Stat(indexPath); err != nil {
fatal(fmt.Errorf("web-console/build/index.html not found; run ./scripts/build_web_console.sh first: %w", err))
}
indexHTML, err := os.ReadFile(indexPath)
if err != nil {
fatal(err)
}
if bytes.Contains(indexHTML, []byte("/src/index.js")) {
fatal(fmt.Errorf("web-console/build/index.html is the Vite source page, not a production build"))
}

destDir := filepath.Join(root, "pkg", "http_router", "web-console", "assets")
if err := os.MkdirAll(destDir, 0o755); err != nil {
Expand Down
Loading
Loading