Files
triangles_v5/.github/workflows/build-all.yml
T
Sami Ahmed 3a4f27132a [grade=B] build(fuzz): wire transaction_deserialize_fuzz target + CI smoke job
The transaction_deserialize_fuzz harness was committed in fab44bb but never
wired into the CMake build or CI. Wire it up:

src/CMakeLists.txt: add a second target inside the BUILD_FUZZ=ON block.
Uses its OWN link wrapper (link_txdeser.sh) because fuzz_script's wrapper
excludes script.cpp.o from triangles_common (fuzz_script recompiles
script.cpp with clang instrumentation). wallet.cpp.o in trianglesd_objects
calls ExtractDestination / SignSignature / Solver / IsMine — all defined in
script.cpp.o — so excluding it produces 'undefined reference' link errors.
The new wrapper excludes only init.cpp.o (which defines daemon main() and
would conflict with libFuzzer's main). fuzz_script continues to use its
original wrapper; both targets build cleanly with -DBUILD_FUZZ=ON.

.github/workflows/build-all.yml: add test-fuzz-smoke-tx job mirroring
test-fuzz-smoke but for the new target. Runs the fuzzer for 5 minutes
on a fresh empty corpus with ASan+UBSan+libFuzzer, fails the PR if any
crash artifacts are produced.

Verified locally with -DCMAKE_C_COMPILER=clang-15 -DCMAKE_CXX_COMPILER=clang++-15:
- transaction_deserialize_fuzz links cleanly and runs (smoke: 191781 inline
  8-bit counters, 7 NEW_FUNC in 20s)
- fuzz_script continues to build and link (existing target unbroken)

Codex verdict: urn:ump:exyiqbu7gdr2eow5b4osh67xiaserhfg6cz74pnzgwrwj6xr7ypa (grade B)
2026-07-31 20:54:52 -07:00

1112 lines
48 KiB
YAML

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/
test-fuzz-smoke-tx:
# libFuzzer smoke test for src/test/fuzz/transaction_deserialize_fuzz.cpp.
# Mirrors test-fuzz-smoke but exercises CTransaction deserialization
# instead of the script interpreter. Any crash is uploaded as an artifact
# and the job fails — fuzz regressions must block the PR.
# See src/test/fuzz/transaction_deserialize_fuzz.cpp 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.
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 transaction_deserialize_fuzz
# 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.
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.
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 transaction_deserialize_fuzz
# CMake target is named `transaction_deserialize_fuzz` (matches
# add_custom_target(transaction_deserialize_fuzz ...) in src/CMakeLists.txt).
run: cmake --build build-fuzz --target transaction_deserialize_fuzz -j$(nproc)
- 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.
# The transaction_deserialize_fuzz target does not need a seed
# corpus — it accepts arbitrary bytes as a transaction payload.
run: |
mkdir -p build-fuzz/fuzz_artifacts_tx build-fuzz/fuzz_corpus_tx
set +e
./build-fuzz/bin/transaction_deserialize_fuzz \
-max_total_time=300 \
-max_len=200000 \
-artifact_prefix=build-fuzz/fuzz_artifacts_tx/ \
build-fuzz/fuzz_corpus_tx/ \
2>&1 | tee build-fuzz/fuzz_log.txt
FUZZ_EXIT=${PIPESTATUS[0]}
set -e
if [ -n "$(ls -A build-fuzz/fuzz_artifacts_tx/ 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-tx
path: build-fuzz/fuzz_artifacts_tx/
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 <dev@cryptographic-triangles.org>
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 }}"