* fix(windows): ship yams-daemon and yams-mcp-server in the MSI The Windows release already builds both and meson install stages them next to yams.exe, but yams.wxs only packaged yams.exe, so daemon-backed commands and yams serve failed after an MSI install. Package both, and make build-msi.ps1 fail when a required executable is missing from the stage or a staged executable is not packaged. Signed-off-by: trvon <me@trevon.dev> * ci(release): fall back to the stable R2 and signing secrets for experimental The experimental channel needs EXPERIMENTAL_* Cloudflare and GPG secrets that were never created, so even a passing nightly could not publish and repo.yamsmemory.ai/experimental/* has never existed. Fall back to the stable token, account and key when the channel-specific secrets are unset; setting them later still takes precedence. Signed-off-by: trvon <me@trevon.dev> * ci: add a manual Windows workflow that finds hanging test cases search_service times out at 360 s on Windows in every Tests run, which keeps experimental from going green and blocks every nightly. The meson log only shows the timeout. This workflow builds once and runs each Catch2 case on its own with a per-case timeout, then reports durations and the cases that hang. It runs only on workflow_dispatch or a push to a diagnose/** branch. Signed-off-by: trvon <me@trevon.dev> --------- Signed-off-by: trvon <me@trevon.dev>
175 lines
7.3 KiB
YAML
175 lines
7.3 KiB
YAML
name: Publish Package Repositories
|
|
|
|
"on":
|
|
workflow_dispatch:
|
|
inputs:
|
|
channel:
|
|
description: "Repository channel expected from the trusted release manifest"
|
|
type: choice
|
|
options:
|
|
- stable
|
|
- experimental
|
|
required: true
|
|
default: stable
|
|
run_id:
|
|
description: "Exact successful Release run ID (required for experimental)"
|
|
required: false
|
|
default: ""
|
|
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
|
|
jobs:
|
|
publish:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 25
|
|
steps:
|
|
- name: Checkout publication policy
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
submodules: false
|
|
|
|
- name: Validate and resolve trusted Release run
|
|
id: resolve-run
|
|
shell: bash
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
REQUESTED_CHANNEL: ${{ inputs.channel }}
|
|
REQUESTED_RUN_ID: ${{ inputs.run_id }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "$REQUESTED_CHANNEL" != stable ] && [ "$REQUESTED_CHANNEL" != experimental ]; then
|
|
echo "::error::Missing or unsupported repository channel: $REQUESTED_CHANNEL"
|
|
exit 1
|
|
fi
|
|
if [ "$REQUESTED_CHANNEL" = experimental ] && [ -z "$REQUESTED_RUN_ID" ]; then
|
|
echo "::error::Experimental publication requires an exact Release run ID"
|
|
exit 1
|
|
fi
|
|
|
|
RUN_ID="$REQUESTED_RUN_ID"
|
|
if [ -z "$RUN_ID" ]; then
|
|
RUN_ID=$(gh api "repos/${{ github.repository }}/actions/workflows/release.yml/runs?per_page=100" \
|
|
--jq '.workflow_runs[] | select(.conclusion == "success" and .event == "push") | .id' | head -1)
|
|
if [ -z "$RUN_ID" ]; then
|
|
echo "::error::No successful tag-triggered Release workflow run found"
|
|
exit 1
|
|
fi
|
|
fi
|
|
if ! [[ "$RUN_ID" =~ ^[1-9][0-9]*$ ]]; then
|
|
echo "::error::run_id must be a positive numeric Actions run ID"
|
|
exit 1
|
|
fi
|
|
|
|
RUN_INFO=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}" \
|
|
--jq '{conclusion: .conclusion, status: .status, event: .event, path: .path, run_attempt: .run_attempt}')
|
|
CONCLUSION=$(jq -r '.conclusion' <<<"$RUN_INFO")
|
|
STATUS=$(jq -r '.status' <<<"$RUN_INFO")
|
|
EVENT=$(jq -r '.event' <<<"$RUN_INFO")
|
|
WORKFLOW_PATH=$(jq -r '.path' <<<"$RUN_INFO")
|
|
RUN_ATTEMPT=$(jq -r '.run_attempt' <<<"$RUN_INFO")
|
|
if [ "$STATUS" != completed ] || [ "$CONCLUSION" != success ]; then
|
|
echo "::error::Release run $RUN_ID is not a completed success"
|
|
exit 1
|
|
fi
|
|
if [ "$EVENT" = pull_request ] || [ "$EVENT" = pull_request_target ]; then
|
|
echo "::error::Refusing repository artifacts from a pull-request run"
|
|
exit 1
|
|
fi
|
|
if [ "$WORKFLOW_PATH" != ".github/workflows/release.yml" ]; then
|
|
echo "::error::Run $RUN_ID belongs to $WORKFLOW_PATH, not release.yml"
|
|
exit 1
|
|
fi
|
|
if ! [[ "$RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]]; then
|
|
echo "::error::Release run has invalid attempt metadata"
|
|
exit 1
|
|
fi
|
|
echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
|
|
echo "run_attempt=$RUN_ATTEMPT" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Download trusted release manifest
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
run-id: ${{ steps.resolve-run.outputs.run_id }}
|
|
name: release-manifest
|
|
path: trusted-manifest
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Download APT repository artifact
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
run-id: ${{ steps.resolve-run.outputs.run_id }}
|
|
name: apt-repo
|
|
path: repository-input/aptrepo
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Download YUM repository artifact
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
run-id: ${{ steps.resolve-run.outputs.run_id }}
|
|
name: yum-repo
|
|
path: repository-input/yumrepo
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Download Arch repository artifact
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
run-id: ${{ steps.resolve-run.outputs.run_id }}
|
|
name: arch-repo
|
|
path: repository-input/archrepo
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Publish validated channel to R2
|
|
env:
|
|
STABLE_CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN || secrets.R2_SECRET_ACCESS_KEY }}
|
|
STABLE_CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
EXPERIMENTAL_CLOUDFLARE_API_TOKEN: ${{ secrets.EXPERIMENTAL_CLOUDFLARE_API_TOKEN || secrets.CLOUDFLARE_API_TOKEN || secrets.R2_SECRET_ACCESS_KEY }}
|
|
EXPERIMENTAL_CLOUDFLARE_ACCOUNT_ID: ${{ secrets.EXPERIMENTAL_CLOUDFLARE_ACCOUNT_ID || secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
STABLE_GPG_PUBLIC_KEY_B64: ${{ secrets.GPG_PUBLIC_KEY_B64 || '' }}
|
|
EXPERIMENTAL_GPG_PUBLIC_KEY_B64: ${{ secrets.EXPERIMENTAL_GPG_PUBLIC_KEY_B64 || secrets.GPG_PUBLIC_KEY_B64 || '' }}
|
|
REQUESTED_CHANNEL: ${{ inputs.channel }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "$REQUESTED_CHANNEL" = stable ]; then
|
|
CLOUDFLARE_API_TOKEN="$STABLE_CLOUDFLARE_API_TOKEN"
|
|
CLOUDFLARE_ACCOUNT_ID="$STABLE_CLOUDFLARE_ACCOUNT_ID"
|
|
GPG_PUBLIC_KEY_B64="$STABLE_GPG_PUBLIC_KEY_B64"
|
|
else
|
|
CLOUDFLARE_API_TOKEN="$EXPERIMENTAL_CLOUDFLARE_API_TOKEN"
|
|
CLOUDFLARE_ACCOUNT_ID="$EXPERIMENTAL_CLOUDFLARE_ACCOUNT_ID"
|
|
GPG_PUBLIC_KEY_B64="$EXPERIMENTAL_GPG_PUBLIC_KEY_B64"
|
|
fi
|
|
export CLOUDFLARE_API_TOKEN CLOUDFLARE_ACCOUNT_ID
|
|
unset STABLE_CLOUDFLARE_API_TOKEN STABLE_CLOUDFLARE_ACCOUNT_ID
|
|
unset EXPERIMENTAL_CLOUDFLARE_API_TOKEN EXPERIMENTAL_CLOUDFLARE_ACCOUNT_ID
|
|
unset STABLE_GPG_PUBLIC_KEY_B64 EXPERIMENTAL_GPG_PUBLIC_KEY_B64
|
|
if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ -z "$CLOUDFLARE_ACCOUNT_ID" ]; then
|
|
echo "Missing channel-specific Cloudflare R2 authority" >&2
|
|
exit 1
|
|
fi
|
|
|
|
WRANGLER_ROOT="${RUNNER_TEMP:-/tmp}/wrangler-cli"
|
|
WRANGLER_BIN="$WRANGLER_ROOT/node_modules/.bin/wrangler"
|
|
if [ ! -x "$WRANGLER_BIN" ]; then
|
|
npm install --prefix "$WRANGLER_ROOT" wrangler@4.69.0 >/dev/null
|
|
fi
|
|
"$WRANGLER_BIN" --version >&2
|
|
|
|
public_key_args=()
|
|
if [ -n "$GPG_PUBLIC_KEY_B64" ]; then
|
|
echo "$GPG_PUBLIC_KEY_B64" | base64 -d > channel-gpg.key
|
|
public_key_args=(--public-key channel-gpg.key)
|
|
fi
|
|
python3 scripts/ci/publish_repository.py \
|
|
--repo-root repository-input \
|
|
--manifest trusted-manifest/latest.json \
|
|
--expected-channel "$REQUESTED_CHANNEL" \
|
|
--selected-run-id "${{ steps.resolve-run.outputs.run_id }}" \
|
|
--selected-run-attempt "${{ steps.resolve-run.outputs.run_attempt }}" \
|
|
--bucket yams-repository \
|
|
--wrangler "$WRANGLER_BIN" \
|
|
"${public_key_args[@]}"
|
|
echo "Published $REQUESTED_CHANNEL without pruning any channel objects." >&2
|