Compare commits

...

18 Commits

Author SHA1 Message Date
jwinterm
86b4040c9b Linux: package release as self-contained AppImage
The previous Linux artifact was a zip of the Flutter bundle/ directory.
That's awkward to distribute — users have to unzip, find the binary,
chmod +x, and figure out the lib/ layout themselves. Replace with a
proper AppImage: single file, double-click to run, no install, no
Flutter knowledge required.

Build steps:
- Stage AppDir with the Flutter bundle under usr/bin/ (the binary's
  RPATH=\$ORIGIN/lib already finds plugin libs there).
- Add .desktop file (Hash Bags / Office;Finance;) and 256x256 hash
  icon scaled from the iOS marketing PNG.
- Download appimagetool and invoke with --appimage-extract-and-run
  so it works inside the build container without FUSE.

Artifact name: hash-wallet-linux-appimage-<sha>.
2026-06-03 20:11:43 -04:00
jwinterm
2abff1fe06 Android version-from-pubspec + proper iOS tinted icons
Android had the same hardcoded CAKEWALLET_BUILD_NUMBER=1 bug we just
fixed for iOS — bumping pubspec_description.yaml would not have moved
Play Store versionCode at all, and the second AAB upload would have
been rejected for duplicate versionCode. Mirror the iOS fix: parse
the build number from pubspec_description.yaml in scripts/android/app_env.sh.

Regenerate the 16 Tinted iOS icon slices as black bg + white hash mark.
The previous slices reused the green-bg light variant, which iOS 18+
cannot meaningfully tint (green-on-green collapses when the system
applies the home-screen accent color). Black background + white shape
gives iOS a clean luminance map to tint against.

Wownero monero! audit: cleared. Audit flagged two callsites in
fees_view_model.dart, but both wownero!.getWowneroTransactionPriority*
and monero!.getMoneroTransactionPriority* return the same
MoneroTransactionPriority singleton (cw_wownero.dart:158 +
cw_monero.dart:169), so the cross-call is inelegant but not buggy.

Bump 1.0.0+6 -> 1.0.0+7.
2026-06-03 09:31:24 -04:00
jwinterm
21f3a75aa5 Wownero: dispatch getCurrentAccount by wallet.type in AccountCustomizer
The "Wallet Accounts" reorder modal threw on open for Wownero wallets:
account_customizer.dart unconditionally called monero!.getCurrentAccount,
which casts the wallet to MoneroWallet internally. WowneroWallet is a
sibling type, not a subtype, so the cast blew up before the active-card
swap logic ran.

Dispatch on wallet.type and use the wownero! proxy for Wownero, matching
the pattern already in card_customizer_bloc.dart, addresses_page.dart,
and dashboard_view_model.dart. Skip the swap entirely for any other
wallet type rather than guessing.

Bump 1.0.0+5 -> 1.0.0+6.
2026-06-03 09:15:10 -04:00
jwinterm
a1b3add2a6 iOS: source CAKEWALLET build number from pubspec_description.yaml
Bumping version in pubspec_description.yaml has had zero effect on
the iOS CFBundleVersion: app_env.sh hardcoded CAKEWALLET_BUILD_NUMBER=1,
app_config.sh substituted that literal into Info.plist's
\$(CURRENT_PROJECT_VERSION) placeholder via PlistBuddy before flutter
even saw it, and every TestFlight upload shipped as build 1 regardless
of what we bumped in pubspec. Apple rejected the +4 and +5 IPAs as
duplicates of the +3 that got through as build 1.

Make pubspec_description.yaml the single source of truth by parsing
its `version: X.Y.Z+N` line in app_env.sh.
2026-06-03 08:02:55 -04:00
jwinterm
5ff5ebf910 iOS: declare ITSAppUsesNonExemptEncryption=false (open-source exemption)
Hash Bags' crypto is either OS-provided (TLS / CryptoKit / CommonCrypto)
or standard published algorithms shipped as MIT-licensed open source
(secp256k1, ed25519, SHA-2/3, AES, Argon2, ChaCha20). All of that
qualifies as "exempt" under BIS export controls (§740.17(b)(3)).
Declaring the answer in Info.plist skips the per-build App Store Connect
encryption questionnaire and removes the annual BIS self-classification
filing requirement.

