mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Three desktop targets, three different states, one destination. macOS did not build at all. There was no workflow, only Cake's from-source scripts and an Xcode project. It turns out most of the work was already done: monero_c ships aarch64-apple-darwin and x86_64-apple-darwin, and Runner.xcodeproj already references both dylibs with CodeSignOnCopy. So this fetches the same prebuilt bundle the iOS and Android legs use, lipos the two slices into universal libraries, and builds. One download serves Apple Silicon and Intel, because a single-arch build looks correct on the machine that made it and fails on half of everyone else's. It signs with a Developer ID Application certificate, which is NOT the Apple Distribution certificate the TestFlight workflow uses. They are not interchangeable: an App Store cert on a directly downloaded app is refused by Gatekeeper. Signing is inside-out by hand rather than --deep, which Apple deprecated and which skips nested code it does not recognise. Then notarytool with the App Store Connect key this repository already has, then stapler, so Gatekeeper clears the app without a network round trip. Every credential is optional and the preflight says, before spending forty minutes, exactly which stages the run can reach and what the user will see if it cannot reach them. Absent secrets downgrade the build loudly; they do not fail it and they do not pass silently. Windows and Linux already built. Windows already Authenticode-signs through the Fleet broker. What both lacked was a way to get the artifact to anybody: upload-artifact is the only publication and Gitea Actions artifacts here upload and then list as zero, so the signed Windows zip has never been reachable. Both now publish to the GitHub release, on the smirk pattern: tag-triggered, the release must already exist so a half-populated one never goes public, and a same-named asset from an earlier attempt is replaced rather than duplicated. Windows does its API work in PowerShell because git-bash there has neither python nor node guaranteed and ConvertFrom-Json is native. Tags now build automatically on all three. A tag is the thing a release is cut from, and the artifact that gets signed should come from it rather than from whatever was dispatched by hand. scripts/release/sign-release.sh detach-signs the collected artifacts and signs SHA256SUMS, which is the signature that matters when a download reaches someone from a mirror without its .asc. It verifies even immediately after signing: a signature written but never checked invites trust it has not earned. Not done here, and both need a human: GH_RELEASE_TOKEN does not exist in this repository's Gitea secrets, and the release key's user ID still says Smirk.
652 lines
32 KiB
YAML
652 lines
32 KiB
YAML
name: Hash Bags Windows build
|
|
|
|
# Phase 1 of Windows CI: produce an unsigned Windows release bundle (.zip of
|
|
# the Flutter Release output + MSVC runtime DLLs). Drop the zip on any Win
|
|
# 10/11 box, extract, run hash_wallet.exe.
|
|
#
|
|
# Phase 2 (separate iteration): wire up Inno Setup installer once the
|
|
# scripts/windows/build_exe_installer.iss file is rebranded from Cake Wallet
|
|
# → Hash Bags.
|
|
|
|
on:
|
|
# Manual, or a release tag. workflow_dispatch runs as the triggering user, so
|
|
# secrets are always available (PR triggers from forks would strip them — not
|
|
# relevant here since the runner is self-hosted and listening on a
|
|
# self-hosted Gitea). A tag builds automatically because a tag is the thing a
|
|
# release is cut from, and the signed artifact should come from it rather
|
|
# than from whatever happened to be dispatched by hand.
|
|
push:
|
|
tags:
|
|
- 'hash-bags-*'
|
|
workflow_dispatch:
|
|
inputs:
|
|
release_tag:
|
|
description: >-
|
|
Existing release tag to publish this build to (e.g. hash-bags-1.0.3).
|
|
Leave empty to build without publishing. The release must already
|
|
exist; this never creates one.
|
|
required: false
|
|
default: ''
|
|
|
|
# Cancel in-flight runs when a newer commit lands on the same branch.
|
|
concurrency:
|
|
group: windows-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
defaults:
|
|
run:
|
|
# Explicit Git Bash path (not `shell: bash`) because Windows resolves
|
|
# bare `bash` to C:\Windows\System32\bash.exe (WSL), which refuses to
|
|
# run when the act_runner service is hosted under LocalSystem:
|
|
# "Running WSL as local system is not supported."
|
|
# Git Bash works fine under LocalSystem and matches how GitHub's
|
|
# hosted windows-latest runner invokes bash.
|
|
shell: '"C:\Program Files\Git\bin\bash.exe" --noprofile --norc -eo pipefail {0}'
|
|
|
|
jobs:
|
|
build:
|
|
# Matches the label our act_runner.exe advertised: "windows:host" → executor=host.
|
|
runs-on: windows
|
|
env:
|
|
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
|
# Belt-and-suspenders fallback that bypasses Node TLS verification for
|
|
# actions/* against the self-hosted runner's internal git host. Same
|
|
# fallback as build-ios-sim.yml.
|
|
NODE_TLS_REJECT_UNAUTHORIZED: '0'
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
# ---- Git config for LocalSystem ---------------------------------------
|
|
# The runner service runs as LocalSystem, which has no global git
|
|
# identity. scripts/monero_c/apply_patches.sh (and a few other
|
|
# submodule operations) do `git commit`-equivalents that need
|
|
# user.email + user.name set. Also pre-empt git's "safe.directory"
|
|
# check, which fires when the checkout dir owner != runner identity.
|
|
- name: Configure git identity + safe.directory
|
|
run: |
|
|
git config --global --add safe.directory '*'
|
|
git config --global user.email "ci@suchsoftware.com"
|
|
git config --global user.name "Hash Bags CI"
|
|
git config --global --list
|
|
|
|
# ---- Rust toolchain ---------------------------------------------------
|
|
# The dnssec_proof plugin builds its native lib through cargokit
|
|
# (windows/CMakeLists.txt -> apply_cargokit), so `flutter build windows`
|
|
# genuinely needs cargo/rustc — the prebuilt monero_c bundle doesn't
|
|
# cover it.
|
|
#
|
|
# setup-windows-runner.ps1 installs Rust to C:\Rust and puts
|
|
# C:\Rust\cargo\bin on the *Machine* PATH, but the act_runner service
|
|
# only picks up Machine PATH changes when it restarts, and a rebuilt or
|
|
# re-imaged box loses it entirely. Either way the job died at exit 127
|
|
# ("rustc: command not found"). Locate it ourselves, and install it if
|
|
# it really isn't there, so CI doesn't depend on the box's env being
|
|
# right. No-op when rustc is already on PATH.
|
|
- name: Ensure Rust toolchain is on PATH
|
|
run: |
|
|
set -e
|
|
if command -v rustc >/dev/null 2>&1 && command -v cargo >/dev/null 2>&1; then
|
|
echo "Rust already on PATH: $(command -v rustc)"
|
|
exit 0
|
|
fi
|
|
|
|
# Known install locations, in preference order. First is where
|
|
# setup-windows-runner.ps1 puts it; the rest cover a rustup-init
|
|
# run under a normal user account.
|
|
CANDIDATES=(
|
|
"/c/Rust/cargo/bin"
|
|
"$USERPROFILE/.cargo/bin"
|
|
"/c/Users/Administrator/.cargo/bin"
|
|
"/c/Windows/System32/config/systemprofile/.cargo/bin"
|
|
)
|
|
for d in "${CANDIDATES[@]}"; do
|
|
if [[ -x "$d/rustc.exe" ]]; then
|
|
echo "Found Rust at $d — adding to PATH for later steps"
|
|
export PATH="$d:$PATH"
|
|
# $GITHUB_PATH wants a Windows-style path; MSYS converts the
|
|
# resulting semicolon PATH back to POSIX form on shell start.
|
|
cygpath -w "$d" >> "$GITHUB_PATH"
|
|
# rustup's shims resolve toolchains via RUSTUP_HOME; without it
|
|
# they look in %USERPROFILE%\.rustup, which is empty for the
|
|
# LocalSystem service account.
|
|
if [[ -d "$(dirname "$(dirname "$d")")/rustup" ]]; then
|
|
RUSTUP_DIR="$(dirname "$(dirname "$d")")/rustup"
|
|
# Export for THIS step as well as writing to $GITHUB_ENV.
|
|
# $GITHUB_ENV only reaches later steps, and the verification on
|
|
# the next line is itself a rustup shim: without the export it
|
|
# falls back to %USERPROFILE%\.rustup, which on this runner is
|
|
# a file rather than a directory, so it fails with os error 183
|
|
# having ignored the toolchain we just found.
|
|
RUSTUP_HOME="$(cygpath -w "$RUSTUP_DIR")"
|
|
CARGO_HOME="$(cygpath -w "$(dirname "$d")")"
|
|
export RUSTUP_HOME CARGO_HOME
|
|
echo "RUSTUP_HOME=$RUSTUP_HOME" >> "$GITHUB_ENV"
|
|
echo "CARGO_HOME=$CARGO_HOME" >> "$GITHUB_ENV"
|
|
fi
|
|
rustc --version
|
|
exit 0
|
|
fi
|
|
done
|
|
|
|
echo "Rust not found in any known location — installing to C:\\Rust"
|
|
CARGO_HOME="C:\\Rust\\cargo"
|
|
RUSTUP_HOME="C:\\Rust\\rustup"
|
|
export CARGO_HOME RUSTUP_HOME
|
|
mkdir -p /c/Rust/cargo /c/Rust/rustup
|
|
# act_runner sets RUNNER_TEMP, but don't hard-depend on it.
|
|
DL_DIR="${RUNNER_TEMP:-/c/Rust}"
|
|
curl -fsSL -o "$DL_DIR/rustup-init.exe" https://win.rustup.rs/x86_64
|
|
"$DL_DIR/rustup-init.exe" -y \
|
|
--default-toolchain stable \
|
|
--profile minimal \
|
|
--target x86_64-pc-windows-msvc \
|
|
--no-modify-path
|
|
echo "C:\\Rust\\cargo\\bin" >> "$GITHUB_PATH"
|
|
echo "CARGO_HOME=$CARGO_HOME" >> "$GITHUB_ENV"
|
|
echo "RUSTUP_HOME=$RUSTUP_HOME" >> "$GITHUB_ENV"
|
|
/c/Rust/cargo/bin/rustc --version
|
|
|
|
# ---- Windows Developer Mode (symlink support) ------------------------
|
|
# `flutter build windows` materialises each plugin under
|
|
# windows/flutter/ephemeral/.plugin_symlinks/ as a real NTFS symlink, and
|
|
# creating one needs SeCreateSymbolicLinkPrivilege. Flutter doesn't test
|
|
# the privilege directly — it reads the AppModelUnlock registry key and
|
|
# bails with:
|
|
# "Building with plugins requires symlink support.
|
|
# Please enable Developer Mode in your system settings."
|
|
# Setting the key is exactly what the Settings toggle does; HKLM is
|
|
# writable by the LocalSystem service account, so no interactive login
|
|
# is involved. Idempotent — a no-op once the value is 1.
|
|
- name: Enable Developer Mode (symlink support for plugin builds)
|
|
run: |
|
|
set -e
|
|
powershell -NoProfile -Command '
|
|
$key = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock"
|
|
if (-not (Test-Path $key)) { New-Item -Path $key -Force | Out-Null }
|
|
$cur = (Get-ItemProperty -Path $key -Name AllowDevelopmentWithoutDevLicense -ErrorAction SilentlyContinue).AllowDevelopmentWithoutDevLicense
|
|
if ($cur -eq 1) {
|
|
Write-Host "Developer Mode already enabled"
|
|
} else {
|
|
Set-ItemProperty -Path $key -Name AllowDevelopmentWithoutDevLicense -Value 1 -Type DWord
|
|
Write-Host "Developer Mode enabled (was: $cur)"
|
|
}
|
|
'
|
|
|
|
# ---- Flutter, pinned -------------------------------------------------
|
|
# Every other platform builds on 3.32.0: Linux and Android inside the
|
|
# cake_wallet debian13-flutter3.32.0 image, iOS by installing it
|
|
# explicitly. Windows was the one that took whatever the runner happened
|
|
# to have, and on 2026-09-19 that was 3.38.5 / Dart 3.10.4. Dart 3.10
|
|
# removed the _macros package, so `flutter pub get` could not solve
|
|
# hive_generator against build_resolvers and the build died at exit 69
|
|
# long before it compiled anything. A build machine's drifting toolchain
|
|
# must not decide which dependencies resolve.
|
|
- name: Require Flutter 3.32.0
|
|
run: |
|
|
set -e
|
|
ver="$(flutter --version 2>/dev/null | head -1 || true)"
|
|
echo "runner has: ${ver:-<no flutter on PATH>}"
|
|
case "$ver" in
|
|
*3.32.0*) exit 0 ;;
|
|
esac
|
|
cat >&2 <<'MSG'
|
|
|
|
Refusing to build: this runner's Flutter is not the pinned 3.32.0.
|
|
|
|
Every other platform is pinned, Linux and Android by the
|
|
cake_wallet debian13-flutter3.32.0 image and iOS by installing it,
|
|
so a drifted Windows SDK breaks this platform alone and does it
|
|
during dependency resolution rather than at compile time.
|
|
|
|
Fix it on the runner rather than here. Installing an SDK per build
|
|
would download a gigabyte to paper over a machine that provisioning
|
|
is supposed to own, and subosito/flutter-action cannot do it anyway:
|
|
its setup script needs jq, which this runner does not have.
|
|
|
|
powershell -File scripts\windows\setup-windows-runner.ps1
|
|
|
|
That now checks the version rather than only that flutter.bat
|
|
exists, which is why it had been reporting a drifted SDK as done.
|
|
MSG
|
|
exit 1
|
|
|
|
# ---- Toolchain sanity ------------------------------------------------
|
|
# Our self-hosted runner has Flutter / Rust / VS Build Tools / Inno
|
|
# Setup pre-installed (see scripts/windows/setup-windows-runner.ps1).
|
|
# This step just prints versions so failures are easy to diagnose.
|
|
- name: Show installed toolchain versions
|
|
run: |
|
|
set -x
|
|
# PATH may need refreshing after install — refresh from the registry.
|
|
# Git Bash inherits Windows PATH at shell start, so just printing
|
|
# `which` is enough.
|
|
which flutter dart cargo rustc git 2>/dev/null || true
|
|
flutter --version
|
|
dart --version
|
|
rustc --version
|
|
cargo --version
|
|
|
|
# ---- External path-dep plugins ----------------------------------------
|
|
# Several plugins are path-deps in pubspec.yaml. The path directories
|
|
# must exist BEFORE `flutter pub get` runs against the regenerated
|
|
# pubspec, or pub fails with "could not find package X at <path>".
|
|
#
|
|
# The Android + iOS + Linux workflows use upstream-published .tar.gz
|
|
# tarballs. We don't, because those tarballs contain example/*
|
|
# symlinks pointing to absolute /home/runner/.pub-cache/ paths
|
|
# (artifacts from the upstream build host) — Linux tar tolerates
|
|
# dangling symlinks, Windows tar bails the whole extraction. Clone
|
|
# the source repos at the matching tagged versions instead. Same
|
|
# pattern as bitbox_flutter further down.
|
|
|
|
- name: Clone torch_dart at v1.0.17
|
|
run: |
|
|
set -x -e
|
|
pushd scripts
|
|
rm -rf torch_dart
|
|
git clone --depth 1 --branch v1.0.17 https://github.com/MrCyjaneK/torch_dart
|
|
popd
|
|
|
|
- name: Clone reown_flutter at v0.0.4
|
|
run: |
|
|
set -x -e
|
|
pushd scripts
|
|
rm -rf reown_flutter
|
|
git clone --depth 1 --branch v0.0.4 https://github.com/cake-tech/reown_flutter
|
|
popd
|
|
|
|
- name: Clone BitBox Flutter
|
|
run: |
|
|
# Pubspec has bitbox_flutter as a path dep — the directory must
|
|
# exist for pub get even if BitBox Windows support isn't
|
|
# functional. Same approach as the iOS workflow: clone the repo
|
|
# at a pinned commit, don't run any Android-binding build step.
|
|
set -x -e
|
|
pushd scripts
|
|
if [[ ! -d bitbox_flutter ]]; then
|
|
git clone https://github.com/konstantinullrich/bitbox_flutter
|
|
fi
|
|
cd bitbox_flutter
|
|
git fetch -a
|
|
git reset --hard
|
|
git checkout 5a6e6dd388ef64003f86094af80d5453518b601d
|
|
git reset --hard
|
|
popd
|
|
|
|
# ---- Native crypto cores (monero_c prebuilt bundle) ------------------
|
|
# The same release-bundle.zip that Android + iOS use. Contains
|
|
# pre-cross-compiled native libs for many target triples. We'll need
|
|
# the x86_64-w64-mingw32 (Windows MinGW) or x86_64-pc-windows-msvc
|
|
# entries — inspection step below logs what's actually shipped.
|
|
- name: Fetch prebuilt monero_c bundle
|
|
run: |
|
|
set -x -e
|
|
./scripts/prepare_moneroc.sh
|
|
MONERO_C_TAG=$(cd scripts/monero_c && git describe --tags)
|
|
echo "monero_c TAG: $MONERO_C_TAG"
|
|
mkdir -p "scripts/monero_c/release/$MONERO_C_TAG"
|
|
pushd "scripts/monero_c/release/$MONERO_C_TAG"
|
|
curl -fsSL -O https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC1/release-bundle.zip
|
|
unzip -q release-bundle.zip
|
|
rm release-bundle.zip
|
|
echo "=== bundle contents (top level) ==="
|
|
ls
|
|
popd
|
|
|
|
- name: Inspect Windows targets in monero_c bundle
|
|
run: |
|
|
set -x
|
|
MONERO_C_TAG=$(cd scripts/monero_c && git describe --tags)
|
|
BUNDLE_DIR="scripts/monero_c/release/$MONERO_C_TAG"
|
|
echo "=== full mingw32 dir contents ==="
|
|
ls -la "$BUNDLE_DIR/x86_64-w64-mingw32" 2>/dev/null || true
|
|
|
|
# ---- Stage monero_c DLLs into the layout windows/CMakeLists.txt expects
|
|
# Upstream Cake's Windows build cross-compiles monero_c on Linux via
|
|
# scripts/windows/build_all.sh, which lays files out as:
|
|
# scripts/monero_c/release/<coin>/<triple>_<basename>.dll
|
|
# The CMakeLists.txt at windows/CMakeLists.txt:84-94 hardcodes those
|
|
# paths in install(FILES ...) rules — it copies + RENAMEs them to the
|
|
# final names (monero_libwallet2_api_c.dll etc.) in the Release dir.
|
|
#
|
|
# We're using the prebuilt bundle from MrCyjaneK's release, which has
|
|
# a different layout:
|
|
# scripts/monero_c/release/<TAG>/<triple>/lib<coin>_wallet2_api_c.dll
|
|
# So we restage the bundle's files into the upstream-build-all-style
|
|
# layout before flutter build windows runs.
|
|
- name: Stage monero_c bundle into upstream cross-compile layout
|
|
run: |
|
|
set -x -e
|
|
MONERO_C_TAG=$(cd scripts/monero_c && git describe --tags)
|
|
SRC="scripts/monero_c/release/$MONERO_C_TAG/x86_64-w64-mingw32"
|
|
MONERO_DST="scripts/monero_c/release/monero"
|
|
WOWNERO_DST="scripts/monero_c/release/wownero"
|
|
mkdir -p "$MONERO_DST" "$WOWNERO_DST"
|
|
|
|
# The wallet C-API DLLs, one per coin
|
|
cp -v "$SRC/libmonero_wallet2_api_c.dll" "$MONERO_DST/x86_64-w64-mingw32_libwallet2_api_c.dll"
|
|
cp -v "$SRC/libwownero_wallet2_api_c.dll" "$WOWNERO_DST/x86_64-w64-mingw32_libwallet2_api_c.dll"
|
|
|
|
# MinGW runtime DLLs (libssp-0, libwinpthread-1) — the wallet libs
|
|
# are dynamically linked against these. MrCyjaneK's prebuilt
|
|
# release-bundle.zip does NOT ship them (only the wallet *.dll +
|
|
# *.dll.a import libs), so we source them from Git Bash's bundled
|
|
# MinGW-w64 distribution, which ships the same x86_64-w64-mingw32
|
|
# ABI used to build the wallet libs.
|
|
# ORDERING MATTERS: monero_c's prebuilt wallet DLLs are built against
|
|
# recent MinGW-w64 (post-2022 winpthreads ABI with pthread_cond_timedwait64).
|
|
# Flutter's bundled mingit ships a 2017-vintage libwinpthread that's
|
|
# missing newer symbols and crashes wallet creation at runtime. Prefer
|
|
# Git for Windows' mingw64/bin which is updated frequently.
|
|
RUNTIME_PATHS=(
|
|
"/c/Program Files/Git/mingw64/bin" # Git for Windows — modern MinGW runtime (preferred)
|
|
"$SRC" # monero_c bundle (in case it grows DLLs)
|
|
"/c/ProgramData/chocolatey/lib/mingw/tools/install/mingw64/bin" # choco install -y mingw
|
|
"/c/flutter/bin/mingit/mingw64/bin" # Flutter's bundled mingit — old (2017), use only as last resort
|
|
"/mingw64/bin" # MSYS2 fallback
|
|
)
|
|
for runtime in libssp-0.dll libwinpthread-1.dll; do
|
|
src=""
|
|
for d in "${RUNTIME_PATHS[@]}"; do
|
|
if [[ -f "$d/$runtime" ]]; then
|
|
src="$d/$runtime"
|
|
break
|
|
fi
|
|
done
|
|
if [[ -z "$src" ]]; then
|
|
echo "FATAL: $runtime not found in any of:"
|
|
printf ' %s\n' "${RUNTIME_PATHS[@]}"
|
|
for d in "${RUNTIME_PATHS[@]}"; do
|
|
echo "--- contents of $d (if exists) ---"
|
|
ls "$d" 2>/dev/null | head -20 || echo "(missing)"
|
|
done
|
|
exit 1
|
|
fi
|
|
cp -v "$src" "$MONERO_DST/x86_64-w64-mingw32_${runtime}"
|
|
done
|
|
|
|
echo "=== final staged layout ==="
|
|
ls -la "$MONERO_DST/" "$WOWNERO_DST/"
|
|
|
|
# ---- Configure: pubspec.yaml + per-coin enablement -------------------
|
|
# Mirror of hashwallet.bat — drives tool/configure.dart with the same
|
|
# set of coin flags that upstream Cake's Windows build uses.
|
|
- name: Configure pubspec + Wallet types
|
|
run: |
|
|
set -x -e
|
|
cp -f pubspec_description.yaml pubspec.yaml
|
|
flutter pub get
|
|
dart run tool/generate_pubspec.dart
|
|
flutter pub get
|
|
dart run tool/configure.dart \
|
|
--monero --bitcoin --ethereum --polygon --nano --bitcoinCash \
|
|
--wownero --dogecoin --base --arbitrum --bsc
|
|
|
|
# ---- Secrets ---------------------------------------------------------
|
|
- name: Generate per-module secrets.g.dart files (empty defaults)
|
|
run: dart run tool/generate_new_secrets.dart
|
|
|
|
|
|
- name: Pin crypto secrets (never ship random salts)
|
|
env:
|
|
CRYPTO_SALT: ${{ secrets.CRYPTO_SALT }}
|
|
CRYPTO_KEYCHAIN_SALT: ${{ secrets.CRYPTO_KEYCHAIN_SALT }}
|
|
CRYPTO_KEY: ${{ secrets.CRYPTO_KEY }}
|
|
CRYPTO_WALLET_SALT: ${{ secrets.CRYPTO_WALLET_SALT }}
|
|
CRYPTO_SHORT_KEY: ${{ secrets.CRYPTO_SHORT_KEY }}
|
|
CRYPTO_BACKUP_SALT: ${{ secrets.CRYPTO_BACKUP_SALT }}
|
|
CRYPTO_BACKUP_KEYCHAIN_SALT: ${{ secrets.CRYPTO_BACKUP_KEYCHAIN_SALT }}
|
|
CRYPTO_WALLET_GROUP_SALT: ${{ secrets.CRYPTO_WALLET_GROUP_SALT }}
|
|
run: |
|
|
set -e
|
|
# generate_new_secrets.dart mints RANDOM salts each run. Shipping
|
|
# those breaks decryption of everything the previous build stored
|
|
# (2026-08-29 "Corrupted seeds" incident). Overwrite with the pinned
|
|
# values and refuse to build if any is missing.
|
|
for v in CRYPTO_SALT CRYPTO_KEYCHAIN_SALT CRYPTO_KEY CRYPTO_WALLET_SALT CRYPTO_SHORT_KEY CRYPTO_BACKUP_SALT CRYPTO_BACKUP_KEYCHAIN_SALT CRYPTO_WALLET_GROUP_SALT; do
|
|
eval "val=\${$v:-}"
|
|
[ -n "$val" ] || { echo "FATAL: $v is empty — refusing to ship random crypto secrets"; exit 1; }
|
|
done
|
|
sed -i.bak \
|
|
-e "s|const salt = '.*';|const salt = '${CRYPTO_SALT}';|" \
|
|
-e "s|const keychainSalt = '.*';|const keychainSalt = '${CRYPTO_KEYCHAIN_SALT}';|" \
|
|
-e "s|const key = '.*';|const key = '${CRYPTO_KEY}';|" \
|
|
-e "s|const walletSalt = '.*';|const walletSalt = '${CRYPTO_WALLET_SALT}';|" \
|
|
-e "s|const shortKey = '.*';|const shortKey = '${CRYPTO_SHORT_KEY}';|" \
|
|
-e "s|const backupSalt = '.*';|const backupSalt = '${CRYPTO_BACKUP_SALT}';|" \
|
|
-e "s|const backupKeychainSalt = '.*';|const backupKeychainSalt = '${CRYPTO_BACKUP_KEYCHAIN_SALT}';|" \
|
|
-e "s|const walletGroupSalt = '.*';|const walletGroupSalt = '${CRYPTO_WALLET_GROUP_SALT}';|" \
|
|
lib/.secrets.g.dart
|
|
rm -f lib/.secrets.g.dart.bak
|
|
for c in salt keychainSalt key walletSalt shortKey backupSalt backupKeychainSalt walletGroupSalt; do
|
|
grep -qE "^const $c = '[0-9a-f]{8,}';" lib/.secrets.g.dart || { echo "FATAL: $c not pinned"; exit 1; }
|
|
done
|
|
echo "crypto secrets pinned (values not printed)"
|
|
- name: Inject Trocador affiliate secrets into lib/.secrets.g.dart
|
|
env:
|
|
TROCADOR_API_KEY: ${{ secrets.TROCADOR_API_KEY }}
|
|
TROCADOR_MONERO_API_KEY: ${{ secrets.TROCADOR_MONERO_API_KEY }}
|
|
TROCADOR_EXCHANGE_MARKUP: ${{ secrets.TROCADOR_EXCHANGE_MARKUP }}
|
|
run: |
|
|
# Length-only debug visibility — never prints actual secrets.
|
|
echo "TROCADOR_API_KEY length: ${#TROCADOR_API_KEY}"
|
|
echo "TROCADOR_MONERO_API_KEY length: ${#TROCADOR_MONERO_API_KEY}"
|
|
echo "TROCADOR_EXCHANGE_MARKUP length: ${#TROCADOR_EXCHANGE_MARKUP}"
|
|
if [[ -z "$TROCADOR_API_KEY" ]]; then
|
|
echo "WARN: TROCADOR_API_KEY not reaching runner — build will proceed"
|
|
echo " but Trocador exchange features won't work in this binary."
|
|
fi
|
|
sed -i \
|
|
-e "s|const trocadorApiKey = '';|const trocadorApiKey = '${TROCADOR_API_KEY}';|" \
|
|
-e "s|const trocadorMoneroApiKey = '';|const trocadorMoneroApiKey = '${TROCADOR_MONERO_API_KEY}';|" \
|
|
-e "s|const trocadorExchangeMarkup = '';|const trocadorExchangeMarkup = '${TROCADOR_EXCHANGE_MARKUP:-1}';|" \
|
|
lib/.secrets.g.dart
|
|
grep '^const trocador' lib/.secrets.g.dart
|
|
|
|
# ---- Codegen ---------------------------------------------------------
|
|
- name: Build generated code (mobx + hive adapters)
|
|
run: bash model_generator.sh
|
|
|
|
- name: Generate localization
|
|
run: dart run tool/generate_localization.dart
|
|
|
|
- name: Compile SVG assets (res/pictures/*.svg → assets/new-ui/*.svg.vec)
|
|
run: ./compile_graphics.sh
|
|
|
|
# ---- Build the Windows .exe ------------------------------------------
|
|
- name: Build Windows release
|
|
run: |
|
|
set -x -e
|
|
flutter config --enable-windows-desktop
|
|
flutter build windows --dart-define-from-file=env.json --release --verbose
|
|
|
|
# ---- Reconcile monero/wownero DLL naming for Dart FFI ----------------
|
|
# windows/CMakeLists.txt's install(FILES ... RENAME ...) rules produce
|
|
# <coin>_libwallet2_api_c.dll, but package:monero's Dart FFI loader
|
|
# (mrcyjanek's package) opens lib<coin>_wallet2_api_c.dll — different
|
|
# filename layout, runtime error "Failed to load dynamic library".
|
|
# Create the lib-prefixed copies so both name conventions resolve.
|
|
- name: Mirror monero/wownero DLLs under lib-prefixed names
|
|
run: |
|
|
set -x -e
|
|
DST="build/windows/x64/runner/Release"
|
|
for coin in monero wownero; do
|
|
src="$DST/${coin}_libwallet2_api_c.dll"
|
|
dst="$DST/lib${coin}_wallet2_api_c.dll"
|
|
if [[ -f "$src" ]]; then
|
|
cp -v "$src" "$dst"
|
|
else
|
|
echo "WARN: $src not in Release dir — CMake install may have skipped it"
|
|
ls "$DST" | head -20
|
|
fi
|
|
done
|
|
|
|
# ---- Bundle MSVC runtime DLLs with the .exe --------------------------
|
|
# Standalone Windows builds need msvcp140.dll + vcruntime140.dll +
|
|
# vcruntime140_1.dll next to the .exe (or installed via vc_redist on
|
|
# the user's machine). Bundling avoids the "VCRUNTIME140.dll not found"
|
|
# error on machines without VC++ redistributable installed.
|
|
- name: Copy MSVC runtime DLLs next to the .exe
|
|
run: |
|
|
set -x -e
|
|
# Find the redist dir for the currently-installed MSVC version (the
|
|
# version number rolls forward with VS updates, so do NOT hardcode).
|
|
REDIST_BASE="/c/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools/VC/Redist/MSVC"
|
|
REDIST_DIR=$(find "$REDIST_BASE" -type d -name 'Microsoft.VC*.CRT' -path '*/x64/*' 2>/dev/null | sort | tail -1)
|
|
if [[ -z "$REDIST_DIR" ]]; then
|
|
echo "FATAL: could not find VC redist dir under $REDIST_BASE"
|
|
ls "$REDIST_BASE" 2>/dev/null || echo "(redist base dir missing entirely)"
|
|
exit 1
|
|
fi
|
|
echo "Using redist dir: $REDIST_DIR"
|
|
cp -v "$REDIST_DIR/msvcp140.dll" "$REDIST_DIR/vcruntime140.dll" "$REDIST_DIR/vcruntime140_1.dll" \
|
|
build/windows/x64/runner/Release/
|
|
|
|
# ---- Authenticode sign the launcher ----------------------------------
|
|
# This workflow already runs on the machine with the EV code-signing
|
|
# token in it, but the runner (NT SERVICE\act_runner) cannot use the
|
|
# token: SafeNet holds the login per user and an operator establishes it
|
|
# interactively. So the exe is queued for the Fleet-installed signing
|
|
# broker (such-fleet roles/windows_code_signing), which signs it in the
|
|
# operator's session. CI never holds the key or the token password, so it
|
|
# cannot spend one of the token's three password attempts.
|
|
#
|
|
# Only HashWallet.exe is signed. The bundled MSVC runtime DLLs are
|
|
# already Microsoft-signed and must not be re-signed, and the plugin DLLs
|
|
# are loaded by an already-trusted process -- it is the launcher the user
|
|
# double-clicks that SmartScreen judges.
|
|
- name: Sign HashWallet.exe with the hardware token
|
|
shell: powershell
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
$release = "build\windows\x64\runner\Release"
|
|
$exe = Join-Path $release 'HashWallet.exe'
|
|
if (-not (Test-Path $exe)) { throw "no HashWallet.exe at $exe" }
|
|
|
|
if (Test-Path 'C:\signing\SIGNING_LOCKED') {
|
|
Get-Content 'C:\signing\SIGNING_LOCKED'
|
|
throw "The signing broker's circuit breaker is tripped. An operator must clear it."
|
|
}
|
|
|
|
$queued = "hashbags-$env:GITHUB_RUN_ID.exe"
|
|
$inPath = "C:\signing\in\$queued"
|
|
$outPath = "C:\signing\out\$queued"
|
|
Copy-Item $exe $inPath -Force
|
|
Set-Content -LiteralPath "$inPath.ready" -Value $env:GITHUB_RUN_ID
|
|
Write-Host "queued $queued"
|
|
|
|
$deadline = (Get-Date).AddMinutes(10)
|
|
while ((Get-Date) -lt $deadline) {
|
|
if (Test-Path "$outPath.done") { break }
|
|
if (Test-Path 'C:\signing\SIGNING_LOCKED') {
|
|
Get-Content 'C:\signing\SIGNING_LOCKED'
|
|
throw "The broker tripped its circuit breaker while signing this artifact."
|
|
}
|
|
Start-Sleep -Seconds 5
|
|
}
|
|
if (-not (Test-Path "$outPath.done")) { throw "Timed out waiting for the signing broker." }
|
|
|
|
Move-Item $outPath $exe -Force
|
|
Remove-Item "$outPath.done" -Force -ErrorAction SilentlyContinue
|
|
|
|
# Verify on the binary that actually ships. A bad signature or a
|
|
# missing timestamp blocks the release.
|
|
$sig = Get-AuthenticodeSignature $exe
|
|
$sig | Format-List Status, StatusMessage,
|
|
@{n='Signer';e={$_.SignerCertificate.Subject}},
|
|
@{n='TimeStamper';e={$_.TimeStamperCertificate.Subject}}
|
|
if ($sig.Status -ne 'Valid') { throw "Authenticode status is $($sig.Status)" }
|
|
if (-not $sig.TimeStamperCertificate) { throw "Signature carries no RFC 3161 timestamp" }
|
|
if ($sig.SignerCertificate.Subject -notmatch 'Such Software LLC') {
|
|
throw "Unexpected signer: $($sig.SignerCertificate.Subject)"
|
|
}
|
|
Write-Host "SHA256 $((Get-FileHash $exe -Algorithm SHA256).Hash)"
|
|
|
|
# ---- Package + upload ------------------------------------------------
|
|
- name: List built artifacts (for debug visibility)
|
|
run: |
|
|
ls -la build/windows/x64/runner/Release/
|
|
|
|
# ---- Package + upload as zip -----------------------------------------
|
|
# The Flutter Windows build produces a directory tree (~500 files —
|
|
# HashWallet.exe + plugin DLLs + data/ + locales/ + bundled monero_c
|
|
# + MinGW runtime DLLs). Ship as a zip; users extract and run.
|
|
#
|
|
# Tried wrapping with 7-Zip SFX for a single-file "portable" .exe
|
|
# earlier but path_provider_windows computes the wallet storage dir
|
|
# from the .exe location, and SFX extracts to a different %TEMP%
|
|
# subdir on every launch — wallet keys couldn't be found across
|
|
# runs. Multi-file zip is what Firefox Portable / VS Code Portable
|
|
# / every other Flutter Windows desktop app does too.
|
|
- name: Create Hash Bags Windows release zip
|
|
run: |
|
|
set -x -e
|
|
cd build/windows/x64/runner/Release
|
|
# PowerShell's Compress-Archive is in PATH; no extra deps needed.
|
|
powershell -NoProfile -Command "Compress-Archive -Path .\* -DestinationPath ..\..\..\..\..\HashBags-windows-${{ github.sha }}.zip -Force"
|
|
cd "$GITHUB_WORKSPACE"
|
|
ls -la HashBags-windows-*.zip
|
|
echo "Size: $(du -h HashBags-windows-${{ github.sha }}.zip | cut -f1)"
|
|
|
|
- name: Upload Hash Bags Windows zip
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: hash-bags-windows-${{ github.sha }}
|
|
path: HashBags-windows-${{ github.sha }}.zip
|
|
retention-days: 14
|
|
|
|
# ---- Publish to the GitHub release ------------------------------------
|
|
# PowerShell rather than the bash used elsewhere in this file: this step
|
|
# parses JSON, and git-bash here has neither python nor node guaranteed,
|
|
# while ConvertFrom-Json is native. Skips quietly without a token.
|
|
- name: Resolve the release tag
|
|
id: reltag
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "${GITHUB_REF:-}" != "${GITHUB_REF#refs/tags/}" ]; then
|
|
TAG="${GITHUB_REF#refs/tags/}"
|
|
else
|
|
TAG="${{ github.event.inputs.release_tag }}"
|
|
fi
|
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
if [ -n "$TAG" ]; then echo "publishing to $TAG"; else echo "no tag; build only"; fi
|
|
|
|
- name: Publish the signed zip to the GitHub release
|
|
if: steps.reltag.outputs.tag != ''
|
|
shell: powershell
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GH_RELEASE_TOKEN }}
|
|
TAG: ${{ steps.reltag.outputs.tag }}
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
if (-not $env:GH_TOKEN) {
|
|
Write-Host 'GH_RELEASE_TOKEN not set; skipping the GitHub upload'
|
|
exit 0
|
|
}
|
|
$repo = 'Such-Software/hash-wallet'
|
|
$api = "https://api.github.com/repos/$repo"
|
|
$hdr = @{ Authorization = "Bearer $env:GH_TOKEN"; 'User-Agent' = 'hash-bags-ci' }
|
|
$src = Get-ChildItem -Path . -Filter 'HashBags-windows-*.zip' | Select-Object -First 1
|
|
if (-not $src) { throw 'no Windows zip to upload' }
|
|
# Named for the tag, not the commit: a release asset called
|
|
# HashBags-windows-<40 hex>.zip tells a downloader nothing.
|
|
$name = "HashBags-windows-$env:TAG.zip"
|
|
try {
|
|
$rel = Invoke-RestMethod -Headers $hdr -Uri "$api/releases/tags/$env:TAG"
|
|
} catch {
|
|
Write-Host "no GitHub release for $env:TAG; skipping"
|
|
exit 0
|
|
}
|
|
# Replace a same-named asset from an earlier attempt, so a re-run does
|
|
# not leave two that differ only by which build produced them.
|
|
foreach ($a in @($rel.assets | Where-Object { $_.name -eq $name })) {
|
|
Invoke-RestMethod -Headers $hdr -Method Delete -Uri "$api/releases/assets/$($a.id)" | Out-Null
|
|
}
|
|
$up = "https://uploads.github.com/repos/$repo/releases/$($rel.id)/assets?name=$name"
|
|
Invoke-RestMethod -Headers $hdr -Method Post -ContentType 'application/octet-stream' `
|
|
-InFile $src.FullName -Uri $up | Out-Null
|
|
Write-Host "uploaded $name to the GitHub release $env:TAG"
|