From a6d4f075b208d2ddf70c8a915494fe4dd27ded63 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Wed, 1 Oct 2025 21:36:22 +0200 Subject: [PATCH 01/97] Delete .github/workflows/build-pr-temp.yml --- .github/workflows/build-pr-temp.yml | 71 ----------------------------- 1 file changed, 71 deletions(-) delete mode 100644 .github/workflows/build-pr-temp.yml diff --git a/.github/workflows/build-pr-temp.yml b/.github/workflows/build-pr-temp.yml deleted file mode 100644 index e5c45d59..00000000 --- a/.github/workflows/build-pr-temp.yml +++ /dev/null @@ -1,71 +0,0 @@ ---- -name: Build - -on: - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: ['20.17.0'] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - - name: Install pnpm - uses: pnpm/action-setup@v2 - with: - version: 8 - run_install: false - - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - name: Setup pnpm cache - uses: actions/cache@v4 - with: - path: | - ${{ env.STORE_PATH }} - ${{ github.workspace }}/.next/cache - key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }} - restore-keys: | - ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}- - - - name: Install dependencies - run: pnpm install - - - name: Build - run: pnpm run build - - - name: Get Commit SHA (short) - id: get_version - run: | - # Get the short 8-character commit SHA - VERSION=$(git rev-parse --short=8 HEAD) - echo "Commit SHA is $VERSION" - echo "tag=$VERSION" >> $GITHUB_OUTPUT - - - name: SonarQube Analysis (Pull Request) - uses: SonarSource/sonarqube-scan-action@v6 - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} - with: - args: > - -Dsonar.projectVersion=${{ steps.get_version.outputs.tag }} - -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} - -Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }} - -Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }} From 1127879b3020f13df83991a12f47a9f8b1b58b0d Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Wed, 1 Oct 2025 21:37:36 +0200 Subject: [PATCH 02/97] Add SonarQube host URL to properties file --- sonar-project.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sonar-project.properties b/sonar-project.properties index 5f07148d..04171928 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,3 +5,5 @@ sonar.projectDescription=An Open-Source Social Media Scheduler # Scan Performace sonar.javascript.node.maxspace=24576 sonar.exclusions=**/node_modules/**,**/dist/**,**/build/**,**/coverage/**,**/*.spec.ts,**/*.test.ts,*.png + +sonar.host.url=https://sonarqube.ennogelhaus.de/ From 5f9e8012f6caf9f8b57c1ffba850c4929782a129 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Wed, 1 Oct 2025 21:38:06 +0200 Subject: [PATCH 03/97] Change pull_request_target to pull_request event --- .github/workflows/build-pr.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 4cbfe2b2..22f07a53 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -2,15 +2,12 @@ name: Build on: - pull_request_target: + pull_request: jobs: build: runs-on: ubuntu-latest - environment: - name: build-pr - strategy: matrix: node-version: ['20.17.0'] @@ -20,7 +17,6 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 @@ -64,9 +60,6 @@ jobs: - name: SonarQube Analysis (Pull Request) uses: SonarSource/sonarqube-scan-action@v6 - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} with: args: > -Dsonar.projectVersion=${{ steps.get_version.outputs.tag }} From 1f14c8690d5cf4886ff8ad47bad4819eac7cd353 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Wed, 1 Oct 2025 21:46:10 +0200 Subject: [PATCH 04/97] Update build-pr.yml --- .github/workflows/build-pr.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 22f07a53..e5c45d59 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -60,6 +60,9 @@ jobs: - name: SonarQube Analysis (Pull Request) uses: SonarSource/sonarqube-scan-action@v6 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} with: args: > -Dsonar.projectVersion=${{ steps.get_version.outputs.tag }} From 6d7ed26e3c494db9ffe786b3e4a63d9e24a3f205 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Wed, 1 Oct 2025 21:49:10 +0200 Subject: [PATCH 05/97] Update build-pr.yml --- .github/workflows/build-pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index e5c45d59..70fa4373 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -61,8 +61,8 @@ jobs: - name: SonarQube Analysis (Pull Request) uses: SonarSource/sonarqube-scan-action@v6 env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + SONAR_TOKEN: ${{ vars.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }} with: args: > -Dsonar.projectVersion=${{ steps.get_version.outputs.tag }} From 0b3f559cf5f44aa4f94acddd6eb94b5f4dc7e931 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Wed, 1 Oct 2025 21:55:36 +0200 Subject: [PATCH 06/97] Add environment name to build PR workflow --- .github/workflows/build-pr.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 70fa4373..1dbd85b3 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -13,6 +13,9 @@ jobs: node-version: ['20.17.0'] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + environment: + name: build-pr + steps: - uses: actions/checkout@v4 with: From 39f617a54aa8a6b2e690824e62481d41f0275863 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Wed, 1 Oct 2025 22:03:44 +0200 Subject: [PATCH 07/97] Update sonar-project.properties --- sonar-project.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sonar-project.properties b/sonar-project.properties index 04171928..884703b3 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -7,3 +7,6 @@ sonar.javascript.node.maxspace=24576 sonar.exclusions=**/node_modules/**,**/dist/**,**/build/**,**/coverage/**,**/*.spec.ts,**/*.test.ts,*.png sonar.host.url=https://sonarqube.ennogelhaus.de/ + +# This Token only has read permissions to analysis creation to Postiu App +sonar.login=sqp_a62bce8d2d33a7b751ae194353f8174b5563053c From 74afd9149c86732f4211ad29d84f245dc8625ccf Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Wed, 1 Oct 2025 22:04:37 +0200 Subject: [PATCH 08/97] Remove SONAR_TOKEN and SONAR_HOST_URL from workflow Removed environment variables for SonarQube analysis. --- .github/workflows/build-pr.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 1dbd85b3..513fe803 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -63,9 +63,6 @@ jobs: - name: SonarQube Analysis (Pull Request) uses: SonarSource/sonarqube-scan-action@v6 - env: - SONAR_TOKEN: ${{ vars.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }} with: args: > -Dsonar.projectVersion=${{ steps.get_version.outputs.tag }} From 313830806627da9d4df541cc97945512f5175cec Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Wed, 1 Oct 2025 22:10:31 +0200 Subject: [PATCH 09/97] Update SonarQube authentication method Replaced sonar.login with sonar.token for authentication. --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 884703b3..d61ed3a8 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -9,4 +9,4 @@ sonar.exclusions=**/node_modules/**,**/dist/**,**/build/**,**/coverage/**,**/*.s sonar.host.url=https://sonarqube.ennogelhaus.de/ # This Token only has read permissions to analysis creation to Postiu App -sonar.login=sqp_a62bce8d2d33a7b751ae194353f8174b5563053c +sonar.token=sqp_a62bce8d2d33a7b751ae194353f8174b5563053c From 7becf450eded6495d0089436c572db2683349ab9 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Thu, 2 Oct 2025 07:49:25 +0200 Subject: [PATCH 10/97] sonar-project.properties --- sonar-project.properties | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sonar-project.properties b/sonar-project.properties index d61ed3a8..5f07148d 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,8 +5,3 @@ sonar.projectDescription=An Open-Source Social Media Scheduler # Scan Performace sonar.javascript.node.maxspace=24576 sonar.exclusions=**/node_modules/**,**/dist/**,**/build/**,**/coverage/**,**/*.spec.ts,**/*.test.ts,*.png - -sonar.host.url=https://sonarqube.ennogelhaus.de/ - -# This Token only has read permissions to analysis creation to Postiu App -sonar.token=sqp_a62bce8d2d33a7b751ae194353f8174b5563053c From 477366164c5f9430d44f354bcd0312d11a9159fa Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 14:24:01 +0200 Subject: [PATCH 11/97] Remove legacy CI/CD configurations and implement new workflows for building and analyzing Node.js applications with SonarQube --- .github/workflows/{build.yaml => build} | 0 .github/workflows/{build-pr.yml => build-pr} | 0 Jenkins/Build.Jenkinsfile | 104 ++++++++++++++++++ Jenkins/BuildPR.Jenkinsfile | 108 +++++++++++++++++++ Jenkinsfile | 70 ------------ 5 files changed, 212 insertions(+), 70 deletions(-) rename .github/workflows/{build.yaml => build} (100%) rename .github/workflows/{build-pr.yml => build-pr} (100%) create mode 100644 Jenkins/Build.Jenkinsfile create mode 100644 Jenkins/BuildPR.Jenkinsfile delete mode 100644 Jenkinsfile diff --git a/.github/workflows/build.yaml b/.github/workflows/build similarity index 100% rename from .github/workflows/build.yaml rename to .github/workflows/build diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr similarity index 100% rename from .github/workflows/build-pr.yml rename to .github/workflows/build-pr diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile new file mode 100644 index 00000000..75228a90 --- /dev/null +++ b/Jenkins/Build.Jenkinsfile @@ -0,0 +1,104 @@ +// Declarative Pipeline for building Node.js application and running SonarQube analysis. +pipeline { + // Defines the execution environment. Replace 'linux-agent' with your specific agent label. + agent { + label 'linux-agent' + } + + // Configure options, primarily to ensure full Git history is fetched for SonarQube and versioning. + options { + // Skip the default checkout to manage it explicitly and ensure fetch-depth: 0. + skipDefaultCheckout() + } + + stages { + // Stage 1: Checkout the code with full history (fetch-depth: 0) + stage('Source Checkout') { + steps { + script { + // This performs a deep clone (fetch-depth: 0) + // NOTE: You must replace 'YOUR_GIT_CREDENTIALS_ID' with the actual Jenkins credential ID + // that has access to your repository. If using Anonymous checkout, remove the credentialsId line. + checkout([ + $class: 'GitSCM', + branches: [[name: 'HEAD']], + extensions: [ + [$class: 'WipeWorkspace'], + [$class: 'CleanBeforeCheckout'], + [$class: 'CloneOption', depth: 0, noTags: false, reference: '', shallow: false] + ], + userRemoteConfigs: [ + [url: env.GIT_URL ?: ''] // Replace env.GIT_URL if needed + ] + ]) + } + } + } + + // Stage 2: Setup Node.js v20 and install pnpm + stage('Setup Environment') { + steps { + sh ''' + # Install Node.js v20 (closest matching the specified version '20.17.0') + # This uses Nodesource to ensure a specific major version is available. + curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + sudo apt-get install -y nodejs + + echo "Node.js version: \$(node -v)" + + # Install pnpm globally (version 8) + npm install -g pnpm@8 + echo "pnpm version: \$(pnpm -v)" + ''' + + // Skipping the complex pnpm cache setup as it relies on specific GitHub Actions features. + // In Jenkins, artifact caching is typically handled differently (e.g., using dedicated workspace cache plugins). + } + } + + // Stage 3: Install dependencies and build the application + stage('Install and Build') { + steps { + sh 'pnpm install' + sh 'pnpm run build' + } + } + + // Stage 4: Retrieve secrets from Vault and run SonarQube analysis + stage('SonarQube Analysis') { + steps { + script { + // 1. Get the short 8-character commit SHA for project versioning + def commitShaShort = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim() + echo "Commit SHA (short) is: ${commitShaShort}" + + // 2. Retrieve secrets from HashiCorp Vault using the dedicated plugin binding. + // The secret values will be available as the environment variables SONAR_TOKEN and SONAR_HOST_URL + // only within this 'withCredentials' block. + withCredentials([ + // The $class: 'VaultSecretCredentialsBinding' requires the Jenkins HashiCorp Vault Plugin + [$class: 'VaultSecretCredentialsBinding', + vaultSecrets: [ + // Map key 'SONAR_TOKEN' from Vault path 'postiz/data/ci/sonar' to Jenkins environment variable 'SONAR_TOKEN' + [$class: 'VaultSecret', secretPath: 'postiz/data/ci/sonar', secretKey: 'SONAR_TOKEN', envVar: 'SONAR_TOKEN'], + // Map key 'SONAR_HOST_URL' from Vault path 'postiz/data/ci/sonar' to Jenkins environment variable 'SONAR_HOST_URL' + [$class: 'VaultSecret', secretPath: 'postiz/data/ci/sonar', secretKey: 'SONAR_HOST_URL', envVar: 'SONAR_HOST_URL'] + ]] + ]) { + // 3. Execute sonar-scanner CLI + // NOTE: sonar-scanner must be installed and available on the agent's PATH, + // or configured via Jenkins' Global Tool Configuration. + sh """ + echo "Starting SonarQube Analysis for project version: ${commitShaShort}" + sonar-scanner \\ + -Dsonar.projectVersion=${commitShaShort} \\ + -Dsonar.token=\${SONAR_TOKEN} \\ + -Dsonar.host.url=\${SONAR_HOST_URL} + # Add other analysis properties here if needed (e.g., -Dsonar.projectKey=...) + """ + } + } + } + } + } +} diff --git a/Jenkins/BuildPR.Jenkinsfile b/Jenkins/BuildPR.Jenkinsfile new file mode 100644 index 00000000..12be0457 --- /dev/null +++ b/Jenkins/BuildPR.Jenkinsfile @@ -0,0 +1,108 @@ +// Declarative Pipeline for building Node.js application and running SonarQube analysis for a Pull Request. +pipeline { + // Defines the execution environment. Replace 'linux-agent' with your specific agent label. + agent { + label 'linux-agent' + } + + // Configure options, primarily to ensure full Git history is fetched for SonarQube and versioning. + options { + // Skip the default checkout to manage it explicitly and ensure fetch-depth: 0. + skipDefaultCheckout() + } + + // Environment variables that hold PR details (set automatically by Jenkins SCM plugins like Git/GitHub Branch Source) + environment { + // These variables are provided by Jenkins (e.g., in a Multibranch Pipeline setup) + // CHANGE_ID corresponds to ${{ github.event.pull_request.number }} + // CHANGE_BRANCH corresponds to ${{ github.event.pull_request.head.ref }} + // CHANGE_TARGET corresponds to ${{ github.event.pull_request.base.ref }} + PR_KEY = env.CHANGE_ID + PR_BRANCH = env.CHANGE_BRANCH + PR_BASE = env.CHANGE_TARGET + } + + stages { + // Stage 1: Checkout the code with full history (fetch-depth: 0) + stage('Source Checkout') { + steps { + script { + // This performs a deep clone (fetch-depth: 0) + // NOTE: You must replace 'YOUR_GIT_CREDENTIALS_ID' with the actual Jenkins credential ID + // that has access to your repository. + checkout([ + $class: 'GitSCM', + branches: [[name: 'HEAD']], + extensions: [ + [$class: 'WipeWorkspace'], + [$class: 'CleanBeforeCheckout'], + [$class: 'CloneOption', depth: 0, noTags: false, reference: '', shallow: false] + ], + userRemoteConfigs: [ + [url: env.GIT_URL ?: ''] + ] + ]) + } + } + } + + // Stage 2: Setup Node.js v20 and install pnpm + stage('Setup Environment') { + steps { + sh ''' + # Install Node.js v20 (closest matching the specified version '20.17.0') + curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + sudo apt-get install -y nodejs + + echo "Node.js version: \$(node -v)" + + # Install pnpm globally (version 8) + npm install -g pnpm@8 + echo "pnpm version: \$(pnpm -v)" + ''' + } + } + + // Stage 3: Install dependencies and build the application + stage('Install and Build') { + steps { + sh 'pnpm install' + sh 'pnpm run build' + } + } + + // Stage 4: Retrieve secrets from Vault and run SonarQube PR analysis + stage('SonarQube Pull Request Analysis') { + steps { + script { + // 1. Get the short 8-character commit SHA for project versioning + def commitShaShort = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim() + echo "Commit SHA (short) is: ${commitShaShort}" + + // 2. Retrieve secrets from HashiCorp Vault using the dedicated plugin binding. + withCredentials([ + // Requires the Jenkins HashiCorp Vault Plugin + [$class: 'VaultSecretCredentialsBinding', + vaultSecrets: [ + [$class: 'VaultSecret', secretPath: 'postiz/data/ci/sonar', secretKey: 'SONAR_TOKEN', envVar: 'SONAR_TOKEN'], + [$class: 'VaultSecret', secretPath: 'postiz/data/ci/sonar', secretKey: 'SONAR_HOST_URL', envVar: 'SONAR_HOST_URL'] + ]] + ]) { + // 3. Execute sonar-scanner CLI with Pull Request parameters + sh """ + echo "Starting SonarQube Pull Request Analysis for PR #${PR_KEY}" + sonar-scanner \\ + -Dsonar.projectVersion=${commitShaShort} \\ + -Dsonar.token=\${SONAR_TOKEN} \\ + -Dsonar.host.url=\${SONAR_HOST_URL} \\ + -Dsonar.pullrequest.key=${PR_KEY} \\ + -Dsonar.pullrequest.branch=${PR_BRANCH} \\ + -Dsonar.pullrequest.base=${PR_BASE} + # Add other analysis properties here if needed (e.g., -Dsonar.projectKey=...) + """ + } + } + } + } + } +} diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index e7a614d0..00000000 --- a/Jenkinsfile +++ /dev/null @@ -1,70 +0,0 @@ -pipeline { - agent any - - environment { - NODE_VERSION = '20.17.0' - PR_NUMBER = "${env.CHANGE_ID}" // PR number comes from webhook payload - IMAGE_TAG="ghcr.io/gitroomhq/postiz-app-pr:${env.CHANGE_ID}" - } - - stages { - stage('Checkout Repository') { - steps { - checkout scm - } - } - - stage('Check Node.js and npm') { - steps { - script { - sh "node -v" - sh "npm -v" - } - } - } - - stage('Install Dependencies') { - steps { - sh 'npm ci' - } - } - - stage('Build Project') { - steps { - sh 'npm run build' - } - } - - stage('Build and Push Docker Image') { - when { - expression { return env.CHANGE_ID != null } // Only run if it's a PR - } - steps { - withCredentials([string(credentialsId: 'gh-pat', variable: 'GITHUB_PASS')]) { - // Docker login step - sh ''' - echo "$GITHUB_PASS" | docker login ghcr.io -u "egelhaus" --password-stdin - ''' - // Build Docker image - sh ''' - docker build -f Dockerfile.dev -t $IMAGE_TAG . - ''' - // Push Docker image to GitHub Container Registry - sh ''' - docker push $IMAGE_TAG - ''' - } - } - } - } - post { - success { - echo 'Build completed successfully!' - - } - failure { - echo 'Build failed!' - - } - } -} From ebbb64b9d89c5760ee3d9e623f28f32659bd6544 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 14:28:05 +0200 Subject: [PATCH 12/97] Enhance Jenkins pipeline comments and update Git checkout configuration for clarity and accuracy --- Jenkins/Build.Jenkinsfile | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile index 75228a90..923bb392 100644 --- a/Jenkins/Build.Jenkinsfile +++ b/Jenkins/Build.Jenkinsfile @@ -1,4 +1,4 @@ -// Declarative Pipeline for building Node.js application and running SonarQube analysis. +// Declarative Pipeline for building Node.js application and running SonarQube analysis triggered by a push event. pipeline { // Defines the execution environment. Replace 'linux-agent' with your specific agent label. agent { @@ -17,8 +17,8 @@ pipeline { steps { script { // This performs a deep clone (fetch-depth: 0) - // NOTE: You must replace 'YOUR_GIT_CREDENTIALS_ID' with the actual Jenkins credential ID - // that has access to your repository. If using Anonymous checkout, remove the credentialsId line. + // NOTE: Replace 'YOUR_GIT_CREDENTIALS_ID' with the actual Jenkins credential ID + // that has access to your repository. checkout([ $class: 'GitSCM', branches: [[name: 'HEAD']], @@ -28,7 +28,7 @@ pipeline { [$class: 'CloneOption', depth: 0, noTags: false, reference: '', shallow: false] ], userRemoteConfigs: [ - [url: env.GIT_URL ?: ''] // Replace env.GIT_URL if needed + [credentialsId: 'YOUR_GIT_CREDENTIALS_ID', url: env.GIT_URL ?: ''] // Replace env.GIT_URL if needed ] ]) } @@ -42,6 +42,7 @@ pipeline { # Install Node.js v20 (closest matching the specified version '20.17.0') # This uses Nodesource to ensure a specific major version is available. curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + sudo apt-get update sudo apt-get install -y nodejs echo "Node.js version: \$(node -v)" @@ -50,9 +51,6 @@ pipeline { npm install -g pnpm@8 echo "pnpm version: \$(pnpm -v)" ''' - - // Skipping the complex pnpm cache setup as it relies on specific GitHub Actions features. - // In Jenkins, artifact caching is typically handled differently (e.g., using dedicated workspace cache plugins). } } @@ -73,10 +71,8 @@ pipeline { echo "Commit SHA (short) is: ${commitShaShort}" // 2. Retrieve secrets from HashiCorp Vault using the dedicated plugin binding. - // The secret values will be available as the environment variables SONAR_TOKEN and SONAR_HOST_URL - // only within this 'withCredentials' block. withCredentials([ - // The $class: 'VaultSecretCredentialsBinding' requires the Jenkins HashiCorp Vault Plugin + // Requires the Jenkins HashiCorp Vault Plugin [$class: 'VaultSecretCredentialsBinding', vaultSecrets: [ // Map key 'SONAR_TOKEN' from Vault path 'postiz/data/ci/sonar' to Jenkins environment variable 'SONAR_TOKEN' @@ -86,8 +82,6 @@ pipeline { ]] ]) { // 3. Execute sonar-scanner CLI - // NOTE: sonar-scanner must be installed and available on the agent's PATH, - // or configured via Jenkins' Global Tool Configuration. sh """ echo "Starting SonarQube Analysis for project version: ${commitShaShort}" sonar-scanner \\ From 762384d45ee572f71898f3d4f1161ce25dab0e7c Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 14:32:30 +0200 Subject: [PATCH 13/97] Refactor Jenkins pipeline to use 'agent any' for improved flexibility in execution environment --- Jenkins/Build.Jenkinsfile | 4 +--- Jenkins/BuildPR.Jenkinsfile | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile index 923bb392..b4924c91 100644 --- a/Jenkins/Build.Jenkinsfile +++ b/Jenkins/Build.Jenkinsfile @@ -1,9 +1,7 @@ // Declarative Pipeline for building Node.js application and running SonarQube analysis triggered by a push event. pipeline { // Defines the execution environment. Replace 'linux-agent' with your specific agent label. - agent { - label 'linux-agent' - } + agent any // Configure options, primarily to ensure full Git history is fetched for SonarQube and versioning. options { diff --git a/Jenkins/BuildPR.Jenkinsfile b/Jenkins/BuildPR.Jenkinsfile index 12be0457..49a6b868 100644 --- a/Jenkins/BuildPR.Jenkinsfile +++ b/Jenkins/BuildPR.Jenkinsfile @@ -1,10 +1,7 @@ // Declarative Pipeline for building Node.js application and running SonarQube analysis for a Pull Request. pipeline { // Defines the execution environment. Replace 'linux-agent' with your specific agent label. - agent { - label 'linux-agent' - } - + agent any // Configure options, primarily to ensure full Git history is fetched for SonarQube and versioning. options { // Skip the default checkout to manage it explicitly and ensure fetch-depth: 0. From 6e19591998eed889a0acf73876531ad18f0f6685 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 14:35:15 +0200 Subject: [PATCH 14/97] Remove unnecessary options configuration from Jenkins pipeline for cleaner setup --- Jenkins/Build.Jenkinsfile | 6 ------ Jenkins/BuildPR.Jenkinsfile | 5 ----- 2 files changed, 11 deletions(-) diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile index b4924c91..34d1d059 100644 --- a/Jenkins/Build.Jenkinsfile +++ b/Jenkins/Build.Jenkinsfile @@ -3,12 +3,6 @@ pipeline { // Defines the execution environment. Replace 'linux-agent' with your specific agent label. agent any - // Configure options, primarily to ensure full Git history is fetched for SonarQube and versioning. - options { - // Skip the default checkout to manage it explicitly and ensure fetch-depth: 0. - skipDefaultCheckout() - } - stages { // Stage 1: Checkout the code with full history (fetch-depth: 0) stage('Source Checkout') { diff --git a/Jenkins/BuildPR.Jenkinsfile b/Jenkins/BuildPR.Jenkinsfile index 49a6b868..3840b726 100644 --- a/Jenkins/BuildPR.Jenkinsfile +++ b/Jenkins/BuildPR.Jenkinsfile @@ -2,11 +2,6 @@ pipeline { // Defines the execution environment. Replace 'linux-agent' with your specific agent label. agent any - // Configure options, primarily to ensure full Git history is fetched for SonarQube and versioning. - options { - // Skip the default checkout to manage it explicitly and ensure fetch-depth: 0. - skipDefaultCheckout() - } // Environment variables that hold PR details (set automatically by Jenkins SCM plugins like Git/GitHub Branch Source) environment { From 789c4b4c7eb7ab050a243ebceca23e971d5c8015 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 14:41:42 +0200 Subject: [PATCH 15/97] Refactor Jenkins pipeline to improve Git checkout process and ensure necessary dependencies are installed --- Jenkins/Build.Jenkinsfile | 48 ++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile index 34d1d059..9c73c399 100644 --- a/Jenkins/Build.Jenkinsfile +++ b/Jenkins/Build.Jenkinsfile @@ -1,29 +1,36 @@ // Declarative Pipeline for building Node.js application and running SonarQube analysis triggered by a push event. pipeline { - // Defines the execution environment. Replace 'linux-agent' with your specific agent label. - agent any + // Defines the execution environment. Using 'agent any' to ensure an agent is available. + agent any stages { // Stage 1: Checkout the code with full history (fetch-depth: 0) stage('Source Checkout') { steps { - script { - // This performs a deep clone (fetch-depth: 0) - // NOTE: Replace 'YOUR_GIT_CREDENTIALS_ID' with the actual Jenkins credential ID - // that has access to your repository. - checkout([ - $class: 'GitSCM', - branches: [[name: 'HEAD']], - extensions: [ - [$class: 'WipeWorkspace'], - [$class: 'CleanBeforeCheckout'], - [$class: 'CloneOption', depth: 0, noTags: false, reference: '', shallow: false] - ], - userRemoteConfigs: [ - [credentialsId: 'YOUR_GIT_CREDENTIALS_ID', url: env.GIT_URL ?: ''] // Replace env.GIT_URL if needed - ] - ]) - } + // STEP 1: Install Git and other necessary system dependencies first. + // This command ensures the 'git' command is available on the agent's PATH, + // which is the root cause of the previous 'No such file or directory' error. + sh ''' + echo "Ensuring git, curl, and build tools are installed..." + sudo apt-get update + sudo apt-get install -y git curl build-essential + ''' + + // STEP 2: Perform the deep clone checkout using the installed Git. + // NOTE: Replace 'YOUR_GIT_CREDENTIALS_ID' with the actual Jenkins credential ID + // that has access to your repository. + checkout([ + $class: 'GitSCM', + branches: [[name: 'HEAD']], + extensions: [ + [$class: 'WipeWorkspace'], + [$class: 'CleanBeforeCheckout'], + [$class: 'CloneOption', depth: 0, noTags: false, reference: '', shallow: false] + ], + userRemoteConfigs: [ + [url: env.GIT_URL ?: ''] // Replace env.GIT_URL if needed + ] + ]) } } @@ -32,7 +39,6 @@ pipeline { steps { sh ''' # Install Node.js v20 (closest matching the specified version '20.17.0') - # This uses Nodesource to ensure a specific major version is available. curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt-get update sudo apt-get install -y nodejs @@ -69,7 +75,7 @@ pipeline { vaultSecrets: [ // Map key 'SONAR_TOKEN' from Vault path 'postiz/data/ci/sonar' to Jenkins environment variable 'SONAR_TOKEN' [$class: 'VaultSecret', secretPath: 'postiz/data/ci/sonar', secretKey: 'SONAR_TOKEN', envVar: 'SONAR_TOKEN'], - // Map key 'SONAR_HOST_URL' from Vault path 'postiz/data/ci/sonar' to Jenkins environment variable 'SONAR_HOST_URL' + // Map key 'SONAR_HOST_URL' from Vault path 'postiz/data/ci/sonar', to Jenkins environment variable 'SONAR_HOST_URL'] [$class: 'VaultSecret', secretPath: 'postiz/data/ci/sonar', secretKey: 'SONAR_HOST_URL', envVar: 'SONAR_HOST_URL'] ]] ]) { From febfb1e7d62a7cd8838672bc03c6f5f3543e9296 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 14:44:05 +0200 Subject: [PATCH 16/97] Refactor Jenkins pipeline to remove unnecessary Git installation steps and enhance apt command execution with retry logic --- Jenkins/Build.Jenkinsfile | 47 +++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile index 9c73c399..f90a7480 100644 --- a/Jenkins/Build.Jenkinsfile +++ b/Jenkins/Build.Jenkinsfile @@ -7,16 +7,10 @@ pipeline { // Stage 1: Checkout the code with full history (fetch-depth: 0) stage('Source Checkout') { steps { - // STEP 1: Install Git and other necessary system dependencies first. - // This command ensures the 'git' command is available on the agent's PATH, - // which is the root cause of the previous 'No such file or directory' error. - sh ''' - echo "Ensuring git, curl, and build tools are installed..." - sudo apt-get update - sudo apt-get install -y git curl build-essential - ''' + // Since 'git' is confirmed to be installed on agent2 (per log 'git version 2.43.0'), + // we remove the apt-get installation command which was failing due to a lock. - // STEP 2: Perform the deep clone checkout using the installed Git. + // STEP: Perform the deep clone checkout using the existing Git executable. // NOTE: Replace 'YOUR_GIT_CREDENTIALS_ID' with the actual Jenkins credential ID // that has access to your repository. checkout([ @@ -28,7 +22,7 @@ pipeline { [$class: 'CloneOption', depth: 0, noTags: false, reference: '', shallow: false] ], userRemoteConfigs: [ - [url: env.GIT_URL ?: ''] // Replace env.GIT_URL if needed + [credentialsId: 'YOUR_GIT_CREDENTIALS_ID', url: env.GIT_URL ?: ''] // Replace env.GIT_URL if needed ] ]) } @@ -38,14 +32,39 @@ pipeline { stage('Setup Environment') { steps { sh ''' - # Install Node.js v20 (closest matching the specified version '20.17.0') + ATTEMPTS=0 + MAX_ATTEMPTS=5 + + # Function to robustly run apt commands with retries in case of lock conflict + install_with_retry() { + CMD=\$1 + ATTEMPTS=0 + while [ \$ATTEMPTS -lt \$MAX_ATTEMPTS ]; do + if sudo apt-get update && \$CMD; then + return 0 # Success + else + ATTEMPTS=\$((ATTEMPTS + 1)) + if [ \$ATTEMPTS -lt \$MAX_ATTEMPTS ]; then + echo "Apt lock detected or command failed. Retrying in 5 seconds (Attempt \$ATTEMPTS of \$MAX_ATTEMPTS)..." + sleep 5 + fi + fi + done + echo "Failed to execute apt command after \$MAX_ATTEMPTS attempts." + return 1 # Failure + } + + # 1. Install curl (required for NodeSource script) + install_with_retry "sudo apt-get install -y curl" || exit 1 + + # 2. Install Node.js v20 (closest matching the specified version '20.17.0') + # This step uses curl (installed above) and needs its own apt-get execution. curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - - sudo apt-get update - sudo apt-get install -y nodejs + install_with_retry "sudo apt-get install -y nodejs" || exit 1 echo "Node.js version: \$(node -v)" - # Install pnpm globally (version 8) + # 3. Install pnpm globally (version 8) npm install -g pnpm@8 echo "pnpm version: \$(pnpm -v)" ''' From b71f8a7acbef1c6255c43bcf8469304fb480efc7 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 14:44:51 +0200 Subject: [PATCH 17/97] Remove credentialsId from Git checkout configuration for improved security and flexibility --- Jenkins/Build.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile index f90a7480..220acfee 100644 --- a/Jenkins/Build.Jenkinsfile +++ b/Jenkins/Build.Jenkinsfile @@ -22,7 +22,7 @@ pipeline { [$class: 'CloneOption', depth: 0, noTags: false, reference: '', shallow: false] ], userRemoteConfigs: [ - [credentialsId: 'YOUR_GIT_CREDENTIALS_ID', url: env.GIT_URL ?: ''] // Replace env.GIT_URL if needed + [url: env.GIT_URL ?: ''] // Replace env.GIT_URL if needed ] ]) } From b2f5fb17d4624649175579cecfa8c7befdc57eca Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 14:46:26 +0200 Subject: [PATCH 18/97] Refactor Jenkins pipeline to remove source checkout stage and enhance apt command execution with update step --- Jenkins/Build.Jenkinsfile | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile index 220acfee..44bd71f3 100644 --- a/Jenkins/Build.Jenkinsfile +++ b/Jenkins/Build.Jenkinsfile @@ -4,30 +4,6 @@ pipeline { agent any stages { - // Stage 1: Checkout the code with full history (fetch-depth: 0) - stage('Source Checkout') { - steps { - // Since 'git' is confirmed to be installed on agent2 (per log 'git version 2.43.0'), - // we remove the apt-get installation command which was failing due to a lock. - - // STEP: Perform the deep clone checkout using the existing Git executable. - // NOTE: Replace 'YOUR_GIT_CREDENTIALS_ID' with the actual Jenkins credential ID - // that has access to your repository. - checkout([ - $class: 'GitSCM', - branches: [[name: 'HEAD']], - extensions: [ - [$class: 'WipeWorkspace'], - [$class: 'CleanBeforeCheckout'], - [$class: 'CloneOption', depth: 0, noTags: false, reference: '', shallow: false] - ], - userRemoteConfigs: [ - [url: env.GIT_URL ?: ''] // Replace env.GIT_URL if needed - ] - ]) - } - } - // Stage 2: Setup Node.js v20 and install pnpm stage('Setup Environment') { steps { @@ -40,6 +16,7 @@ pipeline { CMD=\$1 ATTEMPTS=0 while [ \$ATTEMPTS -lt \$MAX_ATTEMPTS ]; do + # Run update first, then the command if sudo apt-get update && \$CMD; then return 0 # Success else From c124aa648b2ebda7371014d3ed88e095737e91fc Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 15:08:46 +0200 Subject: [PATCH 19/97] Refactor Jenkins pipeline to streamline Node.js setup and enhance SonarQube analysis stage --- Jenkins/Build.Jenkinsfile | 68 +++++++++++++-------------------------- 1 file changed, 23 insertions(+), 45 deletions(-) diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile index 44bd71f3..cdea710c 100644 --- a/Jenkins/Build.Jenkinsfile +++ b/Jenkins/Build.Jenkinsfile @@ -4,44 +4,28 @@ pipeline { agent any stages { + // Stage 1: Checkout the code (Relies on the initial SCM checkout done by Jenkins) + stage('Source Checkout') { + steps { + echo "Workspace already populated by the initial SCM checkout. Proceeding." + // No explicit checkout needed, as the initial checkout is successful. + } + } + // Stage 2: Setup Node.js v20 and install pnpm stage('Setup Environment') { steps { + // Simplified environment setup based on previous successful execution. sh ''' - ATTEMPTS=0 - MAX_ATTEMPTS=5 - - # Function to robustly run apt commands with retries in case of lock conflict - install_with_retry() { - CMD=\$1 - ATTEMPTS=0 - while [ \$ATTEMPTS -lt \$MAX_ATTEMPTS ]; do - # Run update first, then the command - if sudo apt-get update && \$CMD; then - return 0 # Success - else - ATTEMPTS=\$((ATTEMPTS + 1)) - if [ \$ATTEMPTS -lt \$MAX_ATTEMPTS ]; then - echo "Apt lock detected or command failed. Retrying in 5 seconds (Attempt \$ATTEMPTS of \$MAX_ATTEMPTS)..." - sleep 5 - fi - fi - done - echo "Failed to execute apt command after \$MAX_ATTEMPTS attempts." - return 1 # Failure - } - - # 1. Install curl (required for NodeSource script) - install_with_retry "sudo apt-get install -y curl" || exit 1 - - # 2. Install Node.js v20 (closest matching the specified version '20.17.0') - # This step uses curl (installed above) and needs its own apt-get execution. + # 1. Install Node.js v20 (closest matching the specified version '20.17.0') + # We assume 'curl' is available and installation proceeds without lock conflicts now. + echo "Setting up Node.js v20..." curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - - install_with_retry "sudo apt-get install -y nodejs" || exit 1 + sudo apt-get install -y nodejs echo "Node.js version: \$(node -v)" - # 3. Install pnpm globally (version 8) + # 2. Install pnpm globally (version 8) npm install -g pnpm@8 echo "pnpm version: \$(pnpm -v)" ''' @@ -56,7 +40,7 @@ pipeline { } } - // Stage 4: Retrieve secrets from Vault and run SonarQube analysis + // Stage 4: Run SonarQube analysis using the Jenkins plugin's environment. stage('SonarQube Analysis') { steps { script { @@ -64,25 +48,19 @@ pipeline { def commitShaShort = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim() echo "Commit SHA (short) is: ${commitShaShort}" - // 2. Retrieve secrets from HashiCorp Vault using the dedicated plugin binding. - withCredentials([ - // Requires the Jenkins HashiCorp Vault Plugin - [$class: 'VaultSecretCredentialsBinding', - vaultSecrets: [ - // Map key 'SONAR_TOKEN' from Vault path 'postiz/data/ci/sonar' to Jenkins environment variable 'SONAR_TOKEN' - [$class: 'VaultSecret', secretPath: 'postiz/data/ci/sonar', secretKey: 'SONAR_TOKEN', envVar: 'SONAR_TOKEN'], - // Map key 'SONAR_HOST_URL' from Vault path 'postiz/data/ci/sonar', to Jenkins environment variable 'SONAR_HOST_URL'] - [$class: 'VaultSecret', secretPath: 'postiz/data/ci/sonar', secretKey: 'SONAR_HOST_URL', envVar: 'SONAR_HOST_URL'] - ]] - ]) { + // 2. Use withSonarQubeEnv to set up the environment and PATH for sonar-scanner. + // IMPORTANT: Replace 'YourSonarServerName' with the name you configured + // for your SonarQube server instance in Jenkins (Manage Jenkins -> Configure System). + withSonarQubeEnv(installationName: 'YourSonarServerName') { // 3. Execute sonar-scanner CLI sh """ echo "Starting SonarQube Analysis for project version: ${commitShaShort}" sonar-scanner \\ -Dsonar.projectVersion=${commitShaShort} \\ - -Dsonar.token=\${SONAR_TOKEN} \\ - -Dsonar.host.url=\${SONAR_HOST_URL} - # Add other analysis properties here if needed (e.g., -Dsonar.projectKey=...) + -Dsonar.sources=. \\ + -Dsonar.host.url=\${SONAR_HOST_URL} \\ + -Dsonar.token=\${SONAR_TOKEN} + # Add -Dsonar.projectKey=YourKeyHere if not defined in sonar-project.properties """ } } From 77b91d4d7c2d5045549b9a4381005e5fa2ccf942 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 15:16:34 +0200 Subject: [PATCH 20/97] Update SonarQube environment configuration to use 'SonarQube-Server' and change token variable to 'SONAR_AUTH_TOKEN' --- Jenkins/Build.Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile index cdea710c..0328b5db 100644 --- a/Jenkins/Build.Jenkinsfile +++ b/Jenkins/Build.Jenkinsfile @@ -51,7 +51,7 @@ pipeline { // 2. Use withSonarQubeEnv to set up the environment and PATH for sonar-scanner. // IMPORTANT: Replace 'YourSonarServerName' with the name you configured // for your SonarQube server instance in Jenkins (Manage Jenkins -> Configure System). - withSonarQubeEnv(installationName: 'YourSonarServerName') { + withSonarQubeEnv(installationName: 'SonarQube-Server') { // 3. Execute sonar-scanner CLI sh """ echo "Starting SonarQube Analysis for project version: ${commitShaShort}" @@ -59,7 +59,7 @@ pipeline { -Dsonar.projectVersion=${commitShaShort} \\ -Dsonar.sources=. \\ -Dsonar.host.url=\${SONAR_HOST_URL} \\ - -Dsonar.token=\${SONAR_TOKEN} + -Dsonar.token=\${SONAR_AUTH_TOKEN} # Add -Dsonar.projectKey=YourKeyHere if not defined in sonar-project.properties """ } From 29384eebab3c1abd8073cdb941368a854c4154c1 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 15:23:21 +0200 Subject: [PATCH 21/97] Refactor Jenkins pipeline to enhance SonarQube analysis stage with manual scanner installation and improved environment variable handling --- Jenkins/Build.Jenkinsfile | 64 ++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile index 0328b5db..0e6f79f7 100644 --- a/Jenkins/Build.Jenkinsfile +++ b/Jenkins/Build.Jenkinsfile @@ -3,29 +3,42 @@ pipeline { // Defines the execution environment. Using 'agent any' to ensure an agent is available. agent any + // Define environment variables for fixed values and credentials needed later + environment { + // Sonar Host URL taken from your previous successful log injection + SONAR_HOST_URL = 'https://sonarqube.ennogelhaus.de/' + // IMPORTANT: Replace this with the ID of your Jenkins Secret Text credential containing the Sonar Token + SONAR_TOKEN_CREDENTIAL_ID = 'YOUR_SECRET_TOKEN_ID' + // This will hold the path to the downloaded scanner directory later + SCANNER_HOME = '' + } + stages { // Stage 1: Checkout the code (Relies on the initial SCM checkout done by Jenkins) stage('Source Checkout') { steps { echo "Workspace already populated by the initial SCM checkout. Proceeding." - // No explicit checkout needed, as the initial checkout is successful. } } // Stage 2: Setup Node.js v20 and install pnpm stage('Setup Environment') { steps { - // Simplified environment setup based on previous successful execution. + // Ensure required utilities are installed (curl, unzip, which is needed for scanner extraction) + sh ''' + echo "Ensuring required utilities are installed (curl, unzip)..." + sudo apt-get update + sudo apt-get install -y curl unzip + ''' + + // Install Node.js v20 and pnpm sh ''' - # 1. Install Node.js v20 (closest matching the specified version '20.17.0') - # We assume 'curl' is available and installation proceeds without lock conflicts now. echo "Setting up Node.js v20..." curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt-get install -y nodejs - echo "Node.js version: \$(node -v)" - # 2. Install pnpm globally (version 8) + echo "Installing pnpm globally..." npm install -g pnpm@8 echo "pnpm version: \$(pnpm -v)" ''' @@ -40,27 +53,44 @@ pipeline { } } - // Stage 4: Run SonarQube analysis using the Jenkins plugin's environment. + // Stage 4: Manual SonarQube Analysis (Download Scanner + Execute) stage('SonarQube Analysis') { steps { script { // 1. Get the short 8-character commit SHA for project versioning def commitShaShort = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim() echo "Commit SHA (short) is: ${commitShaShort}" - - // 2. Use withSonarQubeEnv to set up the environment and PATH for sonar-scanner. - // IMPORTANT: Replace 'YourSonarServerName' with the name you configured - // for your SonarQube server instance in Jenkins (Manage Jenkins -> Configure System). - withSonarQubeEnv(installationName: 'SonarQube-Server') { - // 3. Execute sonar-scanner CLI + + // --- Manual Scanner Installation --- + // Download the latest scanner CLI package and extract it into the workspace + sh """ + echo "Downloading Sonar Scanner CLI..." + # Using a stable, public download link for the scanner CLI + curl -sS -o sonar-scanner.zip \ + "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747.zip" + + unzip -q sonar-scanner.zip -d . + + # Find the extracted directory name (e.g., sonar-scanner-4.7.0.2747) + def scannerDir = sh(returnStdout: true, script: 'find . -maxdepth 1 -type d -name "sonar-scanner*" | head -n 1').trim() + + echo "Scanner extracted to: \${scannerDir}" + env.SCANNER_HOME = "\${scannerDir}" + """ + + // 2. Use withCredentials to inject the token securely + // The token is temporarily available as SONAR_TOKEN_VAR inside this block. + withCredentials([string(credentialsId: env.SONAR_TOKEN_CREDENTIAL_ID, variable: 'SONAR_TOKEN_VAR')]) { + // 3. Execute sonar-scanner CLI using the direct path sh """ echo "Starting SonarQube Analysis for project version: ${commitShaShort}" - sonar-scanner \\ + + \${SCANNER_HOME}/bin/sonar-scanner \\ -Dsonar.projectVersion=${commitShaShort} \\ -Dsonar.sources=. \\ - -Dsonar.host.url=\${SONAR_HOST_URL} \\ - -Dsonar.token=\${SONAR_AUTH_TOKEN} - # Add -Dsonar.projectKey=YourKeyHere if not defined in sonar-project.properties + -Dsonar.host.url=${env.SONAR_HOST_URL} \\ + -Dsonar.token=\${env.SONAR_AUTH_TOKEN} + # Replace 'YOUR_PROJECT_KEY_HERE' with the unique key for your project in SonarQube. """ } } From 022cc3c32f8002883a75aac47acf7fc439700cb6 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 15:27:01 +0200 Subject: [PATCH 22/97] Refactor Jenkins pipeline to streamline environment setup by removing unnecessary environment variables and enhancing SonarQube analysis with manual scanner installation --- Jenkins/Build.Jenkinsfile | 88 +++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 50 deletions(-) diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile index 0e6f79f7..ff637371 100644 --- a/Jenkins/Build.Jenkinsfile +++ b/Jenkins/Build.Jenkinsfile @@ -3,16 +3,6 @@ pipeline { // Defines the execution environment. Using 'agent any' to ensure an agent is available. agent any - // Define environment variables for fixed values and credentials needed later - environment { - // Sonar Host URL taken from your previous successful log injection - SONAR_HOST_URL = 'https://sonarqube.ennogelhaus.de/' - // IMPORTANT: Replace this with the ID of your Jenkins Secret Text credential containing the Sonar Token - SONAR_TOKEN_CREDENTIAL_ID = 'YOUR_SECRET_TOKEN_ID' - // This will hold the path to the downloaded scanner directory later - SCANNER_HOME = '' - } - stages { // Stage 1: Checkout the code (Relies on the initial SCM checkout done by Jenkins) stage('Source Checkout') { @@ -21,27 +11,45 @@ pipeline { } } - // Stage 2: Setup Node.js v20 and install pnpm - stage('Setup Environment') { + // Stage 2: Setup Node.js v20, install pnpm, and MANUALLY install Sonar Scanner + stage('Setup Environment and Tools') { steps { - // Ensure required utilities are installed (curl, unzip, which is needed for scanner extraction) sh ''' - echo "Ensuring required utilities are installed (curl, unzip)..." + echo "Ensuring required utilities and Node.js are installed..." sudo apt-get update - sudo apt-get install -y curl unzip - ''' - - // Install Node.js v20 and pnpm - sh ''' - echo "Setting up Node.js v20..." + sudo apt-get install -y curl unzip nodejs + + # 1. Install Node.js v20 + # We are using the package manager approach which we fixed earlier curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt-get install -y nodejs + echo "Node.js version: \$(node -v)" - echo "Installing pnpm globally..." + # 2. Install pnpm globally (version 8) npm install -g pnpm@8 echo "pnpm version: \$(pnpm -v)" ''' + + // --- MANUALLY INSTALL THE SONAR SCANNER CLI --- + // We do this to work around the path injection failure of the SonarQube plugin. + script { + sh """ + echo "Manually downloading and installing Sonar Scanner CLI..." + # Download the stable scanner CLI package + curl -sS -o sonar-scanner.zip \ + "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747.zip" + + unzip -q sonar-scanner.zip -d . + + # Find the extracted directory name + def scannerDir = sh(returnStdout: true, script: 'find . -maxdepth 1 -type d -name "sonar-scanner*" | head -n 1').trim() + + # Add the scanner's bin directory to the execution PATH for subsequent steps + echo "Adding ${scannerDir}/bin to PATH" + env.PATH = "\$PATH:\$PWD/${scannerDir}/bin" + """ + } } } @@ -53,44 +61,24 @@ pipeline { } } - // Stage 4: Manual SonarQube Analysis (Download Scanner + Execute) + // Stage 4: Run SonarQube analysis using the plugin's variables but the manually added executable path. stage('SonarQube Analysis') { steps { script { // 1. Get the short 8-character commit SHA for project versioning def commitShaShort = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim() echo "Commit SHA (short) is: ${commitShaShort}" - - // --- Manual Scanner Installation --- - // Download the latest scanner CLI package and extract it into the workspace - sh """ - echo "Downloading Sonar Scanner CLI..." - # Using a stable, public download link for the scanner CLI - curl -sS -o sonar-scanner.zip \ - "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747.zip" - - unzip -q sonar-scanner.zip -d . - - # Find the extracted directory name (e.g., sonar-scanner-4.7.0.2747) - def scannerDir = sh(returnStdout: true, script: 'find . -maxdepth 1 -type d -name "sonar-scanner*" | head -n 1').trim() - - echo "Scanner extracted to: \${scannerDir}" - env.SCANNER_HOME = "\${scannerDir}" - """ - - // 2. Use withCredentials to inject the token securely - // The token is temporarily available as SONAR_TOKEN_VAR inside this block. - withCredentials([string(credentialsId: env.SONAR_TOKEN_CREDENTIAL_ID, variable: 'SONAR_TOKEN_VAR')]) { - // 3. Execute sonar-scanner CLI using the direct path + + // 2. Use withSonarQubeEnv to set up the secure variables (which worked last time) + // IMPORTANT: Replace 'YOUR_SONAR_INSTALLATION_NAME' + withSonarQubeEnv(installationName: 'SonarQube-Server') { + // 3. Execute sonar-scanner CLI (which is now in PATH) sh """ echo "Starting SonarQube Analysis for project version: ${commitShaShort}" - - \${SCANNER_HOME}/bin/sonar-scanner \\ + # SONAR_HOST_URL and SONAR_TOKEN are injected by withSonarQubeEnv + sonar-scanner \\ -Dsonar.projectVersion=${commitShaShort} \\ - -Dsonar.sources=. \\ - -Dsonar.host.url=${env.SONAR_HOST_URL} \\ - -Dsonar.token=\${env.SONAR_AUTH_TOKEN} - # Replace 'YOUR_PROJECT_KEY_HERE' with the unique key for your project in SonarQube. + -Dsonar.sources=. """ } } From 22dac4413c75cd6e13f77713e27dc834069bd69a Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 15:29:26 +0200 Subject: [PATCH 23/97] Refactor Jenkins pipeline to enhance SonarQube analysis by setting the global SONAR_SCANNER_PATH and improving scanner installation process --- Jenkins/Build.Jenkinsfile | 43 ++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile index ff637371..ebc3c0cf 100644 --- a/Jenkins/Build.Jenkinsfile +++ b/Jenkins/Build.Jenkinsfile @@ -3,6 +3,11 @@ pipeline { // Defines the execution environment. Using 'agent any' to ensure an agent is available. agent any + // Global environment variable to store the path to the manually installed scanner. + environment { + SONAR_SCANNER_PATH = '' + } + stages { // Stage 1: Checkout the code (Relies on the initial SCM checkout done by Jenkins) stage('Source Checkout') { @@ -20,10 +25,8 @@ pipeline { sudo apt-get install -y curl unzip nodejs # 1. Install Node.js v20 - # We are using the package manager approach which we fixed earlier curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt-get install -y nodejs - echo "Node.js version: \$(node -v)" # 2. Install pnpm globally (version 8) @@ -31,11 +34,11 @@ pipeline { echo "pnpm version: \$(pnpm -v)" ''' - // --- MANUALLY INSTALL THE SONAR SCANNER CLI --- - // We do this to work around the path injection failure of the SonarQube plugin. + // --- MANUALLY INSTALL THE SONAR SCANNER CLI (FIXED GROOVY SCOPE) --- script { sh """ echo "Manually downloading and installing Sonar Scanner CLI..." + # Download the stable scanner CLI package curl -sS -o sonar-scanner.zip \ "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747.zip" @@ -43,12 +46,20 @@ pipeline { unzip -q sonar-scanner.zip -d . # Find the extracted directory name - def scannerDir = sh(returnStdout: true, script: 'find . -maxdepth 1 -type d -name "sonar-scanner*" | head -n 1').trim() + def scannerDir = \$(find . -maxdepth 1 -type d -name "sonar-scanner*" | head -n 1) - # Add the scanner's bin directory to the execution PATH for subsequent steps - echo "Adding ${scannerDir}/bin to PATH" - env.PATH = "\$PATH:\$PWD/${scannerDir}/bin" + echo "Scanner extracted to: \${scannerDir}" + + # Set the global environment variable (SONAR_SCANNER_PATH) + // This allows us to run the scanner by full path in the next stage + echo "SONAR_SCANNER_PATH=\${scannerDir}/bin" > .scanner_path.env """ + // Load the environment variable set by the shell script + // This is the correct way to pass shell variables back to Groovy/Jenkins environment + def scannerPath = readProperties file: '.scanner_path.env' + env.SONAR_SCANNER_PATH = "${env.WORKSPACE}/${scannerPath.SONAR_SCANNER_PATH}" + + echo "Global Sonar Path set to: ${env.SONAR_SCANNER_PATH}" } } } @@ -61,7 +72,7 @@ pipeline { } } - // Stage 4: Run SonarQube analysis using the plugin's variables but the manually added executable path. + // Stage 4: Run SonarQube analysis using the plugin's variables and the manual path. stage('SonarQube Analysis') { steps { script { @@ -69,16 +80,20 @@ pipeline { def commitShaShort = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim() echo "Commit SHA (short) is: ${commitShaShort}" - // 2. Use withSonarQubeEnv to set up the secure variables (which worked last time) - // IMPORTANT: Replace 'YOUR_SONAR_INSTALLATION_NAME' + // 2. Use withSonarQubeEnv to set up the secure variables (HOST and TOKEN) + // The 'SonarQube-Server' name is used as per your previous log. withSonarQubeEnv(installationName: 'SonarQube-Server') { - // 3. Execute sonar-scanner CLI (which is now in PATH) + // 3. Execute the scanner using the manually determined full path. + // We rely on the sonar-project.properties file for the project key. sh """ echo "Starting SonarQube Analysis for project version: ${commitShaShort}" - # SONAR_HOST_URL and SONAR_TOKEN are injected by withSonarQubeEnv - sonar-scanner \\ + + \${SONAR_SCANNER_PATH}/sonar-scanner \\ -Dsonar.projectVersion=${commitShaShort} \\ -Dsonar.sources=. + + # SONAR_HOST_URL and SONAR_TOKEN are automatically passed as environment variables + # by the withSonarQubeEnv block. """ } } From 6ef1543dcd9c8de9b314b956aab57c9bafc1a906 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 15:56:16 +0200 Subject: [PATCH 24/97] Refactor Jenkins pipeline to remove global environment variable for Sonar Scanner and streamline installation process within the analysis stage --- Jenkins/Build.Jenkinsfile | 71 +++++++++++++---------------- Jenkins/BuildPR.Jenkinsfile | 89 ++++++++++++++++++------------------- 2 files changed, 76 insertions(+), 84 deletions(-) diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile index ebc3c0cf..886ade0a 100644 --- a/Jenkins/Build.Jenkinsfile +++ b/Jenkins/Build.Jenkinsfile @@ -3,10 +3,7 @@ pipeline { // Defines the execution environment. Using 'agent any' to ensure an agent is available. agent any - // Global environment variable to store the path to the manually installed scanner. - environment { - SONAR_SCANNER_PATH = '' - } + // Global environment block removed to prevent Groovy scoping issues with manual path calculation. stages { // Stage 1: Checkout the code (Relies on the initial SCM checkout done by Jenkins) @@ -16,7 +13,7 @@ pipeline { } } - // Stage 2: Setup Node.js v20, install pnpm, and MANUALLY install Sonar Scanner + // Stage 2: Setup Node.js v20 and install pnpm stage('Setup Environment and Tools') { steps { sh ''' @@ -33,34 +30,6 @@ pipeline { npm install -g pnpm@8 echo "pnpm version: \$(pnpm -v)" ''' - - // --- MANUALLY INSTALL THE SONAR SCANNER CLI (FIXED GROOVY SCOPE) --- - script { - sh """ - echo "Manually downloading and installing Sonar Scanner CLI..." - - # Download the stable scanner CLI package - curl -sS -o sonar-scanner.zip \ - "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747.zip" - - unzip -q sonar-scanner.zip -d . - - # Find the extracted directory name - def scannerDir = \$(find . -maxdepth 1 -type d -name "sonar-scanner*" | head -n 1) - - echo "Scanner extracted to: \${scannerDir}" - - # Set the global environment variable (SONAR_SCANNER_PATH) - // This allows us to run the scanner by full path in the next stage - echo "SONAR_SCANNER_PATH=\${scannerDir}/bin" > .scanner_path.env - """ - // Load the environment variable set by the shell script - // This is the correct way to pass shell variables back to Groovy/Jenkins environment - def scannerPath = readProperties file: '.scanner_path.env' - env.SONAR_SCANNER_PATH = "${env.WORKSPACE}/${scannerPath.SONAR_SCANNER_PATH}" - - echo "Global Sonar Path set to: ${env.SONAR_SCANNER_PATH}" - } } } @@ -72,7 +41,7 @@ pipeline { } } - // Stage 4: Run SonarQube analysis using the plugin's variables and the manual path. + // Stage 4: Run SonarQube analysis: Install scanner, get version, and execute. stage('SonarQube Analysis') { steps { script { @@ -80,15 +49,39 @@ pipeline { def commitShaShort = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim() echo "Commit SHA (short) is: ${commitShaShort}" - // 2. Use withSonarQubeEnv to set up the secure variables (HOST and TOKEN) - // The 'SonarQube-Server' name is used as per your previous log. + // --- 2. MANUALLY INSTALL THE SONAR SCANNER CLI LOCALLY IN THIS STAGE --- + sh """ + echo "Manually downloading and installing Sonar Scanner CLI..." + + # Download the stable scanner CLI package + curl -sS -o sonar-scanner.zip \ + "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747.zip" + + # Added -o flag to force overwrite and prevent interactive prompt failure + unzip -o -q sonar-scanner.zip -d . + """ + + // 3. Find the extracted directory name and capture the full absolute bin path in Groovy + // This is defined locally and used directly, avoiding environment variable issues. + def scannerBinPath = sh( + returnStdout: true, + script: ''' + SCANNER_DIR=$(find . -maxdepth 1 -type d -name "sonar-scanner*" | head -n 1) + # Get the full absolute path to the executable file + echo \$(pwd)/\${SCANNER_DIR}/bin/sonar-scanner + ''' + ).trim() + + echo "Scanner executable path captured: ${scannerBinPath}" + + // 4. Use withSonarQubeEnv to set up the secure variables (HOST and TOKEN) withSonarQubeEnv(installationName: 'SonarQube-Server') { - // 3. Execute the scanner using the manually determined full path. - // We rely on the sonar-project.properties file for the project key. + // 5. Execute the scanner using the Groovy variable directly. sh """ echo "Starting SonarQube Analysis for project version: ${commitShaShort}" - \${SONAR_SCANNER_PATH}/sonar-scanner \\ + # Execute the full, absolute path captured in the Groovy variable. + '${scannerBinPath}' \\ -Dsonar.projectVersion=${commitShaShort} \\ -Dsonar.sources=. diff --git a/Jenkins/BuildPR.Jenkinsfile b/Jenkins/BuildPR.Jenkinsfile index 3840b726..49237fc8 100644 --- a/Jenkins/BuildPR.Jenkinsfile +++ b/Jenkins/BuildPR.Jenkinsfile @@ -1,54 +1,38 @@ // Declarative Pipeline for building Node.js application and running SonarQube analysis for a Pull Request. pipeline { - // Defines the execution environment. Replace 'linux-agent' with your specific agent label. + // Defines the execution environment. Using 'agent any' to ensure an agent is available. agent any - - // Environment variables that hold PR details (set automatically by Jenkins SCM plugins like Git/GitHub Branch Source) + + // Environment variables that hold PR details, provided by Jenkins Multibranch setup. environment { - // These variables are provided by Jenkins (e.g., in a Multibranch Pipeline setup) - // CHANGE_ID corresponds to ${{ github.event.pull_request.number }} - // CHANGE_BRANCH corresponds to ${{ github.event.pull_request.head.ref }} - // CHANGE_TARGET corresponds to ${{ github.event.pull_request.base.ref }} + // These variables are typically provided by Jenkins (e.g., in a Multibranch Pipeline setup) PR_KEY = env.CHANGE_ID PR_BRANCH = env.CHANGE_BRANCH PR_BASE = env.CHANGE_TARGET } stages { - // Stage 1: Checkout the code with full history (fetch-depth: 0) + // Stage 1: Checkout the code (Relies on the initial SCM checkout done by Jenkins) stage('Source Checkout') { steps { - script { - // This performs a deep clone (fetch-depth: 0) - // NOTE: You must replace 'YOUR_GIT_CREDENTIALS_ID' with the actual Jenkins credential ID - // that has access to your repository. - checkout([ - $class: 'GitSCM', - branches: [[name: 'HEAD']], - extensions: [ - [$class: 'WipeWorkspace'], - [$class: 'CleanBeforeCheckout'], - [$class: 'CloneOption', depth: 0, noTags: false, reference: '', shallow: false] - ], - userRemoteConfigs: [ - [url: env.GIT_URL ?: ''] - ] - ]) - } + echo "Workspace already populated by the initial SCM checkout. Proceeding." } } - // Stage 2: Setup Node.js v20 and install pnpm - stage('Setup Environment') { + // Stage 2: Setup Node.js v20, install pnpm, and install required tools (curl, unzip) + stage('Setup Environment and Tools') { steps { sh ''' - # Install Node.js v20 (closest matching the specified version '20.17.0') + echo "Ensuring required utilities and Node.js are installed..." + sudo apt-get update + sudo apt-get install -y curl unzip nodejs + + # 1. Install Node.js v20 (closest matching the specified version '20.17.0') curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt-get install -y nodejs - echo "Node.js version: \$(node -v)" - # Install pnpm globally (version 8) + # 2. Install pnpm globally (version 8) npm install -g pnpm@8 echo "pnpm version: \$(pnpm -v)" ''' @@ -63,7 +47,7 @@ pipeline { } } - // Stage 4: Retrieve secrets from Vault and run SonarQube PR analysis + // Stage 4: Run SonarQube PR analysis: Install scanner locally, get version, and execute. stage('SonarQube Pull Request Analysis') { steps { script { @@ -71,26 +55,41 @@ pipeline { def commitShaShort = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim() echo "Commit SHA (short) is: ${commitShaShort}" - // 2. Retrieve secrets from HashiCorp Vault using the dedicated plugin binding. - withCredentials([ - // Requires the Jenkins HashiCorp Vault Plugin - [$class: 'VaultSecretCredentialsBinding', - vaultSecrets: [ - [$class: 'VaultSecret', secretPath: 'postiz/data/ci/sonar', secretKey: 'SONAR_TOKEN', envVar: 'SONAR_TOKEN'], - [$class: 'VaultSecret', secretPath: 'postiz/data/ci/sonar', secretKey: 'SONAR_HOST_URL', envVar: 'SONAR_HOST_URL'] - ]] - ]) { - // 3. Execute sonar-scanner CLI with Pull Request parameters + // --- 2. MANUALLY INSTALL THE SONAR SCANNER CLI LOCALLY --- + sh """ + echo "Manually downloading and installing Sonar Scanner CLI..." + curl -sS -o sonar-scanner.zip \ + "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747.zip" + unzip -o -q sonar-scanner.zip -d . + """ + + // 3. Find the extracted directory name and capture the full absolute executable path. + def scannerBinPath = sh( + returnStdout: true, + script: ''' + SCANNER_DIR=$(find . -maxdepth 1 -type d -name "sonar-scanner*" | head -n 1) + # Get the full absolute path to the executable file + echo \$(pwd)/\${SCANNER_DIR}/bin/sonar-scanner + ''' + ).trim() + + echo "Scanner executable path captured: ${scannerBinPath}" + + // 4. Use withSonarQubeEnv to set up the secure variables (HOST and TOKEN) + withSonarQubeEnv(installationName: 'SonarQube-Server') { + // 5. Execute the scanner using the Groovy variable directly with PR parameters. sh """ echo "Starting SonarQube Pull Request Analysis for PR #${PR_KEY}" - sonar-scanner \\ + + '${scannerBinPath}' \\ -Dsonar.projectVersion=${commitShaShort} \\ - -Dsonar.token=\${SONAR_TOKEN} \\ - -Dsonar.host.url=\${SONAR_HOST_URL} \\ + -Dsonar.sources=. \\ -Dsonar.pullrequest.key=${PR_KEY} \\ -Dsonar.pullrequest.branch=${PR_BRANCH} \\ -Dsonar.pullrequest.base=${PR_BASE} - # Add other analysis properties here if needed (e.g., -Dsonar.projectKey=...) + + # SONAR_HOST_URL and SONAR_TOKEN are automatically passed as environment variables + # by the withSonarQubeEnv block. """ } } From 826f8031747d185eeaa4dff729cc6441c5a95a31 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Fri, 3 Oct 2025 16:04:18 +0200 Subject: [PATCH 25/97] Fix quoting of environment variables in Jenkins pipeline to resolve compilation errors --- Jenkins/BuildPR.Jenkinsfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Jenkins/BuildPR.Jenkinsfile b/Jenkins/BuildPR.Jenkinsfile index 49237fc8..6e279b0e 100644 --- a/Jenkins/BuildPR.Jenkinsfile +++ b/Jenkins/BuildPR.Jenkinsfile @@ -5,10 +5,11 @@ pipeline { // Environment variables that hold PR details, provided by Jenkins Multibranch setup. environment { - // These variables are typically provided by Jenkins (e.g., in a Multibranch Pipeline setup) - PR_KEY = env.CHANGE_ID - PR_BRANCH = env.CHANGE_BRANCH - PR_BASE = env.CHANGE_TARGET + // FIX: Environment variables must be quoted or wrapped in a function call. + // We quote the 'env.CHANGE_ID' reference to fix the compilation error. + PR_KEY = "${env.CHANGE_ID}" + PR_BRANCH = "${env.CHANGE_BRANCH}" + PR_BASE = "${env.CHANGE_TARGET}" } stages { From df47e9f5edad1e2836f094c92fe46da75eda5a20 Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Sat, 4 Oct 2025 00:07:31 +0200 Subject: [PATCH 26/97] Fix Sentry sample rates for production environment --- .../src/sentry/initialize.sentry.client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts b/libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts index 549c250c..ade3cc82 100644 --- a/libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts +++ b/libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts @@ -16,8 +16,8 @@ export const initializeSentryClient = (environment: string, dsn: string) => autoInject: false, }), ], - replaysSessionSampleRate: environment === 'development' ? 1.0 : 0.5, + replaysSessionSampleRate: environment == 1.0, replaysOnErrorSampleRate: 1.0, - profilesSampleRate: environment === 'development' ? 1.0 : 0.45, + profilesSampleRate: environment === 'development' ? 1.0 : 0.75, }); From 0128eb77b01fb8ea51519817075de4154e9ee76b Mon Sep 17 00:00:00 2001 From: Enno Gelhaus Date: Sat, 4 Oct 2025 00:07:47 +0200 Subject: [PATCH 27/97] Set replaysSessionSampleRate to a fixed value of 1.0 --- .../src/sentry/initialize.sentry.client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts b/libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts index ade3cc82..db2010ac 100644 --- a/libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts +++ b/libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts @@ -16,7 +16,7 @@ export const initializeSentryClient = (environment: string, dsn: string) => autoInject: false, }), ], - replaysSessionSampleRate: environment == 1.0, + replaysSessionSampleRate: 1.0, replaysOnErrorSampleRate: 1.0, profilesSampleRate: environment === 'development' ? 1.0 : 0.75, From 73ba9acf8c537e7fffb55ad76888597e6c36f81a Mon Sep 17 00:00:00 2001 From: Nevo David Date: Tue, 7 Oct 2025 11:07:21 +0700 Subject: [PATCH 28/97] feat: agent --- .vscode/settings.json | 28 + .../src/api/routes/copilot.controller.ts | 77 +- apps/backend/src/app.module.ts | 7 +- .../src/services/auth/auth.middleware.ts | 2 +- .../src/app/(app)/(site)/agents/page.tsx | 11 + apps/frontend/src/app/global.scss | 21 + .../src/components/agents/agent.input.tsx | 120 + .../src/components/agents/agent.textarea.tsx | 77 + apps/frontend/src/components/agents/agent.tsx | 342 + .../src/components/layout/top.menu.tsx | 18 + .../src/components/media/media.component.tsx | 6 +- .../new-launch/providers/vk/vk.provider.tsx | 1 + .../helpers/src/utils/use.wait.for.class.tsx | 43 + .../src/chat/agent.tool.interface.ts | 4 + .../nestjs-libraries/src/chat/chat.module.ts | 13 + .../src/chat/load.tools.service.ts | 83 + .../src/chat/mastra.service.ts | 21 + .../nestjs-libraries/src/chat/mastra.store.ts | 5 + .../chat/tools/integration.schedule.post.ts | 143 + .../chat/tools/integration.trigger.tool.ts | 151 + .../chat/tools/integration.validation.tool.ts | 100 + .../src/chat/tools/tool.list.ts | 9 + .../posts/providers-settings/farcaster.dto.ts | 17 + .../src/integrations/integration.manager.ts | 18 + .../integrations/social/bluesky.provider.ts | 3 + .../integrations/social/dev.to.provider.ts | 10 +- .../integrations/social/discord.provider.ts | 8 + .../integrations/social/dribbble.provider.ts | 7 + .../integrations/social/facebook.provider.ts | 14 +- .../integrations/social/farcaster.provider.ts | 12 +- .../integrations/social/hashnode.provider.ts | 6 + .../integrations/social/instagram.provider.ts | 37 +- .../social/instagram.standalone.provider.ts | 17 +- .../src/integrations/social/lemmy.provider.ts | 15 + .../integrations/social/linkedin.provider.ts | 4 +- .../integrations/social/listmonk.provider.ts | 8 + .../integrations/social/mastodon.provider.ts | 3 + .../integrations/social/medium.provider.ts | 7 + .../src/integrations/social/nostr.provider.ts | 4 + .../integrations/social/pinterest.provider.ts | 7 + .../integrations/social/reddit.provider.ts | 26 + .../src/integrations/social/slack.provider.ts | 12 + .../social/social.integrations.interface.ts | 2 + .../integrations/social/telegram.provider.ts | 9 +- .../integrations/social/threads.provider.ts | 21 +- .../integrations/social/tiktok.provider.ts | 5 +- .../src/integrations/social/vk.provider.ts | 3 + .../integrations/social/wordpress.provider.ts | 9 + .../src/integrations/social/x.provider.ts | 6 + .../integrations/social/youtube.provider.ts | 4 + .../src/integrations/tool.decorator.ts | 17 + .../nestjs-libraries/src/mcp/mcp.types.ts | 4 +- package.json | 17 +- pnpm-lock.yaml | 6680 ++++++++++++----- 54 files changed, 6261 insertions(+), 2033 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 apps/frontend/src/app/(app)/(site)/agents/page.tsx create mode 100644 apps/frontend/src/components/agents/agent.input.tsx create mode 100644 apps/frontend/src/components/agents/agent.textarea.tsx create mode 100644 apps/frontend/src/components/agents/agent.tsx create mode 100644 libraries/helpers/src/utils/use.wait.for.class.tsx create mode 100644 libraries/nestjs-libraries/src/chat/agent.tool.interface.ts create mode 100644 libraries/nestjs-libraries/src/chat/chat.module.ts create mode 100644 libraries/nestjs-libraries/src/chat/load.tools.service.ts create mode 100644 libraries/nestjs-libraries/src/chat/mastra.service.ts create mode 100644 libraries/nestjs-libraries/src/chat/mastra.store.ts create mode 100644 libraries/nestjs-libraries/src/chat/tools/integration.schedule.post.ts create mode 100644 libraries/nestjs-libraries/src/chat/tools/integration.trigger.tool.ts create mode 100644 libraries/nestjs-libraries/src/chat/tools/integration.validation.tool.ts create mode 100644 libraries/nestjs-libraries/src/chat/tools/tool.list.ts create mode 100644 libraries/nestjs-libraries/src/dtos/posts/providers-settings/farcaster.dto.ts create mode 100644 libraries/nestjs-libraries/src/integrations/tool.decorator.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..0c522537 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,28 @@ +{ + "explorer.compactFolders": false, + "explorer.fileNesting.enabled": false, + "editor.lineHeight": 27, + "editor.folding": true, + "editor.foldingImportsByDefault": true, + "editor.fontLigatures": false, + "gitlens.codeLens.enabled": false, + "gitlens.currentLine.enabled": false, + "typescript.updateImportsOnFileMove.enabled": "always", + "typescript.suggest.autoImports": true, + "javascript.suggest.autoImports": true, + "editor.codeActionsOnSave": { + "source.addMissingImports": "explicit" + }, + "workbench.activityBar.orientation": "vertical", + "workbench.colorCustomizations": { + "activityBar.background": "#393c3e", + "editor.background": "#2b2b2b", + "editorGutter.background": "#2b2b2b", + "editorIndentGuide.background1": "#373737", + "editorIndentGuide.activeBackground1": "#587973", + "editor.lineHighlightBackground": "#323232", + "editor.selectionHighlightBackground": "#40404080", + "editor.selectionBackground": "#214283", + "editor.hoverHighlightBackground": "#40404080", + } +} diff --git a/apps/backend/src/api/routes/copilot.controller.ts b/apps/backend/src/api/routes/copilot.controller.ts index 581343fb..5e23e6fd 100644 --- a/apps/backend/src/api/routes/copilot.controller.ts +++ b/apps/backend/src/api/routes/copilot.controller.ts @@ -2,15 +2,29 @@ import { Logger, Controller, Get, Post, Req, Res, Query } from '@nestjs/common'; import { CopilotRuntime, OpenAIAdapter, - copilotRuntimeNestEndpoint, + copilotRuntimeNodeHttpEndpoint, } from '@copilotkit/runtime'; import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request'; import { Organization } from '@prisma/client'; import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service'; +import { MastraAgent } from '@ag-ui/mastra'; +import { MastraService } from '@gitroom/nestjs-libraries/chat/mastra.service'; +import { Mastra } from '@mastra/core/dist/mastra'; +import { Request, Response } from 'express'; +import { RuntimeContext } from '@mastra/core/di'; +let mastra: Mastra; + +export type ChannelsContext = { + integrations: string; + organization: string; +}; @Controller('/copilot') export class CopilotController { - constructor(private _subscriptionService: SubscriptionService) {} + constructor( + private _subscriptionService: SubscriptionService, + private _mastraService: MastraService + ) {} @Post('/chat') chat(@Req() req: Request, @Res() res: Response) { if ( @@ -21,28 +35,67 @@ export class CopilotController { return; } - const copilotRuntimeHandler = copilotRuntimeNestEndpoint({ + const copilotRuntimeHandler = copilotRuntimeNodeHttpEndpoint({ endpoint: '/copilot/chat', runtime: new CopilotRuntime(), serviceAdapter: new OpenAIAdapter({ - model: - // @ts-ignore - req?.body?.variables?.data?.metadata?.requestType === - 'TextareaCompletion' - ? 'gpt-4o-mini' - : 'gpt-4.1', + model: 'gpt-4.1', + }), + }); + + return copilotRuntimeHandler(req, res); + } + + @Post('/agent') + async agent( + @Req() req: Request, + @Res() res: Response, + @GetOrgFromRequest() organization: Organization + ) { + if ( + process.env.OPENAI_API_KEY === undefined || + process.env.OPENAI_API_KEY === '' + ) { + Logger.warn('OpenAI API key not set, chat functionality will not work'); + return; + } + mastra = mastra || (await this._mastraService.mastra()); + const runtimeContext = new RuntimeContext(); + runtimeContext.set( + 'integrations', + req?.body?.variables?.properties?.integrations || [] + ); + + runtimeContext.set('organization', organization.id); + + const runtime = new CopilotRuntime({ + agents: MastraAgent.getLocalAgents({ + mastra, + // @ts-ignore + runtimeContext, + }), + }); + + const copilotRuntimeHandler = copilotRuntimeNodeHttpEndpoint({ + endpoint: '/copilot/agent', + runtime, + properties: req.body.variables.properties, + serviceAdapter: new OpenAIAdapter({ + model: 'gpt-4.1', }), }); - // @ts-ignore return copilotRuntimeHandler(req, res); } @Get('/credits') calculateCredits( @GetOrgFromRequest() organization: Organization, - @Query('type') type: 'ai_images' | 'ai_videos', + @Query('type') type: 'ai_images' | 'ai_videos' ) { - return this._subscriptionService.checkCredits(organization, type || 'ai_images'); + return this._subscriptionService.checkCredits( + organization, + type || 'ai_images' + ); } } diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index c5bbf8ae..b1066fce 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -11,8 +11,9 @@ import { AgentModule } from '@gitroom/nestjs-libraries/agent/agent.module'; import { McpModule } from '@gitroom/backend/mcp/mcp.module'; import { ThirdPartyModule } from '@gitroom/nestjs-libraries/3rdparties/thirdparty.module'; import { VideoModule } from '@gitroom/nestjs-libraries/videos/video.module'; -import { SentryModule } from "@sentry/nestjs/setup"; +import { SentryModule } from '@sentry/nestjs/setup'; import { FILTER } from '@gitroom/nestjs-libraries/sentry/sentry.exception'; +import { ChatModule } from '@gitroom/nestjs-libraries/chat/chat.module'; @Global() @Module({ @@ -26,6 +27,7 @@ import { FILTER } from '@gitroom/nestjs-libraries/sentry/sentry.exception'; McpModule, ThirdPartyModule, VideoModule, + ChatModule, ThrottlerModule.forRoot([ { ttl: 3600000, @@ -43,7 +45,7 @@ import { FILTER } from '@gitroom/nestjs-libraries/sentry/sentry.exception'; { provide: APP_GUARD, useClass: PoliciesGuard, - } + }, ], exports: [ BullMqModule, @@ -53,6 +55,7 @@ import { FILTER } from '@gitroom/nestjs-libraries/sentry/sentry.exception'; AgentModule, McpModule, ThrottlerModule, + ChatModule, ], }) export class AppModule {} diff --git a/apps/backend/src/services/auth/auth.middleware.ts b/apps/backend/src/services/auth/auth.middleware.ts index 0ef8377a..3bb3fea5 100644 --- a/apps/backend/src/services/auth/auth.middleware.ts +++ b/apps/backend/src/services/auth/auth.middleware.ts @@ -6,6 +6,7 @@ import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/o import { UsersService } from '@gitroom/nestjs-libraries/database/prisma/users/users.service'; import { getCookieUrlFromDomain } from '@gitroom/helpers/subdomain/subdomain.management'; import { HttpForbiddenException } from '@gitroom/nestjs-libraries/services/exception.filter'; +import { MastraService } from '@gitroom/nestjs-libraries/chat/mastra.service'; export const removeAuth = (res: Response) => { res.cookie('auth', '', { @@ -20,7 +21,6 @@ export const removeAuth = (res: Response) => { expires: new Date(0), maxAge: -1, }); - res.header('logout', 'true'); }; diff --git a/apps/frontend/src/app/(app)/(site)/agents/page.tsx b/apps/frontend/src/app/(app)/(site)/agents/page.tsx new file mode 100644 index 00000000..ac278b81 --- /dev/null +++ b/apps/frontend/src/app/(app)/(site)/agents/page.tsx @@ -0,0 +1,11 @@ +import { Metadata } from 'next'; +import { Agent } from '@gitroom/frontend/components/agents/agent'; +export const metadata: Metadata = { + title: 'Agent', + description: '', +}; +export default async function Page() { + return ( + + ); +} diff --git a/apps/frontend/src/app/global.scss b/apps/frontend/src/app/global.scss index 3b446329..acde904a 100644 --- a/apps/frontend/src/app/global.scss +++ b/apps/frontend/src/app/global.scss @@ -678,4 +678,25 @@ html[dir='rtl'] [dir='ltr'] { } .blur-xs { filter: blur(4px); +} + +.agent { + .copilotKitInputContainer { + padding: 0 24px !important; + } + + .copilotKitInput { + width: 100% !important; + } +} +.rm-bg .b2 { + padding-top: 0 !important; +} +.rm-bg .b1 { + background: transparent !important; + gap: 0 !important; +} + +.copilotKitMessage img { + width: 200px; } \ No newline at end of file diff --git a/apps/frontend/src/components/agents/agent.input.tsx b/apps/frontend/src/components/agents/agent.input.tsx new file mode 100644 index 00000000..85d22b9b --- /dev/null +++ b/apps/frontend/src/components/agents/agent.input.tsx @@ -0,0 +1,120 @@ +import React, { useMemo, useRef, useState } from 'react'; +import { useCopilotContext, useCopilotReadable } from '@copilotkit/react-core'; +import AutoResizingTextarea from '@gitroom/frontend/components/agents/agent.textarea'; +import { useChatContext } from '@copilotkit/react-ui'; +import { InputProps } from '@copilotkit/react-ui/dist/components/chat/props'; +const MAX_NEWLINES = 6; + +export const Input = ({ + inProgress, + onSend, + isVisible = false, + onStop, + onUpload, + hideStopButton = false, + onChange, +}: InputProps & { onChange: (value: string) => void }) => { + const context = useChatContext(); + const copilotContext = useCopilotContext(); + const showPoweredBy = !copilotContext.copilotApiConfig?.publicApiKey; + + const textareaRef = useRef(null); + const [isComposing, setIsComposing] = useState(false); + + const handleDivClick = (event: React.MouseEvent) => { + const target = event.target as HTMLElement; + + // If the user clicked a button or inside a button, don't focus the textarea + if (target.closest('button')) return; + + // If the user clicked the textarea, do nothing (it's already focused) + if (target.tagName === 'TEXTAREA') return; + + // Otherwise, focus the textarea + textareaRef.current?.focus(); + }; + + const [text, setText] = useState(''); + const send = () => { + if (inProgress) return; + onSend(text); + setText(''); + + textareaRef.current?.focus(); + }; + + const isInProgress = inProgress; + const buttonIcon = + isInProgress && !hideStopButton + ? context.icons.stopIcon + : context.icons.sendIcon; + + const canSend = useMemo(() => { + const interruptEvent = copilotContext.langGraphInterruptAction?.event; + const interruptInProgress = + interruptEvent?.name === 'LangGraphInterruptEvent' && + !interruptEvent?.response; + + return !isInProgress && text.trim().length > 0 && !interruptInProgress; + }, [copilotContext.langGraphInterruptAction?.event, isInProgress, text]); + + const canStop = useMemo(() => { + return isInProgress && !hideStopButton; + }, [isInProgress, hideStopButton]); + + const sendDisabled = !canSend && !canStop; + + return ( +
+
+ { + onChange(event.target.value); + setText(event.target.value); + }} + onCompositionStart={() => setIsComposing(true)} + onCompositionEnd={() => setIsComposing(false)} + onKeyDown={(event) => { + if (event.key === 'Enter' && !event.shiftKey && !isComposing) { + event.preventDefault(); + if (canSend) { + send(); + } + } + }} + /> +
+ {onUpload && ( + + )} + +
+ +
+
+
+ ); +}; diff --git a/apps/frontend/src/components/agents/agent.textarea.tsx b/apps/frontend/src/components/agents/agent.textarea.tsx new file mode 100644 index 00000000..ad1f9e1e --- /dev/null +++ b/apps/frontend/src/components/agents/agent.textarea.tsx @@ -0,0 +1,77 @@ +import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from "react"; + +interface AutoResizingTextareaProps { + maxRows?: number; + placeholder?: string; + value: string; + onChange: (event: React.ChangeEvent) => void; + onKeyDown?: (event: React.KeyboardEvent) => void; + onCompositionStart?: () => void; + onCompositionEnd?: () => void; + autoFocus?: boolean; +} + +const AutoResizingTextarea = forwardRef( + ( + { + maxRows = 1, + placeholder, + value, + onChange, + onKeyDown, + onCompositionStart, + onCompositionEnd, + autoFocus, + }, + ref, + ) => { + const internalTextareaRef = useRef(null); + const [maxHeight, setMaxHeight] = useState(0); + + useImperativeHandle(ref, () => internalTextareaRef.current as HTMLTextAreaElement); + + useEffect(() => { + const calculateMaxHeight = () => { + const textarea = internalTextareaRef.current; + if (textarea) { + textarea.style.height = "auto"; + const singleRowHeight = textarea.scrollHeight; + setMaxHeight(singleRowHeight * maxRows); + if (autoFocus) { + textarea.focus(); + } + } + }; + + calculateMaxHeight(); + }, [maxRows]); + + useEffect(() => { + const textarea = internalTextareaRef.current; + if (textarea) { + textarea.style.height = "auto"; + textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`; + } + }, [value, maxHeight]); + + return ( +