Bump 1.0.0+4 -> 1.0.0+5 in case +4 already claimed an Apple build slot.
2026-06-02 20:16:27 -04:00
jwinterm
6e6cf33bf1 iOS icons: replace Cake artwork with Hash Bags # mark, rename dir
The iOS build was still pulling Cake Wallet's purple/orange wallet
icon from assets/images/ios_icons/cakewallet_ios_icons/ — visible
in TestFlight as the wrong brand. Replace all 69 light/dark/tinted
slices with the Hash Bags green hash mark from hash.boats/icon-512.png,
flattened against #1a5c38 so the 1024 marketing icon is opaque (no
alpha, as App Store requires) and iOS applies its own corner mask.

Rename the directory cakewallet_ios_icons -> hashwallet_ios_icons so
the repo doesn't carry Cake-branded paths. The build-time flavor is
still named "cakewallet" internally (huge blast radius to rename),
so app_icon.sh just maps that flavor to the new directory name.

Bump build 1.0.0+3 -> 1.0.0+4 for re-upload.
2026-06-02 20:11:07 -04:00
jwinterm
ac294874bf iOS TestFlight: patch LibTorch Info.plist to match binary LC_BUILD_VERSION
LibTorch.xcframework ships with each slice's Info.plist claiming
MinimumOSVersion=12.0, but the Mach-O LC_BUILD_VERSION inside the
binaries actually requires 13.0 (device) and 14.0 (simulator). Apple's
validator catches the lie and rejects TestFlight uploads with ITMS-90208
("LibTorch.framework does not support the minimum OS Version specified
in the Info.plist") regardless of the app's deployment target.

Revert the previous 15.0 deployment-target bump (which was based on the
wrong hypothesis) back to 13.0 — that's actually what LibTorch supports.
Add a workflow step that plutil-rewrites both slice Info.plists to match
the value their binary actually requires. Bump build number 1.0.0+2 ->
1.0.0+3 for the re-upload.
2026-06-02 13:39:30 -04:00
jwinterm
52fd1d784f iOS: bump deployment target 12/13 → 15 (Apple ITMS-90208 fix)
First TestFlight upload was accepted and processed all the way through,
then Apple's binary validation kicked back ITMS-90208 Invalid Bundle:

  The bundle Runner.app/Frameworks/LibTorch.framework does not support
  the minimum OS Version specified in the Info.plist.

torch_dart-v1.0.17 ships a LibTorch.xcframework whose Info.plist
declares a higher MinimumOSVersion than our app's 13.0. Apple's
notarization-style check rejects an app that claims to run on iOS
versions where a bundled framework refuses to load.

Bump the four spots iOS deployment target lives:
  - ios/Podfile (was 13.0)
  - ios/Flutter/AppFrameworkInfo.plist MinimumOSVersion (was 12.0)
  - ios/Runner.xcodeproj/project.pbxproj — 6 IPHONEOS_DEPLOYMENT_TARGET
    entries (mixed 12.0 / 13.0)
all → 15.0. iPhone 6s (2015) and newer; very wide 2026 coverage.

Also bump build number 1.0.0+1 → 1.0.0+2 in pubspec_description.yaml.
Apple rejects re-uploads with the same build number (ITMS-90478), so
the next TestFlight upload after this fix needs a fresh +N.

Workflow diagnostic step that dumps LibTorch.xcframework's Info.plist
MinimumOSVersion now lives in the "Fetch prebuilt torch_dart" step. If
15.0 still isn't enough, the log tells us exactly what to bump to next.
2026-06-02 13:02:08 -04:00
jwinterm
be4cf0e49a iOS TestFlight: validate .p8 AuthKey before altool upload
altool bailed with "Failed to load AuthKey file. (-39) The file ...
does not contain a valid authentication key." — same shape as the
.p12 truncation we hit earlier. The .p8 stored secret is probably
truncated or has stray characters from the original web-UI paste.

Add fail-fast validation right after decoding the .p8 to disk:
prints the first + last lines (just the PEM markers, no key body)
and runs `openssl pkey -in file -noout` to actually parse it. If
either check fails the step bombs immediately with a clear "re-paste
via Gitea API" instruction, saving 30+ seconds before altool fails
the same way with a less useful error.
2026-06-01 21:01:38 -04:00
jwinterm
fc813368cc iOS TestFlight: also install nightly Rust + iOS targets
One of the Flutter plugins in our dep chain (sp_scanner / payjoin /
something in the cargokit family) compiles its rust component with
+nightly. The archive step ran for 27s then bombed with:

  Error (Xcode): Missing manifest in toolchain 'nightly-aarch64-apple-darwin'

We were installing only stable + iOS targets. Add nightly toolchain
(minimal profile) + iOS targets for it alongside the stable ones.
~250 MB extra download once per runner workspace lifetime.

build-ios-sim.yml has the same stable-only setup but never tripped on
this — likely because the failing plugin's release-only build path
isn't exercised in the unsigned sim build. Leaving sim workflow
alone for now; copy the nightly steps over if it ever surfaces there.
2026-06-01 20:42:11 -04:00
jwinterm
2294bfabe7 iOS TestFlight: patch signing in pbxproj, not Release.xcconfig
Xcode build-setting precedence puts TARGET-LEVEL pbxproj settings
ABOVE the target's xcconfig. So the earlier append to Release.xcconfig
(CODE_SIGN_STYLE = Manual etc.) was being silently overridden by the
pbxproj's CODE_SIGN_STYLE = Automatic. The build did Automatic
signing anyway and bombed with:

  Error (Xcode): No Accounts: Add a new account in Accounts settings.
  Error (Xcode): No profiles for 'com.suchsoftware.hashwallet' were
  found: Xcode couldn't find any iOS App Development provisioning
  profiles matching 'com.suchsoftware.hashwallet'.

(Note "App Development" not "App Distribution" — confirming the
project was still asking for a dev profile via Automatic signing.)

sed-patch the Runner target's Release config block in
project.pbxproj directly, scoped by the stable Release config UUID
(97C147071CF9000F007C117D) so we don't touch Debug/Profile configs.
Sets:
  CODE_SIGN_IDENTITY  = "Apple Distribution"
  CODE_SIGN_STYLE     = Manual
  DEVELOPMENT_TEAM    = ${APPLE_TEAM_ID}
  PROVISIONING_PROFILE_SPECIFIER = ${PROFILE_NAME}

Patch runs only at CI time; the committed pbxproj stays on Automatic
signing for local Xcode dev. The dumped block at the end of the
step confirms which lines actually changed, so the next failure
mode (if any) is debuggable.
2026-06-01 20:25:22 -04:00
jwinterm
2dd7c84246 iOS TestFlight: drop openssl conversion, import .p12 directly
The openssl pkcs12 round-trip step was speculative defense against
"modern macOS Keychain exports incompatible with security import" —
a problem we made up. What was actually happening:
  1. The original failure ("SecKeychainItemImport: Unknown format")
     was caused by a TRUNCATED base64 secret value. security wasn't
     rejecting the format; it was rejecting bytes it couldn't parse
     because the file was 29 bytes short.
  2. The follow-on failure ("RC2-40-CBC unsupported") was the openssl
     conversion step bombing because openssl 3.x has RC2-40-CBC
     disabled by default. macOS Keychain Access actually exports
     PKCS12 with RC2-40-CBC + PBE-SHA1 — exactly what `security
     import` reads natively.

With the truncated-secret fixed (re-pasted via Gitea API), the .p12
on disk is now correct. Import it directly without round-tripping
through openssl. Less code, fewer failure modes, faster.
2026-06-01 19:59:50 -04:00
jwinterm
5a43cc1114 iOS TestFlight: re-export .p12 as legacy PKCS12 before security import
macOS Keychain Access on Sonoma+ exports .p12 with modern crypto
(AES-256-GCM + PBKDF2 + SHA-256). The `security import` binary on
some macOS releases — including ours apparently — rejects it with:
  SecKeychainItemImport: Unknown format in import.

The openssl decode itself was fine (now using openssl base64 -d -A),
but security can't import the resulting PKCS12 envelope.

Pipe the .p12 through openssl: unpack with the user's password, then
re-export using legacy PBE-SHA1-3DES for both certs and keys, plus
SHA1 MAC. The resulting .p12 uses 90s-era crypto for the OUTER
envelope, which is what security import wants — the actual signing
keys + cert inside are unchanged. No security implication; the legacy
format encryption only matters for the few seconds the .p12 lives in
$RUNNER_TEMP before being imported and deleted.

Also added diagnostic output (file size, magic bytes, file(1)) so the
next failure mode (if any) is easier to characterize.
2026-06-01 19:17:04 -04:00
jwinterm
91dadfd35e iOS TestFlight: use openssl base64 -d -A, stop fighting BSD base64
`base64 --decode` also failed on the Mac runner with the same
"(null): error decoding base64 input stream" — so either this macOS
version's BSD base64 doesn't honor --decode (only -D) or it chokes
on the input format. Either way, swap the three secret decodes to
`openssl base64 -d -A`:

  - openssl ships with macOS by default; no extra install
  - -d = decode (same on BSD and Linux, no platform skew)
  - -A treats the input as one continuous line (matches our
    `base64 -i x | tr -d '\n'` encoding flow)

If openssl base64 ever bites us we'll fall back to python3's
base64.b64decode, but openssl has been bulletproof for ~25 years.
2026-06-01 19:08:20 -04:00
jwinterm
4f38322c92 iOS TestFlight: macOS base64 wants --decode, not -d
macOS uses BSD base64 where -d historically means *debug*, not
*decode* like GNU base64. On the Mac runner that bit us — the
first base64 -d invocation ran in debug mode, consumed no stdin,
and produced an empty .p12 file, then the keychain import bombed
with the now-classic "(null): error decoding base64 input stream"
once a downstream tool tried to read the empty file.

Switch all three secret decodes (.p12 cert, .mobileprovision,
.p8 API key) to base64 --decode. Long form is identical between
GNU coreutils and BSD base64.

The Apple secrets themselves were fine — sanity-check step
confirmed all non-zero lengths (TEAM_ID/KEY_ID showed as ***0
in the log, which is the actual literal 10 with the 1 redacted
because some other secret value contains the substring "1").
2026-06-01 18:59:37 -04:00
jwinterm
5e6a9d5743 iOS TestFlight: sanity-check Apple secrets reach the runner
First TestFlight trigger failed at the .p12 import step with
"base64: stdin: (null): error decoding base64 input stream" — meaning
$APPLE_DIST_CERT_P12_BASE64 was empty when reaching the keychain step.

Add a length-only debug step at the top of the Apple-signing section
that prints the length of every required Apple secret and bails
fast with a clear "set this secret on Builds/hash-wallet" message if
any is zero-length. Same pattern build-android.yml uses for the
Android keystore + Trocador secrets.

The likely cause is the Apple secrets being set on github-such-
software/hash-wallet but not on the Builds fork the workflow runs
in — Gitea secrets are repo-scoped, not org-scoped.
2026-06-01 18:54:42 -04:00
jwinterm
b6f6f3f4da Wire error reporting to hash.boats/report, surface Wownero primary address
Two unrelated wallet-UX fixes in one commit:

1) lib/utils/exception_handler.dart — switch the "Report Error" flow
   from "open prefilled GitHub issue URL" to "copy error trace to
   clipboard + open https://hash.boats/report/". The hash.boats backend
   (Node service + nginx proxy + Telegram relay) was already deployed
   to the suchwow server; the in-app handler just never got wired to
   use it. No GitHub account required, works on every platform.

2) lib/wownero/cw_wownero.dart — add 'primaryAddress' to the keys map
   returned by getKeys(). The lib/monero/cw_monero.dart equivalent
   already had this; without it, WalletKeysViewModel renders zero
   primary-address entry for Wownero wallets and users can't easily
   read off the main address for sharing (e.g. to set up a view-only
   wallet on another device). The underlying WowneroWalletBase.keys
   already populated primaryAddress — only the getKeys map projection
   was missing it.
2026-06-01 18:39:57 -04:00
jwinterm
e6195d1e98 iOS TestFlight: override signing in Release.xcconfig for CI
The committed project.pbxproj Release config uses CODE_SIGN_STYLE =
Automatic + CODE_SIGN_IDENTITY = "Apple Development" — both set up for
local Xcode-signed-into-an-Apple-ID development workflow. CI has only
the Distribution cert + provisioning profile we install into a temp
keychain, no Apple ID session, so Automatic signing fails at archive
time.

Append manual-signing overrides to ios/Flutter/Release.xcconfig in CI
(xcconfig values take precedence over pbxproj defaults, so this
overrides without modifying the committed project file):
  CODE_SIGN_IDENTITY = Apple Distribution
  CODE_SIGN_STYLE = Manual
  DEVELOPMENT_TEAM = ${APPLE_TEAM_ID}
  PROVISIONING_PROFILE_SPECIFIER = ${PROFILE_NAME}

PROFILE_NAME is parsed earlier from the .mobileprovision Name field.
APPLE_TEAM_ID comes from the existing Gitea secret. Local Debug/Profile
builds remain on Automatic signing — unchanged.
2026-06-01 18:30:34 -04:00
151 changed files with 261 additions and 56 deletions

View File

@@ -90,9 +90,19 @@ jobs:
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)
@@ -134,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: |
@@ -208,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 }}
@@ -222,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"
@@ -240,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 - -)
@@ -261,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: |
@@ -284,6 +380,36 @@ jobs:
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:

View File

@@ -168,18 +168,86 @@ jobs:
- 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1019 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1019 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Some files were not shown because too many files have changed in this diff Show More