Compare commits
47 Commits
76fcee2202
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86b4040c9b | ||
|
|
2abff1fe06 | ||
|
|
21f3a75aa5 | ||
|
|
a1b3add2a6 | ||
|
|
5ff5ebf910 | ||
|
|
6e6cf33bf1 | ||
|
|
ac294874bf | ||
|
|
52fd1d784f | ||
|
|
be4cf0e49a | ||
|
|
fc813368cc | ||
|
|
2294bfabe7 | ||
|
|
2dd7c84246 | ||
|
|
5a43cc1114 | ||
|
|
91dadfd35e | ||
|
|
4f38322c92 | ||
|
|
5e6a9d5743 | ||
|
|
b6f6f3f4da | ||
|
|
e6195d1e98 | ||
|
|
59d7833668 | ||
|
|
0ad4a4eb8a | ||
|
|
bdbd3388a3 | ||
|
|
3bf9509f08 | ||
|
|
b8ca749e1e | ||
|
|
8c4fbed869 | ||
|
|
1ad70a210a | ||
|
|
713eba2a53 | ||
|
|
bab0d252ee | ||
|
|
20ad49a403 | ||
|
|
a2a9c1b0aa | ||
|
|
bba8a975cf | ||
|
|
a6bb3c595d | ||
|
|
52b8cd090d | ||
|
|
c506679086 | ||
|
|
6c765a6e0e | ||
|
|
ee4ccacab0 | ||
|
|
0aa9f03f77 | ||
|
|
ba5c81e78b | ||
|
|
9f832633b9 | ||
|
|
1c844c58cf | ||
|
|
8f583b14ea | ||
|
|
e277d1ba33 | ||
|
|
a19d0615c8 | ||
|
|
e21430e1d6 | ||
|
|
bf2a35893d | ||
|
|
d21c944f3f | ||
|
|
1e4af3dbde | ||
|
|
71da372d83 |
29
.github/workflows/build-android.yml
vendored
@@ -5,10 +5,9 @@ name: Hash Bags Android build
|
||||
# - PRs targeting dev/main (gate merges)
|
||||
# - manual via workflow_dispatch
|
||||
on:
|
||||
push:
|
||||
branches: [dev, main]
|
||||
pull_request:
|
||||
branches: [dev, main]
|
||||
# Manual-only for now. Trigger via Actions → "Hash Bags Android build"
|
||||
# → Run workflow when you want a build. workflow_dispatch runs as the
|
||||
# triggering user, so secrets are always available (unlike PR triggers).
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@@ -189,8 +188,19 @@ jobs:
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
run: |
|
||||
set -e
|
||||
# Debug visibility — prints lengths only, never values. If a secret
|
||||
# length is 0, Gitea Actions is not passing it to this run (most
|
||||
# commonly because the run was triggered by a from-fork PR).
|
||||
echo "ANDROID_KEYSTORE_BASE64 length: ${#ANDROID_KEYSTORE_BASE64}"
|
||||
echo "ANDROID_KEYSTORE_PASSWORD length: ${#ANDROID_KEYSTORE_PASSWORD}"
|
||||
echo "ANDROID_KEY_ALIAS length: ${#ANDROID_KEY_ALIAS}"
|
||||
echo "ANDROID_KEY_PASSWORD length: ${#ANDROID_KEY_PASSWORD}"
|
||||
if [[ -z "$ANDROID_KEYSTORE_BASE64" ]]; then
|
||||
echo "FATAL: ANDROID_KEYSTORE_BASE64 not set — configure Gitea Actions secrets first"
|
||||
echo "FATAL: ANDROID_KEYSTORE_BASE64 not reaching the runner."
|
||||
echo "Check: (1) secret is in repo Settings → Actions → Secrets;"
|
||||
echo " (2) workflow was triggered by 'push' or 'workflow_dispatch'"
|
||||
echo " (PR triggers from a fork strip secrets);"
|
||||
echo " (3) Gitea's runner is configured to pass secrets."
|
||||
exit 1
|
||||
fi
|
||||
# Write decoded keystore next to build.gradle (storeFile path
|
||||
@@ -218,15 +228,20 @@ jobs:
|
||||
- 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 + sign --------------------------------------------------
|
||||
# Universal APK (single binary covers all 4 ABIs, easy to sideload).
|
||||
- name: Build release APK (universal, all ABIs)
|
||||
run: flutter build apk --dart-define-from-file=env.json --release
|
||||
|
||||
# AAB for Play Console upload (Google generates per-ABI APKs server-side
|
||||
# via Play App Signing).
|
||||
# via Play App Signing). --verbose surfaces the full gradle stacktrace
|
||||
# when FinalizeBundleTask / signReleaseBundle fails — without it the
|
||||
# error is opaque ('FinalizeBundleTask$BundleToolRunnable failed').
|
||||
- name: Build release AAB
|
||||
run: flutter build appbundle --dart-define-from-file=env.json --release
|
||||
run: flutter build appbundle --dart-define-from-file=env.json --release --verbose
|
||||
|
||||
- name: Sanity-check signature on APK
|
||||
run: |
|
||||
|
||||
148
.github/workflows/build-ios-sim.yml
vendored
@@ -8,10 +8,8 @@ name: Hash Bags iOS Simulator build
|
||||
# Phase 2 (separate workflow): full TestFlight pipeline with signing.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev, main]
|
||||
pull_request:
|
||||
branches: [dev, main]
|
||||
# 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
|
||||
@@ -30,6 +28,16 @@ jobs:
|
||||
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
|
||||
# If you added an /etc/hosts entry on the Mac mini that points
|
||||
# git.such.software at NPM's LAN IP (so the runner hits NPM's
|
||||
# Let's Encrypt cert instead of Gitea's internal self-signed),
|
||||
# this env var can be removed. Left in as a belt-and-suspenders
|
||||
# fallback — has no effect if certs already verify.
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: '0'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -62,11 +70,9 @@ jobs:
|
||||
pod --version
|
||||
exit 0
|
||||
fi
|
||||
# Prefer Homebrew on macOS — no sudo, installs into its own prefix.
|
||||
if command -v brew >/dev/null; then
|
||||
brew install cocoapods
|
||||
else
|
||||
# Fallback: user-local gem install (no sudo).
|
||||
export GEM_HOME="$HOME/.gem"
|
||||
export PATH="$GEM_HOME/bin:$PATH"
|
||||
echo "GEM_HOME=$HOME/.gem" >> "$GITHUB_ENV"
|
||||
@@ -75,6 +81,45 @@ jobs:
|
||||
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
|
||||
@@ -108,11 +153,23 @@ jobs:
|
||||
rm reown_flutter.tar.gz
|
||||
popd
|
||||
|
||||
- name: Clone BitBox Flutter
|
||||
- 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
|
||||
./build_bitbox_flutter.sh
|
||||
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) ------------------
|
||||
@@ -130,17 +187,27 @@ jobs:
|
||||
ls
|
||||
popd
|
||||
|
||||
# iOS native lib staging: Cake's scripts/ios/setup.sh + gen_framework.sh
|
||||
# expect pre-built .a archives in specific paths. The first run will
|
||||
# likely surface what's missing; we iterate from there.
|
||||
- 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"
|
||||
# List anything iOS-y
|
||||
find "$BUNDLE_DIR" -maxdepth 1 -name '*ios*' -o -name '*apple*' 2>/dev/null || true
|
||||
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)
|
||||
@@ -182,6 +249,9 @@ jobs:
|
||||
- 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
|
||||
@@ -202,6 +272,58 @@ jobs:
|
||||
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:
|
||||
|
||||
196
.github/workflows/build-ios-testflight.yml
vendored
@@ -35,6 +35,12 @@ jobs:
|
||||
env:
|
||||
APP_IOS_TYPE: cakewallet
|
||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
# CocoaPods needs UTF-8; Gitea runner shell defaults to ASCII-8BIT.
|
||||
LANG: en_US.UTF-8
|
||||
LC_ALL: en_US.UTF-8
|
||||
# See note in build-ios-sim.yml — fallback if /etc/hosts trick
|
||||
# isn't in place. Safe no-op once it is.
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: '0'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -76,6 +82,48 @@ jobs:
|
||||
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
|
||||
rm -rf "$HOME/.rustup/downloads"/*.partial 2>/dev/null || true
|
||||
# Stable + iOS targets (most plugins use these)
|
||||
rustup target add aarch64-apple-ios
|
||||
rustup target add aarch64-apple-ios-sim
|
||||
rustup target add x86_64-apple-ios
|
||||
# Nightly + iOS targets — at least one plugin (sp_scanner / payjoin
|
||||
# / something in the cargokit chain) builds its rust component
|
||||
# with +nightly. The TestFlight archive bombs at "Missing manifest
|
||||
# in toolchain 'nightly-aarch64-apple-darwin'" if nightly isn't
|
||||
# installed alongside stable.
|
||||
rustup toolchain install nightly --profile minimal
|
||||
rustup target add aarch64-apple-ios --toolchain nightly
|
||||
rustup target add aarch64-apple-ios-sim --toolchain nightly
|
||||
rustup target add x86_64-apple-ios --toolchain nightly
|
||||
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
|
||||
GOPATH=$(go env GOPATH)
|
||||
echo "$GOPATH/bin" >> "$GITHUB_PATH"
|
||||
export PATH="$PATH:$GOPATH/bin"
|
||||
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
|
||||
@@ -96,6 +144,25 @@ jobs:
|
||||
tar -xzf torch_dart.tar.gz -C torch_dart
|
||||
rm torch_dart.tar.gz
|
||||
popd
|
||||
# LibTorch.xcframework ships with a lie: each slice's Info.plist
|
||||
# claims MinimumOSVersion=12.0, but the Mach-O LC_BUILD_VERSION
|
||||
# in the binaries says 13.0 (device) and 14.0 (simulator).
|
||||
# Apple's TestFlight validator catches this mismatch and rejects
|
||||
# the upload with ITMS-90208. Patch the Info.plists to match
|
||||
# the actual binary build versions.
|
||||
DEVICE_PLIST=scripts/torch_dart/ios/LibTorch.xcframework/ios-arm64/LibTorch.framework/Info.plist
|
||||
SIM_PLIST=scripts/torch_dart/ios/LibTorch.xcframework/ios-arm64-simulator/LibTorch.framework/Info.plist
|
||||
for entry in "$DEVICE_PLIST:13.0" "$SIM_PLIST:14.0"; do
|
||||
plist="${entry%:*}"
|
||||
min="${entry#*:}"
|
||||
if [[ -f "$plist" ]]; then
|
||||
plutil -replace MinimumOSVersion -string "$min" "$plist"
|
||||
echo "patched $plist -> MinimumOSVersion $min"
|
||||
plutil -p "$plist" | grep -E "MinimumOSVersion|CFBundleName" || true
|
||||
else
|
||||
echo "WARNING: $plist not found"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Fetch prebuilt reown_flutter
|
||||
run: |
|
||||
@@ -108,11 +175,20 @@ jobs:
|
||||
rm reown_flutter.tar.gz
|
||||
popd
|
||||
|
||||
- name: Clone BitBox Flutter
|
||||
- name: Clone BitBox Flutter (iOS — skip Android bindings)
|
||||
run: |
|
||||
# See note in build-ios-sim.yml — iOS uses bitbox's native plugin,
|
||||
# not the .aar that build_bindings.sh generates.
|
||||
set -x -e
|
||||
pushd scripts
|
||||
./build_bitbox_flutter.sh
|
||||
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
|
||||
|
||||
- name: Fetch prebuilt monero_c bundle
|
||||
@@ -127,6 +203,14 @@ jobs:
|
||||
rm release-bundle.zip
|
||||
popd
|
||||
|
||||
- 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, etc. ----------------------
|
||||
- name: Run iOS configure (cakewallet profile)
|
||||
run: |
|
||||
@@ -153,6 +237,40 @@ jobs:
|
||||
lib/.secrets.g.dart
|
||||
|
||||
# ---- Apple signing setup --------------------------------------------
|
||||
- name: Sanity-check Apple secrets are reaching the runner
|
||||
env:
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
|
||||
APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
|
||||
APPLE_API_KEY_P8_BASE64: ${{ secrets.APPLE_API_KEY_P8_BASE64 }}
|
||||
APPLE_DIST_CERT_P12_BASE64: ${{ secrets.APPLE_DIST_CERT_P12_BASE64 }}
|
||||
APPLE_DIST_CERT_P12_PASSWORD: ${{ secrets.APPLE_DIST_CERT_P12_PASSWORD }}
|
||||
APPLE_PROVISIONING_PROFILE_BASE64: ${{ secrets.APPLE_PROVISIONING_PROFILE_BASE64 }}
|
||||
APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
|
||||
run: |
|
||||
# Print LENGTHS only — never the secret values. Length 0 means the
|
||||
# secret isn't actually reaching the runner (most often: secret is
|
||||
# set on a different Gitea repo than this workflow is running in).
|
||||
set +e
|
||||
echo "APPLE_TEAM_ID length: ${#APPLE_TEAM_ID}"
|
||||
echo "APPLE_KEY_ID length: ${#APPLE_KEY_ID}"
|
||||
echo "APPLE_ISSUER_ID length: ${#APPLE_ISSUER_ID}"
|
||||
echo "APPLE_API_KEY_P8_BASE64 length: ${#APPLE_API_KEY_P8_BASE64}"
|
||||
echo "APPLE_DIST_CERT_P12_BASE64 length: ${#APPLE_DIST_CERT_P12_BASE64}"
|
||||
echo "APPLE_DIST_CERT_P12_PASSWORD length: ${#APPLE_DIST_CERT_P12_PASSWORD}"
|
||||
echo "APPLE_PROVISIONING_PROFILE_BASE64 length: ${#APPLE_PROVISIONING_PROFILE_BASE64}"
|
||||
echo "APPLE_KEYCHAIN_PASSWORD length: ${#APPLE_KEYCHAIN_PASSWORD}"
|
||||
set -e
|
||||
# Fail fast on any zero-length secret so we don't waste time on the
|
||||
# subsequent steps.
|
||||
for n in TEAM_ID KEY_ID ISSUER_ID API_KEY_P8_BASE64 DIST_CERT_P12_BASE64 DIST_CERT_P12_PASSWORD PROVISIONING_PROFILE_BASE64 KEYCHAIN_PASSWORD; do
|
||||
var="APPLE_$n"
|
||||
if [[ -z "${!var}" ]]; then
|
||||
echo "FATAL: $var is empty — set it as a Gitea repo secret on the repo this workflow runs in (Builds/hash-wallet)."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Create temp keychain + import distribution cert
|
||||
env:
|
||||
APPLE_DIST_CERT_P12_BASE64: ${{ secrets.APPLE_DIST_CERT_P12_BASE64 }}
|
||||
@@ -167,9 +285,22 @@ jobs:
|
||||
security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
# Add to user search list so codesign can find it
|
||||
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | sed 's/"//g')
|
||||
# Import the .p12
|
||||
# Decode the user-uploaded .p12 directly. macOS Keychain Access
|
||||
# exports .p12 with legacy PKCS12 crypto (RC2-40-CBC, PBE-SHA1)
|
||||
# which is exactly what `security import` expects — no openssl
|
||||
# conversion needed. Earlier attempts to "convert to legacy"
|
||||
# via openssl 3.x failed because openssl 3.x has RC2-40-CBC
|
||||
# disabled by default; that was a non-problem we made for
|
||||
# ourselves. The original "Unknown format" failure was caused
|
||||
# by a truncated base64 secret value, since fixed by re-pasting
|
||||
# the secret via Gitea's API.
|
||||
P12="$RUNNER_TEMP/dist.p12"
|
||||
echo "$APPLE_DIST_CERT_P12_BASE64" | base64 -d > "$P12"
|
||||
echo "$APPLE_DIST_CERT_P12_BASE64" | openssl base64 -d -A > "$P12"
|
||||
echo "=== decoded .p12 ==="
|
||||
ls -la "$P12"
|
||||
xxd "$P12" | head -1
|
||||
file "$P12" || true
|
||||
|
||||
security import "$P12" -k "$KEYCHAIN_PATH" -P "$APPLE_DIST_CERT_P12_PASSWORD" \
|
||||
-T /usr/bin/codesign -T /usr/bin/security
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
@@ -185,7 +316,7 @@ jobs:
|
||||
PROFILES_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
mkdir -p "$PROFILES_DIR"
|
||||
PROFILE="$RUNNER_TEMP/Hash_Wallet.mobileprovision"
|
||||
echo "$APPLE_PROVISIONING_PROFILE_BASE64" | base64 -d > "$PROFILE"
|
||||
echo "$APPLE_PROVISIONING_PROFILE_BASE64" | openssl base64 -d -A > "$PROFILE"
|
||||
|
||||
# Parse the .mobileprovision to get its UUID + Name (CMS-decoded plist).
|
||||
PROFILE_UUID=$(security cms -D -i "$PROFILE" | plutil -extract UUID raw -o - -)
|
||||
@@ -206,9 +337,29 @@ jobs:
|
||||
run: |
|
||||
set -e
|
||||
mkdir -p "$HOME/.appstoreconnect/private_keys"
|
||||
echo "$APPLE_API_KEY_P8_BASE64" | base64 -d > "$HOME/.appstoreconnect/private_keys/AuthKey_${APPLE_KEY_ID}.p8"
|
||||
P8="$HOME/.appstoreconnect/private_keys/AuthKey_${APPLE_KEY_ID}.p8"
|
||||
echo "$APPLE_API_KEY_P8_BASE64" | openssl base64 -d -A > "$P8"
|
||||
ls -la "$HOME/.appstoreconnect/private_keys/"
|
||||
|
||||
# Diagnostics — verify the PEM file is well-formed without
|
||||
# printing the private key body. A valid AuthKey .p8 must:
|
||||
# - Start with "-----BEGIN PRIVATE KEY-----"
|
||||
# - End with "-----END PRIVATE KEY-----"
|
||||
# - openssl pkey -in file -noout must succeed (real ASN.1 parse)
|
||||
echo "=== .p8 first/last lines (no key body) ==="
|
||||
head -1 "$P8"
|
||||
tail -1 "$P8"
|
||||
echo "=== openssl parse check (silent on success) ==="
|
||||
if openssl pkey -in "$P8" -noout 2>&1; then
|
||||
echo "OK — .p8 parses as a valid EC private key"
|
||||
else
|
||||
echo "FATAL: .p8 doesn't parse — secret likely truncated or wrong content"
|
||||
echo "Re-paste APPLE_API_KEY_P8_BASE64 via the Gitea API (same flow"
|
||||
echo "as the .p12 re-paste); the file decoded to:"
|
||||
wc -c "$P8"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---- Flutter init + codegen + pod install ---------------------------
|
||||
- name: Initialize Flutter SDK (iOS precache)
|
||||
run: |
|
||||
@@ -221,11 +372,44 @@ jobs:
|
||||
- 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
|
||||
|
||||
- name: pod install
|
||||
run: |
|
||||
cd ios
|
||||
pod install --repo-update
|
||||
|
||||
# ---- Override signing in project.pbxproj for the archive step -------
|
||||
# The committed Release config uses Automatic signing + "Apple
|
||||
# Development" identity (set up for local dev where Xcode is signed
|
||||
# into an Apple ID). On CI we have only the cert + provisioning
|
||||
# profile we installed in the temp keychain; no Xcode account.
|
||||
#
|
||||
# First attempt was an xcconfig append, but Xcode build-setting
|
||||
# precedence puts target-level pbxproj settings ABOVE xcconfig —
|
||||
# so the pbxproj values won and the build did Automatic signing
|
||||
# anyway. Patch the Release config in pbxproj directly, only
|
||||
# within the Runner target block (id 97C147071CF9000F007C117D);
|
||||
# leaves Debug/Profile configs untouched so local builds stay
|
||||
# on Automatic signing.
|
||||
- name: Override signing settings in pbxproj for archive
|
||||
env:
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
set -x
|
||||
PBX=ios/Runner.xcodeproj/project.pbxproj
|
||||
# BSD sed (macOS): -i '' for in-place, regex range scoped to
|
||||
# the Runner target's Release config by its stable UUID.
|
||||
sed -i '' "/97C147071CF9000F007C117D \/\* Release/,/name = Release;/ {
|
||||
s/CODE_SIGN_IDENTITY = \"Apple Development\";/CODE_SIGN_IDENTITY = \"Apple Distribution\";/
|
||||
s/CODE_SIGN_STYLE = Automatic;/CODE_SIGN_STYLE = Manual;/
|
||||
s/DEVELOPMENT_TEAM = [A-Z0-9]\{1,\};/DEVELOPMENT_TEAM = ${APPLE_TEAM_ID};/
|
||||
s|PROVISIONING_PROFILE_SPECIFIER = \"\";|PROVISIONING_PROFILE_SPECIFIER = \"${PROFILE_NAME}\";|
|
||||
}" "$PBX"
|
||||
echo "=== patched Runner Release config ==="
|
||||
sed -n '/97C147071CF9000F007C117D \/\* Release/,/name = Release;/p' "$PBX"
|
||||
|
||||
# ---- Generate ExportOptions.plist + build IPA -----------------------
|
||||
- name: Write ExportOptions.plist
|
||||
env:
|
||||
|
||||
92
.github/workflows/build-linux.yml
vendored
@@ -5,10 +5,9 @@ name: Hash Bags Linux build
|
||||
# - on PRs targeting dev/main (gates merges)
|
||||
# - manual via workflow_dispatch ("Run workflow" button in the UI)
|
||||
on:
|
||||
push:
|
||||
branches: [dev, main]
|
||||
pull_request:
|
||||
branches: [dev, main]
|
||||
# Manual-only for now — auto-run on every push was too noisy. Trigger
|
||||
# via Actions → "Hash Bags Linux build" → Run workflow when you want a
|
||||
# build. Add `push:` back here if/when we want pre-merge validation.
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@@ -162,22 +161,93 @@ jobs:
|
||||
- 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
|
||||
|
||||
- name: Compress release bundle
|
||||
# ---- 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: |
|
||||
pushd build/linux/x64/release
|
||||
zip -r hash_wallet_linux_${{ github.sha }}.zip bundle
|
||||
popd
|
||||
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 artifact
|
||||
- name: Upload AppImage artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: hash-wallet-linux-${{ github.sha }}
|
||||
path: build/linux/x64/release/hash_wallet_linux_*.zip
|
||||
name: hash-wallet-linux-appimage-${{ github.sha }}
|
||||
path: build/linux/x64/release/Hash_Bags-x86_64.AppImage
|
||||
retention-days: 14
|
||||
|
||||
348
.github/workflows/build-windows.yml
vendored
Normal file
@@ -0,0 +1,348 @@
|
||||
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-only. Trigger via Actions → "Hash Bags Windows build" → Run workflow.
|
||||
# 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).
|
||||
workflow_dispatch:
|
||||
|
||||
# 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: bypass Node TLS verification for actions/* that
|
||||
# talk back to git.such.software. 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
|
||||
|
||||
# ---- 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: 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/
|
||||
|
||||
# ---- 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
|
||||
1
.gitignore
vendored
@@ -233,6 +233,7 @@ scripts/reown_flutter
|
||||
# Hash Wallet: untracked notes / internal playbooks
|
||||
docs/links.md
|
||||
docs/UPSTREAM_SYNC.md
|
||||
scripts/windows/setup-windows-runner.ps1
|
||||
|
||||
# Hash Wallet: local mirror of hash.boats website (deploy target on suchwow server)
|
||||
hash.boats/
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data
|
||||
android:scheme="cakewallet"
|
||||
android:scheme="hashbags"
|
||||
android:host="y.at" />
|
||||
</intent-filter>
|
||||
<!-- currencies qr code scheme -->
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
org.gradle.jvmargs=-Xmx6144M
|
||||
# AAB universal-bundle packaging (AabFlinger.writeZip) OOMs at 6G because
|
||||
# parallel ForkJoinPool workers all share the heap while buffering compressed
|
||||
# native libs. Bumped to 10G + capped worker count to flatten the peak.
|
||||
# linux-beast Docker container has plenty of RAM; bump higher if needed.
|
||||
org.gradle.jvmargs=-Xmx10240M -XX:MaxMetaspaceSize=1024M -Dfile.encoding=UTF-8
|
||||
org.gradle.workers.max=2
|
||||
android.enableR8=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
},
|
||||
{
|
||||
"question" : "Wie kontaktiere ich den Hash Wallet-Support?",
|
||||
"answer" : "Senden Sie eine E-Mail an support@cakewallet.com, schließen Sie sich dem Telegramm unter @cakewallet_bot an oder twittern Sie @CakeWalletXMR!\n"
|
||||
"answer" : "Senden Sie eine E-Mail an support@such.software, schließen Sie sich dem Telegramm unter @such_software an oder twittern Sie @such_software!\n"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
},
|
||||
{
|
||||
"question" : "How do I contact Hash Wallet support?",
|
||||
"answer" : "Email support@cakewallet.com, join the Telegram at @cakewallet_bot, or tweet @CakeWalletXMR!\n"
|
||||
"answer" : "Email support@such.software, join the Telegram at @such_software, or tweet @such_software!\n"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
},
|
||||
{
|
||||
"question" : "¿Cómo contacto al soporte de Hash Wallet?",
|
||||
"answer" : "¡Envíe un correo electrónico a support@cakewallet.com, únase al Telegram en @cakewallet_bot o envíe un tweet a @CakeWalletXMR!\n"
|
||||
"answer" : "¡Envíe un correo electrónico a support@such.software, únase al Telegram en @such_software o envíe un tweet a @such_software!\n"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -54,6 +54,6 @@
|
||||
},
|
||||
{
|
||||
"question" : "Comment puis-je contacter le support de Hash Wallet ?",
|
||||
"answer" : "Envoyez un email à support@cakewallet.com, rejoignez le groupe Telegram @cakewallet_bot, ou @CakeWalletXMR sur Twitter!\n"
|
||||
"answer" : "Envoyez un email à support@such.software, rejoignez le groupe Telegram @such_software, ou @such_software sur Twitter!\n"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
},
|
||||
{
|
||||
"question" : "मैं केक वॉलेट से कैसे संपर्क करूं?",
|
||||
"answer" : "ईमेल support@cakewallet.com, @cakewallet_bot पर टेलीग्राम में शामिल हों, या @CakeWalletXMR पर ट्वीट करें!\n"
|
||||
"answer" : "ईमेल support@such.software, @such_software पर टेलीग्राम में शामिल हों, या @such_software पर ट्वीट करें!\n"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
},
|
||||
{
|
||||
"question" : "Hash Walletサポートに連絡するにはどうすればよいですか?",
|
||||
"answer" : "support@cakewallet.comにメールを送信するか、@cakewallet_botで電報に参加するか、@CakeWalletXMRにツイートしてください。\n"
|
||||
"answer" : "support@such.softwareにメールを送信するか、@such_softwareで電報に参加するか、@such_softwareにツイートしてください。\n"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
},
|
||||
{
|
||||
"question" : "Hash Wallet 지원팀에 연락하려면 어떻게해야합니까?",
|
||||
"answer" : "support@cakewallet.com로 이메일을 보내거나 @cakewallet_bot에서 전보에 가입하거나 @CakeWalletXMR을 트윗하십시오!\n"
|
||||
"answer" : "support@such.software로 이메일을 보내거나 @such_software에서 전보에 가입하거나 @such_software을 트윗하십시오!\n"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
},
|
||||
{
|
||||
"question" : "Hoe neem ik contact op met Hash Wallet-ondersteuning?",
|
||||
"answer" : "E-mail support@cakewallet.com, word lid van het Telegram op @cakewallet_bot of tweet @CakeWalletXMR!\n"
|
||||
"answer" : "E-mail support@such.software, word lid van het Telegram op @such_software of tweet @such_software!\n"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
},
|
||||
{
|
||||
"question" : "Jak skontaktować się z obsługą Hash Wallet?",
|
||||
"answer" : "Wyślij e-mail na adres support@cakewallet.com, dołącz do telegramu na @cakewallet_bot lub tweet @CakeWalletXMR!\n"
|
||||
"answer" : "Wyślij e-mail na adres support@such.software, dołącz do telegramu na @such_software lub tweet @such_software!\n"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
},
|
||||
{
|
||||
"question" : "Como entro em contato com o suporte da Hash Wallet?",
|
||||
"answer" : "Envie um e-mail para support@cakewallet.com, participe do Telegram em @cakewallet_bot ou envie um tweet para @CakeWalletXMR!\n"
|
||||
"answer" : "Envie um e-mail para support@such.software, participe do Telegram em @such_software ou envie um tweet para @such_software!\n"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
},
|
||||
{
|
||||
"question" : "Как мне связаться со службой поддержки Hash Wallet?",
|
||||
"answer" : "По электронной почте support@cakewallet.com, присоединитесь к Telegram по адресу @cakewallet_bot или отправьте твит @CakeWalletXMR!\n"
|
||||
"answer" : "По электронной почте support@such.software, присоединитесь к Telegram по адресу @such_software или отправьте твит @such_software!\n"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
},
|
||||
{
|
||||
"question" : "Як мені зв'язатися зі службою підтримки Hash Wallet?",
|
||||
"answer" : "По електронній пошті support@cakewallet.com, приєднайтеся до Telegram за адресою @cakewallet_bot або надішліть твіт @CakeWalletXMR!\n"
|
||||
"answer" : "По електронній пошті support@such.software, приєднайтеся до Telegram за адресою @such_software або надішліть твіт @such_software!\n"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -53,6 +53,6 @@
|
||||
},
|
||||
{
|
||||
"question" : "如何联系蛋糕钱包支持?",
|
||||
"answer" : "电子邮件support@cakewallet.com,通过@cakewallet_bot加入电报,或在@CakeWalletXMR上发布推文!\n"
|
||||
"answer" : "电子邮件support@such.software,通过@such_software加入电报,或在@such_software上发布推文!\n"
|
||||
}
|
||||
]
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 654 B |
|
Before Width: | Height: | Size: 852 B After Width: | Height: | Size: 177 B |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 644 B |
|
Before Width: | Height: | Size: 459 B After Width: | Height: | Size: 172 B |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 747 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 180 B |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 822 B |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 187 B |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 925 B |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 197 B |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 27 KiB |
@@ -5,14 +5,18 @@
|
||||
hash_wallet_logos.svg (#1a5c38). iOS/Android round the corners
|
||||
automatically; we ship a square fill. -->
|
||||
<rect width="1024" height="1024" fill="#1a5c38"/>
|
||||
<!-- # mark, white, centered. ui-monospace falls back to the platform's
|
||||
default monospace; the rasterizer bakes this into a PNG so font
|
||||
availability on user devices doesn't matter. -->
|
||||
<text x="512" y="512"
|
||||
text-anchor="middle"
|
||||
dominant-baseline="central"
|
||||
font-family="ui-monospace, 'JetBrains Mono', 'Courier New', monospace"
|
||||
font-size="720"
|
||||
font-weight="700"
|
||||
fill="#ffffff">#</text>
|
||||
<!-- # mark, drawn as 4 vector lines centered at (512, 512). Previous
|
||||
version used <text> with dominant-baseline="central", but the
|
||||
rendered # was visually biased toward the top because monospaced
|
||||
font glyph metrics don't put the # at the geometric center of the
|
||||
em-box. Path-based rendering removes the font dependency
|
||||
entirely. -->
|
||||
<g stroke="#ffffff" stroke-width="80" stroke-linecap="square">
|
||||
<!-- horizontal bars (top, bottom) — at y = 512 ± 100 -->
|
||||
<line x1="212" y1="412" x2="812" y2="412"/>
|
||||
<line x1="212" y1="612" x2="812" y2="612"/>
|
||||
<!-- vertical bars (left, right) — at x = 512 ± 100 -->
|
||||
<line x1="412" y1="212" x2="412" y2="812"/>
|
||||
<line x1="612" y1="212" x2="612" y2="812"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 861 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 591 B |
|
Before Width: | Height: | Size: 1019 B |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1019 B |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 325 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 385 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |