🛡️ chore: Harden CI Supply Chain Workflows (#13090)

* chore: harden CI supply chain workflows

* chore: address CI hardening review feedback

* chore: tighten GitNexus dispatch hardening

* chore: use app token for Locize PR automation

* chore: use dedicated token for Locize PR automation
This commit is contained in:
Danny Avila 2026-05-18 16:55:25 -04:00 committed by GitHub
parent 68eac104ad
commit 21574f02ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 347 additions and 108 deletions

View file

@ -11,6 +11,10 @@ on:
required: true
default: 'false'
permissions:
contents: read
pull-requests: write
jobs:
axe-linter:
runs-on: ubuntu-latest

View file

@ -5,6 +5,9 @@ on:
- 'api/**'
- 'packages/**'
permissions:
contents: read
env:
NODE_ENV: CI
NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}'

View file

@ -3,6 +3,9 @@ name: Linux_Container_Workflow
on:
workflow_dispatch:
permissions:
contents: read
env:
RUNNER_VERSION: 2.293.0
@ -12,26 +15,26 @@ jobs:
steps:
# checkout the repo
- name: 'Checkout GitHub Action'
uses: actions/checkout@main
uses: actions/checkout@v4
- name: 'Login via Azure CLI'
uses: azure/login@v1
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: 'Build GitHub Runner container image'
uses: azure/docker-login@v1
uses: docker/login-action@v3
with:
login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
registry: ${{ secrets.REGISTRY_LOGIN_SERVER }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- run: |
docker build --build-arg RUNNER_VERSION=${{ env.RUNNER_VERSION }} -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/pwd9000-github-runner-lin:${{ env.RUNNER_VERSION }} .
- name: 'Push container image to ACR'
uses: azure/docker-login@v1
uses: docker/login-action@v3
with:
login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
registry: ${{ secrets.REGISTRY_LOGIN_SERVER }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- run: |

View file

@ -15,6 +15,9 @@ on:
- 'redis-config/**'
- '.github/workflows/cache-integration-tests.yml'
permissions:
contents: read
jobs:
cache_integration_tests:
name: Integration Tests that use actual Redis Cache

View file

@ -14,31 +14,27 @@ on:
default: 'Manual publish requested'
permissions:
id-token: write # Required for OIDC trusted publishing
contents: read
jobs:
build-and-publish:
pack:
runs-on: ubuntu-latest
environment: publish # Must match npm trusted publisher config
outputs:
skip: ${{ steps.check.outputs.skip }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Update npm for OIDC support
run: npm install -g npm@latest # Must be 11.5.1+ for provenance
- name: Install client dependencies
run: cd packages/client && npm ci
- name: Build client
run: cd packages/client && npm run build
- name: Check version change
id: check
working-directory: packages/client
@ -52,13 +48,47 @@ jobs:
echo "Version changed, proceeding with publish"
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Pack package
if: steps.check.outputs.skip != 'true'
working-directory: packages/client
run: npm pack
- name: Publish
run: |
mkdir -p "$GITHUB_WORKSPACE/npm-package"
npm pack --pack-destination "$GITHUB_WORKSPACE/npm-package"
- name: Upload package
if: steps.check.outputs.skip != 'true'
working-directory: packages/client
uses: actions/upload-artifact@v4
with:
name: librechat-client-package
path: npm-package/*.tgz
if-no-files-found: error
retention-days: 2
publish-npm:
needs: pack
if: github.ref == 'refs/heads/main' && needs.pack.outputs.skip != 'true'
runs-on: ubuntu-latest
environment: publish # Must match npm trusted publisher config
permissions:
contents: read
id-token: write # Required for OIDC trusted publishing
steps:
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Install npm with OIDC support
run: npm install -g npm@11.14.1 --ignore-scripts
- name: Download package
uses: actions/download-artifact@v4
with:
name: librechat-client-package
path: npm-package
- name: Publish
working-directory: npm-package
run: npm publish *.tgz --access public --provenance

View file

@ -14,11 +14,10 @@ on:
default: 'Manual publish requested'
permissions:
id-token: write # Required for OIDC trusted publishing
contents: read
jobs:
build:
pack:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -27,21 +26,42 @@ jobs:
node-version: 20
- run: cd packages/data-provider && npm ci
- run: cd packages/data-provider && npm run build
- name: Pack package
run: |
mkdir -p npm-package
cd packages/data-provider
npm pack --pack-destination "$GITHUB_WORKSPACE/npm-package"
- name: Upload package
uses: actions/upload-artifact@v4
with:
name: librechat-data-provider-package
path: npm-package/*.tgz
if-no-files-found: error
retention-days: 2
publish-npm:
needs: build
needs: pack
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: publish # Must match npm trusted publisher config
permissions:
contents: read
id-token: write # Required for OIDC trusted publishing
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'
- name: Update npm for OIDC support
run: npm install -g npm@latest # Must be 11.5.1+ for provenance
- run: cd packages/data-provider && npm ci
- run: cd packages/data-provider && npm run build
- run: cd packages/data-provider && npm publish --provenance
- name: Install npm with OIDC support
run: npm install -g npm@11.14.1 --ignore-scripts
- name: Download package
uses: actions/download-artifact@v4
with:
name: librechat-data-provider-package
path: npm-package
- name: Publish package
working-directory: npm-package
run: npm publish *.tgz --provenance

View file

@ -14,31 +14,27 @@ on:
default: 'Manual publish requested'
permissions:
id-token: write # Required for OIDC trusted publishing
contents: read
jobs:
build-and-publish:
pack:
runs-on: ubuntu-latest
environment: publish # Must match npm trusted publisher config
outputs:
skip: ${{ steps.check.outputs.skip }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Update npm for OIDC support
run: npm install -g npm@latest # Must be 11.5.1+ for provenance
- name: Install dependencies
run: cd packages/data-schemas && npm ci
- name: Build
run: cd packages/data-schemas && npm run build
- name: Check version change
id: check
working-directory: packages/data-schemas
@ -52,13 +48,47 @@ jobs:
echo "Version changed, proceeding with publish"
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Pack package
if: steps.check.outputs.skip != 'true'
working-directory: packages/data-schemas
run: npm pack
- name: Publish
run: |
mkdir -p "$GITHUB_WORKSPACE/npm-package"
npm pack --pack-destination "$GITHUB_WORKSPACE/npm-package"
- name: Upload package
if: steps.check.outputs.skip != 'true'
working-directory: packages/data-schemas
uses: actions/upload-artifact@v4
with:
name: librechat-data-schemas-package
path: npm-package/*.tgz
if-no-files-found: error
retention-days: 2
publish-npm:
needs: pack
if: github.ref == 'refs/heads/main' && needs.pack.outputs.skip != 'true'
runs-on: ubuntu-latest
environment: publish # Must match npm trusted publisher config
permissions:
contents: read
id-token: write # Required for OIDC trusted publishing
steps:
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Install npm with OIDC support
run: npm install -g npm@11.14.1 --ignore-scripts
- name: Download package
uses: actions/download-artifact@v4
with:
name: librechat-data-schemas-package
path: npm-package
- name: Publish
working-directory: npm-package
run: npm publish *.tgz --access public --provenance

View file

@ -7,6 +7,9 @@ on:
- completed
workflow_dispatch:
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
@ -29,7 +32,7 @@ jobs:
DO_HOST: ${{ secrets.DO_HOST }}
DO_USER: ${{ secrets.DO_USER }}
run: |
ssh -o StrictHostKeyChecking=no ${DO_USER}@${DO_HOST} << EOF
ssh ${DO_USER}@${DO_HOST} << EOF
sudo -i -u danny bash << 'EEOF'
cd ~/LibreChat && \
git fetch origin main && \

View file

@ -3,6 +3,9 @@ name: Deploy_GHRunner_Linux_ACI
on:
workflow_dispatch:
permissions:
contents: read
env:
RUNNER_VERSION: 2.293.0
ACI_RESOURCE_GROUP: 'Demo-ACI-GitHub-Runners-RG'
@ -20,7 +23,7 @@ jobs:
uses: actions/checkout@v4
- name: 'Login via Azure CLI'
uses: azure/login@v1
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

View file

@ -10,6 +10,10 @@ on:
- 'client/**'
- 'packages/**'
permissions:
contents: read
packages: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@ -43,7 +47,7 @@ jobs:
# Log in to GitHub Container Registry
- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}

View file

@ -10,6 +10,10 @@ on:
- 'client/**'
- 'packages/**'
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
@ -39,7 +43,7 @@ jobs:
# Log in to GitHub Container Registry
- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}

View file

@ -3,6 +3,10 @@ name: Docker Dev Staging Images Build
on:
workflow_dispatch:
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
@ -31,7 +35,7 @@ jobs:
# Log in to GitHub Container Registry
- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
@ -63,4 +67,3 @@ jobs:
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
platforms: linux/amd64,linux/arm64
target: ${{ matrix.target }}

View file

@ -6,6 +6,9 @@ on:
- 'client/**'
- 'packages/data-provider/**'
permissions:
contents: read
env:
NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}'

View file

@ -7,14 +7,17 @@ on:
paths:
- 'docs/**'
permissions:
contents: read
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: supabase/embeddings-generator@v0.0.5
with:
supabase-url: ${{ secrets.SUPABASE_URL }}
supabase-service-role-key: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
openai-key: ${{ secrets.OPENAI_DOC_EMBEDDINGS_KEY }}
docs-root-path: 'docs'
docs-root-path: 'docs'

View file

@ -25,9 +25,13 @@ on:
type: string
default: ''
pr_ref:
description: 'PR head SHA or ref to check out (set by /gitnexus command)'
description: 'Optional PR head ref to check out; defaults to refs/pull/<pr_number>/head when pr_number is set'
type: string
default: ''
deploy_after:
description: 'Dispatch GitNexus Deploy after a successful index run'
type: boolean
default: false
permissions:
contents: read
@ -61,10 +65,35 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Validate dispatch inputs
if: github.event_name == 'workflow_dispatch'
env:
PR_NUMBER: ${{ inputs.pr_number }}
PR_REF: ${{ inputs.pr_ref }}
run: |
set -euo pipefail
if [ -n "$PR_NUMBER" ]; then
if [[ ! "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
echo "::error::pr_number must be numeric"
exit 1
fi
EXPECTED_REF="refs/pull/${PR_NUMBER}/head"
if [ -n "$PR_REF" ] && [ "$PR_REF" != "$EXPECTED_REF" ]; then
echo "::error::pr_ref must match ${EXPECTED_REF}"
exit 1
fi
elif [ -n "$PR_REF" ]; then
echo "::error::pr_ref requires pr_number"
exit 1
fi
- name: Resolve GitNexus flags
id: flags
env:
EVENT_NAME: ${{ github.event_name }}
ENABLE_EMBEDDINGS_INPUT: ${{ inputs.embeddings }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUM: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail
@ -79,15 +108,14 @@ jobs:
# (default false). This also covers the
# /gitnexus index [embeddings] command.
ENABLE_EMBEDDINGS=false
case "${{ github.event_name }}" in
case "$EVENT_NAME" in
workflow_dispatch)
[ "${{ inputs.embeddings }}" = "true" ] && ENABLE_EMBEDDINGS=true
[ "$ENABLE_EMBEDDINGS_INPUT" = "true" ] && ENABLE_EMBEDDINGS=true
;;
push)
ENABLE_EMBEDDINGS=true
;;
pull_request)
PR_NUM="${{ github.event.pull_request.number }}"
CHANGED=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUM/files" \
--paginate --jq '.[].filename' 2>/dev/null || echo "")
if printf '%s\n' "$CHANGED" | grep -qE '^(api/|client/|packages/)'; then
@ -145,13 +173,15 @@ jobs:
# repo for every PR, so checkout works for fork PRs too. When
# pr_ref is empty (native push/pull_request), fall back to the
# default ref actions/checkout would use.
ref: ${{ inputs.pr_ref || '' }}
ref: ${{ inputs.pr_ref || (inputs.pr_number != '' && format('refs/pull/{0}/head', inputs.pr_number) || '') }}
fetch-depth: 1
persist-credentials: false
- name: Run GitNexus Analyze
working-directory: ${{ runner.temp }}
env:
ENABLE_EMBEDDINGS: ${{ steps.flags.outputs.enable_embeddings }}
FORCE: ${{ inputs.force }}
GITNEXUS_BIN: ${{ runner.temp }}/gitnexus-cli/node_modules/.bin/gitnexus
NPM_CONFIG_AUDIT: false
NPM_CONFIG_CACHE: ${{ runner.temp }}/gitnexus-npm-cache
@ -163,10 +193,10 @@ jobs:
set -euo pipefail
FLAGS=(--skip-agents-md --verbose)
if [ "${{ steps.flags.outputs.enable_embeddings }}" = "true" ]; then
if [ "$ENABLE_EMBEDDINGS" = "true" ]; then
FLAGS+=(--embeddings)
fi
if [ "${{ inputs.force }}" = "true" ]; then
if [ "$FORCE" = "true" ]; then
FLAGS+=(--force)
fi
"$GITNEXUS_BIN" analyze "$GITHUB_WORKSPACE" "${FLAGS[@]}"
@ -206,27 +236,25 @@ jobs:
if: |
always() &&
(inputs.pr_number != '' ||
(github.triggering_actor == 'github-actions[bot]' && needs.index.result == 'success'))
inputs.deploy_after)
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
actions: write # dispatch gitnexus-deploy.yml on bot-triggered runs
actions: write # dispatch gitnexus-deploy.yml when deploy_after is set
pull-requests: write # post completion comments for /gitnexus command runs
steps:
# GitHub suppresses workflow_run events for workflow runs whose
# triggering actor is GITHUB_TOKEN (to prevent recursive chaining).
# That means when this workflow is dispatched by gitnexus-pr-command
# via `gh api workflow_dispatch`, the deploy workflow's workflow_run
# trigger never fires. Manually dispatch the deploy here in that
# specific case — user-triggered runs continue to rely on the
# existing workflow_run trigger, so we don't double-deploy.
- name: Trigger deploy workflow for bot-triggered runs
if: github.triggering_actor == 'github-actions[bot]' && needs.index.result == 'success'
# GitHub suppresses workflow_run events for workflow runs triggered
# by GITHUB_TOKEN (to prevent recursive chaining). Command-triggered
# index runs opt into a deploy by setting deploy_after=true.
- name: Trigger deploy workflow after command-triggered runs
if: inputs.deploy_after && needs.index.result == 'success'
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ inputs.pr_number }}
with:
script: |
core.info('Triggering actor is github-actions[bot]; workflow_run would not fire. Dispatching gitnexus-deploy.yml manually.');
core.info('deploy_after=true; dispatching gitnexus-deploy.yml manually.');
// Pass pr_number through so the deploy workflow knows which
// PR to post its completion comment on (for /gitnexus
// command runs this will be set; for other bot dispatches
@ -237,7 +265,7 @@ jobs:
workflow_id: 'gitnexus-deploy.yml',
ref: 'main',
inputs: {
pr_number: '${{ inputs.pr_number }}',
pr_number: process.env.PR_NUMBER || '',
},
});
@ -249,19 +277,28 @@ jobs:
- name: Comment on PR — index complete
if: inputs.pr_number != ''
uses: actions/github-script@v7
env:
EMBEDDINGS_INPUT: ${{ inputs.embeddings }}
INDEX_RESULT: ${{ needs.index.result }}
PR_NUMBER: ${{ inputs.pr_number }}
with:
script: |
const outcome = '${{ needs.index.result }}' === 'success' ? '✅ indexed' : '❌ index failed';
const prNum = parseInt('${{ inputs.pr_number }}', 10);
const indexSucceeded = process.env.INDEX_RESULT === 'success';
const outcome = indexSucceeded ? '✅ indexed' : '❌ index failed';
const prNum = parseInt(process.env.PR_NUMBER || '', 10);
if (!Number.isSafeInteger(prNum)) {
core.setFailed(`Invalid PR number: ${process.env.PR_NUMBER}`);
return;
}
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const embeddingsFlag = '${{ inputs.embeddings }}' === 'true' ? 'with embeddings' : 'graph-only';
const embeddingsFlag = process.env.EMBEDDINGS_INPUT === 'true' ? 'with embeddings' : 'graph-only';
const body = [
`### GitNexus: ${outcome}`,
``,
`PR #${prNum} was indexed ${embeddingsFlag}.`,
`[Index run](${runUrl})`,
'',
'${{ needs.index.result }}' === 'success'
indexSucceeded
? '⏳ Waiting for deploy to serve the fresh index…'
: '_Index run failed — the previous index (if any) continues to be served._',
].join('\n');

View file

@ -94,18 +94,38 @@ jobs:
- name: Dispatch gitnexus-index workflow
uses: actions/github-script@v7
env:
EMBEDDINGS: ${{ steps.parse.outputs.embeddings }}
PR_NUMBER: ${{ steps.parse.outputs.pr_number }}
PR_REF: ${{ steps.parse.outputs.pr_ref }}
with:
script: |
const prNumber = process.env.PR_NUMBER || '';
const prRef = process.env.PR_REF || '';
const embeddings = process.env.EMBEDDINGS || 'false';
if (!/^[0-9]+$/.test(prNumber)) {
core.setFailed(`Invalid PR number: ${prNumber}`);
return;
}
if (prRef !== `refs/pull/${prNumber}/head`) {
core.setFailed(`Invalid PR ref: ${prRef}`);
return;
}
if (!['true', 'false'].includes(embeddings)) {
core.setFailed(`Invalid embeddings value: ${embeddings}`);
return;
}
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'gitnexus-index.yml',
ref: 'main',
inputs: {
pr_number: '${{ steps.parse.outputs.pr_number }}',
pr_ref: '${{ steps.parse.outputs.pr_ref }}',
embeddings: '${{ steps.parse.outputs.embeddings }}',
pr_number: prNumber,
pr_ref: prRef,
embeddings,
force: 'false',
deploy_after: 'true',
},
});

View file

@ -40,13 +40,14 @@ jobs:
env:
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
CHART_VERSION="${REF_NAME#chart-}"
SEMVER_REGEX='^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'
if [[ ! "$CHART_VERSION" =~ $SEMVER_REGEX ]]; then
echo "Invalid chart version: $CHART_VERSION" >&2
SEMVER_REGEX='^[0-9]+[.][0-9]+[.][0-9]+(-[0-9A-Za-z.-]+)?([+][0-9A-Za-z.-]+)?$'
if [[ "$REF_NAME" != chart-* || ! "$CHART_VERSION" =~ $SEMVER_REGEX ]]; then
echo "::error::Chart tags must use the form chart-<semver>, for example chart-2.0.3"
exit 1
fi
echo "CHART_VERSION=${CHART_VERSION}" >> "$GITHUB_OUTPUT"
printf 'CHART_VERSION=%s\n' "$CHART_VERSION" >> "$GITHUB_OUTPUT"
# Log in to GitHub Container Registry
- name: Log in to GitHub Container Registry

View file

@ -18,10 +18,11 @@ jobs:
detect-unused-i18n-keys:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Find unused i18next keys
id: find-unused
@ -140,7 +141,7 @@ jobs:
gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
-f body="$COMMENT_BODY" \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}"
-H "Authorization: token $GITHUB_TOKEN"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -6,6 +6,9 @@ on:
repository_dispatch:
types: [locize/versionPublished]
permissions:
contents: read
jobs:
sync-translations:
name: Sync Translation Keys with Locize
@ -13,6 +16,8 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set Up Node.js
uses: actions/setup-node@v4
@ -25,9 +30,12 @@ jobs:
# Sync translations (Push missing keys & remove deleted ones)
- name: Sync Locize with Repository
if: ${{ github.event_name == 'push' }}
env:
LOCIZE_API_KEY: ${{ secrets.LOCIZE_API_KEY }}
LOCIZE_PROJECT_ID: ${{ secrets.LOCIZE_PROJECT_ID }}
run: |
cd client/src/locales
locize sync --api-key ${{ secrets.LOCIZE_API_KEY }} --project-id ${{ secrets.LOCIZE_PROJECT_ID }} --language en
locize sync --api-key "$LOCIZE_API_KEY" --project-id "$LOCIZE_PROJECT_ID" --language en
# When triggered by repository_dispatch, skip sync step.
- name: Skip sync step on non-push events
@ -39,12 +47,13 @@ jobs:
runs-on: ubuntu-latest
needs: sync-translations
permissions:
contents: write
pull-requests: write
contents: read
steps:
# 1. Check out the repository.
- name: Checkout Repository
uses: actions/checkout@v4
with:
persist-credentials: false
# 2. Download translation files from locize.
- name: Download Translations from locize
@ -53,12 +62,14 @@ jobs:
project-id: ${{ secrets.LOCIZE_PROJECT_ID }}
path: "client/src/locales"
# 3. Create a Pull Request using built-in functionality.
# 3. Create a Pull Request using a dedicated fine-grained PAT so this
# workflow does not depend on the global GITHUB_TOKEN PR-creation setting.
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
sign-commits: true
token: ${{ secrets.LOCIZE_PR_TOKEN }}
add-paths: |
client/src/locales/**
commit-message: "🌍 i18n: Update translation.json with latest translations"
base: main
branch: i18n/locize-translation-update
@ -69,4 +80,4 @@ jobs:
- 🎯 **Objective**: Update `translation.json` with the latest translations from locize.
- 🔍 **Details**: This PR is automatically generated upon receiving a versionPublished event with version "latest". It reflects the newest translations provided by locize.
- ✅ **Status**: Ready for review.
labels: "🌍 i18n"
labels: "🌍 i18n"

View file

@ -3,6 +3,10 @@ name: Docker Compose Build Latest Main Image Tag (Manual Dispatch)
on:
workflow_dispatch:
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
@ -19,11 +23,20 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
- name: Fetch tags and set the latest tag
run: |
git fetch --tags
echo "LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_ENV
set -euo pipefail
git fetch --tags --force
LATEST_TAG=$(git tag --list 'v[0-9]*' --sort=-v:refname | grep -E '^v[0-9]+[.][0-9]+[.][0-9]+$' | head -n 1)
if [ -z "$LATEST_TAG" ]; then
echo "::error::No stable v<semver> tag found"
exit 1
fi
printf 'LATEST_TAG=%s\n' "$LATEST_TAG" >> "$GITHUB_ENV"
# Set up QEMU
- name: Set up QEMU
@ -35,7 +48,7 @@ jobs:
# Log in to GitHub Container Registry
- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}

View file

@ -3,7 +3,11 @@ name: Docker Images Build on Tag
on:
push:
tags:
- '*'
- 'v*'
permissions:
contents: read
packages: write
jobs:
build:
@ -23,6 +27,25 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Validate release tag
id: release-tag
env:
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
TAG_REGEX='^v[0-9]+[.][0-9]+[.][0-9]+(-rc[0-9]+)?$'
STABLE_TAG_REGEX='^v[0-9]+[.][0-9]+[.][0-9]+$'
if [[ ! "$REF_NAME" =~ $TAG_REGEX ]]; then
echo "::error::Docker release tags must use v<semver> or v<semver>-rcN, for example v0.8.5 or v0.8.5-rc1"
exit 1
fi
printf 'image_tag=%s\n' "$REF_NAME" >> "$GITHUB_OUTPUT"
if [[ "$REF_NAME" =~ $STABLE_TAG_REGEX ]]; then
echo "is_stable=true" >> "$GITHUB_OUTPUT"
else
echo "is_stable=false" >> "$GITHUB_OUTPUT"
fi
# Set up QEMU
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@ -33,7 +56,7 @@ jobs:
# Log in to GitHub Container Registry
- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
@ -51,6 +74,26 @@ jobs:
run: |
cp .env.example .env
- name: Resolve image tags
id: image-tags
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
IMAGE_NAME: ${{ matrix.image_name }}
IMAGE_TAG: ${{ steps.release-tag.outputs.image_tag }}
IS_STABLE: ${{ steps.release-tag.outputs.is_stable }}
run: |
set -euo pipefail
{
echo 'tags<<EOF'
printf 'ghcr.io/%s/%s:%s\n' "$GITHUB_REPOSITORY_OWNER" "$IMAGE_NAME" "$IMAGE_TAG"
printf '%s/%s:%s\n' "$DOCKERHUB_USERNAME" "$IMAGE_NAME" "$IMAGE_TAG"
if [ "$IS_STABLE" = "true" ]; then
printf 'ghcr.io/%s/%s:latest\n' "$GITHUB_REPOSITORY_OWNER" "$IMAGE_NAME"
printf '%s/%s:latest\n' "$DOCKERHUB_USERNAME" "$IMAGE_NAME"
fi
echo 'EOF'
} >> "$GITHUB_OUTPUT"
# Build and push Docker images for each target
- name: Build and push Docker images
uses: docker/build-push-action@v5
@ -58,10 +101,6 @@ jobs:
context: .
file: ${{ matrix.file }}
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:${{ github.ref_name }}
ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:latest
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:${{ github.ref_name }}
${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
tags: ${{ steps.image-tags.outputs.tags }}
platforms: linux/amd64,linux/arm64
target: ${{ matrix.target }}

View file

@ -14,6 +14,7 @@ jobs:
detect-unused-packages:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
@ -272,7 +273,7 @@ jobs:
gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
-f body="$COMMENT_BODY" \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}"
-H "Authorization: token $GITHUB_TOKEN"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}