mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
The first macOS run died after 88 seconds on `cd scripts/monero_c: No such file
or directory`. That was the cheap failure. The workflow had been written fresh
rather than ported, and behind that missing clone it had also skipped the step
that pins the crypto salts. Had it got further it would have shipped a build
that mints random salts, which is the 2026-08-29 corrupted-seeds incident on a
new platform: wallets created on it would not decrypt after the next upgrade.
So the prerequisites are now ported step for step from build-linux.yml, the
desktop build known to pass: torch_dart, reown_flutter, bitbox (the Apple
variant, without the gomobile Android bindings), prepare_moneroc.sh, configure,
secrets generation, salt pinning with the same refusal, Trocador injection,
codegen, localization, and compile_graphics.sh. curl for wget and BSD sed's
-i.bak form, because the runner is a Mac.
Two more things the fresh version got wrong, found while porting:
configure was run without sourcing app_env.sh, and app_config.sh writes
CFBundleName, the bundle id and the version into Info.plist from variables only
app_env.sh sets. The app would have shipped with an empty name. The step now
prints the three values it wrote.
And app_env.sh hardcoded 1.0.0 (1) on macOS, 6.1.0 (79) on Linux and 1.9.0 (29)
in the fish variant, the last two being Cake Wallet's own version numbers. On
macOS PlistBuddy writes that into the bundle, so every macOS build would have
called itself 1.0.0. Linux only escaped because its substitution looks for a
version: 0.0.0 placeholder that pubspec_description.yaml does not contain. All
three now derive from pubspec_description.yaml the way Android already does, and
the two sh variants resolve to 1.0.4 (14). The fish one is unverified: fish is
not installed here and CI does not use it.
Separately, four workflows verified their secret injection by grepping
lib/.secrets.g.dart into the log, which prints the values. Android's printed the
Trocador, WalletConnect, MoonPay and exchange-helper keys. Gitea likely masks
registered secrets, but masking is a best-effort layer and values never belong
in output. Each now reports every key's name and length and nothing else.
Windows is not in this commit. Its run refused correctly, on the guard added in
c11c79b0c: the runner has Flutter 3.38.5, not the pinned 3.32.0. That is fixed on
the runner, not in the workflow.
368 lines
16 KiB
YAML
368 lines
16 KiB
YAML
name: Hash Bags iOS Simulator build
|
|
|
|
# Phase 1 of iOS CI: validate the Mac runner end-to-end by producing an
|
|
# unsigned simulator .app. Drop the resulting Runner.app into the iOS
|
|
# Simulator on any Apple Silicon Mac to verify the app actually compiles
|
|
# and launches.
|
|
#
|
|
# Phase 2 (separate workflow): full TestFlight pipeline with signing.
|
|
|
|
on:
|
|
# Manual-only for now. Trigger via Actions → "Hash Bags iOS Simulator
|
|
# build" → Run workflow when you want a build.
|
|
workflow_dispatch:
|
|
|
|
# Cancel in-flight runs when a newer commit lands on the same branch — so a
|
|
# burst of commits doesn't pile up N copies of the same long build.
|
|
concurrency:
|
|
group: ios-sim-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: macos-latest
|
|
env:
|
|
APP_IOS_TYPE: cakewallet
|
|
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
|
# CocoaPods refuses to operate when LANG is ASCII-8BIT (default for the
|
|
# Gitea runner shell). Force UTF-8 globally so `pod install` succeeds.
|
|
LANG: en_US.UTF-8
|
|
LC_ALL: en_US.UTF-8
|
|
# Belt-and-suspenders fallback for when the self-hosted runner's internal
|
|
# git host presents a self-signed cert. Preferred fix is to trust the
|
|
# internal CA on the runner, after which this can be removed. (Runner
|
|
# host and network specifics live in the internal CI docs, not this repo.)
|
|
NODE_TLS_REJECT_UNAUTHORIZED: '0'
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
# Self-hosted Mac runner persists Flutter between runs. Only install if
|
|
# it's not already on PATH or version doesn't match.
|
|
- name: Check Flutter 3.32.0
|
|
id: flutter_check
|
|
run: |
|
|
if command -v flutter >/dev/null && flutter --version 2>/dev/null | grep -q "3\.32\.0"; then
|
|
echo "installed=true" >> "$GITHUB_OUTPUT"
|
|
flutter --version
|
|
else
|
|
echo "installed=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Install Flutter 3.32.0 (if missing)
|
|
if: steps.flutter_check.outputs.installed != 'true'
|
|
uses: subosito/flutter-action@v2
|
|
with:
|
|
flutter-version: '3.32.0'
|
|
channel: stable
|
|
cache: true
|
|
|
|
- name: Install CocoaPods (if missing)
|
|
run: |
|
|
if command -v pod >/dev/null; then
|
|
pod --version
|
|
exit 0
|
|
fi
|
|
if command -v brew >/dev/null; then
|
|
brew install cocoapods
|
|
else
|
|
export GEM_HOME="$HOME/.gem"
|
|
export PATH="$GEM_HOME/bin:$PATH"
|
|
echo "GEM_HOME=$HOME/.gem" >> "$GITHUB_ENV"
|
|
echo "$HOME/.gem/bin" >> "$GITHUB_PATH"
|
|
gem install --user-install cocoapods --no-document
|
|
fi
|
|
pod --version
|
|
|
|
- name: Install Rust + iOS targets (if missing)
|
|
run: |
|
|
if ! command -v rustup >/dev/null; then
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --no-modify-path
|
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
export PATH="$HOME/.cargo/bin:$PATH"
|
|
fi
|
|
# Clean any partial downloads from a previously crashed install — those
|
|
# are the source of the 'could not rename .partial → final: No such
|
|
# file or directory' race that breaks parallel pod builds.
|
|
rm -rf "$HOME/.rustup/downloads"/*.partial 2>/dev/null || true
|
|
# Pre-install iOS targets serially so the pod build doesn't try to
|
|
# do it under concurrency.
|
|
rustup target add aarch64-apple-ios
|
|
rustup target add aarch64-apple-ios-sim
|
|
rustup target add x86_64-apple-ios
|
|
rustup show
|
|
|
|
- name: Install Go + gomobile (if missing)
|
|
run: |
|
|
if ! command -v go >/dev/null; then
|
|
if command -v brew >/dev/null; then
|
|
brew install go
|
|
else
|
|
echo "Go missing and brew not available"; exit 1
|
|
fi
|
|
fi
|
|
go version
|
|
# Ensure $(go env GOPATH)/bin is on PATH for subsequent steps.
|
|
GOPATH=$(go env GOPATH)
|
|
echo "$GOPATH/bin" >> "$GITHUB_PATH"
|
|
export PATH="$PATH:$GOPATH/bin"
|
|
# gomobile + gobind are needed by scripts/build_bitbox_flutter.sh
|
|
if ! command -v gomobile >/dev/null; then
|
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
|
go install golang.org/x/mobile/cmd/gobind@latest
|
|
fi
|
|
which gomobile && gomobile version || true
|
|
|
|
- name: Show toolchain
|
|
run: |
|
|
set -x
|
|
flutter --version
|
|
xcodebuild -version
|
|
pod --version
|
|
uname -m
|
|
which curl unzip
|
|
|
|
# ---- External prebuilt deps (same as android/linux) ------------------
|
|
# macOS doesn't ship wget by default; using curl everywhere.
|
|
- name: Fetch prebuilt torch_dart
|
|
run: |
|
|
set -x -e
|
|
pushd scripts
|
|
rm -rf torch_dart torch_dart.tar.gz
|
|
curl -fsSL -o torch_dart.tar.gz https://github.com/MrCyjaneK/torch_dart/releases/download/v1.0.17/torch_dart-v1.0.17.tar.gz
|
|
mkdir torch_dart
|
|
tar -xzf torch_dart.tar.gz -C torch_dart
|
|
rm torch_dart.tar.gz
|
|
popd
|
|
|
|
- name: Fetch prebuilt reown_flutter
|
|
run: |
|
|
set -x -e
|
|
pushd scripts
|
|
rm -rf reown_flutter reown_flutter.tar.gz
|
|
curl -fsSL -o reown_flutter.tar.gz https://github.com/cake-tech/reown_flutter/releases/download/v0.0.4/reown_flutter-v0.0.4.tar.gz
|
|
mkdir reown_flutter
|
|
tar -xzf reown_flutter.tar.gz -C reown_flutter
|
|
rm reown_flutter.tar.gz
|
|
popd
|
|
|
|
- name: Clone BitBox Flutter (iOS — skip Android bindings)
|
|
run: |
|
|
# Pubspec has bitbox_flutter as a path: dep at scripts/bitbox_flutter,
|
|
# so the directory must exist for pub get. The bundled build_bindings.sh
|
|
# runs `gomobile bind -target=android` which needs the Android SDK we
|
|
# don't have on the Mac runner — and the resulting .aar is Android-only.
|
|
# iOS uses bitbox_flutter's native ios/Classes plugin, no .aar required.
|
|
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) ------------------
|
|
- name: Fetch prebuilt monero_c bundle
|
|
run: |
|
|
set -x -e
|
|
./scripts/prepare_moneroc.sh
|
|
MONERO_C_TAG=$(cd scripts/monero_c && git describe --tags)
|
|
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 (one level) ==="
|
|
ls
|
|
popd
|
|
|
|
- name: Inspect iOS targets available 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 "=== bundle top-level ==="
|
|
ls "$BUNDLE_DIR" || true
|
|
echo "=== iOS-y subdirs ==="
|
|
find "$BUNDLE_DIR" -maxdepth 1 -type d \( -name '*ios*' -o -name '*apple*' \) 2>/dev/null
|
|
echo "=== sample contents of one apple-ios target ==="
|
|
for d in "$BUNDLE_DIR"/aarch64-apple-ios "$BUNDLE_DIR"/aarch64-apple-ios-simulator; do
|
|
[[ -d "$d" ]] && { echo "--- $d ---"; ls "$d"; }
|
|
done
|
|
|
|
- name: Build MoneroWallet + WowneroWallet XCFrameworks
|
|
run: |
|
|
set -x -e
|
|
pushd scripts/ios
|
|
bash ./gen_framework.sh
|
|
popd
|
|
ls -la ios/MoneroWallet.xcframework ios/WowneroWallet.xcframework
|
|
|
|
# ---- Configure: pubspec.yaml, Info.plist, GeneratedPluginRegistrant ---
|
|
- name: Run iOS configure (cakewallet profile)
|
|
run: |
|
|
set -x -e
|
|
pushd scripts/ios
|
|
source ./app_env.sh cakewallet
|
|
./app_config.sh
|
|
popd
|
|
|
|
# ---- Secrets ---------------------------------------------------------
|
|
- name: Generate per-module secrets.g.dart files (all 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
|
|
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: |
|
|
# macOS sed needs '' after -i; Linux sed doesn't accept that. This
|
|
# workflow runs on macos only.
|
|
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
|
|
awk -F"'" '/^const (trocador[A-Za-z]*) = / { split($1, a, " "); v = (length($2) ? "set (" length($2) " chars)" : "EMPTY"); print a[2] ": " v }' lib/.secrets.g.dart # lengths only; values never reach the log
|
|
|
|
# ---- Flutter init + codegen -----------------------------------------
|
|
- name: Initialize Flutter SDK (iOS precache)
|
|
run: |
|
|
flutter --version
|
|
flutter precache --ios --no-linux --no-android --no-macos --no-windows --no-fuchsia --no-web || true
|
|
|
|
- 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
|
|
|
|
# CocoaPods: the iOS Podfile installs Flutter plugin pods. Required
|
|
# before flutter build can link them.
|
|
- name: pod install
|
|
run: |
|
|
cd ios
|
|
pod install --repo-update
|
|
|
|
# ---- Build iOS Simulator app (no codesign) ---------------------------
|
|
- name: Build iOS Simulator app
|
|
run: |
|
|
set -x -e
|
|
flutter build ios --simulator --no-codesign \
|
|
--dart-define-from-file=env.json
|
|
ls -la build/ios/iphonesimulator/
|
|
|
|
- name: Zip Runner.app
|
|
run: |
|
|
cd build/ios/iphonesimulator
|
|
zip -r hash_wallet_ios_sim_${{ github.sha }}.zip Runner.app
|
|
|
|
# Confirm git.such.software resolves to a local target (the Mac itself).
|
|
# The runner inherits the Mac's /etc/hosts directly (host-mode act_runner),
|
|
# so the user should have:
|
|
# 127.0.0.1 git.such.software
|
|
# already in /etc/hosts. We only assert that here, don't modify (sudo
|
|
# without NOPASSWD prompts for a password the runner can't enter).
|
|
#
|
|
# macOS's `getent` doesn't exist and `nslookup` bypasses /etc/hosts.
|
|
# We grep /etc/hosts directly — that's what Node's getaddrinfo will
|
|
# actually consult.
|
|
- name: Verify Gitea hostname resolves locally
|
|
run: |
|
|
if grep -qE '^[[:space:]]*(127\.0\.0\.1|host\.docker\.internal|192\.168\.)[[:space:]]+git\.such\.software' /etc/hosts; then
|
|
echo "✓ /etc/hosts points git.such.software at a local target:"
|
|
grep git.such.software /etc/hosts
|
|
else
|
|
echo "✗ /etc/hosts is MISSING a local override for git.such.software."
|
|
echo " Node will resolve to the public IP and fail on NAT hairpin."
|
|
echo " Add (once, on the Mac mini):"
|
|
echo " sudo sh -c 'echo \"127.0.0.1 git.such.software\" >> /etc/hosts'"
|
|
exit 1
|
|
fi
|
|
|
|
# Diagnostic: print exactly where upload-artifact will try to send data.
|
|
# act_runner injects these env vars; their values tell us whether the
|
|
# upload targets git.such.software (uses our /etc/hosts override) or
|
|
# a LAN IP directly (override irrelevant). Also probe the URL with
|
|
# curl -k so we see the cert + reachability before the real upload.
|
|
- name: Diagnose Actions API endpoint
|
|
run: |
|
|
set +e
|
|
echo "=== Actions runtime env ==="
|
|
echo "ACTIONS_RUNTIME_URL=${ACTIONS_RUNTIME_URL:-<unset>}"
|
|
echo "ACTIONS_RESULTS_URL=${ACTIONS_RESULTS_URL:-<unset>}"
|
|
echo "ACTIONS_CACHE_URL=${ACTIONS_CACHE_URL:-<unset>}"
|
|
echo "GITHUB_SERVER_URL=${GITHUB_SERVER_URL:-<unset>}"
|
|
echo "GITHUB_API_URL=${GITHUB_API_URL:-<unset>}"
|
|
echo "NODE_TLS_REJECT_UNAUTHORIZED=${NODE_TLS_REJECT_UNAUTHORIZED:-<unset>}"
|
|
echo
|
|
# Probe each non-empty URL to see what Node will actually hit.
|
|
for var in ACTIONS_RUNTIME_URL ACTIONS_RESULTS_URL GITHUB_SERVER_URL; do
|
|
url="${!var}"
|
|
[ -z "$url" ] && continue
|
|
host=$(echo "$url" | sed -E 's|^https?://([^/:]+).*|\1|')
|
|
echo "--- $var → $url ---"
|
|
echo "DNS for $host:"
|
|
dscacheutil -q host -a name "$host" 2>/dev/null || nslookup "$host" 2>/dev/null || true
|
|
echo "HEAD probe (curl -kIv, 10s timeout):"
|
|
curl -kIv --max-time 10 "$url" 2>&1 | head -40 || true
|
|
echo
|
|
done
|
|
|
|
- name: Upload .app artifact
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: hash-wallet-ios-sim-${{ github.sha }}
|
|
path: build/ios/iphonesimulator/hash_wallet_ios_sim_*.zip
|
|
retention-days: 14
|