Compare commits

...
Author SHA1 Message Date
jwinterm a526061d97 ci(macos): place appicon.icns into the bundle 2026-08-29 13:15:24 -04:00
jwinterm f4c5ec3c25 ci(macos): non-interactive brew 2026-08-29 13:10:03 -04:00
jwinterm 1217ce174c ci(macos): sign + notarize with Developer ID cert via desktop-lane secrets 2026-08-29 12:17:21 -04:00
jwinterm 914fa1ed32 ci: native macOS lane for the Gitea self-hosted runner
Dispatch-only workflow in .gitea/workflows (GitHub ignores the
directory): brew dependencies, native cmake build with the embedded
wownerod fetched per-arch, unsigned app bundle artifact. Runs on the
mac runner whose work dir lives on DataSSD and is outside every backup
path. Signing/notarization follows once a Developer ID Application
certificate is provisioned as repo secrets.
2026-08-29 07:04:39 -04:00
jwinterm 93d13a6568 v5.0.3: harden embedded-node restart handling + version bump
On top of Lucas's #119 (--db-sync-mode safe):
- DaemonManager: age out the crash-retry budget (m_restarts) so legit node
  toggles/restarts don't trip "maximum retries exceeded" after ~4 spawns over a
  session; the cap now only bites a genuine rapid crash-loop.
- DaemonManager: clear the m_stopping latch on start(), so the crash auto-restart
  keeps working after the node has been toggled off then on (it was a one-shot
  before).
- bump version 5.0.2 -> 5.0.3.
2026-06-27 13:10:54 -04:00
Lucas 64326119eb Updated DaemonManager to ensure new blocks are
committed immediately by disabling LMDB batch write mode.
2026-06-26 21:57:42 +10:00
jwinterm e3ba9c7bad v5.0.2: hide hardware-restore option + version bump
- wizard: hide the hardware-device wallet option (wownero has no Ledger/Trezor
  support; inherited Feather cruft).
- bump version 5.0.1 -> 5.0.2.

v5.0.2 also rebuilds the Linux AppImage in an ubuntu:24.04 container (glibc 2.39)
so it runs on Ubuntu 24.04 / Mint / Debian — v5.0.1's AppImage was built on
glibc 2.43 and would not start elsewhere. No wallet-code change: wallets created
on pre-table-fix builds carry a stale restore height (blank balance); the fix is
to re-restore from seed.
2026-06-25 09:19:19 -04:00
5 changed files with 167 additions and 1 deletions
+139
View File
@@ -0,0 +1,139 @@
name: macos build (gitea self-hosted)
# Runs ONLY on the Gitea mac runner (GitHub ignores .gitea/workflows).
# Dispatch-only: the mac runner has capacity 1 and also serves the Hash Bags
# iOS lane, so macOS wowlet builds are an explicit button, not a push hook.
# All build state lands under the runner's work dir, which lives on
# /Volumes/DataSSD (never backed up: the fleet backup pulls an allowlist of
# gitea data only).
#
# Signing/notarization uses the SAME seven secrets as the smirk-monorepo
# desktop lane, set by ~/keys/suchsoftware/set-gitea-desktop-secrets.sh:
# APPLE_CERTIFICATE (b64 Developer ID Application .p12),
# APPLE_CERTIFICATE_PASSWORD, APPLE_SIGNING_IDENTITY, APPLE_TEAM_ID,
# APPLE_API_ISSUER, APPLE_API_KEY, APPLE_API_KEY_P8_BASE64 (notarytool).
# If APPLE_CERTIFICATE is unset the build ships unsigned instead of failing.
on: [workflow_dispatch]
jobs:
build-macos:
name: "macOS (brew, native)"
runs-on: macos-latest
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_P8_BASE64: ${{ secrets.APPLE_API_KEY_P8_BASE64 }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: install dependencies (brew)
run: |
export PATH="/opt/homebrew/bin:$PATH"
export NONINTERACTIVE=1 HOMEBREW_NO_ENV_HINTS=1
brew install --quiet qt libsodium libzip qrencode unbound cmake boost hidapi openssl expat protobuf pkgconfig zxing-cpp
- name: fetch wownerod to embed
run: |
set -e
ARCH=$(uname -m)
case "$ARCH" in
arm64) WOWTAR=wownero-aarch64-apple-darwin11-v0.11.4.0.tar.bz2 ;;
*) WOWTAR=wownero-x86_64-apple-darwin11-v0.11.4.0.tar.bz2 ;;
esac
mkdir -p /tmp/wownerod-embed
curl -L -o /tmp/wownero.tar.bz2 \
"https://github.com/wownero/wownero-codeberg-mirror/releases/download/v0.11.4.0/$WOWTAR"
tar xjf /tmp/wownero.tar.bz2 -C /tmp
find /tmp -name wownerod -type f -exec cp {} /tmp/wownerod-embed/ \;
chmod +x /tmp/wownerod-embed/wownerod
ls -la /tmp/wownerod-embed
- name: build
run: |
export PATH="/opt/homebrew/bin:$PATH"
mkdir -p build && cd build
cmake -DWITH_SCANNER=OFF -DWOWNEROD_DIR=/tmp/wownerod-embed ..
cmake --build . -j "$(sysctl -n hw.ncpu)"
- name: package app bundle
run: |
set -e
cd build
APP=$(find . -maxdepth 3 -name "*.app" -type d | head -1)
[ -n "$APP" ] || { echo "no .app bundle produced"; ls -la bin/; exit 1; }
mkdir -p dist
cp -R "$APP" dist/Wowlet.app
# cmake never places the icon in the bundle (upstream only copies it
# for the dormant `installed/` layout)
mkdir -p dist/Wowlet.app/Contents/Resources
cp ../src/assets/images/appicons/appicon.icns dist/Wowlet.app/Contents/Resources/
# bundle Qt frameworks/plugins so the app runs off-machine
/opt/homebrew/opt/qt/bin/macdeployqt dist/Wowlet.app
ls -la dist/
- name: sign and notarize (skipped when secrets absent)
run: |
set -e
cd build
if [ -z "$APPLE_CERTIFICATE" ]; then
echo "APPLE_CERTIFICATE not set — shipping UNSIGNED build"
exit 0
fi
KC=/tmp/wowlet-ci.keychain-db
KCPW=$(/usr/bin/uuidgen)
echo "$KCPW" > /tmp/wowlet-ci-kcpw # for the cleanup step only
security create-keychain -p "$KCPW" "$KC"
security set-keychain-settings -lut 3600 "$KC"
security unlock-keychain -p "$KCPW" "$KC"
printf '%s' "$APPLE_CERTIFICATE" | base64 -d > /tmp/dev_id.p12
security import /tmp/dev_id.p12 -k "$KC" -P "$APPLE_CERTIFICATE_PASSWORD" \
-T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KCPW" "$KC" >/dev/null
security list-keychains -d user -s "$KC" login.keychain-db
# sign nested Mach-O executables first (embedded wownerod etc.),
# then frameworks, then the bundle — all hardened-runtime + timestamp
SIGN() { codesign --force --options runtime --timestamp --keychain "$KC" \
--sign "$APPLE_SIGNING_IDENTITY" "$@"; }
find dist/Wowlet.app -type f -perm +111 ! -path "*/Frameworks/*" \
| while read -r f; do file "$f" | grep -q Mach-O && SIGN "$f" || true; done
find dist/Wowlet.app/Contents/Frameworks -maxdepth 1 \( -name "*.framework" -o -name "*.dylib" \) 2>/dev/null \
| while read -r fw; do SIGN "$fw"; done
SIGN dist/Wowlet.app
codesign --verify --deep --strict dist/Wowlet.app
printf '%s' "$APPLE_API_KEY_P8_BASE64" | base64 -d > /tmp/notary.p8
ditto -c -k --keepParent dist/Wowlet.app /tmp/wowlet-notarize.zip
xcrun notarytool submit /tmp/wowlet-notarize.zip \
--key /tmp/notary.p8 --key-id "$APPLE_API_KEY" --issuer "$APPLE_API_ISSUER" \
--wait --timeout 30m
xcrun stapler staple dist/Wowlet.app
echo "signed + notarized + stapled"
- name: zip artifact
run: |
set -e
cd build/dist
ditto -c -k --keepParent Wowlet.app ../wowlet-macos-$(uname -m).zip
ls -la ../wowlet-macos-*.zip
- name: cleanup signing material
if: always()
run: |
if [ -f /tmp/wowlet-ci-kcpw ]; then
security delete-keychain /tmp/wowlet-ci.keychain-db || true
fi
rm -f /tmp/dev_id.p12 /tmp/notary.p8 /tmp/wowlet-ci-kcpw /tmp/wowlet-notarize.zip
- name: upload artifact
uses: actions/upload-artifact@v3
with:
name: wowlet-macos
path: build/wowlet-macos-*.zip
retention-days: 14
+1 -1
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.18)
project(feather
VERSION "5.0.1"
VERSION "5.0.3"
DESCRIPTION "A free Monero desktop wallet"
LANGUAGES CXX C ASM
)
+22
View File
@@ -4,6 +4,7 @@
#include "utils/DaemonManager.h"
#include <QCoreApplication>
#include <QDateTime>
#include <QDir>
#include <QDirIterator>
#include <QEventLoop>
@@ -62,6 +63,11 @@ void DaemonManager::start() {
if (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting)
return;
// wowlet: a deliberate (re)start clears the stop latch, so a later UNEXPECTED exit can auto-restart
// again. Without this, m_stopping stays true after the first node toggle-off and the crash
// auto-restart is silently dead for the rest of the session.
m_stopping = false;
if (!this->unpackBins()) {
this->setErrorMessage("Unable to unpack the embedded wownerod binary.");
return;
@@ -85,6 +91,14 @@ void DaemonManager::start() {
// and surfaces a clear error via handleProcessError().
}
// wowlet: the retry cap guards against a rapid crash-loop, not legit restarts. If the node had been
// up a while before this (re)start, clear the budget — otherwise a few node toggles over a session
// trip "maximum retries exceeded" and it won't start again until wowlet is relaunched.
const qint64 nowMs = QDateTime::currentMSecsSinceEpoch();
if (m_lastSpawnMs != 0 && (nowMs - m_lastSpawnMs) > 60000)
m_restarts = 0;
m_lastSpawnMs = nowMs;
m_restarts += 1;
if (m_restarts > 4) {
this->setErrorMessage("wownerod failed to start: maximum retries exceeded");
@@ -108,6 +122,14 @@ void DaemonManager::start() {
arguments << "--no-igd"; // no UPnP port mapping
arguments << "--out-peers" << "16";
arguments << "--log-file" << QDir(dataDir).filePath("wownerod.log");
// wowlet: disable LMDB batch write mode so every block is committed in its own transaction.
// Without this, wownero accumulates blocks in a single large write transaction that is ABORTED
// (not committed) when the daemon shuts down mid-sync — causing all progress since the last
// batch boundary to be silently lost. This is visible in wownerod.log: sessions consistently
// revert to a stale height despite a full graceful shutdown sequence. Using "safe" mode is
// slower for initial sync but is the only correct choice for an embedded node that starts and
// stops with the wallet; the wownero chain is small enough that the overhead is acceptable.
arguments << "--db-sync-mode" << "safe";
// Full archival by default (wownero's chain is small); prune is an explicit opt-in.
if (conf()->get(Config::pruneBlockchain).toBool())
+1
View File
@@ -73,6 +73,7 @@ private:
bool m_synced = false;
bool m_stopping = false;
int m_restarts = 0;
qint64 m_lastSpawnMs = 0; // wowlet: wall-clock of last spawn — ages out the crash-retry budget
static QPointer<DaemonManager> m_instance;
};
+4
View File
@@ -15,6 +15,10 @@ PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget
, m_fields(fields)
{
ui->setupUi(this);
// wowlet: hide the hardware-device option — wownero has no Ledger/Trezor support
ui->radioCreateFromDevice->setVisible(false);
this->setButtonText(QWizard::FinishButton, "Open recent wallet");
ui->label_version->setText(QString("wowlet %1 — by wownero.org").arg(FEATHER_VERSION));