Replace CircleCI with GitHub Actions (#7520)
What changed, and why it matters
This commit is a routine infrastructure change: BTCPay Server is switching its automated testing and release system from CircleCI to GitHub Actions. It copies the same test scripts, signing checks, and Docker publishing steps into GitHub Actions workflows. There is no change to the actual BTCPay Server application code that users interact with, and nothing in the commit suggests a security vulnerability was fixed or introduced.
No security action required. Reviewers may verify that GitHub Actions secrets (DOCKERHUB_USER, DOCKERHUB_PASS, DOCKERHUB_REPO, GH_PAT) are configured with appropriate least-privilege scopes, and that branch protection rules still require the new CI checks before merging.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff removes CircleCI configuration files and adds equivalent GitHub Actions workflows (ci.yml and release.yml) plus supporting bash scripts. The workflows run the same test suites (fast, playwright, integration, release checks, plugin compatibility) and the same Docker build/publish and commit-signature verification steps. Minor adjustments include using docker compose as a fallback, updating badge links in README.md, and changing comments that referenced CircleCI to generic ‘CI’ references. No application logic, authentication, cryptography, or network-facing code is modified.
Changed components
CI/CD configuration (CircleCI removed, GitHub Actions added).github/workflows/ci.yml.github/workflows/release.yml.github/scripts/*.shREADME.md badgeTest comment strings in BTCPayServer.Tests/ThirdPartyTests.cs and UnitTest1.csInspect captured patch +224 / −183
### .circleci/can-build.sh
@@ -1,6 +0,0 @@
-#!/bin/sh
-set -e
-
-echo "Checking if it is possible to build Bitcoin only..."
-cd ../BTCPayServer.Tests
-docker-compose -f "docker-compose.yml" build
\ No newline at end of file
### .circleci/config.yml
@@ -1,139 +0,0 @@
-version: 2
-jobs:
- fast_tests:
- machine:
- image: ubuntu-2004:2024.11.1
- steps:
- - checkout
- - run:
- command: |
- cd .circleci && ./run-tests.sh "Fast=Fast|ThirdParty=ThirdParty" && ./can-build.sh
- playwright_tests:
- machine:
- image: ubuntu-2004:2024.11.1
- steps:
- - checkout
- - run:
- command: |
- cd .circleci && ./run-tests.sh "Playwright=Playwright"
- - run:
- when: always
- command: |
- docker run --rm -v btcpayservertests_tests_datadir:/data -v /tmp/Artifacts:/host alpine sh -c "cp -r /data/. /host/"
- - store_artifacts:
- path: /tmp/Artifacts
- playwright_2_tests:
- machine:
- image: ubuntu-2004:2024.11.1
- steps:
- - checkout
- - run:
- command: |
- cd .circleci && ./run-tests.sh "Playwright=Playwright-2"
- - run:
- when: always
- command: |
- docker run --rm -v btcpayservertests_tests_datadir:/data -v /tmp/Artifacts:/host alpine sh -c "cp -r /data/. /host/"
- - store_artifacts:
- path: /tmp/Artifacts
- integration_tests:
- machine:
- image: ubuntu-2004:2024.11.1
- steps:
- - checkout
- - run:
- command: |
- cd .circleci && ./run-tests.sh "Integration=Integration"
- trigger_docs_build:
- machine:
- image: ubuntu-2004:2024.11.1
- steps:
- - run:
- command: |
- curl -X POST -H "Authorization: token $GH_PAT" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/btcpayserver/btcpayserver-doc/dispatches --data '{"event_type": "build_docs"}'
- release_checks:
- machine:
- image: ubuntu-2004:2024.11.1
- steps:
- - checkout
- - run:
- name: Run pre-release checks
- command: |
- cd .circleci && ./run-tests.sh "PreReleaseCheck=PreReleaseCheck"
- plugin_compatibility:
- docker:
- - image: mcr.microsoft.com/dotnet/sdk:10.0.301-noble
- steps:
- - run:
- name: Install checkout dependencies
- command: |
- apt-get update
- apt-get install -y --no-install-recommends openssh-client
- - checkout
- - run:
- name: Check BTCPay plugin compatibility
- command: |
- cd .circleci
- ./check-btcpay-plugin-compat.sh
- # publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
- docker:
- docker:
- - image: cimg/base:stable
- steps:
- - setup_remote_docker
- - checkout
- - run:
- name: Verify commit is signed
- command: |
- cd .circleci && chmod +x verify-signed-commit.sh && ./verify-signed-commit.sh
- - run:
- name: Build and push Docker images
- command: |
- LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
- GIT_COMMIT=$(git rev-parse HEAD)
- #
- docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
- docker buildx create --use
- DOCKER_BUILDX_OPTS="--platform linux/amd64,linux/arm64,linux/arm/v7 --build-arg GIT_COMMIT=${GIT_COMMIT} --push"
- docker buildx build $DOCKER_BUILDX_OPTS -t $DOCKERHUB_REPO:$LATEST_TAG .
-workflows:
- version: 2
- build_and_test:
- jobs:
- - fast_tests
- - playwright_tests
- - playwright_2_tests
- - integration_tests
- publish:
- jobs:
- - trigger_docs_build:
- filters:
- branches:
- ignore: /.*/
- # only act on version tags
- tags:
- only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/
- - release_checks:
- filters:
- branches:
- ignore: /.*/
- tags:
- only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/
- - plugin_compatibility:
- filters:
- branches:
- ignore: /.*/
- tags:
- only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/
- - docker:
- requires:
- - release_checks
- filters:
- # ignore any commit on any branch by default
- branches:
- ignore: /.*/
- # only act on version tags v1.0.0.88 or v1.0.2-1
- # OR feature tags like vlndseedbackup
- # OR features on specific versions like v1.0.0.88-lndseedbackup-1
- tags:
- only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/
### .circleci/run-tests.sh
@@ -1,18 +0,0 @@
-#!/bin/sh
-set -e
-
-cd ../BTCPayServer.Tests
-docker-compose --version
-docker-compose -f "docker-compose.altcoins.yml" down -v
-
-# For some reason, docker-compose pull fails time to time, so we try several times
-n=0
-until [ "$n" -ge 10 ]
-do
- docker-compose -f "docker-compose.altcoins.yml" pull && break
- n=$((n+1))
- sleep 5
-done
-
-docker-compose -f "docker-compose.altcoins.yml" build
-docker-compose -f "docker-compose.altcoins.yml" run -e "TEST_FILTERS=$1" tests
### .coderabbit.yaml
@@ -99,8 +99,6 @@ reviews:
enabled: true
semgrep:
enabled: true
- circleci:
- enabled: true
sqlfluff:
enabled: true
prismaLint:
### .github/scripts/Kukks.asc
@@ -73,4 +73,4 @@ bobGzGl93yDXKd4vRGugyehCtvRCyQBvj1A0wp0O6NE05ELcFJW610kMQJeI5hZg
F+9OtEmaN9sqxnUmVief4dxRFxhpd5pFKxiDdFH9HH7/p+tRRkcDP+qKCQ6Dff4C
jXOId4nKl2N0HndkM4WW0MzwOUbMCN0ZdT49310Sm6h0YLETaQJsWVB0tGc=
=O9q0
------END PGP PUBLIC KEY BLOCK-----
\ No newline at end of file
+-----END PGP PUBLIC KEY BLOCK-----
### .github/scripts/can-build.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+compose() {
+ if command -v docker-compose >/dev/null 2>&1; then
+ docker-compose "$@"
+ else
+ docker compose "$@"
+ fi
+}
+
+echo "Checking if it is possible to build Bitcoin only..."
+cd "$(dirname "$0")/../../BTCPayServer.Tests"
+compose -f "docker-compose.yml" build
### .github/scripts/check-btcpay-plugin-compat.sh
@@ -2,7 +2,7 @@
set -u
ROOT_DIR="${1:-/tmp/btcpay-plugin-check}"
-BTCPAY_ABS="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+BTCPAY_ABS="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
EXOLIX_REPO="https://github.com/Nisaba/btcpayserver-plugins.git"
SAMROCK_REPO="https://github.com/rockstardev/SamRockProtocol.git"
### .github/scripts/nicolasdorier.asc
@@ -48,4 +48,4 @@ qNj7ylvjGakY3WR+EjPmgU2KGdcKloZLMOOSLq+4kwWPr0+q3dBI0qqXssVPZAtJ
b+lEWZtwBM0n3d8RcNEGywqeZIiAfgvyUQ6rNosDhE51q9nWoJW1i3r9X0ATe+aV
avYCWTKM5AQ7bEIvuVW/4M8PLFClJ2GmI7+YY7gl
=sNb2
------END PGP PUBLIC KEY BLOCK-----
\ No newline at end of file
+-----END PGP PUBLIC KEY BLOCK-----
### .github/scripts/rockstardev.asc
@@ -49,4 +49,4 @@ W20I0HrfpXWpt/GephI/kfccLRtT7JydQh1IGR/MfvsvzPSQ/EnV5KWGjELYOrll
rWHMEizJ2QoDrdsjJWYVklsJy3xESCjkDwFEVX/au8SqthS4CAMjZ2D+W6KYLShB
KVYBghps/tUeWzDiZIVUO/w8y7uHBLL9Dw==
=YJns
------END PGP PUBLIC KEY BLOCK-----
\ No newline at end of file
+-----END PGP PUBLIC KEY BLOCK-----
### .github/scripts/run-tests.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+compose() {
+ if command -v docker-compose >/dev/null 2>&1; then
+ docker-compose "$@"
+ else
+ docker compose "$@"
+ fi
+}
+
+cd "$(dirname "$0")/../../BTCPayServer.Tests"
+compose --version
+compose -f "docker-compose.altcoins.yml" down -v
+
+# Pull can fail transiently; retry to match the previous CI behavior.
+n=0
+until [ "$n" -ge 10 ]; do
+ compose -f "docker-compose.altcoins.yml" pull && break
+ n=$((n+1))
+ sleep 5
+done
+
+compose -f "docker-compose.altcoins.yml" build
+compose -f "docker-compose.altcoins.yml" run -e "TEST_FILTERS=$1" tests
### .github/scripts/verify-signed-commit.sh
@@ -1,12 +1,14 @@
-#!/bin/sh
-set -e
+#!/usr/bin/env bash
+set -euo pipefail
-gpg --batch --import *.asc
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+
+gpg --batch --import "$SCRIPT_DIR"/*.asc
echo "Checking commit signature..."
status=$(git log -1 --format="%G?" HEAD)
case "$status" in
- G|U) ;; # signed (trusted or not)
+ G|U) ;;
*)
echo "ERROR: commit is not properly signed (status: $status)"
exit 1
### .github/workflows/ci.yml
@@ -0,0 +1,81 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - '**'
+ pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+env:
+ COMPOSE_PROJECT_NAME: btcpayservertests
+
+jobs:
+ fast_tests:
+ name: Fast Tests
+ runs-on: ubuntu-24.04
+ timeout-minutes: 120
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Run fast and third-party tests
+ run: bash .github/scripts/run-tests.sh "Fast=Fast|ThirdParty=ThirdParty"
+ - name: Check Bitcoin-only build
+ run: bash .github/scripts/can-build.sh
+
+ playwright_tests:
+ name: Playwright Tests
+ runs-on: ubuntu-24.04
+ timeout-minutes: 120
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Run Playwright tests
+ run: bash .github/scripts/run-tests.sh "Playwright=Playwright"
+ - name: Collect artifacts
+ if: always()
+ run: |
+ mkdir -p /tmp/Artifacts
+ docker run --rm -v "${COMPOSE_PROJECT_NAME}_tests_datadir:/data" -v /tmp/Artifacts:/host alpine sh -c "cp -r /data/. /host/" || true
+ - name: Upload artifacts
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: playwright-tests-artifacts
+ path: /tmp/Artifacts
+ if-no-files-found: ignore
+
+ playwright_2_tests:
+ name: Playwright 2 Tests
+ runs-on: ubuntu-24.04
+ timeout-minutes: 120
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Run Playwright 2 tests
+ run: bash .github/scripts/run-tests.sh "Playwright=Playwright-2"
+ - name: Collect artifacts
+ if: always()
+ run: |
+ mkdir -p /tmp/Artifacts
+ docker run --rm -v "${COMPOSE_PROJECT_NAME}_tests_datadir:/data" -v /tmp/Artifacts:/host alpine sh -c "cp -r /data/. /host/" || true
+ - name: Upload artifacts
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: playwright-2-tests-artifacts
+ path: /tmp/Artifacts
+ if-no-files-found: ignore
+
+ integration_tests:
+ name: Integration Tests
+ runs-on: ubuntu-24.04
+ timeout-minutes: 120
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Run integration tests
+ run: bash .github/scripts/run-tests.sh "Integration=Integration"
### .github/workflows/release.yml
@@ -0,0 +1,85 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - 'v*'
+
+permissions:
+ contents: read
+
+env:
+ COMPOSE_PROJECT_NAME: btcpayservertests
+
+jobs:
+ trigger_docs_build:
+ name: Trigger Docs Build
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Trigger docs repository build
+ env:
+ GH_PAT: ${{ secrets.GH_PAT }}
+ run: |
+ curl -X POST \
+ -H "Authorization: token $GH_PAT" \
+ -H "Accept: application/vnd.github.everest-preview+json" \
+ -H "Content-Type: application/json" \
+ https://api.github.com/repos/btcpayserver/btcpayserver-doc/dispatches \
+ --data '{"event_type": "build_docs"}'
+
+ release_checks:
+ name: Release Checks
+ runs-on: ubuntu-24.04
+ timeout-minutes: 120
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Run pre-release checks
+ run: bash .github/scripts/run-tests.sh "PreReleaseCheck=PreReleaseCheck"
+
+ plugin_compatibility:
+ name: Plugin Compatibility
+ runs-on: ubuntu-24.04
+ container:
+ image: mcr.microsoft.com/dotnet/sdk:10.0.301-noble
+ timeout-minutes: 120
+ steps:
+ - name: Install checkout dependencies
+ run: |
+ apt-get update
+ apt-get install -y --no-install-recommends git openssh-client perl ca-certificates
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Check BTCPay plugin compatibility
+ run: bash .github/scripts/check-btcpay-plugin-compat.sh
+
+ docker:
+ name: Docker
+ runs-on: ubuntu-24.04
+ needs: release_checks
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Verify commit is signed
+ run: bash .github/scripts/verify-signed-commit.sh
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USER }}
+ password: ${{ secrets.DOCKERHUB_PASS }}
+ - name: Build and push Docker images
+ env:
+ DOCKERHUB_REPO: ${{ secrets.DOCKERHUB_REPO }}
+ run: |
+ LATEST_TAG="${GITHUB_REF_NAME#v}"
+ GIT_COMMIT="$(git rev-parse HEAD)"
+ docker buildx build \
+ --platform linux/amd64,linux/arm64,linux/arm/v7 \
+ --build-arg "GIT_COMMIT=${GIT_COMMIT}" \
+ --push \
+ -t "${DOCKERHUB_REPO}:${LATEST_TAG}" \
+ .
### BTCPayServer.Tests/ThirdPartyTests.cs
@@ -289,8 +289,8 @@ private async Task CheckDeadLinks(Regex regex, HttpClient httpClient, string fil
var urlBlacklist = new string[]
{
"https://zaphq.io", // Returns forbidden over test. Opening on tab, it redirects to strike
- "https://www.btse.com", // not allowing to be hit from circleci
- "https://www.bitpay.com", // not allowing to be hit from circleci
+ "https://www.btse.com", // not allowing to be hit from CI
+ "https://www.bitpay.com", // not allowing to be hit from CI
"https://support.bitpay.com",
"https://www.coingecko.com", // unhappy service
"https://www.wasabiwallet.io", // Banning US, CI unhappy
@@ -511,7 +511,7 @@ public async Task CheckJsContent()
EqualJsContent(expected, actual);
// This test is flaky probably because of the CDN sending the wrong file's version in some regions.
- // https://app.circleci.com/pipelines/github/btcpayserver/btcpayserver/13750/workflows/44aaf31d-0057-4fd8-a5bb-1a2c47fc530f/jobs/42963
+ // Observed in CI: the CDN can serve the wrong file version in some regions.
// It works locally depending on where you live.
//actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "dom-confetti", "dom-confetti.min.js").Trim();
### BTCPayServer.Tests/UnitTest1.cs
@@ -227,8 +227,8 @@ private async Task CheckDeadLinks(Regex regex, HttpClient httpClient, string fil
var urlBlacklist = new string[]
{
- "https://www.btse.com", // not allowing to be hit from circleci
- "https://www.bitpay.com", // not allowing to be hit from circleci
+ "https://www.btse.com", // not allowing to be hit from CI
+ "https://www.bitpay.com", // not allowing to be hit from CI
"https://support.bitpay.com"
};
### README.md
@@ -8,8 +8,8 @@
<p align="center"> BTCPay Server is a free and open-source Bitcoin payment processor which allows you to accept bitcoin without fees or intermediaries.
</p>
<p align="center">
- <a href="https://circleci.com/gh/btcpayserver/btcpayserver">
- <img src="https://img.shields.io/circleci/build/github/btcpayserver/btcpayserver"/>
+ <a href="https://github.com/btcpayserver/btcpayserver/actions/workflows/ci.yml">
+ <img src="https://github.com/btcpayserver/btcpayserver/actions/workflows/ci.yml/badge.svg"/>
</a>
<a href="https://github.com/btcpayserver/btcpayserver/releases/">
<img src="https://img.shields.io/github/v/release/btcpayserver/btcpayserver"/>
### btcpayserver.sln
@@ -12,13 +12,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Misc", "Misc", "{29290EC7-0
.editorconfig = .editorconfig
Changelog.md = Changelog.md
.github\codeql\codeql-config.yml = .github\codeql\codeql-config.yml
+ .github\workflows\ci.yml = .github\workflows\ci.yml
+ .github\workflows\release.yml = .github\workflows\release.yml
Build\Common.csproj = Build\Common.csproj
- .circleci\config.yml = .circleci\config.yml
docker-entrypoint.sh = docker-entrypoint.sh
Dockerfile = Dockerfile
- .circleci\run-tests.sh = .circleci\run-tests.sh
Build\Version.csproj = Build\Version.csproj
- .circleci\check-btcpay-plugin-compat.sh = .circleci\check-btcpay-plugin-compat.sh
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer.Rating", "BTCPayServer.Rating\BTCPayServer.Rating.csproj", "{6DC77459-D52F-45EE-B3F3-315043D33A1B}"Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.