mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-09 04:30:20 +08:00
Consolidate subscription usage policy enforcement, privacy-safe persistence, and gateway security hardening into one reviewable change. Includes bounded HTTP and execution envelopes, header and protocol guards, DNS and relay validation, authentication and secret projection hardening, secure backup/install paths, and regression coverage.
471 lines
16 KiB
YAML
471 lines
16 KiB
YAML
name: Release Aether
|
|
|
|
on:
|
|
push:
|
|
tags: ['v*']
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: release-aether-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
REGISTRY: ghcr.io
|
|
GHCR_IMAGE: fawney19/aether
|
|
DOCKERHUB_IMAGE: fawney19/aether
|
|
|
|
jobs:
|
|
preflight:
|
|
name: Release preflight
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
publish: ${{ steps.classify.outputs.publish }}
|
|
version_tag: ${{ steps.classify.outputs.version_tag }}
|
|
prerelease: ${{ steps.classify.outputs.prerelease }}
|
|
make_latest: ${{ steps.classify.outputs.make_latest }}
|
|
steps:
|
|
- name: Classify release tag
|
|
id: classify
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
echo "publish=false" >> "${GITHUB_OUTPUT}"
|
|
echo "version_tag=" >> "${GITHUB_OUTPUT}"
|
|
echo "prerelease=false" >> "${GITHUB_OUTPUT}"
|
|
echo "make_latest=false" >> "${GITHUB_OUTPUT}"
|
|
|
|
if [[ "${GITHUB_REF_TYPE}" != "tag" ]]; then
|
|
echo "Manual release build; publish jobs will be skipped."
|
|
exit 0
|
|
fi
|
|
|
|
tag="${GITHUB_REF_NAME}"
|
|
if [[ ! "${tag}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-(beta|rc)\.[0-9]+)?$ ]]; then
|
|
echo "Unsupported release tag: ${tag}" >&2
|
|
echo "Expected vX.Y.Z, vX.Y.Z-beta.N, or vX.Y.Z-rc.N." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "version_tag=${tag}" >> "${GITHUB_OUTPUT}"
|
|
|
|
if [[ "${tag}" == *-* ]]; then
|
|
echo "prerelease=true" >> "${GITHUB_OUTPUT}"
|
|
else
|
|
echo "make_latest=true" >> "${GITHUB_OUTPUT}"
|
|
fi
|
|
|
|
if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
|
|
echo "publish=true" >> "${GITHUB_OUTPUT}"
|
|
else
|
|
echo "Manual release build for ${tag}; publish jobs will be skipped."
|
|
fi
|
|
|
|
frontend:
|
|
name: Build frontend
|
|
needs: preflight
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
with:
|
|
node-version: 22
|
|
cache: npm
|
|
cache-dependency-path: |
|
|
frontend/package-lock.json
|
|
aether-vscodex/web/package-lock.json
|
|
|
|
- name: Build aether-vscodex web
|
|
working-directory: aether-vscodex/web
|
|
run: |
|
|
npm ci
|
|
npm run build
|
|
|
|
- name: Install & build
|
|
working-directory: frontend
|
|
run: |
|
|
npm ci
|
|
npm run build
|
|
|
|
- name: Upload frontend artifact
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
with:
|
|
name: frontend-dist
|
|
path: frontend/dist/
|
|
if-no-files-found: error
|
|
retention-days: 1
|
|
|
|
vscodex:
|
|
name: Build VS Code Codex extension
|
|
needs: preflight
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
with:
|
|
node-version: 22
|
|
cache: npm
|
|
cache-dependency-path: |
|
|
aether-vscodex/package-lock.json
|
|
aether-vscodex/web/package-lock.json
|
|
aether-vscodex/vscode-extension/package-lock.json
|
|
|
|
- name: Install module test dependencies
|
|
working-directory: aether-vscodex
|
|
run: npm ci
|
|
|
|
- name: Build the embedded Web UI
|
|
working-directory: aether-vscodex/web
|
|
run: |
|
|
npm ci
|
|
npm run build
|
|
|
|
- name: Install extension dependencies
|
|
working-directory: aether-vscodex/vscode-extension
|
|
run: npm ci
|
|
|
|
- name: Check and compile the extension
|
|
working-directory: aether-vscodex/vscode-extension
|
|
run: |
|
|
npm run check
|
|
npm run build
|
|
|
|
- name: Run module tests
|
|
working-directory: aether-vscodex
|
|
run: npm test
|
|
|
|
- name: Run Web UI tests
|
|
working-directory: aether-vscodex/web
|
|
run: npm test
|
|
|
|
- name: Package VSIX
|
|
working-directory: aether-vscodex/vscode-extension
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
version="$(node -p "require('./package.json').version")"
|
|
npx --yes @vscode/vsce package --no-update-package-json --allow-missing-repository
|
|
source_vsix="codex-remote-collab-${version}.vsix"
|
|
test -f "${source_vsix}"
|
|
mv "${source_vsix}" "aether-vscodex-${version}.vsix"
|
|
unzip -l "aether-vscodex-${version}.vsix" | grep 'extension/node_modules/ws/index.js' >/dev/null
|
|
|
|
- name: Upload VSIX artifact
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
with:
|
|
name: aether-vscodex-vsix
|
|
path: aether-vscodex/vscode-extension/aether-vscodex-*.vsix
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
build:
|
|
name: Build ${{ matrix.name }}
|
|
needs: preflight
|
|
runs-on: ${{ matrix.os }}
|
|
strategy:
|
|
fail-fast: true
|
|
matrix:
|
|
include:
|
|
- name: linux-amd64
|
|
target: x86_64-unknown-linux-musl
|
|
platform: linux
|
|
arch: amd64
|
|
os: ubuntu-latest
|
|
use_cross: true
|
|
- name: linux-arm64
|
|
target: aarch64-unknown-linux-musl
|
|
platform: linux
|
|
arch: arm64
|
|
os: ubuntu-latest
|
|
use_cross: true
|
|
- name: macos-amd64
|
|
target: x86_64-apple-darwin
|
|
platform: macos
|
|
arch: amd64
|
|
os: macos-15-intel
|
|
use_cross: false
|
|
- name: macos-arm64
|
|
target: aarch64-apple-darwin
|
|
platform: macos
|
|
arch: arm64
|
|
os: macos-15
|
|
use_cross: false
|
|
steps:
|
|
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
|
|
- name: Rust cache
|
|
uses: Swatinem/rust-cache@49a0bdc70d2e1b713ca9e2869b211fcce03d3c1c # v2
|
|
with:
|
|
shared-key: release-${{ matrix.target }}
|
|
workspaces: . -> target
|
|
|
|
- name: Install cross
|
|
if: matrix.use_cross
|
|
uses: taiki-e/install-action@1ae7257be536a92d9218a6b343dc6e6ba650f7e1 # cross
|
|
|
|
- name: Build
|
|
env:
|
|
AETHER_VERSION: ${{ needs.preflight.outputs.version_tag }}
|
|
AETHER_BUILD_TYPE: release
|
|
CARGO_TERM_COLOR: always
|
|
shell: bash
|
|
run: |
|
|
if [[ "${{ matrix.use_cross }}" == "true" ]]; then
|
|
cross build --release --locked -p aether-gateway --target ${{ matrix.target }}
|
|
else
|
|
cargo build --release --locked -p aether-gateway --target ${{ matrix.target }}
|
|
fi
|
|
|
|
- name: Upload binary artifact
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
with:
|
|
name: aether-gateway-${{ matrix.platform }}-${{ matrix.arch }}
|
|
path: target/${{ matrix.target }}/release/aether-gateway
|
|
if-no-files-found: error
|
|
retention-days: 1
|
|
|
|
docker:
|
|
name: Docker multi-arch
|
|
needs: [preflight, frontend, build]
|
|
if: needs.preflight.outputs.publish == 'true'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
actions: read
|
|
attestations: write
|
|
contents: read
|
|
id-token: write
|
|
packages: write
|
|
steps:
|
|
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
|
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
|
|
with:
|
|
path: artifacts
|
|
|
|
- name: Prepare dist layout
|
|
run: |
|
|
mkdir -p dist
|
|
cp artifacts/aether-gateway-linux-amd64/aether-gateway dist/aether-gateway-amd64
|
|
cp artifacts/aether-gateway-linux-arm64/aether-gateway dist/aether-gateway-arm64
|
|
chmod +x dist/aether-gateway-amd64 dist/aether-gateway-arm64
|
|
cp -r artifacts/frontend-dist dist/frontend
|
|
|
|
- name: Set up QEMU
|
|
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
|
|
|
- name: Log in to GHCR
|
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Log in to Docker Hub
|
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
|
with:
|
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
|
|
- name: Extract metadata
|
|
id: meta
|
|
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
|
|
with:
|
|
images: |
|
|
${{ env.REGISTRY }}/${{ env.GHCR_IMAGE }}
|
|
docker.io/${{ env.DOCKERHUB_IMAGE }}
|
|
tags: |
|
|
type=semver,pattern={{version}}
|
|
type=semver,pattern={{major}}.{{minor}},enable=${{ needs.preflight.outputs.make_latest == 'true' }}
|
|
type=raw,value=latest,enable=${{ needs.preflight.outputs.make_latest == 'true' }}
|
|
type=raw,value=beta,enable=${{ contains(github.ref_name, '-beta.') }}
|
|
type=raw,value=rc,enable=${{ contains(github.ref_name, '-rc.') }}
|
|
type=sha,prefix=
|
|
flavor: |
|
|
latest=false
|
|
|
|
- name: Build and push
|
|
id: push
|
|
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
|
with:
|
|
context: .
|
|
file: ./Dockerfile.app
|
|
push: true
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
platforms: linux/amd64,linux/arm64
|
|
|
|
- name: Attest GHCR image provenance
|
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
|
with:
|
|
subject-name: ${{ env.REGISTRY }}/${{ env.GHCR_IMAGE }}
|
|
subject-digest: ${{ steps.push.outputs.digest }}
|
|
push-to-registry: true
|
|
create-storage-record: false
|
|
|
|
- name: Attest Docker Hub image provenance
|
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
|
with:
|
|
subject-name: docker.io/${{ env.DOCKERHUB_IMAGE }}
|
|
subject-digest: ${{ steps.push.outputs.digest }}
|
|
push-to-registry: true
|
|
create-storage-record: false
|
|
|
|
package:
|
|
name: Release tarballs
|
|
needs: [preflight, frontend, build]
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
actions: read
|
|
attestations: write
|
|
contents: read
|
|
id-token: write
|
|
steps:
|
|
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
|
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
|
|
with:
|
|
path: artifacts
|
|
|
|
- name: Build release packages
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
|
|
VERSION="${GITHUB_REF_NAME}"
|
|
SOURCE_REF="${GITHUB_REF_NAME}"
|
|
else
|
|
VERSION="snapshot-${GITHUB_SHA::7}"
|
|
SOURCE_REF="${GITHUB_SHA}"
|
|
fi
|
|
|
|
mkdir -p package release-assets
|
|
for platform in linux macos; do
|
|
for arch in amd64 arm64; do
|
|
bundle="aether-${VERSION}-${platform}-${arch}"
|
|
root="package/${bundle}"
|
|
mkdir -p \
|
|
"${root}/bin" \
|
|
"${root}/frontend"
|
|
|
|
install -m 0755 "artifacts/aether-gateway-${platform}-${arch}/aether-gateway" "${root}/bin/aether-gateway"
|
|
cp -R artifacts/frontend-dist/. "${root}/frontend/"
|
|
sed \
|
|
-e "s/^SOURCE_REF=\"\${AETHER_SOURCE_REF:-main}\"/SOURCE_REF=\"\${AETHER_SOURCE_REF:-${SOURCE_REF}}\"/" \
|
|
-e "s/^VERSION=\"\${AETHER_VERSION:-}\"/VERSION=\"\${AETHER_VERSION:-${VERSION}}\"/" \
|
|
install.sh > "${root}/install.sh"
|
|
chmod 0755 "${root}/install.sh"
|
|
install -m 0755 update.sh "${root}/update.sh"
|
|
install -m 0644 docker-compose.yml "${root}/docker-compose.yml"
|
|
install -m 0644 docker-compose.single-node.yml "${root}/docker-compose.single-node.yml"
|
|
install -m 0644 .env.example "${root}/.env.example"
|
|
install -m 0755 generate_keys.sh "${root}/generate_keys.sh"
|
|
install -m 0644 README.md "${root}/README.md"
|
|
install -m 0644 LICENSE "${root}/LICENSE"
|
|
|
|
tar -C package -czf "release-assets/${bundle}.tar.gz" "${bundle}"
|
|
done
|
|
done
|
|
|
|
sed \
|
|
-e "s/^SOURCE_REF=\"\${AETHER_SOURCE_REF:-main}\"/SOURCE_REF=\"\${AETHER_SOURCE_REF:-${SOURCE_REF}}\"/" \
|
|
-e "s/^VERSION=\"\${AETHER_VERSION:-}\"/VERSION=\"\${AETHER_VERSION:-${VERSION}}\"/" \
|
|
install.sh > release-assets/install.sh
|
|
chmod +x release-assets/install.sh
|
|
(cd release-assets && sha256sum *.tar.gz > SHA256SUMS)
|
|
|
|
- name: Attest release package provenance
|
|
id: attest-release
|
|
if: needs.preflight.outputs.publish == 'true'
|
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
|
with:
|
|
subject-path: |
|
|
release-assets/*.tar.gz
|
|
release-assets/install.sh
|
|
release-assets/SHA256SUMS
|
|
|
|
- name: Bundle release package provenance
|
|
if: needs.preflight.outputs.publish == 'true'
|
|
env:
|
|
ATTESTATION_BUNDLE: ${{ steps.attest-release.outputs.bundle-path }}
|
|
run: install -m 0644 "${ATTESTATION_BUNDLE}" release-assets/AETHER_RELEASE_PROVENANCE.sigstore.json
|
|
|
|
- name: Upload release package artifact
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
with:
|
|
name: release-assets
|
|
path: release-assets/*
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
github-release:
|
|
name: GitHub Release assets
|
|
needs: [preflight, docker, package, vscodex]
|
|
if: needs.preflight.outputs.publish == 'true'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
actions: read
|
|
contents: write
|
|
steps:
|
|
- name: Download release package artifact
|
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
|
|
with:
|
|
name: release-assets
|
|
path: release-assets
|
|
|
|
- name: Download VSIX artifact
|
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
|
|
with:
|
|
name: aether-vscodex-vsix
|
|
path: release-assets
|
|
|
|
- name: Delete stale draft releases for tag
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_TAG: ${{ github.ref_name }}
|
|
REPOSITORY: ${{ github.repository }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
draft_ids="$(gh api "repos/${REPOSITORY}/releases" --paginate --jq '.[] | select(.tag_name == env.RELEASE_TAG and .draft == true) | .id')"
|
|
|
|
if [[ -z "${draft_ids}" ]]; then
|
|
echo "No stale draft releases for ${RELEASE_TAG}"
|
|
exit 0
|
|
fi
|
|
|
|
while IFS= read -r release_id; do
|
|
[[ -z "${release_id}" ]] && continue
|
|
echo "Deleting stale draft release ${release_id} for ${RELEASE_TAG}"
|
|
gh api -X DELETE "repos/${REPOSITORY}/releases/${release_id}"
|
|
done <<< "${draft_ids}"
|
|
|
|
- name: Publish GitHub Release assets
|
|
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
|
|
with:
|
|
generate_release_notes: true
|
|
prerelease: ${{ needs.preflight.outputs.prerelease }}
|
|
make_latest: ${{ needs.preflight.outputs.make_latest }}
|
|
files: |
|
|
release-assets/*.tar.gz
|
|
release-assets/AETHER_RELEASE_PROVENANCE.sigstore.json
|
|
release-assets/SHA256SUMS
|
|
release-assets/install.sh
|
|
release-assets/*.vsix
|