Files
NodeFlare/.github/workflows/release.yml
T
2026-09-12 02:34:48 +08:00

831 lines
33 KiB
YAML
Executable File

name: CI and tagged release
on:
push:
branches:
- main
tags:
- "v*.*.*"
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
metadata:
name: Resolve build metadata
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
version: ${{ steps.version.outputs.version }}
release: ${{ steps.version.outputs.release }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- id: version
name: Resolve release version
shell: bash
env:
RAW_VERSION: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || 'v1.0.0' }}
RELEASE_BUILD: ${{ startsWith(github.ref, 'refs/tags/') }}
run: |
if [ "$RELEASE_BUILD" = "true" ] && [[ ! "$RAW_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Tag must use the v-prefixed format v1.2.3" >&2
exit 1
fi
version="${RAW_VERSION#v}"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "release=$RELEASE_BUILD" >> "$GITHUB_OUTPUT"
security:
name: Dependency security
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.4.0
- name: Audit web dependencies
timeout-minutes: 5
run: bun audit
- name: Install Rust dependency auditor
timeout-minutes: 10
run: cargo install cargo-audit --version 0.22.2 --locked --no-default-features
- name: Audit backend dependencies
run: cargo audit --file backend/Cargo.lock
- name: Audit Agent dependencies
run: cargo audit --file agent/Cargo.lock
quality:
name: Quality checks
needs: metadata
runs-on: ubuntu-latest
timeout-minutes: 40
env:
NODEFLARE_VERSION: ${{ needs.metadata.outputs.version }}
services:
postgres:
image: postgres:18-alpine
env:
POSTGRES_DB: nodeflare_smoke
POSTGRES_USER: nodeflare
POSTGRES_PASSWORD: SmokePostgresPassword123
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U nodeflare -d nodeflare_smoke"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
components: clippy, rustfmt
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.4.0
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Typecheck frontend
run: bunx --bun tsc -b frontend
- name: Test frontend
run: bun test --cwd frontend
- name: Build frontend
run: bun run build:frontend
- name: Check shell scripts
run: |
for script in install.sh agent/agent.sh agent/install-macos.sh agent/install-freebsd.sh scripts/*.sh start.sh dev.sh; do
sh -n "$script" || exit "$?"
done
- name: Check PowerShell scripts
shell: pwsh
run: |
$allErrors = @()
foreach ($file in @("install.ps1", "agent/install.ps1")) {
$tokens = $null
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path $file), [ref]$tokens, [ref]$errors) > $null
$allErrors += $errors
}
if ($allErrors.Count) { $allErrors | ForEach-Object { Write-Error $_ }; exit 1 }
- name: Check backend formatting
run: cargo fmt --manifest-path backend/Cargo.toml -- --check
- name: Check Agent formatting
run: cargo fmt --manifest-path agent/Cargo.toml -- --check
- name: Check telemetry formatting
run: cargo fmt --manifest-path shared/Cargo.toml -- --check
- name: Test telemetry codec
run: CARGO_TARGET_DIR=target/backend cargo test --locked --manifest-path shared/Cargo.toml
- name: Lint backend
run: CARGO_TARGET_DIR=target/backend cargo clippy --locked --manifest-path backend/Cargo.toml --all-targets -- -D warnings
- name: Lint Agent
run: CARGO_TARGET_DIR=target/agent cargo clippy --locked --manifest-path agent/Cargo.toml --all-targets -- -D warnings
- name: Test backend
run: CARGO_TARGET_DIR=target/backend cargo test --locked --manifest-path backend/Cargo.toml
- name: Test PostgreSQL backup and notification persistence
env:
NODEFLARE_TEST_POSTGRES_URL: postgres://nodeflare:SmokePostgresPassword123@127.0.0.1:5432/nodeflare_smoke
run: CARGO_TARGET_DIR=target/backend cargo test --locked --manifest-path backend/Cargo.toml postgres_restores_nullable_batches_and_pending_notifications -- --ignored
- name: Test Agent
run: CARGO_TARGET_DIR=target/agent cargo test --locked --manifest-path agent/Cargo.toml
- name: Build Agent for smoke test
run: CARGO_TARGET_DIR=target/agent cargo build --locked --manifest-path agent/Cargo.toml
- name: Test Agent update and connection recovery
run: MONITOR_AGENT_BINARY=target/agent/debug/nodeflare-agent node scripts/agent-runtime-test.mjs
- name: Build backend
run: CARGO_TARGET_DIR=target/backend cargo build --locked --manifest-path backend/Cargo.toml
- name: Smoke test SQLite and PostgreSQL
shell: bash
env:
POSTGRES_URL: postgres://nodeflare:SmokePostgresPassword123@127.0.0.1:5432/nodeflare_smoke
run: |
set -euo pipefail
sqlite_pid=""
postgres_pid=""
cleanup() {
if [ -n "$sqlite_pid" ]; then kill "$sqlite_pid" 2>/dev/null || true; fi
if [ -n "$postgres_pid" ]; then kill "$postgres_pid" 2>/dev/null || true; fi
}
trap cleanup EXIT
wait_ready() {
local url=$1
local log_file=$2
local process_id=$3
for _ in $(seq 1 60); do
if curl --fail --silent "$url/api/bootstrap" >/dev/null; then return 0; fi
if ! kill -0 "$process_id" 2>/dev/null; then break; fi
sleep 1
done
cat "$log_file"
return 1
}
sqlite_dir="$RUNNER_TEMP/nodeflare-sqlite"
mkdir -p "$sqlite_dir"
sqlite_config="$sqlite_dir/config.toml"
printf '%s\n' \
"database_url = \"sqlite://$sqlite_dir/nodeflare.db\"" \
'bind_addr = "127.0.0.1:18080"' \
'admin_username = "sqlite-smoke"' \
'admin_password = "SmokePassword123"' \
'turnstile_site_key = ""' \
'turnstile_secret_key = ""' \
"frontend_dir = \"$GITHUB_WORKSPACE/frontend/dist\"" \
"admin_frontend_dir = \"$GITHUB_WORKSPACE/frontend/admin-dist\"" \
"theme_dir = \"$sqlite_dir/themes\"" > "$sqlite_config"
target/backend/debug/nodeflare --config "$sqlite_config" > "$sqlite_dir/server.log" 2>&1 &
sqlite_pid=$!
wait_ready http://127.0.0.1:18080 "$sqlite_dir/server.log" "$sqlite_pid"
MONITOR_BASE_URL=http://127.0.0.1:18080 \
MONITOR_ADMIN_USERNAME=sqlite-smoke \
MONITOR_ADMIN_PASSWORD=SmokePassword123 \
MONITOR_AGENT_BINARY="$GITHUB_WORKSPACE/target/agent/debug/nodeflare-agent" \
MONITOR_MIGRATION_URL="$POSTGRES_URL" \
MONITOR_MIGRATION_KIND=postgresql \
sh scripts/smoke-test.sh
grep -F "database_url = \"$POSTGRES_URL\"" "$sqlite_config"
kill "$sqlite_pid"
wait "$sqlite_pid" || true
sqlite_pid=""
postgres_dir="$RUNNER_TEMP/nodeflare-postgres"
mkdir -p "$postgres_dir"
postgres_config="$postgres_dir/config.toml"
printf '%s\n' \
"database_url = \"$POSTGRES_URL\"" \
'bind_addr = "127.0.0.1:18081"' \
'admin_username = "sqlite-smoke"' \
'admin_password = "SmokePassword123"' \
'turnstile_site_key = ""' \
'turnstile_secret_key = ""' \
"frontend_dir = \"$GITHUB_WORKSPACE/frontend/dist\"" \
"admin_frontend_dir = \"$GITHUB_WORKSPACE/frontend/admin-dist\"" \
"theme_dir = \"$postgres_dir/themes\"" > "$postgres_config"
target/backend/debug/nodeflare --config "$postgres_config" > "$postgres_dir/server.log" 2>&1 &
postgres_pid=$!
wait_ready http://127.0.0.1:18081 "$postgres_dir/server.log" "$postgres_pid"
MONITOR_BASE_URL=http://127.0.0.1:18081 \
MONITOR_ADMIN_USERNAME=sqlite-smoke \
MONITOR_ADMIN_PASSWORD=SmokePassword123 \
MONITOR_AGENT_BINARY="$GITHUB_WORKSPACE/target/agent/debug/nodeflare-agent" \
MONITOR_MIGRATION_URL="sqlite://$postgres_dir/round-trip.db" \
MONITOR_MIGRATION_KIND=sqlite \
sh scripts/smoke-test.sh
server:
name: Server (${{ matrix.platform }})
needs: metadata
if: needs.metadata.outputs.release == 'true'
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
permissions:
attestations: write
contents: read
id-token: write
env:
CARGO_TARGET_DIR: target/server
NODEFLARE_VERSION: ${{ needs.metadata.outputs.version }}
strategy:
fail-fast: false
matrix:
include:
- platform: Linux x64 glibc 2.28+
runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
asset: nodeflare-linux-x64-glibc
extension: ""
cross: false
glibc: true
glibc_image: quay.io/pypa/manylinux_2_28_x86_64@sha256:0536c364004fa2a3c5041120b6fe35d84fc5bfe31f04c6a6304f13eac4a67b63
native: true
static_linux: false
emulate: false
freebsd_vm: false
freebsd_arm: false
- platform: Linux x64 musl
runner: ubuntu-latest
target: x86_64-unknown-linux-musl
asset: nodeflare-linux-x64-musl
extension: ""
cross: true
native: true
static_linux: true
emulate: false
freebsd_vm: false
freebsd_arm: false
- platform: Linux ARM64 glibc 2.28+
runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
asset: nodeflare-linux-aarch64-glibc
extension: ""
cross: false
glibc: true
glibc_image: quay.io/pypa/manylinux_2_28_aarch64@sha256:1676a4f178dc6cf8a1d1e3b7e1d71fca891a466ebde72a33880ccda8af3383d8
native: true
static_linux: false
emulate: false
freebsd_vm: false
freebsd_arm: false
- platform: Linux ARM64 musl
runner: ubuntu-latest
target: aarch64-unknown-linux-musl
asset: nodeflare-linux-aarch64-musl
extension: ""
cross: true
native: false
static_linux: true
emulate: true
freebsd_vm: false
freebsd_arm: false
- platform: Windows x64
runner: windows-latest
target: x86_64-pc-windows-msvc
asset: nodeflare-windows-x64.exe
extension: .exe
cross: false
native: true
static_linux: false
emulate: false
freebsd_vm: false
freebsd_arm: false
rustflags: -C target-feature=+crt-static
- platform: macOS ARM64
runner: macos-14
target: aarch64-apple-darwin
asset: nodeflare-macos-aarch64
extension: ""
cross: false
native: true
static_linux: false
emulate: false
freebsd_vm: false
freebsd_arm: false
rustflags: ""
- platform: FreeBSD x64
runner: ubuntu-latest
target: x86_64-unknown-freebsd
asset: nodeflare-freebsd-x64
extension: ""
cross: true
native: false
static_linux: false
emulate: false
freebsd_vm: true
freebsd_arm: false
- platform: FreeBSD ARM64
runner: ubuntu-latest
target: aarch64-unknown-freebsd
asset: nodeflare-freebsd-aarch64
extension: ""
cross: true
native: false
static_linux: false
emulate: false
freebsd_vm: false
freebsd_arm: true
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
targets: ${{ matrix.target }}
- name: Install cross compiler
if: matrix.cross
run: cargo install cross --git https://github.com/cross-rs/cross --rev 8c1a8aa4b661711f4b7b6ac07c2e8929ce2f7d27 --locked
- name: Build server for glibc 2.28
if: matrix.glibc == true
shell: bash
run: |
docker run --rm \
--user "$(id -u):$(id -g)" \
--env CARGO_HOME=/opt/cargo \
--env RUSTUP_HOME=/opt/rustup \
--env RUSTUP_TOOLCHAIN=stable \
--env HOME=/tmp \
--env CARGO_TARGET_DIR \
--env NODEFLARE_VERSION \
--volume "$HOME/.cargo:/opt/cargo" \
--volume "$HOME/.rustup:/opt/rustup" \
--volume "$GITHUB_WORKSPACE:$GITHUB_WORKSPACE" \
--workdir "$GITHUB_WORKSPACE" \
--entrypoint /opt/cargo/bin/cargo \
"${{ matrix.glibc_image }}" \
build --manifest-path backend/Cargo.toml --release --locked --target "${{ matrix.target }}"
- name: Build server with cross
if: matrix.cross
run: CROSS_CONFIG=Cross.toml cross build --manifest-path backend/Cargo.toml --release --locked --target ${{ matrix.target }}
- name: Build server with Cargo
if: matrix.cross == false && matrix.glibc != true
env:
RUSTFLAGS: ${{ matrix.rustflags }}
run: cargo build --manifest-path backend/Cargo.toml --release --locked --target ${{ matrix.target }}
- name: Stage server binary
shell: bash
run: |
mkdir -p release
cp "target/server/${{ matrix.target }}/release/nodeflare${{ matrix.extension }}" "release/${{ matrix.asset }}"
chmod +x "release/${{ matrix.asset }}"
- name: Install ARM64 emulator
if: matrix.emulate
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends qemu-user-static libc6-arm64-cross libgcc-s1-arm64-cross
- name: Verify server version natively
if: matrix.native
shell: bash
run: |
version_output=$("release/${{ matrix.asset }}" --version)
actual_version="${version_output##* }"
if [ "$actual_version" != "${{ needs.metadata.outputs.version }}" ]; then
echo "Server version $actual_version does not match tag ${{ needs.metadata.outputs.version }}" >&2
exit 1
fi
- name: Verify emulated server version
if: matrix.emulate
shell: bash
run: |
version_output=$(qemu-aarch64-static -L /usr/aarch64-linux-gnu "release/${{ matrix.asset }}" --version)
actual_version="${version_output##* }"
if [ "$actual_version" != "${{ needs.metadata.outputs.version }}" ]; then
echo "Server version $actual_version does not match tag ${{ needs.metadata.outputs.version }}" >&2
exit 1
fi
- name: Verify static server binary
if: matrix.static_linux
shell: bash
run: |
if readelf -l "release/${{ matrix.asset }}" | grep -q 'Requesting program interpreter'; then
echo "Linux server must be statically linked" >&2
exit 1
fi
- name: Verify glibc server binary
if: endsWith(matrix.target, '-unknown-linux-gnu')
shell: bash
run: |
readelf -l "release/${{ matrix.asset }}" | grep -q 'Requesting program interpreter'
scripts/check-glibc-version.sh "release/${{ matrix.asset }}" 2.28
- name: Verify FreeBSD server
if: matrix.freebsd_vm || matrix.freebsd_arm
uses: vmactions/freebsd-vm@f0552d3b69211736abd97f02ff3d4674c56b73b1 # v1.5.5
with:
release: "15"
arch: ${{ matrix.freebsd_arm && 'aarch64' || 'x86_64' }}
envs: NODEFLARE_VERSION
copyback: false
run: |
set -eu
version_output=$(./release/${{ matrix.asset }} --version)
actual_version=${version_output##* }
if [ "$actual_version" != "$NODEFLARE_VERSION" ]; then
echo "Server version $actual_version does not match tag $NODEFLARE_VERSION" >&2
exit 1
fi
- uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4
with:
subject-path: release/${{ matrix.asset }}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.asset }}
path: release/${{ matrix.asset }}
if-no-files-found: error
agent:
name: Agent (${{ matrix.platform }})
needs: metadata
if: needs.metadata.outputs.release == 'true'
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
permissions:
attestations: write
contents: read
id-token: write
env:
CARGO_TARGET_DIR: target/agent
NODEFLARE_VERSION: ${{ needs.metadata.outputs.version }}
strategy:
fail-fast: false
matrix:
include:
- platform: Linux x64 glibc 2.28+
runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
asset: agent-linux-x64-glibc
extension: ""
cross: false
glibc: true
glibc_image: quay.io/pypa/manylinux_2_28_x86_64@sha256:0536c364004fa2a3c5041120b6fe35d84fc5bfe31f04c6a6304f13eac4a67b63
native: true
emulate: false
static_linux: false
- platform: Linux x64 musl
runner: ubuntu-latest
target: x86_64-unknown-linux-musl
asset: agent-linux-x64-musl
extension: ""
cross: true
native: true
emulate: false
static_linux: true
- platform: Linux ARM64 glibc 2.28+
runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
asset: agent-linux-aarch64-glibc
extension: ""
cross: false
glibc: true
glibc_image: quay.io/pypa/manylinux_2_28_aarch64@sha256:1676a4f178dc6cf8a1d1e3b7e1d71fca891a466ebde72a33880ccda8af3383d8
native: true
emulate: false
static_linux: false
- platform: Linux ARM64 musl
runner: ubuntu-latest
target: aarch64-unknown-linux-musl
asset: agent-linux-aarch64-musl
extension: ""
cross: true
native: false
emulate: true
static_linux: true
- platform: Windows x64
runner: windows-latest
target: x86_64-pc-windows-msvc
asset: agent-windows-x64.exe
extension: .exe
cross: false
native: true
emulate: false
static_linux: false
rustflags: -C target-feature=+crt-static
- platform: macOS ARM64
runner: macos-14
target: aarch64-apple-darwin
asset: agent-macos-aarch64
extension: ""
cross: false
native: true
emulate: false
static_linux: false
rustflags: ""
- platform: FreeBSD x64
runner: ubuntu-latest
target: x86_64-unknown-freebsd
asset: agent-freebsd-x64
extension: ""
cross: true
native: false
emulate: false
static_linux: false
- platform: FreeBSD ARM64
runner: ubuntu-latest
target: aarch64-unknown-freebsd
asset: agent-freebsd-aarch64
extension: ""
cross: true
native: false
emulate: false
static_linux: false
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
targets: ${{ matrix.target }}
- name: Install cross compiler
if: matrix.cross
run: cargo install cross --git https://github.com/cross-rs/cross --rev 8c1a8aa4b661711f4b7b6ac07c2e8929ce2f7d27 --locked
- name: Build Rust Agent for glibc 2.28
if: matrix.glibc == true
shell: bash
run: |
docker run --rm \
--user "$(id -u):$(id -g)" \
--env CARGO_HOME=/opt/cargo \
--env RUSTUP_HOME=/opt/rustup \
--env RUSTUP_TOOLCHAIN=stable \
--env HOME=/tmp \
--env CARGO_TARGET_DIR \
--env NODEFLARE_VERSION \
--volume "$HOME/.cargo:/opt/cargo" \
--volume "$HOME/.rustup:/opt/rustup" \
--volume "$GITHUB_WORKSPACE:$GITHUB_WORKSPACE" \
--workdir "$GITHUB_WORKSPACE" \
--entrypoint /opt/cargo/bin/cargo \
"${{ matrix.glibc_image }}" \
build --manifest-path agent/Cargo.toml --release --locked --target "${{ matrix.target }}"
- name: Build Rust Agent with cross
if: matrix.cross
run: CROSS_CONFIG=Cross.toml cross build --manifest-path agent/Cargo.toml --release --locked --target ${{ matrix.target }}
- name: Build Rust Agent with Cargo
if: matrix.cross == false && matrix.glibc != true
env:
RUSTFLAGS: ${{ matrix.rustflags }}
run: cargo build --manifest-path agent/Cargo.toml --release --locked --target ${{ matrix.target }}
- name: Stage Agent binary
shell: bash
run: |
mkdir -p release
cp "target/agent/${{ matrix.target }}/release/nodeflare-agent${{ matrix.extension }}" "release/${{ matrix.asset }}"
chmod +x "release/${{ matrix.asset }}"
- name: Install ARM64 emulator
if: matrix.emulate
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends qemu-user-static libc6-arm64-cross libgcc-s1-arm64-cross
- name: Verify Agent version natively
if: matrix.native
shell: bash
run: |
version_output=$("release/${{ matrix.asset }}" --version)
actual_version="${version_output##* }"
if [ "$actual_version" != "${{ needs.metadata.outputs.version }}" ]; then
echo "Agent version $actual_version does not match tag ${{ needs.metadata.outputs.version }}" >&2
exit 1
fi
- name: Verify emulated Agent version
if: matrix.emulate
shell: bash
run: |
version_output=$(qemu-aarch64-static -L /usr/aarch64-linux-gnu "release/${{ matrix.asset }}" --version)
actual_version="${version_output##* }"
if [ "$actual_version" != "${{ needs.metadata.outputs.version }}" ]; then
echo "Agent version $actual_version does not match tag ${{ needs.metadata.outputs.version }}" >&2
exit 1
fi
- name: Verify static Linux binary
if: matrix.static_linux
shell: bash
run: |
if readelf -l "release/${{ matrix.asset }}" | grep -q 'Requesting program interpreter'; then
echo "Linux Agent must be statically linked" >&2
exit 1
fi
- name: Verify glibc Agent binary
if: endsWith(matrix.target, '-unknown-linux-gnu')
shell: bash
run: |
readelf -l "release/${{ matrix.asset }}" | grep -q 'Requesting program interpreter'
scripts/check-glibc-version.sh "release/${{ matrix.asset }}" 2.28
- name: Verify FreeBSD Agent in FreeBSD
if: endsWith(matrix.target, '-unknown-freebsd')
uses: vmactions/freebsd-vm@f0552d3b69211736abd97f02ff3d4674c56b73b1 # v1.5.5
with:
release: "15"
arch: ${{ matrix.target == 'aarch64-unknown-freebsd' && 'aarch64' || 'x86_64' }}
envs: NODEFLARE_VERSION
copyback: false
run: |
set -eu
version_output=$(./release/${{ matrix.asset }} --version)
actual_version=${version_output##* }
if [ "$actual_version" != "$NODEFLARE_VERSION" ]; then
echo "Agent version $actual_version does not match tag $NODEFLARE_VERSION" >&2
exit 1
fi
- uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4
with:
subject-path: release/${{ matrix.asset }}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.asset }}
path: release/${{ matrix.asset }}
if-no-files-found: error
frontend:
name: Frontend
needs: metadata
if: needs.metadata.outputs.release == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
attestations: write
contents: read
id-token: write
env:
NODEFLARE_VERSION: ${{ needs.metadata.outputs.version }}
VITE_NODEFLARE_VERSION: ${{ needs.metadata.outputs.version }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.4.0
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build public and admin frontends
run: bun run build:frontend
- name: Package frontend
run: tar -czf "nodeflare-frontend-${{ needs.metadata.outputs.version }}.tar.gz" -C frontend dist admin-dist
- uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4
with:
subject-path: nodeflare-frontend-${{ needs.metadata.outputs.version }}.tar.gz
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: frontend
path: nodeflare-frontend-${{ needs.metadata.outputs.version }}.tar.gz
if-no-files-found: error
server-bundle:
name: Server bundle (${{ matrix.platform }})
needs: [metadata, server, frontend]
if: needs.metadata.outputs.release == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
attestations: write
contents: read
id-token: write
strategy:
fail-fast: false
matrix:
include:
- platform: Linux x64 glibc 2.28+
binary: nodeflare-linux-x64-glibc
package_binary: nodeflare
asset: nodeflare-server-linux-x64-glibc.tar.gz
format: tar
- platform: Linux x64 musl
binary: nodeflare-linux-x64-musl
package_binary: nodeflare
asset: nodeflare-server-linux-x64-musl.tar.gz
format: tar
- platform: Linux ARM64 glibc 2.28+
binary: nodeflare-linux-aarch64-glibc
package_binary: nodeflare
asset: nodeflare-server-linux-aarch64-glibc.tar.gz
format: tar
- platform: Linux ARM64 musl
binary: nodeflare-linux-aarch64-musl
package_binary: nodeflare
asset: nodeflare-server-linux-aarch64-musl.tar.gz
format: tar
- platform: Windows x64
binary: nodeflare-windows-x64.exe
package_binary: nodeflare.exe
asset: nodeflare-server-windows-x64.zip
format: zip
- platform: macOS ARM64
binary: nodeflare-macos-aarch64
package_binary: nodeflare
asset: nodeflare-server-macos-aarch64.tar.gz
format: tar
- platform: FreeBSD x64
binary: nodeflare-freebsd-x64
package_binary: nodeflare
asset: nodeflare-server-freebsd-x64.tar.gz
format: tar
- platform: FreeBSD ARM64
binary: nodeflare-freebsd-aarch64
package_binary: nodeflare
asset: nodeflare-server-freebsd-aarch64.tar.gz
format: tar
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ matrix.binary }}
path: inputs/server
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: frontend
path: inputs/frontend
- name: Install ZIP tool
if: matrix.format == 'zip'
run: |
sudo apt-get update
sudo apt-get install --yes --no-install-recommends zip unzip
- name: Package server
shell: bash
env:
VERSION: ${{ needs.metadata.outputs.version }}
BINARY: ${{ matrix.binary }}
PACKAGE_BINARY: ${{ matrix.package_binary }}
ASSET: ${{ matrix.asset }}
FORMAT: ${{ matrix.format }}
run: |
set -euo pipefail
mkdir -p package/share/frontend package/share/admin release inputs/frontend/unpacked
cp "inputs/server/$BINARY" "package/$PACKAGE_BINARY"
tar -xzf "inputs/frontend/nodeflare-frontend-$VERSION.tar.gz" -C inputs/frontend/unpacked
cp -a inputs/frontend/unpacked/dist/. package/share/frontend/
cp -a inputs/frontend/unpacked/admin-dist/. package/share/admin/
cp docs/nodeflare.service package/nodeflare.service
cp LICENSE package/LICENSE
chmod 0755 "package/$PACKAGE_BINARY"
chmod 0644 package/LICENSE package/nodeflare.service
if [ "$FORMAT" = zip ]; then
(cd package && zip -qr "../release/$ASSET" .)
unzip -tq "release/$ASSET"
else
tar -czf "release/$ASSET" -C package .
tar -tzf "release/$ASSET" >/dev/null
fi
- uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4
with:
subject-path: release/${{ matrix.asset }}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: server-bundle-${{ matrix.binary }}
path: release/${{ matrix.asset }}
if-no-files-found: error
release:
name: Publish GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
needs: [metadata, security, quality, agent, server-bundle]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
actions: read
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Build changelog from commits
shell: bash
run: |
previous_tag=$(git describe --tags --abbrev=0 "${GITHUB_SHA}^" 2>/dev/null || true)
if [ -n "$previous_tag" ]; then
commit_range="${previous_tag}..${GITHUB_SHA}"
else
commit_range="${GITHUB_SHA}"
fi
git log "$commit_range" --no-merges --pretty=format:'- %s' > CHANGELOG.md
if [ ! -s CHANGELOG.md ]; then
git log -1 --pretty=format:'- %s' "$GITHUB_SHA" > CHANGELOG.md
fi
printf '\n' >> CHANGELOG.md
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: agent-*
path: release-assets
merge-multiple: true
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: server-bundle-*
path: release-assets
merge-multiple: true
- uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.3
with:
body_path: CHANGELOG.md
files: release-assets/*
fail_on_unmatched_files: true