chore(workflows): migrate CI workflows from Gitea to GitHub Actions and remove obsolete files

This commit is contained in:
Ivan
2026-04-23 19:49:05 -05:00
parent 863e9923c3
commit 3310b61a8f
22 changed files with 894 additions and 775 deletions
-58
View File
@@ -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
-84
View File
@@ -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/ \;
-195
View File
@@ -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
-176
View File
@@ -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
-102
View File
@@ -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 }}"
+70
View File
@@ -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
-37
View File
@@ -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
-51
View File
@@ -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
-66
View File
@@ -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
+63
View File
@@ -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
+190
View File
@@ -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
+75 -1
View File
@@ -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
+71
View File
@@ -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"
+20
View File
@@ -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 -2
View File
@@ -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
+1 -1
View File
@@ -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"
+5 -2
View File
@@ -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}."