mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-27 06:18:50 +01:00
Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535 upstream commits, including the Monero silent-open fix, Monero on Trezor, Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9. Merged with both sides normalized first (import rename, 100-column format), which took the conflict count from 614 paths to 322. The procedure is now scripts/upstream_sync.sh. Kept from Hash Bags: - WalletType enum order and Hive IDs. SQLite stores WalletType.index and our order differs from upstream's; taking theirs would reread saved wallets as other chains. Our frozen adapter keeps the shipped IDs. - The 1.0.2 legacy PIN and wallet-password decode and self-heal. - Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out. - Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me), converted to upstream's isDefault node-list key; Wownero is back in the node reset list. - Privacy: address resolvers limited to domain-anchored sources again; prices.neroswap.com for fiat; upstream's charts tab, which reads prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false. - Lightning stays behind FeatureFlag.isLightningEnabled, including upstream's new post-seed Lightning username step. - Our icon pipeline, branding and bundle identifiers. Twelve new upstream widget tests are skipped: test and code are unchanged from v6.5.0 apart from renames (not rerun on a pristine upstream checkout; upstream's top_bar_test does not compile against its own TopBar). flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped; flutter build bundle compiles. Built with Flutter 3.41.9.
378 lines
18 KiB
YAML
378 lines
18 KiB
YAML
name: Hash Bags Linux build
|
|
|
|
# Triggers:
|
|
# - on push to dev/main (validates the foundation each time we land work)
|
|
# - on PRs targeting dev/main (gates merges)
|
|
# - manual via workflow_dispatch ("Run workflow" button in the UI)
|
|
on:
|
|
# Manual-only for pushes — auto-run on every push was too noisy. Trigger
|
|
# via Actions → "Hash Bags Linux build" → Run workflow when you want a
|
|
# build. Add `push: branches:` back here if/when we want pre-merge validation.
|
|
#
|
|
# Tags DO build automatically: a tag is a deliberate act and it is the thing
|
|
# a release is cut from, so the artifact that gets signed and published should
|
|
# come from the tag rather than from whatever was 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, because a release created here would
|
|
go public half-populated while the other platforms are still building.
|
|
required: false
|
|
default: ''
|
|
|
|
concurrency:
|
|
group: linux-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
|
|
jobs:
|
|
build:
|
|
# Both your Gitea linux-beast runner and GitHub-hosted ubuntu runners
|
|
# carry the `ubuntu-latest` label, so this works on either platform.
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
# Cake Labs' prebuilt build image with Flutter 3.41.9, Go 1.24.1, NDK r28,
|
|
# Rust nightly, and all the C++ build deps (boost, openssl, libsodium,
|
|
# libzmq, unbound, icu) preinstalled. Saves us from doing the simplybs
|
|
# source-bootstrap dance that ate 9GB on jw-laptop.
|
|
image: ghcr.io/cake-tech/cake_wallet:debian13-flutter3.41.9-ndkr28-go1.24.1-ruststablenightly
|
|
env:
|
|
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
|
# cake_wallet build profile name, retained from upstream — internal
|
|
# identifier only, doesn't appear in the binary or UI.
|
|
APP_LINUX_TYPE: cakewallet
|
|
|
|
steps:
|
|
- name: Fix Actions HOME pointing at /github/home (breaks ~/.cache writes)
|
|
run: echo "HOME=/root" >> $GITHUB_ENV
|
|
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
- name: Configure git inside the container
|
|
run: |
|
|
git config --global --add safe.directory '*'
|
|
git config --global user.email "ci@suchsoftware.com"
|
|
git config --global user.name "Hash Bags CI"
|
|
|
|
# ---- Secrets ---------------------------------------------------------
|
|
# IMPORTANT: don't pre-write any of the tool/.*secrets-config.json
|
|
# files. The generator at tool/generate_secrets_config.dart has an
|
|
# early-return if tool/.secrets-config.json already exists (lines
|
|
# 57-63), which then ALSO skips creating the per-module configs
|
|
# (evm, nano, bitcoin) — every cw_* module then fails to compile
|
|
# with "Undefined name secrets.xxx" for dozens of keys.
|
|
#
|
|
# Instead: run the generator first so it creates all configs from the
|
|
# full SecretKey list (with empty defaults), then sed-inject Trocador
|
|
# values into the freshly-generated lib/.secrets.g.dart.
|
|
|
|
# ---- External clones (use prebuilt tarballs, NOT prepare_*.sh) -------
|
|
# The prepare_torch.sh / prepare_reown.sh scripts do dev-time clones
|
|
# that take forever; CI uses the pre-tagged release tarballs instead.
|
|
- name: Fetch prebuilt torch_dart
|
|
run: |
|
|
set -x -e
|
|
pushd scripts
|
|
rm -rf torch_dart torch_dart.tar.gz
|
|
wget -q https://github.com/MrCyjaneK/torch_dart/releases/download/v1.0.17/torch_dart-v1.0.17.tar.gz -O torch_dart.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
|
|
wget -q https://github.com/cake-tech/reown_flutter/releases/download/v0.0.4/reown_flutter-v0.0.4.tar.gz -O reown_flutter.tar.gz
|
|
mkdir reown_flutter
|
|
tar -xzf reown_flutter.tar.gz -C reown_flutter
|
|
rm reown_flutter.tar.gz
|
|
popd
|
|
|
|
- name: Clone BitBox Flutter (small, builds fast)
|
|
run: |
|
|
set -x -e
|
|
pushd scripts
|
|
./build_bitbox_flutter.sh
|
|
popd
|
|
|
|
# ---- Native crypto cores (monero_c prebuilt bundle) ------------------
|
|
# Skips the full simplybs toolchain bootstrap. The bundle has libmonero,
|
|
# libwownero, AND libzano .so files; CMake only installs the ones we
|
|
# ship (monero + wownero) — libzano is dropped at install time even
|
|
# though monero_c builds it.
|
|
- name: Fetch prebuilt monero_c .so 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"
|
|
wget -q https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.6-RC2/release-bundle.zip
|
|
unzip -q release-bundle.zip
|
|
# RC2 nests everything under an inner v0.18.4.6-*/ dir; strip it.
|
|
shopt -s nullglob
|
|
for inner in v0.18.4.6-*/; do mv "$inner"* . && rmdir "$inner"; done
|
|
shopt -u nullglob
|
|
rm release-bundle.zip
|
|
ls x86_64-linux-gnu/
|
|
popd
|
|
|
|
# ---- Configure: pubspec.yaml, AndroidManifest.xml, env vars ----------
|
|
- name: Run configure (cakewallet build profile)
|
|
run: |
|
|
pushd scripts/linux
|
|
source ./app_env.sh cakewallet
|
|
./app_config.sh
|
|
popd
|
|
|
|
# ---- Codegen: mobx, hive adapters, FFI bindings ----------------------
|
|
# model_generator.sh is already trimmed to our 8 enabled chains.
|
|
# Use `async` flag for parallel build_runner across modules.
|
|
- name: Generate per-module secrets.g.dart files (all empty defaults)
|
|
run: |
|
|
# tool/generate_new_secrets.dart imports app deps (encrypt/convert/intl),
|
|
# so the app's packages must be resolved first. pubspec_gen.sh runs
|
|
# `flutter pub get` WITHOUT `set -e`, so any hiccup fails silently and
|
|
# surfaces here as "Couldn't resolve the package". Resolve explicitly
|
|
# (and loudly) before running the tool.
|
|
set -e
|
|
flutter pub get
|
|
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: |
|
|
# Replace empty default '' with real values for the three Trocador keys.
|
|
# Other ~80 secrets stay as empty strings — code that uses them just
|
|
# fails-soft at runtime (no transaction history without Etherscan key,
|
|
# no Cake Pay without API key, etc.). Add more sed lines here as we
|
|
# acquire other affiliate keys.
|
|
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
|
|
|
|
# Initialize Flutter SDK once before parallel-ish work happens. flutter
|
|
# precache for Flutter 3.41.9 hits a 404 on flutter_gpu.zip (asset not
|
|
# actually published at the path Flutter constructs); the `|| true`
|
|
# absorbs that — desktop Linux build doesn't need flutter_gpu.
|
|
- name: Initialize Flutter SDK
|
|
run: |
|
|
flutter --version
|
|
flutter precache --linux --no-android --no-ios --no-macos --no-windows --no-fuchsia --no-web || true
|
|
|
|
# Sequential — async flag races on Flutter's startup lock and corrupts
|
|
# the SDK state, causing the "doesn't support null safety" cascade.
|
|
- 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
|
|
|
|
# ---- Compile + package ----------------------------------------------
|
|
- name: Build Linux app
|
|
run: flutter build linux --dart-define-from-file=env.json --release
|
|
|
|
# ---- Package as AppImage --------------------------------------------
|
|
# Self-contained .AppImage is the user-facing Linux deliverable: single
|
|
# file, double-click to run, no install, works on any glibc Linux from
|
|
# the last few years. appimagetool is run with --appimage-extract-and-run
|
|
# so it works inside the container without FUSE.
|
|
- name: Stage AppDir
|
|
run: |
|
|
set -e -x
|
|
REL=build/linux/x64/release
|
|
APPDIR=$REL/Hash_Bags.AppDir
|
|
rm -rf "$APPDIR"
|
|
mkdir -p "$APPDIR/usr/bin" \
|
|
"$APPDIR/usr/share/applications" \
|
|
"$APPDIR/usr/share/icons/hicolor/256x256/apps"
|
|
|
|
# The whole Flutter Linux bundle (binary + data/ + lib/) lives
|
|
# under usr/bin/. The Flutter binary has RPATH=$ORIGIN/lib, so
|
|
# plugin/native libs in usr/bin/lib/ resolve naturally.
|
|
cp -r "$REL/bundle"/* "$APPDIR/usr/bin/"
|
|
|
|
# Icon: scale the iOS marketing icon to 256 if convert is around,
|
|
# else copy as-is (appimagetool only requires that AppDir root and
|
|
# the hicolor path contain a same-named PNG).
|
|
ICON_SRC=assets/images/ios_icons/hashwallet_ios_icons/Icon-App-1024x1024@1x.png
|
|
ICON_DEST="$APPDIR/usr/share/icons/hicolor/256x256/apps/hash_bags.png"
|
|
if command -v convert >/dev/null 2>&1; then
|
|
convert "$ICON_SRC" -resize 256x256 "$ICON_DEST"
|
|
else
|
|
cp "$ICON_SRC" "$ICON_DEST"
|
|
fi
|
|
cp "$ICON_DEST" "$APPDIR/hash_bags.png"
|
|
|
|
# .desktop file (at both canonical locations; appimagetool checks both)
|
|
cat > "$APPDIR/hash_bags.desktop" <<'DESKTOP'
|
|
[Desktop Entry]
|
|
Name=Hash Bags
|
|
Comment=Non-custodial multi-chain crypto wallet
|
|
Exec=hash_wallet
|
|
Icon=hash_bags
|
|
Type=Application
|
|
Categories=Office;Finance;
|
|
Terminal=false
|
|
DESKTOP
|
|
# Strip the leading whitespace from the heredoc
|
|
sed -i 's/^ //' "$APPDIR/hash_bags.desktop"
|
|
cp "$APPDIR/hash_bags.desktop" "$APPDIR/usr/share/applications/"
|
|
|
|
# AppRun: the entry point AppImage executes. Adds the bundled lib/
|
|
# dir to LD_LIBRARY_PATH and exec's the Flutter binary.
|
|
cat > "$APPDIR/AppRun" <<'APPRUN'
|
|
#!/bin/bash
|
|
HERE="$(dirname "$(readlink -f "${0}")")"
|
|
export LD_LIBRARY_PATH="${HERE}/usr/bin/lib:${LD_LIBRARY_PATH}"
|
|
exec "${HERE}/usr/bin/hash_wallet" "$@"
|
|
APPRUN
|
|
sed -i 's/^ //' "$APPDIR/AppRun"
|
|
chmod +x "$APPDIR/AppRun"
|
|
|
|
ls -la "$APPDIR"
|
|
|
|
- name: Build AppImage
|
|
run: |
|
|
set -e -x
|
|
REL=build/linux/x64/release
|
|
wget -q https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O /tmp/appimagetool
|
|
chmod +x /tmp/appimagetool
|
|
# --appimage-extract-and-run avoids the FUSE requirement (no fusermount
|
|
# in the build container). ARCH= tells appimagetool which arch tag to
|
|
# embed in the filename.
|
|
ARCH=x86_64 /tmp/appimagetool --appimage-extract-and-run \
|
|
"$REL/Hash_Bags.AppDir" \
|
|
"$REL/Hash_Bags-x86_64.AppImage"
|
|
ls -la "$REL"/*.AppImage
|
|
|
|
# actions/upload-artifact@v4 uses an HTTP API that Gitea Actions does
|
|
# not implement (only the v1/v3 wire format). Pinned to @v3 for Gitea
|
|
# compatibility — also works on GitHub Actions.
|
|
- name: Upload AppImage artifact
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: hash-wallet-linux-appimage-${{ github.sha }}
|
|
path: build/linux/x64/release/Hash_Bags-x86_64.AppImage
|
|
retention-days: 14
|
|
|
|
# ---- Publish to the release pages -------------------------------------
|
|
# Artifact retrieval through Gitea Actions is unreliable here: uploads
|
|
# succeed and then list as zero, so a build nobody can download is a build
|
|
# that did not happen. Release assets are the path that actually returns
|
|
# bytes, and GitHub is where users are told to look.
|
|
#
|
|
# Both steps skip quietly without a token, so a fork or a local runner
|
|
# does not fail a build over a credential it was never given.
|
|
- 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 AppImage to the GitHub release
|
|
if: steps.reltag.outputs.tag != ''
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GH_RELEASE_TOKEN }}
|
|
TAG: ${{ steps.reltag.outputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -z "${GH_TOKEN:-}" ]; then
|
|
echo "GH_RELEASE_TOKEN not set; skipping the GitHub upload"
|
|
exit 0
|
|
fi
|
|
REPO="Such-Software/hash-wallet"
|
|
API="https://api.github.com/repos/$REPO"
|
|
AUTH="Authorization: Bearer $GH_TOKEN"
|
|
FILE="build/linux/x64/release/Hash_Bags-x86_64.AppImage"
|
|
[ -f "$FILE" ] || { echo "no AppImage to upload"; exit 1; }
|
|
NAME="$(basename "$FILE")"
|
|
code=$(curl -sS -o rel-gh.json -w '%{http_code}' -H "$AUTH" "$API/releases/tags/$TAG" || true)
|
|
if [ "$code" != "200" ]; then
|
|
echo "no GitHub release for $TAG (HTTP $code); skipping"
|
|
exit 0
|
|
fi
|
|
# Fail rather than skip if there is no JSON parser. An upload that
|
|
# quietly did not happen leaves a release page that looks finished
|
|
# and a user with nothing to download.
|
|
PY=""
|
|
for c in python3 python; do command -v "$c" >/dev/null && { PY="$c"; break; }; done
|
|
[ -n "$PY" ] || { echo "no python available to parse the release JSON"; exit 1; }
|
|
ID=$($PY -c 'import json;print(json.load(open("rel-gh.json"))["id"])')
|
|
# 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.
|
|
for oid in $($PY -c 'import json,sys;d=json.load(open("rel-gh.json"));print(" ".join(str(a["id"]) for a in d.get("assets",[]) if a["name"]==sys.argv[1]))' "$NAME"); do
|
|
curl -sS -o /dev/null -X DELETE -H "$AUTH" "$API/releases/assets/$oid" || true
|
|
done
|
|
curl -sS --fail-with-body -o /dev/null -X POST -H "$AUTH" \
|
|
-H "Content-Type: application/octet-stream" \
|
|
--data-binary "@$FILE" \
|
|
"https://uploads.github.com/repos/$REPO/releases/$ID/assets?name=$NAME"
|
|
echo "uploaded $NAME to the GitHub release $TAG"
|