name: Build All Platforms on: push: branches: [master, cpp20-modernization] tags: ['v*'] pull_request: branches: [master] workflow_dispatch: permissions: contents: read jobs: test-linux-unit: # This is the canonical CI gate for unit tests. Failures here MUST block # the PR — see PR #26 incident (2026-07-11): the previous # `continue-on-error: true` + `|| true` soft-gate allowed a PR with broken # master-side code to merge because the link failure wasn't blocking. # Sanitizer regression = blocking PR (test-linux-sanitizers below). # Unit regression = blocking PR (this job). runs-on: ubuntu-22.04 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y build-essential cmake ninja-build \ libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \ libevent-dev libminiupnpc-dev zlib1g-dev \ libsnappy-dev liblz4-dev libzstd-dev - name: Build RocksDB from source # Ubuntu 22.04's librocksdb-dev is 6.11.4 which CMakeLists.txt now # refuses to configure against (need >= 7.4 for XXH3 per-block # checksum). Build 8.9.1 from source — same version DNS2 ships — # into /usr/local so CMake's find_library picks it up first. run: sudo bash scripts/ci/build-rocksdb.sh - name: Configure run: | cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_QT=OFF \ -DBUILD_DAEMON=ON \ -DBUILD_TESTS=ON \ -DUSE_UPNP=OFF - name: Build libtor (embedded Tor static lib) # USE_TOR_EMBEDDED defaults to ON and the daemon/Qt GUI both # link -ltor. The Tor source is a git submodule but libtor.a # is NOT built by cmake. build-libtor.sh defaults to /mingw64 # paths which don't exist on the ubuntu-22.04 runner; pass # /usr where libevent-dev/libssl-dev/zlib1g-dev install. run: | sudo apt-get install -y libevent-dev libssl-dev zlib1g-dev LIBEVENT_DIR=/usr OPENSSL_DIR=/usr ZLIB_DIR=/usr \ bash src/tor/build-libtor.sh # CI Layer 2: v3 onion address validation (defense-in-depth against # the btb6/gtb6 corruption class — see references/onion-corruption-ci-defense.md). # Validates: (a) src/onionseed.h hardcoded seeds, (b) contrib/triangles.conf.example # operator-facing example. Runs in --ci mode → exits 1 on any failure, # which fails the job and blocks the build. - name: Validate .onion addresses (CI gate) run: | python3 scripts/validate_onion_seeds.py \ --ci \ --against src/onionseed.h \ src/onionseed.h \ contrib/triangles.conf.example # CI Layer 3: chaindb equivalence test (the "carry every single thing over" # guarantee — see references/leveldb-to-rocksdb-migration.md Phase A). # Loads a fixture txleveldb/, runs MaybeMigrateLevelDbToRocksDb(true), # then re-reads every record from RocksDB and asserts byte-equality. # This is the proof that no data is lost in the LevelDB→RocksDB migration. - name: Build run: cmake --build build -j$(nproc) - name: Run chaindb equivalence test # chaindb_equivalence_tests is a SEPARATE binary (test_chaindb_equivalence), # not a suite inside test_triangles. Run the right binary. run: | if [ -x build/bin/test_chaindb_equivalence ]; then ./build/bin/test_chaindb_equivalence --log_level=test_suite else echo "::error::test_chaindb_equivalence was not built" exit 1 fi - name: Run unit tests # ctest exit code is the gate. NO `|| true` — failures must block # the PR (see comment at top of this job). --output-on-failure gives # the failing assertion + suite name inline rather than requiring a # log download. run: cd build && ctest --output-on-failure test-linux-sanitizers: # ASan + UBSan build of the daemon + unit tests. This is a blocking # signal: sanitizer regressions should fail the PR. runs-on: ubuntu-22.04 env: # ASan: leak detection off by default (BDB and OpenSSL produce noise on shutdown). # Re-enable once we've quieted the legitimate suspects. ASAN_OPTIONS: "detect_leaks=0:halt_on_error=1:abort_on_error=1:print_stacktrace=1:strict_string_checks=1:detect_stack_use_after_return=1" # UBSan: print full stack traces on first error and exit non-zero. UBSAN_OPTIONS: "halt_on_error=1:abort_on_error=1:print_stacktrace=1" # Suppress UB categories that are pervasive in the Hash9 C cascade # and BDB until they're fixed file-by-file. SAN_FLAGS: "-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=undefined -fno-sanitize=alignment,signed-integer-overflow,vptr" steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y build-essential cmake ninja-build \ libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \ libevent-dev libminiupnpc-dev zlib1g-dev \ libsnappy-dev liblz4-dev libzstd-dev - name: Build RocksDB from source run: sudo bash scripts/ci/build-rocksdb.sh - name: Configure with sanitizers run: | cmake -B build-san -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_C_FLAGS="$SAN_FLAGS" \ -DCMAKE_CXX_FLAGS="$SAN_FLAGS" \ -DCMAKE_EXE_LINKER_FLAGS="$SAN_FLAGS" \ -DCMAKE_SHARED_LINKER_FLAGS="$SAN_FLAGS" \ -DBUILD_QT=OFF \ -DBUILD_DAEMON=ON \ -DBUILD_TESTS=ON \ -DUSE_UPNP=OFF - name: Build libtor (embedded Tor static lib) # USE_TOR_EMBEDDED defaults to ON and the daemon/Qt GUI both # link -ltor. The Tor source is a git submodule but libtor.a # is NOT built by cmake. build-libtor.sh defaults to /mingw64 # paths which don't exist on the ubuntu-22.04 runner; pass # /usr where libevent-dev/libssl-dev/zlib1g-dev install. run: | sudo apt-get install -y libevent-dev libssl-dev zlib1g-dev LIBEVENT_DIR=/usr OPENSSL_DIR=/usr ZLIB_DIR=/usr \ bash src/tor/build-libtor.sh - name: Build run: cmake --build build-san -j$(nproc) - name: Run unit tests under sanitizers run: cd build-san && ctest --output-on-failure test-fuzz-smoke: # libFuzzer smoke test for src/script.cpp (fuzz_script harness). # Builds with ASan+UBSan+libFuzzer and runs for 5 minutes. Any crash # is uploaded as an artifact and the job fails — fuzz regressions # must block the PR. # See src/test/fuzz/README.md for harness details. runs-on: ubuntu-22.04 timeout-minutes: 20 env: ASAN_OPTIONS: "detect_leaks=0:halt_on_error=1:abort_on_error=1:print_stacktrace=1" UBSAN_OPTIONS: "halt_on_error=1:abort_on_error=1:print_stacktrace=1" SAN_FLAGS: "-fsanitize=address,undefined,fuzzer-no-link -fno-omit-frame-pointer -fno-sanitize-recover=undefined -fno-sanitize=alignment,signed-integer-overflow,vptr" steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Install clang + dependencies # libFuzzer ships with clang since v6; clang-15 is on the runner. # libgflags-dev: fuzz link line references -lgflags (RocksDB builds # expect gflags as a transitive dep). Without it the link step fails # with "cannot find -lgflags". CI's ubuntu-22.04 runner does NOT ship # it by default; DNS2 has it as an automatic dep of build-essential, # which is why local dry-runs didn't catch this. run: | sudo apt-get update sudo apt-get install -y clang-15 cmake ninja-build \ libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \ libevent-dev libminiupnpc-dev zlib1g-dev \ libsnappy-dev liblz4-dev libzstd-dev \ libgflags-dev sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-15 100 sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-15 100 - name: Build RocksDB from source run: sudo bash scripts/ci/build-rocksdb.sh - name: Configure with fuzzing + sanitizers # NB: do NOT pass -fsanitize=fuzzer in CMAKE_EXE_LINKER_FLAGS — that # pulls libFuzzer's main() into CMake's compiler-probe linker test # and trips "multiple definition of `main`". The fuzz_script target's # custom clang++ link step adds -fsanitize=fuzzer in src/CMakeLists.txt # (see BUILD_FUZZ block). # SECP256K1_ASM=OFF: clang-15+ register allocator is sometimes stricter # than clang-14 about the x86_64 inline asm in scalar_4x64_impl.h and # fails with "inline assembly requires more registers than available" # on some runner images. The fuzz target only exercises script.cpp — # ECC ops use the C fallback (slower, still correct). run: | cmake -B build-fuzz -G Ninja \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_C_FLAGS="$SAN_FLAGS" \ -DCMAKE_CXX_FLAGS="$SAN_FLAGS" \ -DCMAKE_EXE_LINKER_FLAGS="$SAN_FLAGS" \ -DBUILD_QT=OFF \ -DBUILD_DAEMON=ON \ -DBUILD_TESTS=ON \ -DBUILD_FUZZ=ON \ -DUSE_UPNP=OFF \ -DSECP256K1_ASM=OFF - name: Build libtor (embedded Tor static lib) # BUILD_FUZZ pulls in triangles_common + trianglesd_objects (OBJECT lib) # via the fuzz target's CMake deps. The link line references libtor.a, # which the Tor submodule script produces — CMake doesn't build it. # Mirror the unit/sanitizer jobs here before invoking the fuzz target. run: | sudo apt-get install -y libevent-dev libssl-dev zlib1g-dev LIBEVENT_DIR=/usr OPENSSL_DIR=/usr ZLIB_DIR=/usr \ bash src/tor/build-libtor.sh - name: Build fuzz_script # CMake target is named `fuzz_script` (matches FUZZ_BIN_DIR/fuzz_script # in src/CMakeLists.txt — see add_custom_target(fuzz_script ...)). run: cmake --build build-fuzz --target fuzz_script -j$(nproc) - name: Generate seed corpus from JSON fixtures # Uses src/test/data/script_{valid,invalid}.json so the fuzzer # starts from real Bitcoin-style scripts instead of empty input. run: | mkdir -p build-fuzz/fuzz_corpus python3 src/test/fuzz/seed_corpus.py \ src/test/data/script_valid.json \ build-fuzz/fuzz_corpus valid python3 src/test/fuzz/seed_corpus.py \ src/test/data/script_invalid.json \ build-fuzz/fuzz_corpus invalid - name: Run fuzzer for 5 minutes # -max_total_time=300 hard-caps runtime. Crashes go to artifact # prefix; we upload any artifacts and fail the job if any exist. run: | mkdir -p build-fuzz/fuzz_artifacts set +e ./build-fuzz/bin/fuzz_script \ -max_total_time=300 \ -max_len=4096 \ -artifact_prefix=build-fuzz/fuzz_artifacts/ \ build-fuzz/fuzz_corpus/ \ 2>&1 | tee build-fuzz/fuzz_log.txt FUZZ_EXIT=${PIPESTATUS[0]} set -e if [ -n "$(ls -A build-fuzz/fuzz_artifacts/ 2>/dev/null | grep -v '\.tmp$')" ]; then echo "::error::Fuzzer produced crash/leak artifacts" exit 1 fi exit "$FUZZ_EXIT" - name: Upload fuzzer artifacts on success if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: fuzz-artifacts path: build-fuzz/fuzz_artifacts/ build-windows-qt: runs-on: windows-latest defaults: run: shell: msys2 {0} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2 with: msystem: MINGW64 update: true install: >- mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-qt5-base mingw-w64-x86_64-qt5-tools mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-db mingw-w64-x86_64-libevent mingw-w64-x86_64-miniupnpc mingw-w64-x86_64-zlib mingw-w64-x86_64-rocksdb mingw-w64-x86_64-sqlite3 mingw-w64-x86_64-autotools - name: Set VERSION run: | if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV else MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | tr -d '\r' | awk '{print $3}') MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | tr -d '\r' | awk '{print $3}') REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | tr -d '\r' | awk '{print $3}') echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV fi - name: Configure run: | cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_QT=ON \ -DBUILD_DAEMON=OFF \ -DBUILD_TESTS=OFF \ -DUSE_UPNP=OFF \ -DUSE_QRCODE=OFF \ -DUSE_I2P_EMBEDDED=ON - name: Build libtor (embedded Tor static lib) # Windows Qt GUI also transitively links -ltor via triangles_common. # msys2 default install puts everything in /mingw64. run: bash src/tor/build-libtor.sh - name: Build libi2pd (embedded I2P static lib) run: bash src/i2p/build-libi2pd.sh - name: Build run: cmake --build build -j$(nproc) - name: Package run: | mkdir -p dist cp build/bin/triangles-qt.exe dist/ windeployqt dist/triangles-qt.exe || true # Copy ALL runtime DLLs the binary needs # MinGW runtime for dll in libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll; do cp /mingw64/bin/$dll dist/ 2>/dev/null || true done # Boost for dll in /mingw64/bin/libboost_system*.dll /mingw64/bin/libboost_filesystem*.dll \ /mingw64/bin/libboost_thread*.dll /mingw64/bin/libboost_program_options*.dll \ /mingw64/bin/libboost_chrono*.dll; do cp $dll dist/ 2>/dev/null || true done # OpenSSL for dll in /mingw64/bin/libssl*.dll /mingw64/bin/libcrypto*.dll; do cp $dll dist/ 2>/dev/null || true done # BerkeleyDB, libevent, miniupnpc, zlib for dll in /mingw64/bin/libdb*.dll /mingw64/bin/libevent*.dll \ /mingw64/bin/libminiupnpc*.dll /mingw64/bin/zlib1.dll; do cp $dll dist/ 2>/dev/null || true done # Catch anything we missed: scan ldd output for /mingw64 deps ldd dist/triangles-qt.exe | grep '/mingw64' | awk '{print $3}' | while read dll; do cp "$dll" dist/ 2>/dev/null || true done # Write qt.conf so the exe finds plugins relative to itself printf '[Paths]\nPlugins = .\n' > dist/qt.conf # Ensure Qt platform plugins are present (windeployqt sometimes misses them in MSYS2) if [ ! -f dist/platforms/qwindows.dll ]; then echo "WARNING: windeployqt did not copy platform plugins, copying manually..." mkdir -p dist/platforms cp /mingw64/share/qt5/plugins/platforms/qwindows.dll dist/platforms/ 2>/dev/null || \ cp /mingw64/lib/qt5/plugins/platforms/qwindows.dll dist/platforms/ 2>/dev/null || \ find /mingw64 -name 'qwindows.dll' -exec cp {} dist/platforms/ \; 2>/dev/null fi # Also copy styles and imageformats for good measure for plugdir in styles imageformats; do if [ ! -d "dist/$plugdir" ]; then srcdir=$(find /mingw64 -type d -name "$plugdir" -path "*/plugins/*" 2>/dev/null | head -1) if [ -n "$srcdir" ]; then cp -r "$srcdir" dist/ fi fi done strip --strip-all dist/triangles-qt.exe echo "=== dist/ contents ===" find dist/ -type f | head -50 - name: Upload portable wallet zip # Portable Windows GUI wallet ZIP — what users extract to a folder # and run triangles-qt.exe directly. This is what the Chocolatey # package and most manual downloads expect. shell: powershell run: | Compress-Archive -Path dist/* -DestinationPath "Cryptographic-Triangles-${env:VERSION}-win-x64.zip" -Force echo "Created Cryptographic-Triangles-${env:VERSION}-win-x64.zip" Get-Item "Cryptographic-Triangles-${env:VERSION}-win-x64.zip" - name: Upload artifact (portable zip) uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: windows-qt-zip path: Cryptographic-Triangles-*-win-x64.zip - name: Download Tor # Resilient download: archive.torproject.org occasionally times out # from CI egress (observed 2026-07-03: macOS job exit code 6 after # exactly 30s of curl hang). Retries cover transient connection drops; # size check rejects 0-byte "200 OK" responses from broken mirrors. # NOTE: Invoke-WebRequest on PowerShell 5.1 (default on Windows-latest # runners) does NOT accept -ConnectionTimeout/-OperationTimeout — those # are PowerShell 7+. We rely on the retry loop + size check only. shell: powershell run: | $TOR_VERSION = "15.0.9" $TOR_SHA256 = "adebc1b7c65dc1b5e471064ed17585464af6f6198c3fe5c8c9108138b59ccf65" $TOR_URL = "https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz" $torPath = "tor-bundle.tar.gz" $attempts = 0 $maxAttempts = 3 $downloaded = $false while ($attempts -lt $maxAttempts -and -not $downloaded) { $attempts++ try { if (Test-Path $torPath) { Remove-Item $torPath -ErrorAction SilentlyContinue } Invoke-WebRequest -Uri $TOR_URL -OutFile $torPath -UseBasicParsing $size = (Get-Item $torPath).Length if ($size -gt 1MB) { Write-Host "Downloaded $size bytes on attempt $attempts" $downloaded = $true } else { Write-Host "Download too small ($size bytes), retrying..." } } catch { Write-Host "Download attempt $attempts failed: $_" Start-Sleep -Seconds 5 } } if (-not $downloaded) { throw "Tor bundle download failed after $maxAttempts attempts" } $actualSha256 = (Get-FileHash -Algorithm SHA256 $torPath).Hash.ToLowerInvariant() if ($actualSha256 -ne $TOR_SHA256) { throw "Tor bundle SHA256 mismatch: expected $TOR_SHA256, got $actualSha256" } New-Item -ItemType Directory -Path tor-extract -Force tar -xzf tor-bundle.tar.gz -C tor-extract New-Item -ItemType Directory -Path tor-files -Force Copy-Item -Recurse tor-extract/tor/* tor-files/ if (Test-Path tor-extract/tor/pluggable_transports) { Copy-Item -Recurse tor-extract/tor/pluggable_transports tor-files/pluggable_transports -Force } if (Test-Path tor-extract/data) { Copy-Item -Recurse tor-extract/data tor-files/data } Write-Host "Bundled Tor runtime files:" Get-ChildItem -Recurse tor-files | Select-Object FullName - name: Install NSIS via MSYS2 run: pacman -S --noconfirm mingw-w64-x86_64-nsis - name: Build NSIS installer run: makensis //DVERSION=$VERSION contrib/nsis/setup.nsi - name: Upload installer uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: windows-qt-setup path: contrib/nsis/Cryptographic-Triangles-*-setup.exe build-windows-daemon: runs-on: windows-latest defaults: run: shell: msys2 {0} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2 with: msystem: MINGW64 update: true install: >- mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-db mingw-w64-x86_64-libevent mingw-w64-x86_64-miniupnpc mingw-w64-x86_64-zlib mingw-w64-x86_64-rocksdb mingw-w64-x86_64-sqlite3 mingw-w64-x86_64-autotools - name: Configure run: | cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_QT=OFF \ -DBUILD_DAEMON=ON \ -DBUILD_CLI=ON \ -DBUILD_TESTS=OFF \ -DUSE_UPNP=OFF \ -DUSE_I2P_EMBEDDED=ON - name: Build libtor (embedded Tor static lib) # Windows: msys2 default install puts everything in /mingw64, # which is exactly the script's default. Just invoke it. # See v5.9.25-fork-detection run #466 for why this is needed. run: bash src/tor/build-libtor.sh - name: Build libi2pd (embedded I2P static lib) run: bash src/i2p/build-libi2pd.sh - name: Build run: | cmake --build build -j$(nproc) strip --strip-all build/bin/trianglesd.exe strip --strip-all build/bin/triangles-cli.exe - name: Package daemon with DLLs run: bash scripts/ci/package-windows-daemon.sh daemon-dist trianglesd triangles-cli - name: Bundle Tor for daemon # Resilient download: archive.torproject.org occasionally times out # from CI egress (observed 2026-07-03: macOS job exit code 6 after # exactly 30s of curl hang). Retries cover transient connection drops; # size check rejects 0-byte "200 OK" responses from broken mirrors. # NOTE: Invoke-WebRequest on PowerShell 5.1 (default on Windows-latest # runners) does NOT accept -ConnectionTimeout/-OperationTimeout — those # are PowerShell 7+. We rely on the retry loop + size check only. shell: powershell run: | $TOR_VERSION = "15.0.9" $TOR_SHA256 = "adebc1b7c65dc1b5e471064ed17585464af6f6198c3fe5c8c9108138b59ccf65" $TOR_URL = "https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz" $torPath = "tor-bundle.tar.gz" $attempts = 0 $maxAttempts = 3 $downloaded = $false while ($attempts -lt $maxAttempts -and -not $downloaded) { $attempts++ try { if (Test-Path $torPath) { Remove-Item $torPath -ErrorAction SilentlyContinue } Invoke-WebRequest -Uri $TOR_URL -OutFile $torPath -UseBasicParsing $size = (Get-Item $torPath).Length if ($size -gt 1MB) { Write-Host "Downloaded $size bytes on attempt $attempts" $downloaded = $true } else { Write-Host "Download too small ($size bytes), retrying..." } } catch { Write-Host "Download attempt $attempts failed: $_" Start-Sleep -Seconds 5 } } if (-not $downloaded) { throw "Tor bundle download failed after $maxAttempts attempts" } $actualSha256 = (Get-FileHash -Algorithm SHA256 $torPath).Hash.ToLowerInvariant() if ($actualSha256 -ne $TOR_SHA256) { throw "Tor bundle SHA256 mismatch: expected $TOR_SHA256, got $actualSha256" } New-Item -ItemType Directory -Path tor-extract -Force tar -xzf tor-bundle.tar.gz -C tor-extract Copy-Item -Recurse tor-extract/tor/* daemon-dist/tor/ if (Test-Path tor-extract/data) { Copy-Item -Recurse tor-extract/data daemon-dist/tor/data } - name: Upload artifact uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: windows-daemon path: daemon-dist/ build-linux-qt: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Set VERSION run: | if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV else MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | tr -d '\r' | awk '{print $3}') MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | tr -d '\r' | awk '{print $3}') REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | tr -d '\r' | awk '{print $3}') echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV fi - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y build-essential cmake ninja-build \ qtbase5-dev qttools5-dev-tools \ libboost-all-dev libssl-dev libdb++-dev \ libleveldb-dev libevent-dev libminiupnpc-dev zlib1g-dev \ libsnappy-dev liblz4-dev libzstd-dev - name: Build RocksDB from source run: sudo bash scripts/ci/build-rocksdb.sh - name: Configure run: | cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_QT=ON \ -DBUILD_DAEMON=OFF \ -DBUILD_TESTS=OFF \ -DUSE_UPNP=OFF \ -DUSE_I2P_EMBEDDED=ON - name: Build libtor (embedded Tor static lib) # Linux Qt GUI also transitively links -ltor via triangles_common. # build-libtor.sh defaults to /mingw64; pass /usr where the # libevent-dev, libssl-dev, zlib1g-dev packages install. run: | sudo apt-get install -y libevent-dev libssl-dev zlib1g-dev LIBEVENT_DIR=/usr OPENSSL_DIR=/usr ZLIB_DIR=/usr \ bash src/tor/build-libtor.sh - name: Build libi2pd (embedded I2P static lib) run: bash src/i2p/build-libi2pd.sh - name: Build run: cmake --build build -j$(nproc) - name: Strip binary run: strip --strip-all build/bin/triangles-qt - name: Build .deb package (fully self-contained) run: | set -euo pipefail TOR_VERSION="15.0.9" TOR_SHA256="7ea13e14cddafb36c6347a9c4f4e639f6010364c16acfd519157c29e226277f2" # Resilient download: archive.torproject.org occasionally times out # from CI egress (observed 2026-07-03: macOS job exit code 6 after # exactly 30s of curl hang). Retries + --fail-with-body surface the # next failure loudly instead of silently producing a 0-byte file. curl -fSL --connect-timeout 15 --max-time 120 \ --retry 3 --retry-delay 5 --retry-connrefused --retry-all-errors \ "https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-linux-x86_64-${TOR_VERSION}.tar.gz" \ -o tor-bundle.tar.gz printf '%s %s\n' "$TOR_SHA256" tor-bundle.tar.gz | sha256sum --check --strict - mkdir -p tor-extract && tar -xzf tor-bundle.tar.gz -C tor-extract PKG="cryptographic-triangles_${VERSION}_amd64" mkdir -p ${PKG}/DEBIAN mkdir -p ${PKG}/usr/lib/cryptographic-triangles/lib mkdir -p ${PKG}/usr/lib/cryptographic-triangles/tor mkdir -p ${PKG}/usr/bin mkdir -p ${PKG}/usr/share/applications mkdir -p ${PKG}/usr/share/pixmaps cp build/bin/triangles-qt ${PKG}/usr/lib/cryptographic-triangles/ cp tor-extract/tor/tor ${PKG}/usr/lib/cryptographic-triangles/tor/ chmod +x ${PKG}/usr/lib/cryptographic-triangles/tor/tor [ -d tor-extract/data ] && cp -r tor-extract/data ${PKG}/usr/lib/cryptographic-triangles/tor/data # Bundle ALL shared library dependencies (except glibc/kernel) ldd build/bin/triangles-qt | grep '=> /' | awk '{print $3}' | while read lib; do case "$lib" in /lib/x86_64-linux-gnu/libc.so*|/lib/x86_64-linux-gnu/libm.so*|/lib/x86_64-linux-gnu/libpthread.so*|/lib/x86_64-linux-gnu/libdl.so*|/lib/x86_64-linux-gnu/librt.so*|/lib/x86_64-linux-gnu/ld-linux*|/lib64/ld-linux*) ;; # Skip glibc core — always present *) cp -L "$lib" ${PKG}/usr/lib/cryptographic-triangles/lib/ 2>/dev/null || true ;; esac done echo "=== Bundled libs ===" ls ${PKG}/usr/lib/cryptographic-triangles/lib/ | wc -l ls ${PKG}/usr/lib/cryptographic-triangles/lib/ # Launcher with LD_LIBRARY_PATH cat > ${PKG}/usr/bin/cryptographic-triangles << 'LAUNCHER' #!/bin/bash INSTALL_DIR=/usr/lib/cryptographic-triangles export LD_LIBRARY_PATH="${INSTALL_DIR}/lib:${LD_LIBRARY_PATH}" exec "${INSTALL_DIR}/triangles-qt" "$@" LAUNCHER sed -i 's/^ //' ${PKG}/usr/bin/cryptographic-triangles chmod +x ${PKG}/usr/bin/cryptographic-triangles cat > ${PKG}/usr/share/applications/cryptographic-triangles.desktop << 'DESKTOP' [Desktop Entry] Name=Cryptographic Triangles Comment=Triangles Cryptocurrency Wallet Exec=cryptographic-triangles Terminal=false Type=Application Icon=cryptographic-triangles Categories=Finance;Network; DESKTOP sed -i 's/^ //' ${PKG}/usr/share/applications/cryptographic-triangles.desktop cp src/qt/res/icons/triangles.ico ${PKG}/usr/share/pixmaps/cryptographic-triangles.ico 2>/dev/null || true cat > ${PKG}/DEBIAN/control << CTRL Package: cryptographic-triangles Version: ${VERSION} Architecture: amd64 Maintainer: Cryptographic Triangles Description: Cryptographic Triangles wallet with integrated Tor Fully self-contained wallet with all libraries and Tor bundled. No external dependencies required — runs on any x86_64 Linux. Section: finance Priority: optional CTRL sed -i 's/^ //' ${PKG}/DEBIAN/control dpkg-deb --build ${PKG} - name: Upload .deb uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: linux-qt-deb path: cryptographic-triangles_*_amd64.deb build-linux-daemon: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Set VERSION run: | if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV else MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | tr -d '\r' | awk '{print $3}') MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | tr -d '\r' | awk '{print $3}') REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | tr -d '\r' | awk '{print $3}') echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV fi - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y build-essential cmake ninja-build \ libboost-all-dev libssl-dev libdb++-dev libleveldb-dev \ libevent-dev libminiupnpc-dev zlib1g-dev \ libsnappy-dev liblz4-dev libzstd-dev - name: Build RocksDB from source run: sudo bash scripts/ci/build-rocksdb.sh - name: Configure run: | cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_QT=OFF \ -DBUILD_DAEMON=ON \ -DBUILD_CLI=ON \ -DBUILD_TESTS=OFF \ -DUSE_UPNP=OFF \ -DUSE_I2P_EMBEDDED=ON - name: Build libtor (embedded Tor static lib) # USE_TOR_EMBEDDED defaults to ON and the daemon/Qt GUI both # link -ltor. The Tor source is a git submodule but libtor.a # is NOT built by cmake. build-libtor.sh defaults to /mingw64 # paths which don't exist on the ubuntu-22.04 runner; pass # /usr where libevent-dev/libssl-dev/zlib1g-dev install. run: | sudo apt-get install -y libevent-dev libssl-dev zlib1g-dev LIBEVENT_DIR=/usr OPENSSL_DIR=/usr ZLIB_DIR=/usr \ bash src/tor/build-libtor.sh - name: Build libi2pd (embedded I2P static lib) run: bash src/i2p/build-libi2pd.sh - name: Build run: cmake --build build -j$(nproc) - name: Strip binary run: | strip --strip-all build/bin/trianglesd strip --strip-all build/bin/triangles-cli - name: Build .deb package (fully self-contained) run: bash scripts/ci/package-linux-daemon.sh "${VERSION}" - name: Upload .deb uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: linux-daemon-deb path: cryptographic-triangles-daemon_*_amd64.deb build-macos: runs-on: macos-15 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: recursive - name: Set VERSION run: | if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV else MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | tr -d '\r' | awk '{print $3}') MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | tr -d '\r' | awk '{print $3}') REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | tr -d '\r' | awk '{print $3}') echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV fi - name: Install dependencies run: | brew install cmake ninja qt@5 openssl@3 boost berkeley-db@5 leveldb rocksdb libevent miniupnpc zstd - name: Configure # Add -L/opt/homebrew/lib to the link line so rocksdb's # transitive -lzstd resolves. /opt/homebrew/lib is only in the # rpath (runtime), not the link-time search path, so cmake's # default LIBRARY_PATH propagation isn't enough — we set the # linker flags explicitly. run: | export PATH="/opt/homebrew/opt/qt@5/bin:$PATH" cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_QT=ON \ -DBUILD_DAEMON=OFF \ -DBUILD_TESTS=OFF \ -DUSE_UPNP=OFF \ -DUSE_I2P_EMBEDDED=ON \ -DBOOST_ROOT=/opt/homebrew/opt/boost \ -DBDB_INCLUDE_PATH=/opt/homebrew/opt/berkeley-db@5/include \ -DBDB_LIB_PATH=/opt/homebrew/opt/berkeley-db@5/lib \ -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl@3 \ -DEVENT_INCLUDE_PATH=/opt/homebrew/opt/libevent/include \ -DEVENT_LIB_PATH=/opt/homebrew/opt/libevent/lib \ -DMINIUPNPC_INCLUDE_PATH=/opt/homebrew/opt/miniupnpc/include \ -DMINIUPNPC_LIB_PATH=/opt/homebrew/opt/miniupnpc/lib \ -DQt5_DIR=/opt/homebrew/opt/qt@5/lib/cmake/Qt5 \ -DCMAKE_LIBRARY_PATH=/opt/homebrew/lib \ -DCMAKE_EXE_LINKER_FLAGS="-L/opt/homebrew/lib" \ -DCMAKE_SHARED_LINKER_FLAGS="-L/opt/homebrew/lib" - name: Build libtor (embedded Tor static lib) # macOS Qt GUI also transitively links -ltor via triangles_common. # macOS Qt is built with @rpath embedded, so libtor needs to be # at the configured TOR_SOURCE_ROOT location. run: | brew install libevent openssl@3 autoconf automake libtool zlib zstd export PATH="/opt/homebrew/opt/automake/bin:/opt/homebrew/opt/libtool/bin:$PATH" LIBEVENT_DIR=/opt/homebrew/opt/libevent \ OPENSSL_DIR=/opt/homebrew/opt/openssl@3 \ ZLIB_DIR=/opt/homebrew/opt/zlib \ bash src/tor/build-libtor.sh - name: Build libi2pd (embedded I2P static lib) # HOMEBREW=1 tells the i2pd Makefile to use Homebrew paths. run: HOMEBREW=1 bash src/i2p/build-libi2pd.sh - name: Build run: cmake --build build -j$(sysctl -n hw.ncpu) - name: Create .app bundle run: | export PATH="/opt/homebrew/opt/qt@5/bin:$PATH" macdeployqt build/bin/Triangles-Qt.app -verbose=1 || \ macdeployqt build/bin/triangles-qt.app -verbose=1 || true - name: Bundle non-Qt dylibs into app run: | # Find the .app bundle APP=$(find build/bin -name "*.app" -maxdepth 1 | head -1) if [ -z "$APP" ]; then echo "No .app bundle found, creating one manually..." APP="build/bin/Triangles-Qt.app" mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Frameworks" cp build/bin/triangles-qt "$APP/Contents/MacOS/Triangles-Qt" fi FRAMEWORKS="$APP/Contents/Frameworks" BINARY=$(find "$APP/Contents/MacOS" -type f -perm +111 | head -1) # Copy Homebrew dylibs that macdeployqt doesn't handle for lib in boost_system boost_filesystem boost_thread boost_program_options boost_chrono; do DYLIB=$(otool -L "$BINARY" | grep "$lib" | awk '{print $1}') if [ -n "$DYLIB" ] && [ -f "$DYLIB" ]; then cp "$DYLIB" "$FRAMEWORKS/" BASENAME=$(basename "$DYLIB") install_name_tool -change "$DYLIB" "@executable_path/../Frameworks/$BASENAME" "$BINARY" fi done for lib in libssl libcrypto libevent libdb_cxx libminiupnpc libsodium; do DYLIB=$(otool -L "$BINARY" | grep "$lib" | awk '{print $1}') if [ -n "$DYLIB" ] && [ -f "$DYLIB" ]; then cp "$DYLIB" "$FRAMEWORKS/" BASENAME=$(basename "$DYLIB") install_name_tool -change "$DYLIB" "@executable_path/../Frameworks/$BASENAME" "$BINARY" fi done echo "=== Final dylib dependencies ===" otool -L "$BINARY" | head -30 - name: Bundle Tor into app # Resilient download: archive.torproject.org occasionally times out # from Azure westus egress (observed 2026-07-03: macOS job exit code 6 # after exactly 30s of curl hang). --retry 3 with --retry-connrefused # handles transient connection refusals and timeouts; --fail-with-body # surfaces HTTP error bodies so the next failure isn't silent. run: | set -euo pipefail TOR_VERSION="15.0.9" TOR_SHA256="8ab84587b09b0053e85a137969b501744fa14640aa126af6e36997189950d254" curl -fSL --connect-timeout 15 --max-time 120 \ --retry 3 --retry-delay 5 --retry-connrefused --retry-all-errors \ "https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/tor-expert-bundle-macos-aarch64-${TOR_VERSION}.tar.gz" \ -o tor-bundle.tar.gz printf '%s %s\n' "$TOR_SHA256" tor-bundle.tar.gz | shasum -a 256 --check - mkdir -p tor-extract && tar -xzf tor-bundle.tar.gz -C tor-extract APP=$(find build/bin -name "*.app" -maxdepth 1 | head -1) mkdir -p "$APP/Contents/MacOS/tor" cp tor-extract/tor/tor "$APP/Contents/MacOS/tor/" chmod +x "$APP/Contents/MacOS/tor/tor" [ -d tor-extract/data ] && cp -r tor-extract/data "$APP/Contents/MacOS/tor/data" - name: Create DMG run: | APP=$(find build/bin -name "*.app" -maxdepth 1 | head -1) mkdir -p dmg_contents cp -R "$APP" dmg_contents/ ln -s /Applications dmg_contents/Applications hdiutil create -volname "Cryptographic Triangles" \ -srcfolder dmg_contents \ -ov -format UDZO \ "Cryptographic-Triangles-v${VERSION}-macos-arm64.dmg" - name: Upload DMG uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: macos-arm64-dmg path: "*.dmg" release: if: startsWith(github.ref, 'refs/tags/v') needs: [build-windows-qt, build-windows-daemon, build-linux-qt, build-linux-daemon, build-macos] runs-on: ubuntu-latest permissions: contents: write steps: - name: Set VERSION from tag run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV - name: Download all artifacts uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: path: artifacts - name: Prepare release assets run: | mkdir -p release # Windows Qt installer (setup.exe — includes Tor, Start Menu shortcuts, uninstaller) cp artifacts/windows-qt-setup/*.exe release/ # Windows Qt portable zip (extract & run — no install required) cp artifacts/windows-qt-zip/*.zip release/ # Windows daemon (zip with DLLs + Tor) cd artifacts/windows-daemon && zip -r "../../release/Cryptographic-Triangles-${VERSION}-win-x64-daemon.zip" . && cd ../.. # Linux Qt .deb (dpkg -i to install — includes Tor, desktop entry, icon) cp artifacts/linux-qt-deb/*.deb release/ # Linux daemon .deb (dpkg -i to install — includes Tor, systemd service) cp artifacts/linux-daemon-deb/*.deb release/ # macOS DMG (drag to Applications — Tor inside .app bundle) cp artifacts/macos-arm64-dmg/*.dmg release/ ls -la release/ - name: Create Release uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: files: release/* generate_release_notes: true trigger-tripi: name: Trigger TRI-PI ARM64 Build # Only fire on tag-push events. To trigger a TRI-PI rebuild after a # release is created via gh API (without re-pushing the tag), use: # curl -X POST .../repos/SamiAhmed7777/tri-pi/dispatches \ # -d '{"event_type":"new-release","client_payload":{"version":"vX.Y.Z","source_repo":"SamiAhmed7777/triangles_v5"}}' if: startsWith(github.ref, 'refs/tags/v') needs: release runs-on: ubuntu-latest steps: - name: Dispatch tri-pi ARM64 build run: | curl -f -X POST \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer ${{ secrets.TRIPI_BUILD_TOKEN }}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/repos/SamiAhmed7777/tri-pi/dispatches \ -d '{"event_type":"new-release","client_payload":{"version":"${{ github.ref_name }}","source_repo":"SamiAhmed7777/triangles_v5"}}' echo "Triggered tri-pi repository_dispatch for ${{ github.ref_name }}"