mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-06-08 09:51:48 +00:00
chore(workflows): migrate CI workflows from Gitea to GitHub Actions and remove obsolete files
This commit is contained in:
@@ -1,58 +0,0 @@
|
||||
name: Benchmarks
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -eu
|
||||
SERVER="${GITEA_SERVER_URL:-${GITHUB_SERVER_URL:-}}"
|
||||
REPO="${GITEA_REPOSITORY:-${GITHUB_REPOSITORY:-}}"
|
||||
if [ -z "$SERVER" ] || [ -z "$REPO" ]; then
|
||||
echo "Checkout: set GITEA_SERVER_URL/GITEA_REPOSITORY or GITHUB_SERVER_URL/GITHUB_REPOSITORY" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${GITEA_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
TOKEN="${GITEA_TOKEN:-$GITHUB_TOKEN}"
|
||||
git config --global credential.helper "!f() { echo username=x-access-token; echo password=${TOKEN}; }; f"
|
||||
fi
|
||||
git init -q && git remote add origin "${SERVER}/${REPO}.git"
|
||||
git fetch -q --depth=1 origin "${GITHUB_SHA}" && git checkout -q FETCH_HEAD
|
||||
|
||||
- name: Setup Node.js
|
||||
run: sh scripts/ci/setup-node.sh 24
|
||||
|
||||
- name: Setup pnpm
|
||||
run: sh scripts/ci/setup-pnpm.sh
|
||||
|
||||
- name: Setup Python
|
||||
run: sh scripts/ci/setup-python.sh 3.14
|
||||
|
||||
- name: Setup Task
|
||||
run: sh scripts/ci/setup-task.sh
|
||||
|
||||
- name: Setup Poetry
|
||||
run: pip install poetry
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
task install
|
||||
|
||||
- name: Run Benchmarks
|
||||
id: bench
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
set -o pipefail
|
||||
task bench 2>&1 | tee bench_results.txt
|
||||
|
||||
- name: Run Integrity Tests
|
||||
id: integrity
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
set -o pipefail
|
||||
task test-integrity 2>&1 | tee -a bench_results.txt
|
||||
@@ -1,84 +0,0 @@
|
||||
name: Build Test
|
||||
|
||||
on:
|
||||
# Migrated to GitHub Actions (.github/workflows/ci.yml and .github/workflows/build.yml).
|
||||
# Keep this workflow manual for fallback troubleshooting on Gitea runner.
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-test:
|
||||
name: Build and Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone Repo
|
||||
run: |
|
||||
set -eu
|
||||
SERVER="${GITEA_SERVER_URL:-${GITHUB_SERVER_URL:-}}"
|
||||
REPO="${GITEA_REPOSITORY:-${GITHUB_REPOSITORY:-}}"
|
||||
if [ -z "$SERVER" ] || [ -z "$REPO" ]; then
|
||||
echo "Checkout: set GITEA_SERVER_URL/GITEA_REPOSITORY or GITHUB_SERVER_URL/GITHUB_REPOSITORY" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${GITEA_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
TOKEN="${GITEA_TOKEN:-$GITHUB_TOKEN}"
|
||||
git config --global credential.helper "!f() { echo username=x-access-token; echo password=${TOKEN}; }; f"
|
||||
fi
|
||||
git clone "${SERVER}/${REPO}.git" .
|
||||
git checkout "${GITHUB_SHA}"
|
||||
|
||||
- name: Setup Node.js
|
||||
run: sh scripts/ci/setup-node.sh 24
|
||||
|
||||
- name: Setup Python
|
||||
run: sh scripts/ci/setup-python.sh 3.14
|
||||
|
||||
- name: Install Poetry
|
||||
run: python3 -m pip install --upgrade pip poetry>=2.0.0
|
||||
|
||||
- name: Setup pnpm
|
||||
run: sh scripts/ci/setup-pnpm.sh
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sh scripts/ci/exec-priv.sh dpkg --add-architecture i386
|
||||
sh scripts/ci/exec-priv.sh apt-get update
|
||||
sh scripts/ci/exec-priv.sh apt-get install -y patchelf libopusfile0 espeak-ng zip rpm elfutils appstream appstream-util
|
||||
|
||||
- name: Setup Task
|
||||
run: sh scripts/ci/setup-task.sh
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
task install
|
||||
|
||||
- name: Build Frontend
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
task build:fe
|
||||
|
||||
- name: Build Backend (Wheel)
|
||||
run: task build:wheel
|
||||
|
||||
- name: Ensure cx_Freeze build dependencies
|
||||
run: poetry run pip install pycparser cffi
|
||||
|
||||
- name: Build Electron App (Linux)
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
pnpm run dist:linux-x64
|
||||
|
||||
- name: Build Electron App (RPM - Experimental)
|
||||
continue-on-error: true
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
task dist:fe:rpm
|
||||
|
||||
- name: Prepare release assets
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
find dist -maxdepth 1 -type f \( -name "*-linux*.AppImage" -o -name "*-linux*.deb" -o -name "*-linux*.rpm" \) -exec cp {} release-assets/ \;
|
||||
find python-dist -maxdepth 1 -type f -name "*.whl" -exec cp {} release-assets/ \;
|
||||
@@ -1,195 +0,0 @@
|
||||
# Appimage builds produced by action are broken for now
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Release version (e.g., v1.0.0)"
|
||||
required: false
|
||||
type: string
|
||||
build_docker:
|
||||
description: "Build Docker"
|
||||
required: false
|
||||
default: "true"
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
env:
|
||||
COSIGN_VERSION: "3.0.6"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone Repo
|
||||
run: |
|
||||
set -eu
|
||||
SERVER="${GITEA_SERVER_URL:-${GITHUB_SERVER_URL:-}}"
|
||||
REPO="${GITEA_REPOSITORY:-${GITHUB_REPOSITORY:-}}"
|
||||
if [ -z "$SERVER" ] || [ -z "$REPO" ]; then
|
||||
echo "Checkout: set GITEA_SERVER_URL/GITEA_REPOSITORY or GITHUB_SERVER_URL/GITHUB_REPOSITORY" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${GITEA_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
TOKEN="${GITEA_TOKEN:-$GITHUB_TOKEN}"
|
||||
git config --global credential.helper "!f() { echo username=x-access-token; echo password=${TOKEN}; }; f"
|
||||
fi
|
||||
git clone "${SERVER}/${REPO}.git" .
|
||||
git checkout "${GITHUB_SHA}"
|
||||
|
||||
- name: Determine version
|
||||
id: version
|
||||
run: |
|
||||
VERSION=""
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
VERSION="${{ inputs.version || github.event.inputs.version }}"
|
||||
fi
|
||||
|
||||
if [ -n "$VERSION" ]; then
|
||||
echo "Using version from input: $VERSION"
|
||||
elif [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
if [ -z "${VERSION}" ]; then
|
||||
VERSION="${{ github.ref_name }}"
|
||||
fi
|
||||
echo "Using version from tag: $VERSION"
|
||||
else
|
||||
VERSION=$(git rev-parse --short HEAD)
|
||||
echo "Using version from SHA: $VERSION"
|
||||
fi
|
||||
|
||||
if [ "${VERSION}" = "master" ] || [ "${VERSION}" = "dev" ] || [ -z "${VERSION}" ]; then
|
||||
echo "Error: Invalid version '${VERSION}'. Version cannot be a branch name or empty." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup Node.js
|
||||
run: sh scripts/ci/setup-node.sh 24
|
||||
|
||||
- name: Setup Python
|
||||
run: sh scripts/ci/setup-python.sh 3.14
|
||||
|
||||
- name: Install Poetry
|
||||
run: python3 -m pip install --upgrade pip poetry>=2.0.0
|
||||
|
||||
- name: Setup pnpm
|
||||
run: sh scripts/ci/setup-pnpm.sh
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sh scripts/ci/exec-priv.sh dpkg --add-architecture i386
|
||||
sh scripts/ci/exec-priv.sh apt-get update
|
||||
sh scripts/ci/exec-priv.sh apt-get install -y patchelf libopusfile0 espeak-ng zip rpm elfutils
|
||||
|
||||
- name: Setup Task
|
||||
run: sh scripts/ci/setup-task.sh
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
task install
|
||||
|
||||
- name: Build Frontend
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
task build:fe
|
||||
|
||||
- name: Build Python wheel
|
||||
run: task build:wheel
|
||||
|
||||
- name: Build Electron App (x64 AppImage + deb)
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
pnpm run dist:linux-x64
|
||||
|
||||
- name: Build Electron App (arm64 AppImage + deb)
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
pnpm run dist:linux-arm64
|
||||
|
||||
- name: Build Electron App (RPM)
|
||||
continue-on-error: true
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
task dist:fe:rpm
|
||||
|
||||
- name: Prepare release assets
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
find dist -maxdepth 1 -type f \( -name "*-linux*.AppImage" -o -name "*-linux*.deb" -o -name "*-linux*.rpm" \) -exec cp {} release-assets/ \;
|
||||
find python-dist -maxdepth 1 -type f -name "*.whl" -exec cp {} release-assets/ \;
|
||||
|
||||
# Create frontend zip
|
||||
(cd meshchatx/public && zip -r ../../release-assets/meshchatx-frontend.zip .)
|
||||
|
||||
# Generate SBOM (CycloneDX)
|
||||
curl -L -o /tmp/trivy.deb https://git.quad4.io/Quad4-Software/Trivy-Assets/raw/commit/fdfe96b77d2f7b7f5a90cea00af5024c9f728f17/trivy_0.69.3_Linux-64bit.deb
|
||||
sh scripts/ci/exec-priv.sh dpkg -i /tmp/trivy.deb || sh scripts/ci/exec-priv.sh apt-get install -f -y
|
||||
trivy fs --format cyclonedx --include-dev-deps --output release-assets/sbom.cyclonedx.json .
|
||||
|
||||
{
|
||||
echo "## Integrity"
|
||||
echo ""
|
||||
echo "Each artifact may have a matching **\`*.cosign.bundle\`** (SLSA v1 provenance via cosign; see \`SECURITY.md\` for verification)."
|
||||
echo ""
|
||||
echo "SBOM: **\`sbom.cyclonedx.json\`** (CycloneDX)."
|
||||
} > release-body.md
|
||||
|
||||
- name: SLSA attestations (cosign)
|
||||
env:
|
||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_SHA: ${{ github.sha }}
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}
|
||||
GITHUB_WORKFLOW: ${{ github.workflow }}
|
||||
run: |
|
||||
set -eu
|
||||
if [ -z "${COSIGN_PRIVATE_KEY:-}" ]; then
|
||||
echo "Skipping SLSA attestations: add repository secret COSIGN_PRIVATE_KEY (PEM) to sign releases."
|
||||
exit 0
|
||||
fi
|
||||
sh scripts/ci/setup-cosign.sh "${COSIGN_VERSION}"
|
||||
printf '%s\n' "$COSIGN_PRIVATE_KEY" > /tmp/cosign.key
|
||||
chmod 600 /tmp/cosign.key
|
||||
export COSIGN_KEY_PATH=/tmp/cosign.key
|
||||
sh scripts/ci/attest-release-assets.sh ./release-assets
|
||||
rm -f /tmp/cosign.key
|
||||
|
||||
- name: Validate version
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
if [ -z "${VERSION}" ]; then
|
||||
echo "Error: Version is empty" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "${VERSION}" = "master" ] || [ "${VERSION}" = "dev" ]; then
|
||||
echo "Error: Invalid version '${VERSION}'. Version cannot be a branch name." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Using version: ${VERSION}"
|
||||
|
||||
- name: Create Release
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
uses: https://git.quad4.io/actions/gitea-release-action@4875285c0950474efb7ca2df55233c51333eeb74 # v1
|
||||
with:
|
||||
api_url: ${{ secrets.GITEA_API_URL }}
|
||||
gitea_token: ${{ secrets.GITEA_TOKEN }}
|
||||
title: ${{ steps.version.outputs.version }}
|
||||
tag: ${{ steps.version.outputs.version }}
|
||||
files: release-assets/*
|
||||
body_path: "release-body.md"
|
||||
draft: true
|
||||
prerelease: false
|
||||
@@ -1,176 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
# Migrated to GitHub Actions (.github/workflows/ci.yml).
|
||||
# Keep this workflow manual for fallback troubleshooting on Gitea runner.
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -eu
|
||||
SERVER="${GITEA_SERVER_URL:-${GITHUB_SERVER_URL:-}}"
|
||||
REPO="${GITEA_REPOSITORY:-${GITHUB_REPOSITORY:-}}"
|
||||
if [ -z "$SERVER" ] || [ -z "$REPO" ]; then
|
||||
echo "Checkout: set GITEA_SERVER_URL/GITEA_REPOSITORY or GITHUB_SERVER_URL/GITHUB_REPOSITORY" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${GITEA_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
TOKEN="${GITEA_TOKEN:-$GITHUB_TOKEN}"
|
||||
git config --global credential.helper "!f() { echo username=x-access-token; echo password=${TOKEN}; }; f"
|
||||
fi
|
||||
git init -q && git remote add origin "${SERVER}/${REPO}.git"
|
||||
git fetch -q --depth=1 origin "${GITHUB_SHA}" && git checkout -q FETCH_HEAD
|
||||
- name: Setup Node.js
|
||||
run: sh scripts/ci/setup-node.sh 24
|
||||
- name: Setup pnpm
|
||||
run: sh scripts/ci/setup-pnpm.sh
|
||||
- name: Setup Python
|
||||
run: sh scripts/ci/setup-python.sh 3.14
|
||||
- name: Setup Task
|
||||
run: sh scripts/ci/setup-task.sh
|
||||
- name: Setup Poetry
|
||||
run: pip install poetry
|
||||
- name: Setup Python environment
|
||||
run: task setup:be
|
||||
- name: pip-audit
|
||||
run: |
|
||||
poetry run pip install --upgrade "pip>=26.0" pip-audit
|
||||
poetry run pip-audit
|
||||
- name: Install Node dependencies
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
task deps:fe
|
||||
- name: Setup Trivy
|
||||
run: sh scripts/ci/setup-trivy.sh
|
||||
- name: Trivy filesystem scan (Node deps)
|
||||
run: sh scripts/ci/trivy-fs-scan.sh
|
||||
- name: Lint
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
set -o pipefail
|
||||
task lint:all 2>&1 | tee lint_results.txt
|
||||
|
||||
build-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -eu
|
||||
SERVER="${GITEA_SERVER_URL:-${GITHUB_SERVER_URL:-}}"
|
||||
REPO="${GITEA_REPOSITORY:-${GITHUB_REPOSITORY:-}}"
|
||||
if [ -z "$SERVER" ] || [ -z "$REPO" ]; then
|
||||
echo "Checkout: set GITEA_SERVER_URL/GITEA_REPOSITORY or GITHUB_SERVER_URL/GITHUB_REPOSITORY" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${GITEA_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
TOKEN="${GITEA_TOKEN:-$GITHUB_TOKEN}"
|
||||
git config --global credential.helper "!f() { echo username=x-access-token; echo password=${TOKEN}; }; f"
|
||||
fi
|
||||
git init -q && git remote add origin "${SERVER}/${REPO}.git"
|
||||
git fetch -q --depth=1 origin "${GITHUB_SHA}" && git checkout -q FETCH_HEAD
|
||||
- name: Setup Node.js
|
||||
run: sh scripts/ci/setup-node.sh 24
|
||||
- name: Setup pnpm
|
||||
run: sh scripts/ci/setup-pnpm.sh
|
||||
- name: Setup Task
|
||||
run: sh scripts/ci/setup-task.sh
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
task deps:fe
|
||||
- name: Setup Trivy
|
||||
run: sh scripts/ci/setup-trivy.sh
|
||||
- name: Trivy filesystem scan (Node deps)
|
||||
run: sh scripts/ci/trivy-fs-scan.sh
|
||||
- name: Determine version
|
||||
id: version
|
||||
run: |
|
||||
SHORT_SHA=$(git rev-parse --short HEAD)
|
||||
echo "version=${SHORT_SHA}" >> $GITHUB_OUTPUT
|
||||
- name: Build frontend
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
set -o pipefail
|
||||
task build:fe 2>&1 | tee build_results.txt
|
||||
env:
|
||||
VITE_APP_VERSION: ${{ steps.version.outputs.version }}
|
||||
|
||||
test-backend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -eu
|
||||
SERVER="${GITEA_SERVER_URL:-${GITHUB_SERVER_URL:-}}"
|
||||
REPO="${GITEA_REPOSITORY:-${GITHUB_REPOSITORY:-}}"
|
||||
if [ -z "$SERVER" ] || [ -z "$REPO" ]; then
|
||||
echo "Checkout: set GITEA_SERVER_URL/GITEA_REPOSITORY or GITHUB_SERVER_URL/GITHUB_REPOSITORY" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${GITEA_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
TOKEN="${GITEA_TOKEN:-$GITHUB_TOKEN}"
|
||||
git config --global credential.helper "!f() { echo username=x-access-token; echo password=${TOKEN}; }; f"
|
||||
fi
|
||||
git init -q && git remote add origin "${SERVER}/${REPO}.git"
|
||||
git fetch -q --depth=1 origin "${GITHUB_SHA}" && git checkout -q FETCH_HEAD
|
||||
- name: Setup Python
|
||||
run: sh scripts/ci/setup-python.sh 3.14
|
||||
- name: Setup Task
|
||||
run: sh scripts/ci/setup-task.sh
|
||||
- name: Compile backend
|
||||
run: |
|
||||
set -o pipefail
|
||||
task compile 2>&1 | tee compile_results.txt
|
||||
|
||||
test-lang:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -eu
|
||||
SERVER="${GITEA_SERVER_URL:-${GITHUB_SERVER_URL:-}}"
|
||||
REPO="${GITEA_REPOSITORY:-${GITHUB_REPOSITORY:-}}"
|
||||
if [ -z "$SERVER" ] || [ -z "$REPO" ]; then
|
||||
echo "Checkout: set GITEA_SERVER_URL/GITEA_REPOSITORY or GITHUB_SERVER_URL/GITHUB_REPOSITORY" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${GITEA_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
TOKEN="${GITEA_TOKEN:-$GITHUB_TOKEN}"
|
||||
git config --global credential.helper "!f() { echo username=x-access-token; echo password=${TOKEN}; }; f"
|
||||
fi
|
||||
git init -q && git remote add origin "${SERVER}/${REPO}.git"
|
||||
git fetch -q --depth=1 origin "${GITHUB_SHA}" && git checkout -q FETCH_HEAD
|
||||
- name: Setup Node.js
|
||||
run: sh scripts/ci/setup-node.sh 24
|
||||
- name: Setup pnpm
|
||||
run: sh scripts/ci/setup-pnpm.sh
|
||||
- name: Setup Python
|
||||
run: sh scripts/ci/setup-python.sh 3.14
|
||||
- name: Setup Task
|
||||
run: sh scripts/ci/setup-task.sh
|
||||
- name: Setup Poetry
|
||||
run: pip install poetry
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
task install
|
||||
- name: pip-audit
|
||||
run: |
|
||||
poetry run pip install --upgrade "pip>=26.0" pip-audit
|
||||
poetry run pip-audit
|
||||
- name: Setup Trivy
|
||||
run: sh scripts/ci/setup-trivy.sh
|
||||
- name: Trivy filesystem scan (Node deps)
|
||||
run: sh scripts/ci/trivy-fs-scan.sh
|
||||
- name: Run language tests
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
set -o pipefail
|
||||
task test:lang 2>&1 | tee lang_results.txt
|
||||
@@ -1,102 +0,0 @@
|
||||
name: Build and Publish Docker Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
env:
|
||||
REGISTRY: git.quad4.io
|
||||
IMAGE_NAME: rns-things/meshchatx
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
run: |
|
||||
set -eu
|
||||
SERVER="${GITEA_SERVER_URL:-${GITHUB_SERVER_URL:-}}"
|
||||
REPO="${GITEA_REPOSITORY:-${GITHUB_REPOSITORY:-}}"
|
||||
if [ -z "$SERVER" ] || [ -z "$REPO" ]; then
|
||||
echo "Checkout: set GITEA_SERVER_URL/GITEA_REPOSITORY or GITHUB_SERVER_URL/GITHUB_REPOSITORY" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${GITEA_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
TOKEN="${GITEA_TOKEN:-$GITHUB_TOKEN}"
|
||||
git config --global credential.helper "!f() { echo username=x-access-token; echo password=${TOKEN}; }; f"
|
||||
fi
|
||||
git init -q && git remote add origin "${SERVER}/${REPO}.git"
|
||||
git fetch -q --depth=1 origin "${GITHUB_SHA}" && git checkout -q FETCH_HEAD
|
||||
|
||||
- name: Set up Docker (QEMU + Buildx + Login)
|
||||
run: sh scripts/ci/setup-docker.sh "${{ env.REGISTRY }}" "${{ secrets.REGISTRY_USERNAME }}" "${{ secrets.REGISTRY_PASSWORD }}"
|
||||
|
||||
- name: Download Trivy
|
||||
run: |
|
||||
curl -L -o /tmp/trivy.deb https://git.quad4.io/Quad4-Software/Trivy-Assets/raw/commit/fdfe96b77d2f7b7f5a90cea00af5024c9f728f17/trivy_0.69.3_Linux-64bit.deb
|
||||
sh scripts/ci/exec-priv.sh dpkg -i /tmp/trivy.deb || sh scripts/ci/exec-priv.sh apt-get install -f -y
|
||||
|
||||
- name: Trivy FS scan
|
||||
run: trivy fs --exit-code 1 .
|
||||
|
||||
- name: Generate Docker tags
|
||||
id: tags
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||
GITEA_REF: ${{ github.ref }}
|
||||
GITEA_REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
sh scripts/ci/docker-tags.sh "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" /tmp/docker-tags.txt
|
||||
TAGS="$(tr '\n' ' ' < /tmp/docker-tags.txt)"
|
||||
echo "tags=${TAGS}" >> "$GITHUB_OUTPUT"
|
||||
FIRST_TAG="$(head -1 /tmp/docker-tags.txt | sed 's/^-t //')"
|
||||
echo "first_tag=${FIRST_TAG}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: OCI labels (build metadata)
|
||||
id: oci
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||
GITEA_REF: ${{ github.ref }}
|
||||
GITEA_REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -eu
|
||||
echo "created=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> "$GITHUB_OUTPUT"
|
||||
echo "revision=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
ref="${GITEA_REF:-${GITHUB_REF:-}}"
|
||||
ref_name="${GITEA_REF_NAME:-${GITHUB_REF_NAME:-}}"
|
||||
case "$ref" in
|
||||
refs/tags/*)
|
||||
echo "version=${ref_name}" >> "$GITHUB_OUTPUT"
|
||||
;;
|
||||
*)
|
||||
echo "version=sha-$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Build and push Docker image
|
||||
env:
|
||||
OCI_REVISION: ${{ steps.oci.outputs.revision }}
|
||||
OCI_VERSION: ${{ steps.oci.outputs.version }}
|
||||
OCI_CREATED: ${{ steps.oci.outputs.created }}
|
||||
run: |
|
||||
set -eu
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--push \
|
||||
--no-cache \
|
||||
--build-arg "OCI_REVISION=${OCI_REVISION}" \
|
||||
--build-arg "OCI_VERSION=${OCI_VERSION}" \
|
||||
--build-arg "OCI_CREATED=${OCI_CREATED}" \
|
||||
${{ steps.tags.outputs.tags }} \
|
||||
-f ./Dockerfile .
|
||||
|
||||
- name: Scan Docker image
|
||||
run: trivy image --exit-code 0 "${{ steps.tags.outputs.first_tag }}"
|
||||
@@ -0,0 +1,70 @@
|
||||
# After pushing a tag like release_* to Gitea, wait for GitHub Actions to finish
|
||||
# (Build release + Build Linux release), then create/update the GitHub release and upload assets.
|
||||
#
|
||||
# Repository secrets (Gitea):
|
||||
# GH_PAT GitHub PAT: contents:write, actions:read (upload release assets, list workflow runs)
|
||||
# GH_REPOSITORY GitHub repo as owner/name (example: Sudo-Ivan/MeshChatX)
|
||||
#
|
||||
# The tag must exist on GitHub (mirror) so workflows run there for the same ref.
|
||||
|
||||
name: Sync tag release to GitHub
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "release_*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Tag to sync to GitHub (required when not run from a tag ref)
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish-github:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- name: Install tools
|
||||
run: |
|
||||
set -eu
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y --no-install-recommends git curl jq ca-certificates unzip
|
||||
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -eu
|
||||
SERVER="${GITEA_SERVER_URL:-${GITHUB_SERVER_URL:-}}"
|
||||
REPO="${GITEA_REPOSITORY:-${GITHUB_REPOSITORY:-}}"
|
||||
if [ -z "$SERVER" ] || [ -z "$REPO" ]; then
|
||||
echo "Set GITEA_SERVER_URL/GITEA_REPOSITORY (or GitHub equivalents)." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${GITEA_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
TOKEN="${GITEA_TOKEN:-$GITHUB_TOKEN}"
|
||||
git config --global credential.helper "!f() { echo username=x-access-token; echo password=${TOKEN}; }; f"
|
||||
fi
|
||||
git init -q && git remote add origin "${SERVER}/${REPO}.git"
|
||||
git fetch -q --depth=1 origin "${GITHUB_SHA}" && git checkout -q FETCH_HEAD
|
||||
|
||||
- name: Publish GitHub release from Actions artifacts
|
||||
env:
|
||||
GH_REPOSITORY: ${{ secrets.GH_REPOSITORY }}
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
TIMEOUT_SEC: "14400"
|
||||
POLL_INTERVAL: "60"
|
||||
run: |
|
||||
set -eu
|
||||
TAG="${{ github.event.inputs.tag }}"
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="${{ github.ref_name }}"
|
||||
fi
|
||||
if [ -z "$TAG" ] || [ "$TAG" = "master" ] || [ "$TAG" = "dev" ]; then
|
||||
echo "Set workflow input tag= to the GitHub tag, or run this workflow from a tag ref." >&2
|
||||
exit 1
|
||||
fi
|
||||
export TAG
|
||||
bash scripts/ci/wait-github-workflows-and-upload-release.sh
|
||||
@@ -1,37 +0,0 @@
|
||||
name: Rekor tree verification
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "23 11 * * 1"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
rekor-loginfo:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone Repo
|
||||
run: |
|
||||
set -eu
|
||||
SERVER="${GITEA_SERVER_URL:-${GITHUB_SERVER_URL:-}}"
|
||||
REPO="${GITEA_REPOSITORY:-${GITHUB_REPOSITORY:-}}"
|
||||
if [ -z "$SERVER" ] || [ -z "$REPO" ]; then
|
||||
echo "Checkout: set GITEA_SERVER_URL/GITEA_REPOSITORY or GITHUB_SERVER_URL/GITHUB_REPOSITORY" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${GITEA_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
TOKEN="${GITEA_TOKEN:-$GITHUB_TOKEN}"
|
||||
git config --global credential.helper "!f() { echo username=x-access-token; echo password=${TOKEN}; }; f"
|
||||
fi
|
||||
git init -q && git remote add origin "${SERVER}/${REPO}.git"
|
||||
git fetch -q --depth=1 origin "${GITHUB_SHA}" && git checkout -q FETCH_HEAD
|
||||
|
||||
- name: Install rekor-cli
|
||||
run: sh scripts/ci/setup-rekor-cli.sh
|
||||
|
||||
- name: Verify Rekor signed tree head
|
||||
run: |
|
||||
set -eu
|
||||
rekor-cli loginfo --rekor_server "${REKOR_SERVER:-https://rekor.sigstore.dev}" --store_tree_state=false
|
||||
@@ -1,51 +0,0 @@
|
||||
name: Security Scans
|
||||
|
||||
on:
|
||||
# Migrated to GitHub Actions; keep manual fallback only on Gitea.
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -eu
|
||||
SERVER="${GITEA_SERVER_URL:-${GITHUB_SERVER_URL:-}}"
|
||||
REPO="${GITEA_REPOSITORY:-${GITHUB_REPOSITORY:-}}"
|
||||
if [ -z "$SERVER" ] || [ -z "$REPO" ]; then
|
||||
echo "Checkout: set GITEA_SERVER_URL/GITEA_REPOSITORY or GITHUB_SERVER_URL/GITHUB_REPOSITORY" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${GITEA_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
TOKEN="${GITEA_TOKEN:-$GITHUB_TOKEN}"
|
||||
git config --global credential.helper "!f() { echo username=x-access-token; echo password=${TOKEN}; }; f"
|
||||
fi
|
||||
git init -q && git remote add origin "${SERVER}/${REPO}.git"
|
||||
git fetch -q --depth=1 origin "${GITHUB_SHA}" && git checkout -q FETCH_HEAD
|
||||
|
||||
- name: Setup Node.js
|
||||
run: sh scripts/ci/setup-node.sh 24
|
||||
|
||||
- name: Setup pnpm
|
||||
run: sh scripts/ci/setup-pnpm.sh
|
||||
|
||||
- name: Setup Task
|
||||
run: sh scripts/ci/setup-task.sh
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
task deps:fe
|
||||
|
||||
- name: Setup Trivy
|
||||
run: sh scripts/ci/setup-trivy.sh
|
||||
|
||||
- name: Trivy FS scan
|
||||
run: sh scripts/ci/trivy-fs-scan.sh
|
||||
|
||||
- name: Trivy Dockerfile misconfiguration
|
||||
run: trivy config --exit-code 1 Dockerfile
|
||||
@@ -1,66 +0,0 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
# Migrated to GitHub Actions (.github/workflows/ci.yml).
|
||||
# Keep this workflow manual for fallback troubleshooting on Gitea runner.
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -eu
|
||||
SERVER="${GITEA_SERVER_URL:-${GITHUB_SERVER_URL:-}}"
|
||||
REPO="${GITEA_REPOSITORY:-${GITHUB_REPOSITORY:-}}"
|
||||
if [ -z "$SERVER" ] || [ -z "$REPO" ]; then
|
||||
echo "Checkout: set GITEA_SERVER_URL/GITEA_REPOSITORY or GITHUB_SERVER_URL/GITHUB_REPOSITORY" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "${GITEA_TOKEN:-}" ] || [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
TOKEN="${GITEA_TOKEN:-$GITHUB_TOKEN}"
|
||||
git config --global credential.helper "!f() { echo username=x-access-token; echo password=${TOKEN}; }; f"
|
||||
fi
|
||||
git init -q && git remote add origin "${SERVER}/${REPO}.git"
|
||||
git fetch -q --depth=1 origin "${GITHUB_SHA}" && git checkout -q FETCH_HEAD
|
||||
|
||||
- name: Setup Node.js
|
||||
run: sh scripts/ci/setup-node.sh 24
|
||||
|
||||
- name: Setup pnpm
|
||||
run: sh scripts/ci/setup-pnpm.sh
|
||||
|
||||
- name: Setup Python
|
||||
run: sh scripts/ci/setup-python.sh 3.14
|
||||
|
||||
- name: Setup Task
|
||||
run: sh scripts/ci/setup-task.sh
|
||||
|
||||
- name: Setup Poetry
|
||||
run: pip install poetry
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
task install
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
pnpm exec playwright install chromium --with-deps
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
set -o pipefail
|
||||
task test:all 2>&1 | tee test_results.txt
|
||||
|
||||
- name: Run E2E (Playwright)
|
||||
run: |
|
||||
. scripts/ci/ci-node-path.sh
|
||||
set -o pipefail
|
||||
CI=1 pnpm run test:e2e
|
||||
@@ -0,0 +1,63 @@
|
||||
# Benchmarks and integrity checks (workflow_dispatch only).
|
||||
#
|
||||
# Pinned first-party actions (bump tag and SHA together when upgrading):
|
||||
# actions/checkout@v6.0.1 8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
# actions/setup-python@v6.2.0 a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
# actions/setup-node@v6.1.0 395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
|
||||
name: Benchmarks
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
PYTHON_VERSION: "3.14"
|
||||
NODE_VERSION: "24"
|
||||
POETRY_VERSION: "2.3.4"
|
||||
PNPM_VERSION: "10.33.0"
|
||||
|
||||
jobs:
|
||||
bench:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Install Poetry (PyPI pin)
|
||||
env:
|
||||
POETRY_VERSION: ${{ env.POETRY_VERSION }}
|
||||
run: bash scripts/ci/github-install-poetry.sh
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Enable pnpm (corepack)
|
||||
run: corepack enable && corepack prepare "pnpm@${PNPM_VERSION}" --activate
|
||||
|
||||
- name: Install dependencies
|
||||
run: bash scripts/ci/github-install-deps.sh
|
||||
|
||||
- name: Setup Task
|
||||
run: sh scripts/ci/setup-task.sh
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
set -euo pipefail
|
||||
task bench 2>&1 | tee bench_results.txt
|
||||
|
||||
- name: Run integrity tests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
task test-integrity 2>&1 | tee -a bench_results.txt
|
||||
@@ -0,0 +1,190 @@
|
||||
# Linux release binaries (wheel, AppImage, deb, rpm), SBOM, optional cosign bundles,
|
||||
# SLSA Build Level 3 provenance (slsa-github-generator generic), and a draft GitHub release.
|
||||
#
|
||||
# Pinned first-party actions (bump tag and SHA together when upgrading):
|
||||
# actions/checkout@v6.0.1 8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
# actions/setup-python@v6.2.0 a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
# actions/setup-node@v6.1.0 395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
# actions/upload-artifact@v5.0.0 330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
# actions/download-artifact@v5.0.0 634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
#
|
||||
# SLSA generator (must stay @vX.Y.Z semver per upstream):
|
||||
# slsa-framework/slsa-github-generator/generator_generic_slsa3.yml@v2.1.0
|
||||
|
||||
name: Build Linux release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
PYTHON_VERSION: "3.14"
|
||||
NODE_VERSION: "24"
|
||||
POETRY_VERSION: "2.3.4"
|
||||
PNPM_VERSION: "10.33.0"
|
||||
COSIGN_VERSION: "3.0.6"
|
||||
|
||||
jobs:
|
||||
frontend:
|
||||
name: Build frontend artifact
|
||||
uses: ./.github/workflows/frontend-build.yml
|
||||
permissions:
|
||||
contents: read
|
||||
with:
|
||||
artifact_name: meshchatx-frontend-linux-rel-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
retention_days: 7
|
||||
pnpm_version: "10.33.0"
|
||||
|
||||
linux-release:
|
||||
name: Linux release assets
|
||||
needs: frontend
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
outputs:
|
||||
hashes: ${{ steps.slsa-hashes.outputs.hashes }}
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write
|
||||
env:
|
||||
FRONTEND_ARTIFACT_NAME: ${{ needs.frontend.outputs.artifact_name }}
|
||||
MESHCHATX_FRONTEND_PREBUILT: "1"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Install Poetry (PyPI pin)
|
||||
env:
|
||||
POETRY_VERSION: ${{ env.POETRY_VERSION }}
|
||||
run: bash scripts/ci/github-install-poetry.sh
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Enable pnpm (corepack)
|
||||
run: corepack enable && corepack prepare "pnpm@${PNPM_VERSION}" --activate
|
||||
|
||||
- name: Linux packaging APT dependencies
|
||||
run: bash scripts/ci/github-apt-linux-packaging.sh
|
||||
|
||||
- name: Install project dependencies
|
||||
run: bash scripts/ci/github-install-deps.sh
|
||||
|
||||
- name: Download frontend artifact
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
name: ${{ env.FRONTEND_ARTIFACT_NAME }}
|
||||
path: meshchatx/public
|
||||
|
||||
- name: Verify frontend artifact contents
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -f meshchatx/public/index.html
|
||||
test -d meshchatx/public/assets
|
||||
test -d meshchatx/public/reticulum-docs-bundled/current
|
||||
|
||||
- name: Setup Task
|
||||
run: sh scripts/ci/setup-task.sh
|
||||
|
||||
- name: Setup Trivy
|
||||
run: sh scripts/ci/setup-trivy.sh
|
||||
|
||||
- name: Build release-assets
|
||||
run: bash scripts/ci/github-build-linux-release-assets.sh
|
||||
|
||||
- name: SLSA subject hashes
|
||||
id: slsa-hashes
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: bash scripts/ci/github-slsa-hashes-release-assets.sh
|
||||
|
||||
- name: SLSA attestations (cosign)
|
||||
env:
|
||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_SHA: ${{ github.sha }}
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}
|
||||
GITHUB_WORKFLOW: ${{ github.workflow }}
|
||||
COSIGN_VERSION: ${{ env.COSIGN_VERSION }}
|
||||
run: |
|
||||
set -eu
|
||||
if [ -z "${COSIGN_PRIVATE_KEY:-}" ]; then
|
||||
echo "Skipping cosign attestations (no COSIGN_PRIVATE_KEY)."
|
||||
exit 0
|
||||
fi
|
||||
sh scripts/ci/setup-cosign.sh "${COSIGN_VERSION}"
|
||||
printf '%s\n' "$COSIGN_PRIVATE_KEY" > /tmp/cosign.key
|
||||
chmod 600 /tmp/cosign.key
|
||||
export COSIGN_KEY_PATH=/tmp/cosign.key
|
||||
sh scripts/ci/attest-release-assets.sh ./release-assets
|
||||
rm -f /tmp/cosign.key
|
||||
|
||||
- name: Upload Linux release artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
with:
|
||||
name: meshchatx-linux-release-${{ github.ref_name }}-${{ github.run_id }}
|
||||
path: release-assets/
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
|
||||
slsa-provenance-linux:
|
||||
name: SLSA provenance (Linux)
|
||||
needs: [linux-release]
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
actions: read
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||
with:
|
||||
base64-subjects: ${{ needs.linux-release.outputs.hashes }}
|
||||
upload-assets: false
|
||||
provenance-name: meshchatx-linux-${{ github.ref_name }}.intoto.jsonl
|
||||
|
||||
draft-github-release-linux:
|
||||
name: Draft GitHub release (Linux assets + SLSA)
|
||||
needs: [linux-release, slsa-provenance-linux]
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download Linux release assets
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
name: meshchatx-linux-release-${{ github.ref_name }}-${{ github.run_id }}
|
||||
path: upload
|
||||
|
||||
- name: Download SLSA provenance
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
name: ${{ needs.slsa-provenance-linux.outputs.provenance-name }}
|
||||
path: upload
|
||||
|
||||
- name: Upload to draft release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: bash scripts/ci/github-draft-release-upload-assets.sh upload
|
||||
@@ -8,6 +8,9 @@
|
||||
# actions/setup-node@v6.1.0 395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
# actions/upload-artifact@v5.0.0 330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
# actions/download-artifact@v5.0.0 634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
#
|
||||
# SLSA generator (must stay @vX.Y.Z semver per upstream):
|
||||
# slsa-framework/slsa-github-generator/generator_generic_slsa3.yml@v2.1.0
|
||||
|
||||
name: Build release
|
||||
|
||||
@@ -18,8 +21,9 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: write
|
||||
actions: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -145,3 +149,73 @@ jobs:
|
||||
name: ${{ matrix.artifact_prefix }}-${{ github.ref_name }}-${{ github.run_id }}
|
||||
path: dist/
|
||||
if-no-files-found: warn
|
||||
|
||||
collect-desktop-slsa-subjects:
|
||||
name: SLSA subjects (Windows + macOS)
|
||||
needs: [build-release]
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
hashes: ${{ steps.hash.outputs.hashes }}
|
||||
steps:
|
||||
- name: Download Windows dist
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
name: meshchatx-windows-${{ github.ref_name }}-${{ github.run_id }}
|
||||
path: dl/win
|
||||
|
||||
- name: Download macOS dist
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
name: meshchatx-macos-${{ github.ref_name }}-${{ github.run_id }}
|
||||
path: dl/mac
|
||||
|
||||
- name: Hash desktop artifacts
|
||||
id: hash
|
||||
run: bash scripts/ci/github-slsa-hashes-desktop-dist.sh dl/win dl/mac
|
||||
|
||||
slsa-provenance-desktop:
|
||||
name: SLSA provenance (Windows + macOS)
|
||||
needs: [collect-desktop-slsa-subjects]
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
actions: read
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||
with:
|
||||
base64-subjects: ${{ needs.collect-desktop-slsa-subjects.outputs.hashes }}
|
||||
upload-assets: false
|
||||
provenance-name: meshchatx-desktop-${{ github.ref_name }}.intoto.jsonl
|
||||
|
||||
draft-github-release-desktop:
|
||||
name: Draft GitHub release (desktop + SLSA)
|
||||
needs: [build-release, slsa-provenance-desktop]
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download Windows dist
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
name: meshchatx-windows-${{ github.ref_name }}-${{ github.run_id }}
|
||||
path: upload/win
|
||||
|
||||
- name: Download macOS dist
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
name: meshchatx-macos-${{ github.ref_name }}-${{ github.run_id }}
|
||||
path: upload/mac
|
||||
|
||||
- name: Download SLSA provenance
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
with:
|
||||
name: ${{ needs.slsa-provenance-desktop.outputs.provenance-name }}
|
||||
path: upload
|
||||
|
||||
- name: Upload to draft release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: bash scripts/ci/github-draft-release-upload-assets.sh upload
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run inside Dockerfile.build after COPY. Writes outputs to /artifacts.
|
||||
# Env: MESHCHATX_BUILD_TARGETS = all | wheel | electron (electron = AppImage+deb per arch + best-effort RPM, no wheel)
|
||||
set -euo pipefail
|
||||
|
||||
cd /src
|
||||
|
||||
export POETRY_VERSION="${POETRY_VERSION:-2.3.4}"
|
||||
export PNPM_VERSION="${PNPM_VERSION:-10.33.0}"
|
||||
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl git jq unzip xz-utils \
|
||||
build-essential pkg-config python3-dev
|
||||
|
||||
install_electron_builder_libs() {
|
||||
apt-get install -y --no-install-recommends \
|
||||
libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libgbm1 libasound2 \
|
||||
libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libxkbcommon0 \
|
||||
libfuse2 zstd libgtk-3-0
|
||||
}
|
||||
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
|
||||
apt-get install -y nodejs
|
||||
fi
|
||||
|
||||
TASK_VER="${TASK_VERSION:-3.46.4}"
|
||||
curl -fsSL "https://github.com/go-task/task/releases/download/v${TASK_VER}/task_linux_amd64.tar.gz" \
|
||||
| tar xz -C /usr/local/bin task
|
||||
|
||||
corepack enable
|
||||
corepack prepare "pnpm@${PNPM_VERSION}" --activate
|
||||
|
||||
bash scripts/ci/github-install-poetry.sh
|
||||
|
||||
export TRIVY_SBOM=0
|
||||
|
||||
targets="${MESHCHATX_BUILD_TARGETS:-all}"
|
||||
case "$targets" in
|
||||
wheel)
|
||||
bash scripts/ci/github-install-deps.sh
|
||||
export SKIP_ELECTRON=1
|
||||
bash scripts/ci/github-build-linux-release-assets.sh
|
||||
;;
|
||||
electron)
|
||||
install_electron_builder_libs
|
||||
bash scripts/ci/github-apt-linux-packaging.sh
|
||||
bash scripts/ci/github-install-deps.sh
|
||||
task build:fe
|
||||
export SKIP_WHEEL=1
|
||||
bash scripts/ci/github-build-linux-release-assets.sh
|
||||
;;
|
||||
all|*)
|
||||
install_electron_builder_libs
|
||||
bash scripts/ci/github-apt-linux-packaging.sh
|
||||
bash scripts/ci/github-install-deps.sh
|
||||
task build:fe
|
||||
export SKIP_WHEEL=0
|
||||
export SKIP_ELECTRON=0
|
||||
bash scripts/ci/github-build-linux-release-assets.sh
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir -p /artifacts
|
||||
sh -c 'cp -a /src/release-assets/. /artifacts/ 2>/dev/null || true'
|
||||
if [ -z "$(ls -A /artifacts 2>/dev/null || true)" ]; then
|
||||
echo "docker-build-entry.sh: no files under /artifacts" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "docker-build-entry.sh: artifacts ready under /artifacts"
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# APT packages needed for Linux Electron packaging (AppImage, deb, rpm) on Debian/Ubuntu or in Dockerfile.build (root).
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=scripts/ci/priv.sh
|
||||
. "$(dirname "$0")/priv.sh"
|
||||
|
||||
run_priv dpkg --add-architecture i386 || true
|
||||
run_priv apt-get update -y
|
||||
run_priv apt-get install -y --no-install-recommends \
|
||||
patchelf \
|
||||
libopusfile0 \
|
||||
espeak-ng \
|
||||
zip \
|
||||
rpm \
|
||||
elfutils \
|
||||
fakeroot \
|
||||
file \
|
||||
libc6:i386 \
|
||||
libstdc++6:i386
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build wheel, Linux AppImage/deb (x64 + arm64), optional RPM, frontend zip, and SBOM under ./release-assets/.
|
||||
# Expects repo root as cwd, dependencies installed (task install / pnpm), and meshchatx/public populated when building Electron.
|
||||
# Optional: SKIP_WHEEL=1, SKIP_ELECTRON=1, TRIVY_SBOM=0
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
# shellcheck source=scripts/ci/ci-node-path.sh
|
||||
. "$(dirname "$0")/ci-node-path.sh"
|
||||
|
||||
mkdir -p release-assets
|
||||
|
||||
if [ "${SKIP_WHEEL:-0}" != 1 ]; then
|
||||
echo "Building Python wheel..."
|
||||
task build:wheel
|
||||
else
|
||||
echo "Skipping wheel (SKIP_WHEEL=1)."
|
||||
fi
|
||||
|
||||
if [ "${SKIP_ELECTRON:-0}" != 1 ]; then
|
||||
echo "Electron linux x64..."
|
||||
pnpm run dist:linux-x64
|
||||
|
||||
echo "Electron linux arm64..."
|
||||
pnpm run dist:linux-arm64
|
||||
|
||||
echo "RPM (best-effort)..."
|
||||
if ! task dist:fe:rpm; then
|
||||
echo "RPM build failed or skipped; continuing." >&2
|
||||
fi
|
||||
else
|
||||
echo "Skipping Electron packages (SKIP_ELECTRON=1)."
|
||||
fi
|
||||
|
||||
echo "Collecting release files..."
|
||||
find dist -maxdepth 1 -type f \( -name "*-linux*.AppImage" -o -name "*-linux*.deb" -o -name "*-linux*.rpm" \) -exec cp -f {} release-assets/ \; 2>/dev/null || true
|
||||
find python-dist -maxdepth 1 -type f -name "*.whl" -exec cp -f {} release-assets/ \; 2>/dev/null || true
|
||||
|
||||
if [ -d meshchatx/public ] && [ "${SKIP_ELECTRON:-0}" != 1 ]; then
|
||||
( cd meshchatx/public && zip -qr "${ROOT}/release-assets/meshchatx-frontend.zip" . )
|
||||
fi
|
||||
|
||||
{
|
||||
echo "## Integrity"
|
||||
echo ""
|
||||
echo "Each artifact may have a matching **\`*.cosign.bundle\`** when repository signing secrets are configured (see SECURITY.md)."
|
||||
echo ""
|
||||
echo "SBOM: **\`sbom.cyclonedx.json\`** (CycloneDX) when produced by CI."
|
||||
} > release-body.md
|
||||
|
||||
if [ "${TRIVY_SBOM:-1}" != 0 ] && command -v trivy >/dev/null 2>&1; then
|
||||
echo "Generating SBOM..."
|
||||
trivy fs --format cyclonedx --include-dev-deps --output release-assets/sbom.cyclonedx.json .
|
||||
else
|
||||
echo "Skipping SBOM (trivy not on PATH or TRIVY_SBOM=0)." >&2
|
||||
fi
|
||||
|
||||
echo "github-build-linux-release-assets.sh: done; see ./release-assets/"
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# Create the GitHub release as a draft if missing, then upload every file in DIR to that tag.
|
||||
# Requires: gh, GH_TOKEN. TAG from TAG or GITHUB_REF_NAME.
|
||||
set -euo pipefail
|
||||
|
||||
DIR="${1:?path to directory of files to upload}"
|
||||
TAG="${TAG:-${GITHUB_REF_NAME:?set TAG or GITHUB_REF_NAME}}"
|
||||
|
||||
if ! command -v gh >/dev/null 2>&1; then
|
||||
echo "gh is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${GH_TOKEN:-}" ]; then
|
||||
echo "GH_TOKEN is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export GH_TOKEN
|
||||
|
||||
if ! gh release view "$TAG" >/dev/null 2>&1; then
|
||||
gh release create "$TAG" --draft --title "$TAG" --notes "Automated draft release. Review assets and provenance before publishing."
|
||||
fi
|
||||
|
||||
mapfile -t files < <(find "$DIR" -type f)
|
||||
if [ "${#files[@]}" -eq 0 ]; then
|
||||
echo "No files under ${DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gh release upload "$TAG" "${files[@]}" --clobber
|
||||
@@ -5,6 +5,9 @@ set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
# shellcheck source=scripts/ci/priv.sh
|
||||
. "$(dirname "$0")/priv.sh"
|
||||
|
||||
export GIT_TERMINAL_PROMPT=0
|
||||
|
||||
# pycodec2 builds against libcodec2. Export for this step and persist to GITHUB_ENV so
|
||||
@@ -30,8 +33,8 @@ fi
|
||||
# Linux runners do not ship these by default, so backend Opus encode tests fail
|
||||
# with PyOggError until the shared libraries are present.
|
||||
if [[ "$(uname -s)" == "Linux" ]] && command -v apt-get >/dev/null 2>&1; then
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libopus0 libogg0
|
||||
run_priv apt-get update -y
|
||||
run_priv apt-get install -y libopus0 libogg0
|
||||
fi
|
||||
|
||||
python -m poetry check --lock
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# Emit base64-encoded sha256sum lines for Electron outputs under downloaded artifact roots.
|
||||
# Subject paths are the relative paths passed to sha256sum (stable for slsa-verifier).
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "usage: $0 <root> [root...]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
roots=()
|
||||
for d in "$@"; do
|
||||
[ -d "$d" ] && roots+=("$d")
|
||||
done
|
||||
if [ "${#roots[@]}" -eq 0 ]; then
|
||||
echo "No existing directories in: $*" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmp="$(mktemp)"
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
|
||||
find "${roots[@]}" -type f \( \
|
||||
-name '*.exe' -o -name '*.dmg' -o -name '*.blockmap' -o -name '*.yml' -o -name '*.yaml' \
|
||||
\) ! -path '*/.*' -print | LC_ALL=C sort | xargs -r sha256sum >"$tmp"
|
||||
|
||||
if [ ! -s "$tmp" ]; then
|
||||
echo "No matching dist files under: ${roots[*]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
b64="$(base64 -w0 <"$tmp")"
|
||||
if [ -n "${GITHUB_OUTPUT:-}" ]; then
|
||||
echo "hashes=${b64}" >>"$GITHUB_OUTPUT"
|
||||
else
|
||||
printf '%s\n' "$b64"
|
||||
fi
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
# Emit base64-encoded sha256sum lines for files in ./release-assets (SLSA generic generator input).
|
||||
# Excludes *.cosign.bundle. Writes "hashes=<base64>" to GITHUB_OUTPUT when set.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
if [ ! -d release-assets ]; then
|
||||
echo "release-assets/ missing" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmp="$(mktemp)"
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
|
||||
(
|
||||
cd release-assets
|
||||
find . -maxdepth 1 -type f ! -name '*.cosign.bundle' -printf '%P\0' | sort -z | xargs -0 sha256sum
|
||||
) >"$tmp"
|
||||
|
||||
if [ ! -s "$tmp" ]; then
|
||||
echo "No files to hash under release-assets/" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
b64="$(base64 -w0 <"$tmp")"
|
||||
if [ -n "${GITHUB_OUTPUT:-}" ]; then
|
||||
echo "hashes=${b64}" >>"$GITHUB_OUTPUT"
|
||||
else
|
||||
printf '%s\n' "$b64"
|
||||
fi
|
||||
@@ -35,7 +35,7 @@ def _build_type() -> str:
|
||||
os.environ.get("GITHUB_REPOSITORY") or os.environ.get("GITEA_REPOSITORY") or ""
|
||||
)
|
||||
if server and repo:
|
||||
return f"{server}/{repo}/.gitea/workflows/build.yml"
|
||||
return f"{server}/{repo}/.github/workflows/build-linux-release.yml"
|
||||
return "https://slsa.dev/provenance/v1"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Download Windows/macOS build artifacts from GitHub Actions (build-release workflow)
|
||||
# Download Windows/macOS build artifacts from GitHub Actions (build-release.yml)
|
||||
# and attach them to an existing Gitea release. Best-effort: missing platforms are skipped.
|
||||
#
|
||||
# Primary CI and Linux release binaries live under .github/workflows/ (see README.md).
|
||||
# Linux artifacts are produced by build-linux-release.yml on GitHub if you extend this script.
|
||||
#
|
||||
# Required env: TAG, GITHUB_REPOSITORY, GITHUB_PAT, GITEA_API_URL, GITEA_REPOSITORY, GITEA_TOKEN
|
||||
set -euo pipefail
|
||||
|
||||
@@ -112,7 +115,7 @@ fi
|
||||
REL_JSON=$(curl -sS "${AUTH_GITEA[@]}" "${GITEA_API_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/tags/${TAG}")
|
||||
REL_ID=$(printf '%s' "$REL_JSON" | jq -r '.id // empty')
|
||||
if [ -z "$REL_ID" ] || [ "$REL_ID" = "null" ]; then
|
||||
log "Error: No Gitea release for tag '${TAG}'. Create the release first (e.g. push the tag so .gitea/workflows/build.yml runs)."
|
||||
log "Error: No Gitea release for tag '${TAG}'. Create the Gitea release first, then re-run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
#!/usr/bin/env bash
|
||||
# Wait for successful GitHub Actions runs for TAG, download artifacts from listed workflows,
|
||||
# then create or update a GitHub release and upload binaries.
|
||||
#
|
||||
# Required env: TAG, GH_REPOSITORY (owner/repo), GH_PAT
|
||||
# Optional: WORKFLOWS (space-separated, default: build-release.yml build-linux-release.yml)
|
||||
# TIMEOUT_SEC (default 14400), POLL_INTERVAL (default 60), DRAFT (default false)
|
||||
# RELEASE_BODY_FILE (path to markdown; default tries ./release-body.md from cwd)
|
||||
set -euo pipefail
|
||||
|
||||
TAG="${TAG:?set TAG (e.g. v1.2.3 or release_1.2.3)}"
|
||||
GH_REPOSITORY="${GH_REPOSITORY:?set GH_REPOSITORY to owner/repo on github.com}"
|
||||
GH_PAT="${GH_PAT:?set GH_PAT (fine-grained or classic PAT with contents:write, actions:read)}"
|
||||
WORKFLOWS="${WORKFLOWS:-build-release.yml build-linux-release.yml}"
|
||||
TIMEOUT_SEC="${TIMEOUT_SEC:-14400}"
|
||||
POLL_INTERVAL="${POLL_INTERVAL:-60}"
|
||||
DRAFT="${DRAFT:-false}"
|
||||
|
||||
GH_API="https://api.github.com/repos/${GH_REPOSITORY}"
|
||||
AUTH=(-H "Authorization: Bearer ${GH_PAT}" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28")
|
||||
|
||||
WORKDIR=$(mktemp -d)
|
||||
trap 'rm -rf "${WORKDIR}"' EXIT
|
||||
|
||||
log() {
|
||||
printf '%s\n' "$*" >&2
|
||||
}
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
log "Error: jq is required."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
enc_tag() {
|
||||
printf '%s' "$1" | jq -sRr @uri
|
||||
}
|
||||
|
||||
latest_success_run_id() {
|
||||
local wf="$1"
|
||||
local tag="$2"
|
||||
local tag_enc run_id runs_json commit_sha runs_json2
|
||||
|
||||
tag_enc=$(enc_tag "$tag")
|
||||
run_id=""
|
||||
if runs_json=$(curl -sS -f "${AUTH[@]}" "${GH_API}/actions/workflows/${wf}/runs?event=push&branch=${tag_enc}&per_page=30" 2>/dev/null); then
|
||||
run_id=$(printf '%s' "$runs_json" | jq -r '
|
||||
[.workflow_runs[] | select(.conclusion == "success")]
|
||||
| sort_by(.created_at) | reverse | .[0].id // empty
|
||||
')
|
||||
fi
|
||||
|
||||
if [ -z "$run_id" ]; then
|
||||
if ! commit_sha=$(curl -sS -f "${AUTH[@]}" "${GH_API}/commits/${tag_enc}" | jq -r '.sha // empty'); then
|
||||
commit_sha=""
|
||||
fi
|
||||
if [ -n "$commit_sha" ] && runs_json2=$(curl -sS -f "${AUTH[@]}" "${GH_API}/actions/workflows/${wf}/runs?per_page=80" 2>/dev/null); then
|
||||
run_id=$(printf '%s' "$runs_json2" | jq -r --arg sha "$commit_sha" '
|
||||
[.workflow_runs[] | select(.head_sha == $sha and .conclusion == "success")]
|
||||
| sort_by(.created_at) | reverse | .[0].id // empty
|
||||
')
|
||||
fi
|
||||
fi
|
||||
printf '%s' "$run_id"
|
||||
}
|
||||
|
||||
deadline=$(( $(date +%s) + TIMEOUT_SEC ))
|
||||
declare -A RUN_IDS=()
|
||||
|
||||
log "Waiting for workflows (${WORKFLOWS}) on tag ${TAG} (timeout ${TIMEOUT_SEC}s)..."
|
||||
|
||||
while [ "$(date +%s)" -lt "$deadline" ]; do
|
||||
all_set=1
|
||||
for wf in $WORKFLOWS; do
|
||||
rid=$(latest_success_run_id "$wf" "$TAG")
|
||||
if [ -n "$rid" ]; then
|
||||
RUN_IDS["$wf"]=$rid
|
||||
else
|
||||
all_set=0
|
||||
fi
|
||||
done
|
||||
if [ "$all_set" = 1 ]; then
|
||||
break
|
||||
fi
|
||||
log "Not all workflows succeeded yet; sleeping ${POLL_INTERVAL}s..."
|
||||
sleep "$POLL_INTERVAL"
|
||||
done
|
||||
|
||||
for wf in $WORKFLOWS; do
|
||||
if [ -z "${RUN_IDS[$wf]:-}" ]; then
|
||||
log "Timeout or missing successful run for ${wf} (tag=${TAG})."
|
||||
exit 1
|
||||
fi
|
||||
log "Using ${wf} run_id=${RUN_IDS[$wf]}"
|
||||
done
|
||||
|
||||
STAGE="${WORKDIR}/stage"
|
||||
mkdir -p "$STAGE"
|
||||
|
||||
download_and_stage_run() {
|
||||
local wf="$1"
|
||||
local rid="$2"
|
||||
local art_json n
|
||||
|
||||
art_json=$(curl -sS "${AUTH[@]}" "${GH_API}/actions/runs/${rid}/artifacts?per_page=100" || true)
|
||||
n=$(printf '%s' "${art_json:-{}}" | jq -r '(.artifacts // []) | length')
|
||||
if [ "${n:-0}" -eq 0 ]; then
|
||||
log "No artifacts for ${wf} run ${rid}."
|
||||
return
|
||||
fi
|
||||
|
||||
printf '%s' "$art_json" | jq -r '.artifacts[] | "\(.name)|\(.archive_download_url)"' | while IFS='|' read -r art_name dl_url; do
|
||||
case "$wf" in
|
||||
build-release.yml)
|
||||
case "$art_name" in
|
||||
meshchatx-windows-*|meshchatx-macos-*) ;;
|
||||
*)
|
||||
log "Skipping artifact ${art_name} for ${wf}"
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
build-linux-release.yml)
|
||||
case "$art_name" in
|
||||
meshchatx-linux-release-*) ;;
|
||||
*)
|
||||
log "Skipping artifact ${art_name} for ${wf}"
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
log "Unknown workflow file in download filter: ${wf}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
zip_path="${WORKDIR}/$(echo "$art_name" | tr '/' '_').zip"
|
||||
log "Downloading ${art_name}..."
|
||||
if ! curl -sS -fL "${AUTH[@]}" -o "$zip_path" "$dl_url"; then
|
||||
log "Warning: download failed for ${art_name}"
|
||||
continue
|
||||
fi
|
||||
ex="${WORKDIR}/ex-${art_name}"
|
||||
mkdir -p "$ex"
|
||||
if ! unzip -q -o "$zip_path" -d "$ex" 2>/dev/null; then
|
||||
log "Warning: unzip failed for ${art_name}"
|
||||
continue
|
||||
fi
|
||||
find "$ex" -type f \( \
|
||||
-name '*.AppImage' -o -name '*.deb' -o -name '*.rpm' -o -name '*.whl' -o \
|
||||
-name '*.exe' -o -name '*.dmg' -o -name '*.blockmap' -o \
|
||||
-name 'latest*.yml' -o -name 'latest*.yaml' -o -name '*-linux.yml' -o -name '*-linux.yaml' -o \
|
||||
-name 'meshchatx-frontend.zip' -o -name 'sbom.cyclonedx.json' -o -name '*.cosign.bundle' -o -name '*.intoto.jsonl' \
|
||||
\) -print0 2>/dev/null | while IFS= read -r -d '' f; do
|
||||
cp -f "$f" "${STAGE}/$(basename "$f")"
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
for wf in $WORKFLOWS; do
|
||||
download_and_stage_run "$wf" "${RUN_IDS[$wf]}"
|
||||
done
|
||||
|
||||
file_count=$(find "$STAGE" -mindepth 1 -maxdepth 1 -type f 2>/dev/null | wc -l)
|
||||
file_count=${file_count//[[:space:]]/}
|
||||
if [ "${file_count:-0}" -eq 0 ]; then
|
||||
log "No release files staged after downloads."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BODY_FILE="${RELEASE_BODY_FILE:-}"
|
||||
if [ -z "$BODY_FILE" ] && [ -f "./release-body.md" ]; then
|
||||
BODY_FILE="./release-body.md"
|
||||
fi
|
||||
BODY_JSON=$(jq -n '""')
|
||||
if [ -n "$BODY_FILE" ] && [ -f "$BODY_FILE" ]; then
|
||||
BODY_JSON=$(jq -Rs . < "$BODY_FILE")
|
||||
fi
|
||||
|
||||
log "Creating or locating GitHub release for tag ${TAG}..."
|
||||
REL_GET=$(curl -sS -w '%{http_code}' -o "${WORKDIR}/rel.json" "${AUTH[@]}" "${GH_API}/releases/tags/${TAG}" || true)
|
||||
HTTP="${REL_GET: -3}"
|
||||
REL_ID=""
|
||||
if [ "$HTTP" = "200" ]; then
|
||||
REL_ID=$(jq -r '.id // empty' "${WORKDIR}/rel.json")
|
||||
UPLOAD_URL=$(jq -r '.upload_url // empty' "${WORKDIR}/rel.json")
|
||||
log "Release exists id=${REL_ID}"
|
||||
else
|
||||
if [ "$DRAFT" = "true" ] || [ "$DRAFT" = "1" ]; then
|
||||
draft_json="true"
|
||||
else
|
||||
draft_json="false"
|
||||
fi
|
||||
payload=$(jq -n \
|
||||
--arg tag "$TAG" \
|
||||
--arg name "$TAG" \
|
||||
--argjson draft "$draft_json" \
|
||||
--argjson body "$BODY_JSON" \
|
||||
'{tag_name: $tag, name: $name, body: body, draft: draft}')
|
||||
if ! curl -sS -f "${AUTH[@]}" -X POST -H "Content-Type: application/json" \
|
||||
-d "$payload" "${GH_API}/releases" -o "${WORKDIR}/newrel.json"; then
|
||||
log "Failed to create release for ${TAG}"
|
||||
exit 1
|
||||
fi
|
||||
REL_ID=$(jq -r '.id // empty' "${WORKDIR}/newrel.json")
|
||||
UPLOAD_URL=$(jq -r '.upload_url // empty' "${WORKDIR}/newrel.json")
|
||||
log "Created release id=${REL_ID}"
|
||||
fi
|
||||
|
||||
if [ -z "$REL_ID" ] || [ -z "$UPLOAD_URL" ]; then
|
||||
log "Could not resolve release id or upload_url."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BASE_UPLOAD="${UPLOAD_URL%\{*}"
|
||||
|
||||
EXISTING_NAMES=$(curl -sS "${AUTH[@]}" "${GH_API}/releases/${REL_ID}/assets" | jq -c '[.[].name]' 2>/dev/null || echo '[]')
|
||||
|
||||
while IFS= read -r -d '' f; do
|
||||
base=$(basename "$f")
|
||||
if jq -n -e --argjson names "$EXISTING_NAMES" --arg n "$base" '$names | index($n) != null' >/dev/null 2>&1; then
|
||||
log "Skipping existing asset: ${base}"
|
||||
continue
|
||||
fi
|
||||
enc=$(printf '%s' "$base" | jq -sRr @uri)
|
||||
log "Uploading ${base}..."
|
||||
if ! curl -sS -f "${AUTH[@]}" -X POST \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @"$f" \
|
||||
"${BASE_UPLOAD}?name=${enc}" >/dev/null; then
|
||||
log "Upload failed for ${base}"
|
||||
exit 1
|
||||
fi
|
||||
done < <(find "$STAGE" -maxdepth 1 -type f -print0)
|
||||
|
||||
log "Done publishing assets to GitHub release ${TAG}."
|
||||
Reference in New Issue
Block a user