diff --git a/.github/workflows/a11y.yml b/.github/workflows/a11y.yml index a7cfd08169..344592cf3e 100644 --- a/.github/workflows/a11y.yml +++ b/.github/workflows/a11y.yml @@ -11,6 +11,10 @@ on: required: true default: 'false' +permissions: + contents: read + pull-requests: write + jobs: axe-linter: runs-on: ubuntu-latest diff --git a/.github/workflows/backend-review.yml b/.github/workflows/backend-review.yml index 03b7c135d2..1de3a68f84 100644 --- a/.github/workflows/backend-review.yml +++ b/.github/workflows/backend-review.yml @@ -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 }}' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a2131c4b98..9210b80a93 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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: | diff --git a/.github/workflows/cache-integration-tests.yml b/.github/workflows/cache-integration-tests.yml index caebbfc445..c8f10f388b 100644 --- a/.github/workflows/cache-integration-tests.yml +++ b/.github/workflows/cache-integration-tests.yml @@ -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 diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml index e3e3e445e4..881efa0ebe 100644 --- a/.github/workflows/client.yml +++ b/.github/workflows/client.yml @@ -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 diff --git a/.github/workflows/data-provider.yml b/.github/workflows/data-provider.yml index 9a514b0076..3a9db4d8e5 100644 --- a/.github/workflows/data-provider.yml +++ b/.github/workflows/data-provider.yml @@ -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 diff --git a/.github/workflows/data-schemas.yml b/.github/workflows/data-schemas.yml index 882dc4f4b6..977a6eb4c3 100644 --- a/.github/workflows/data-schemas.yml +++ b/.github/workflows/data-schemas.yml @@ -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 diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index a255932e3e..57875bc513 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -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 && \ diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5c143b4531..e4b73da617 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -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 }} diff --git a/.github/workflows/dev-branch-images.yml b/.github/workflows/dev-branch-images.yml index 9d40cd3fc4..464f6ce55a 100644 --- a/.github/workflows/dev-branch-images.yml +++ b/.github/workflows/dev-branch-images.yml @@ -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 }} diff --git a/.github/workflows/dev-images.yml b/.github/workflows/dev-images.yml index a6417556aa..a9fbef8929 100644 --- a/.github/workflows/dev-images.yml +++ b/.github/workflows/dev-images.yml @@ -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 }} diff --git a/.github/workflows/dev-staging-images.yml b/.github/workflows/dev-staging-images.yml index e63dc5f0af..7bb06e5298 100644 --- a/.github/workflows/dev-staging-images.yml +++ b/.github/workflows/dev-staging-images.yml @@ -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 }} - diff --git a/.github/workflows/frontend-review.yml b/.github/workflows/frontend-review.yml index 05b3f4154f..0021124192 100644 --- a/.github/workflows/frontend-review.yml +++ b/.github/workflows/frontend-review.yml @@ -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 }}' diff --git a/.github/workflows/generate_embeddings.yml b/.github/workflows/generate_embeddings.yml index c514f9c1d6..3c6f2717c3 100644 --- a/.github/workflows/generate_embeddings.yml +++ b/.github/workflows/generate_embeddings.yml @@ -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' \ No newline at end of file + docs-root-path: 'docs' diff --git a/.github/workflows/gitnexus-index.yml b/.github/workflows/gitnexus-index.yml index ac7de2973b..d3b8ca95e2 100644 --- a/.github/workflows/gitnexus-index.yml +++ b/.github/workflows/gitnexus-index.yml @@ -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//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'); diff --git a/.github/workflows/gitnexus-pr-command.yml b/.github/workflows/gitnexus-pr-command.yml index b299beb3b1..214a526897 100644 --- a/.github/workflows/gitnexus-pr-command.yml +++ b/.github/workflows/gitnexus-pr-command.yml @@ -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', }, }); diff --git a/.github/workflows/helmcharts.yml b/.github/workflows/helmcharts.yml index b76f088776..9e08c189a7 100644 --- a/.github/workflows/helmcharts.yml +++ b/.github/workflows/helmcharts.yml @@ -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-, 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 diff --git a/.github/workflows/i18n-unused-keys.yml b/.github/workflows/i18n-unused-keys.yml index 8f773532d3..6341c19d14 100644 --- a/.github/workflows/i18n-unused-keys.yml +++ b/.github/workflows/i18n-unused-keys.yml @@ -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 }} diff --git a/.github/workflows/locize-i18n-sync.yml b/.github/workflows/locize-i18n-sync.yml index f34648dfd9..a745414460 100644 --- a/.github/workflows/locize-i18n-sync.yml +++ b/.github/workflows/locize-i18n-sync.yml @@ -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" \ No newline at end of file + labels: "🌍 i18n" diff --git a/.github/workflows/main-image-workflow.yml b/.github/workflows/main-image-workflow.yml index 43c9d95753..348012de22 100644 --- a/.github/workflows/main-image-workflow.yml +++ b/.github/workflows/main-image-workflow.yml @@ -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 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 }} diff --git a/.github/workflows/tag-images.yml b/.github/workflows/tag-images.yml index e90f43978a..4477a89c13 100644 --- a/.github/workflows/tag-images.yml +++ b/.github/workflows/tag-images.yml @@ -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 or v-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<> "$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 }} diff --git a/.github/workflows/unused-packages.yml b/.github/workflows/unused-packages.yml index f67c1d23be..a957bed2e8 100644 --- a/.github/workflows/unused-packages.yml +++ b/.github/workflows/unused-packages.yml @@ -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 }}