linux + macos release builds (unsigned)

split bundle config into tauri.{windows,linux,macos}.conf.json so each
platform only carries its own targets; gen-icon emits icon.icns; macos is
ad-hoc signed since arm64 refuses unsigned downloads
This commit is contained in:
Horizontal42 2026-08-12 22:55:04 +03:00
parent e5667966fd
commit 51ee3b3208
14 changed files with 231 additions and 33 deletions

View file

@ -14,18 +14,40 @@ jobs:
fail-fast: false fail-fast: false
# tauri-action's find-or-create-release check races across parallel # tauri-action's find-or-create-release check races across parallel
# matrix jobs and can create two separate release objects for the # matrix jobs and can create two separate release objects for the
# same tag, each missing the other's assets — force serial builds # same tag, each missing the other's assets — force serial builds.
# Every platform added below shares that release object, so they all
# have to stay in this one serialized matrix.
max-parallel: 1 max-parallel: 1
matrix: matrix:
include: include:
- target: x86_64-pc-windows-msvc - os: windows-latest
target: x86_64-pc-windows-msvc
arch: x64 arch: x64
- target: aarch64-pc-windows-msvc - os: windows-latest
target: aarch64-pc-windows-msvc
arch: arm64 arch: arm64
runs-on: windows-latest # 22.04 is the oldest runner with webkit2gtk-4.1 (what Tauri 2
# needs); building on it keeps the glibc floor as low as possible
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
arch: x64
- os: macos-latest
target: aarch64-apple-darwin
arch: arm64
- os: macos-latest
target: x86_64-apple-darwin
arch: x64
runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y build-essential curl wget file \
libwebkit2gtk-4.1-dev librsvg2-dev libssl-dev libxdo-dev \
libayatana-appindicator3-dev patchelf
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: 22
@ -42,6 +64,10 @@ jobs:
- run: npm ci - run: npm ci
# TAURI_SIGNING_* below is the updater's minisign keypair, not OS code
# signing — there is no Apple certificate, so macOS builds are only
# ad-hoc signed (bundle.macOS.signingIdentity "-") and Gatekeeper still
# asks the user to allow them on first launch.
- uses: tauri-apps/tauri-action@v0 - uses: tauri-apps/tauri-action@v0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -215,6 +215,40 @@ recursing forever.
deleted sheets become `.numi` files under `backups/deleted/` and are pruned deleted sheets become `.numi` files under `backups/deleted/` and are pruned
by age. Saving is debounced at 400 ms. by age. Saving is debounced at 400 ms.
## Platforms and packaging
- One codebase, three desktop targets. `tauri.conf.json` holds everything
shared; the per-platform bits live in `tauri.windows.conf.json`,
`tauri.linux.conf.json` and `tauri.macos.conf.json`, which Tauri merges over
the base config (JSON Merge Patch) according to the target being built. Each
one only sets `bundle.targets` and that platform's bundler options — NSIS on
Windows, deb/rpm/AppImage on Linux, .app/.dmg on macOS.
- `release.yml` builds five artifacts on three runner families: Windows x64 and
arm64, Linux x64 (`ubuntu-22.04` — the oldest runner carrying webkit2gtk-4.1,
which keeps the glibc floor low), macOS arm64 and x64. Two separate macOS
targets rather than `universal-apple-darwin`: it matches how Windows already
ships per-architecture, halves each download, and gives the updater the
`darwin-aarch64`/`darwin-x86_64` keys it looks up by default.
- The whole matrix stays `max-parallel: 1`. Every job hands its assets to the
*same* GitHub Release object, and tauri-action's find-or-create check races if
two jobs reach it at once — adding platforms did not change that constraint,
it made it apply to more jobs.
- No OS code signing anywhere. Windows ships unsigned; macOS is *ad-hoc* signed
(`bundle.macOS.signingIdentity: "-"`), which needs no Apple certificate but is
required for Apple Silicon to run a downloaded binary at all — without it the
arm64 build is rejected as damaged rather than merely warned about. Gatekeeper
still asks the user to allow the app on first launch either way.
- `bundle.fileAssociations` covers `.numi`/`.sum` on Windows (NSIS registry
entries) and macOS (`CFBundleDocumentTypes`). On Linux it writes nothing
useful: registering an extension there also needs a shared-mime-info XML that
Tauri does not generate, so `.numi` files are opened by dragging them into the
window rather than from the file manager.
- `icons/icon.icns` is emitted by `scripts/gen-icon.ts` alongside the PNGs and
the `.ico`, hand-rolled the same dependency-free way: `icns` magic + total
length, then one `[OSType][length][PNG]` chunk per size, using the modern
PNG-payload tags `iconutil` produces (`icp4`/`icp5`, `ic07`–`ic14`). Rerun it
with `npm run icons` after touching the artwork.
## Auto-update ## Auto-update
- `updater.ts` calls `tauri-plugin-updater`'s `check()` on boot; if a signed - `updater.ts` calls `tauri-plugin-updater`'s `check()` on boot; if a signed
@ -242,7 +276,9 @@ recursing forever.
flipping it while the app is running takes effect immediately); the manual flipping it while the app is running takes effect immediately); the manual
button ignores the toggle and always works. button ignores the toggle and always works.
- The update endpoint is `latest.json` published alongside each GitHub - The update endpoint is `latest.json` published alongside each GitHub
Release. `release.yml` signs both architectures with a minisign keypair: Release. `release.yml` signs every platform's updater artifact with a
minisign keypair (this is Tauri's own update signing, unrelated to OS code
signing):
the public half is embedded in `tauri.conf.json`; the private half + its the public half is embedded in `tauri.conf.json`; the private half + its
password live only as `TAURI_SIGNING_PRIVATE_KEY(_PASSWORD)` repo secrets password live only as `TAURI_SIGNING_PRIVATE_KEY(_PASSWORD)` repo secrets
(write-only — there is no durable copy in the repo). Losing that key means (write-only — there is no durable copy in the repo). Losing that key means

View file

@ -217,6 +217,40 @@ src-tauri/
удалённые листы становятся `.numi`-файлами в `backups/deleted/` и удаляются по возрасту. удалённые листы становятся `.numi`-файлами в `backups/deleted/` и удаляются по возрасту.
Сохранение откладывается на 400 мс. Сохранение откладывается на 400 мс.
## Платформы и упаковка
- Одна кодовая база, три десктопных платформы. Всё общее лежит в
`tauri.conf.json`, платформенные куски — в `tauri.windows.conf.json`,
`tauri.linux.conf.json` и `tauri.macos.conf.json`, которые Tauri накладывает
поверх базового конфига (JSON Merge Patch) в зависимости от собираемой цели.
Каждый задаёт только `bundle.targets` и опции своего упаковщика: NSIS на
Windows, deb/rpm/AppImage на Linux, .app/.dmg на macOS.
- `release.yml` собирает пять артефактов на трёх видах раннеров: Windows x64 и
arm64, Linux x64 (`ubuntu-22.04` — самый старый раннер с webkit2gtk-4.1, что
держит планку glibc низко), macOS arm64 и x64. Две отдельные macOS-цели вместо
`universal-apple-darwin`: так же, как Windows уже собирается по архитектурам,
вдвое меньше вес загрузки, и апдейтер получает ключи `darwin-aarch64` /
`darwin-x86_64`, которые ищет по умолчанию.
- Вся матрица остаётся `max-parallel: 1`. Все джобы отдают артефакты в *один и
тот же* объект GitHub Release, а проверка find-or-create в tauri-action гонит,
если две джобы доходят до неё одновременно — добавление платформ это
ограничение не сняло, а распространило на большее число джоб.
- Подписи ОС нет нигде. Windows выходит неподписанным; macOS подписан *ad-hoc*
(`bundle.macOS.signingIdentity: "-"`) — сертификат Apple для этого не нужен, но
без него Apple Silicon вообще откажется запускать скачанный бинарник (сочтёт
повреждённым, а не просто предупредит). Gatekeeper в любом случае попросит
пользователя разрешить приложение при первом запуске.
- `bundle.fileAssociations` покрывает `.numi`/`.sum` на Windows (записи в реестр
из NSIS) и macOS (`CFBundleDocumentTypes`). На Linux он не даёт ничего
полезного: там для регистрации расширения нужен ещё XML для shared-mime-info,
который Tauri не генерирует, так что `.numi` открываются перетаскиванием в
окно, а не из файлового менеджера.
- `icons/icon.icns` создаёт `scripts/gen-icon.ts` рядом с PNG и `.ico`, вручную
и без зависимостей: магия `icns` + общая длина, дальше по чанку
`[OSType][длина][PNG]` на размер, с современными PNG-тегами, которые выдаёт
`iconutil` (`icp4`/`icp5`, `ic07`–`ic14`). После правки картинки перегенерить
через `npm run icons`.
## Автообновление ## Автообновление
- `updater.ts` вызывает `check()` из `tauri-plugin-updater` при запуске; если - `updater.ts` вызывает `check()` из `tauri-plugin-updater` при запуске; если
@ -245,7 +279,9 @@ src-tauri/
тике интервала, так что переключение прямо во время работы срабатывает тике интервала, так что переключение прямо во время работы срабатывает
сразу); ручная кнопка тумблер игнорирует и работает всегда. сразу); ручная кнопка тумблер игнорирует и работает всегда.
- Эндпоинт обновлений — `latest.json`, публикуемый вместе с каждым GitHub - Эндпоинт обновлений — `latest.json`, публикуемый вместе с каждым GitHub
Release. `release.yml` подписывает обе архитектуры парой ключей minisign: Release. `release.yml` подписывает артефакт обновления каждой платформы парой
ключей minisign (это собственная подпись обновлений Tauri, к подписи кода в ОС
отношения не имеет):
публичная половина зашита в `tauri.conf.json`; приватная половина + пароль публичная половина зашита в `tauri.conf.json`; приватная половина + пароль
живут только как секреты репозитория `TAURI_SIGNING_PRIVATE_KEY(_PASSWORD)` живут только как секреты репозитория `TAURI_SIGNING_PRIVATE_KEY(_PASSWORD)`
(write-only — в репозитории нет постоянной копии). Потеря этого ключа (write-only — в репозитории нет постоянной копии). Потеря этого ключа

View file

@ -29,16 +29,29 @@ Works in **English and Russian** at the same time: `5 метров в см`, `20
## Install ## Install
Grab the latest `Summarum_*_x64-setup.exe` (or `arm64-setup.exe` on Windows Grab the build for your system from [Releases](../../releases). The whole app
on ARM) from [Releases](../../releases), run it, done. The whole app is is about 2 MB and checks for updates on its own after that.
about 2 MB. It checks for updates on its own after that.
- **Windows** — `Summarum_*_x64-setup.exe` (or `arm64-setup.exe` on Windows on
ARM). Run it, done.
- **Linux** — `.AppImage` (`chmod +x`, then run) or the `.deb`/`.rpm` if you'd
rather install it properly. Self-updating only works on the AppImage; deb and
rpm are updated by downloading the next one.
- **macOS** — `.dmg`, Apple Silicon or Intel. These builds are not signed with
an Apple Developer certificate, so the first launch has to be a right-click →
**Open** rather than a double-click (or run
`xattr -cr /Applications/Summarum.app` once in a terminal). Every launch after
that is normal.
Requirements: Requirements:
- **Windows 10 (1803+) or Windows 11**, 64-bit. - **Windows 10 (1803+) or Windows 11**, 64-bit, plus the
- **[WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)** — **[WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)** —
already part of Windows 11 and most updated Windows 10 systems; if it's already part of Windows 11 and most updated Windows 10 systems; if it's
missing, the installer downloads it automatically. missing, the installer downloads it automatically.
- **Linux** with `webkit2gtk-4.1` (Ubuntu 22.04+, Debian 12+, Fedora 36+) and
`libayatana-appindicator3` for the tray icon.
- **macOS 10.13** or newer.
- Internet connection is only needed for live rates and market data — everything - Internet connection is only needed for live rates and market data — everything
else works offline (rates fall back to a cached/bundled snapshot). else works offline (rates fall back to a cached/bundled snapshot).
@ -165,19 +178,28 @@ To build from source you need:
- [Node.js](https://nodejs.org/) 22.6 or newer (`npm run icons` relies on - [Node.js](https://nodejs.org/) 22.6 or newer (`npm run icons` relies on
Node's built-in TypeScript support) Node's built-in TypeScript support)
- [Rust](https://rustup.rs/) (stable, MSVC toolchain) - [Rust](https://rustup.rs/), stable
- [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) - On Windows: the MSVC toolchain plus
[Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
with the "Desktop development with C++" workload (the MSVC linker) with the "Desktop development with C++" workload (the MSVC linker)
- On Linux: `libwebkit2gtk-4.1-dev`, `libayatana-appindicator3-dev`,
`librsvg2-dev`, `libxdo-dev`, `libssl-dev`, `patchelf` and a C toolchain
- On macOS: Xcode Command Line Tools
- See the [Tauri prerequisites](https://tauri.app/start/prerequisites/) page - See the [Tauri prerequisites](https://tauri.app/start/prerequisites/) page
if anything refuses to compile if anything refuses to compile
`npm run tauri build` produces whatever the host platform bundles — NSIS on
Windows, deb/rpm/AppImage on Linux, .app/.dmg on macOS. Cross-compiling
between the three is not supported; the release workflow builds each on its
own runner.
Stack: [Tauri 2](https://tauri.app) + TypeScript + CodeMirror 6. Stack: [Tauri 2](https://tauri.app) + TypeScript + CodeMirror 6.
## Credits ## Credits
Inspired by the excellent [Numi](https://numi.app) by Dmitry Nikolaev — Inspired by the excellent [Numi](https://numi.app) by Dmitry Nikolaev —
if you're on macOS, buy it. Summarum is an independent project, built from if you're on macOS, buy it. Summarum is an independent project, built from
scratch for Windows. scratch for Windows, Linux and macOS.
## License ## License

View file

@ -27,14 +27,28 @@ AAPL в USD → $213.55
## Установка ## Установка
Скачай последний `Summarum_*_x64-setup.exe` (или `arm64-setup.exe` для Windows on ARM) из [Releases](../../releases), запусти — готово. Весь установщик около 2 МБ. Дальше обновления проверяются сами. Скачай сборку под свою систему из [Releases](../../releases). Приложение весит
около 2 МБ, дальше обновления проверяются сами.
- **Windows** — `Summarum_*_x64-setup.exe` (или `arm64-setup.exe` для Windows on
ARM). Запусти — готово.
- **Linux** — `.AppImage` (`chmod +x` и запускай) либо `.deb`/`.rpm`, если хочется
поставить нормальным пакетом. Самообновление работает только у AppImage, deb и
rpm обновляются скачиванием следующей версии.
- **macOS** — `.dmg` для Apple Silicon или Intel. Сборки не подписаны сертификатом
Apple Developer, поэтому первый запуск — правый клик → **Открыть**, а не двойной
клик (либо один раз выполнить `xattr -cr /Applications/Summarum.app` в терминале).
Дальше запускается как обычно.
Требования: Требования:
- **Windows 10 (1803+) или Windows 11**, 64-бит. - **Windows 10 (1803+) или Windows 11**, 64-бит, и
- **[WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)** — **[WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)** —
уже встроен в Windows 11 и большинство обновлённых Windows 10; если отсутствует, уже встроен в Windows 11 и большинство обновлённых Windows 10; если отсутствует,
установщик скачает его автоматически. установщик скачает его автоматически.
- **Linux** с `webkit2gtk-4.1` (Ubuntu 22.04+, Debian 12+, Fedora 36+) и
`libayatana-appindicator3` для иконки в трее.
- **macOS 10.13** или новее.
- Интернет нужен только для живых курсов и рыночных данных — всё остальное работает офлайн - Интернет нужен только для живых курсов и рыночных данных — всё остальное работает офлайн
(курсы берутся из кэша или встроенного снимка). (курсы берутся из кэша или встроенного снимка).
@ -159,17 +173,25 @@ npm run tauri build # собрать установщик
Для сборки нужны: Для сборки нужны:
- [Node.js](https://nodejs.org/) 22.6 или новее - [Node.js](https://nodejs.org/) 22.6 или новее
- [Rust](https://rustup.rs/) (stable, MSVC toolchain) - [Rust](https://rustup.rs/), stable
- [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) - На Windows: toolchain MSVC и
[Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
с компонентом «Desktop development with C++» (нужен линковщик MSVC) с компонентом «Desktop development with C++» (нужен линковщик MSVC)
- На Linux: `libwebkit2gtk-4.1-dev`, `libayatana-appindicator3-dev`,
`librsvg2-dev`, `libxdo-dev`, `libssl-dev`, `patchelf` и компилятор C
- На macOS: Xcode Command Line Tools
- Если что-то не собирается — см. [Tauri prerequisites](https://tauri.app/start/prerequisites/) - Если что-то не собирается — см. [Tauri prerequisites](https://tauri.app/start/prerequisites/)
`npm run tauri build` собирает то, что умеет хост-платформа: NSIS на Windows,
deb/rpm/AppImage на Linux, .app/.dmg на macOS. Кросс-компиляция между ними не
поддерживается — релизный workflow собирает каждую платформу на своём раннере.
Стек: [Tauri 2](https://tauri.app) + TypeScript + CodeMirror 6. Стек: [Tauri 2](https://tauri.app) + TypeScript + CodeMirror 6.
## Благодарность ## Благодарность
Вдохновлён [Numi](https://numi.app) от Дмитрия Николаева — Вдохновлён [Numi](https://numi.app) от Дмитрия Николаева —
если ты на macOS, купи его. Summarum — независимый проект, написанный с нуля для Windows. если ты на macOS, купи его. Summarum — независимый проект, написанный с нуля для Windows, Linux и macOS.
## Лицензия ## Лицензия

View file

@ -2,7 +2,7 @@
"name": "summarum", "name": "summarum",
"private": true, "private": true,
"version": "1.2.6", "version": "1.2.6",
"description": "Summarum — natural language calculator notebook for Windows", "description": "Summarum — natural language calculator notebook",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View file

@ -113,7 +113,7 @@ function draw(size: number): Buffer {
fs.mkdirSync(outDir, { recursive: true }); fs.mkdirSync(outDir, { recursive: true });
const pngs = new Map<number, Buffer>(); const pngs = new Map<number, Buffer>();
for (const size of [16, 32, 48, 64, 128, 256, 512]) { for (const size of [16, 32, 48, 64, 128, 256, 512, 1024]) {
pngs.set(size, encodePng(size, draw(size))); pngs.set(size, encodePng(size, draw(size)));
} }
@ -154,3 +154,38 @@ function buildIco(sizes: number[]): Buffer {
fs.writeFileSync(path.join(outDir, "icon.ico"), buildIco([16, 32, 48, 64, 128, 256])); fs.writeFileSync(path.join(outDir, "icon.ico"), buildIco([16, 32, 48, 64, 128, 256]));
console.log("icon.ico (6 sizes)"); console.log("icon.ico (6 sizes)");
// .icns: "icns" magic + total length, then [OSType][length incl. header][PNG].
// Both lengths are big-endian and count their own 8-byte header. The tag set
// mirrors what `iconutil` emits for a modern .iconset — every ic*/icp* entry
// below carries a plain PNG payload (10.7+), so no JPEG 2000 encoder is needed.
const ICNS_ENTRIES: Array<[string, number]> = [
["icp4", 16],
["icp5", 32],
["ic11", 32], // 16@2x
["ic12", 64], // 32@2x
["ic07", 128],
["ic13", 256], // 128@2x
["ic08", 256],
["ic14", 512], // 256@2x
["ic09", 512],
["ic10", 1024], // 512@2x
];
function buildIcns(): Buffer {
const parts = ICNS_ENTRIES.map(([tag, size]) => {
const data = pngs.get(size)!;
const head = Buffer.alloc(8);
head.write(tag, 0, 4, "ascii");
head.writeUInt32BE(8 + data.length, 4);
return Buffer.concat([head, data]);
});
const body = Buffer.concat(parts);
const header = Buffer.alloc(8);
header.write("icns", 0, 4, "ascii");
header.writeUInt32BE(8 + body.length, 4);
return Buffer.concat([header, body]);
}
fs.writeFileSync(path.join(outDir, "icon.icns"), buildIcns());
console.log(`icon.icns (${ICNS_ENTRIES.length} entries)`);

View file

@ -1,7 +1,7 @@
[package] [package]
name = "summarum" name = "summarum"
version = "1.2.6" version = "1.2.6"
description = "Summarum — natural language calculator notebook for Windows" description = "Summarum — natural language calculator notebook"
edition = "2021" edition = "2021"
[build-dependencies] [build-dependencies]

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

View file

@ -1,6 +1,9 @@
// Summarum — Tauri shell: tray, hide-to-tray window, app-data storage, // Summarum — Tauri shell: tray, hide-to-tray window, app-data storage,
// currency rates fetching with an on-disk cache, extension script loading. // currency rates fetching with an on-disk cache, extension script loading.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashSet; use std::collections::HashSet;

View file

@ -28,14 +28,14 @@
}, },
"bundle": { "bundle": {
"active": true, "active": true,
"targets": ["nsis"],
"createUpdaterArtifacts": true, "createUpdaterArtifacts": true,
"icon": [ "icon": [
"icons/32x32.png", "icons/32x32.png",
"icons/128x128.png", "icons/128x128.png",
"icons/128x128@2x.png", "icons/128x128@2x.png",
"icons/icon.png", "icons/icon.png",
"icons/icon.ico" "icons/icon.ico",
"icons/icon.icns"
], ],
"fileAssociations": [ "fileAssociations": [
{ {
@ -44,13 +44,7 @@
"description": "Summarum calculation sheet", "description": "Summarum calculation sheet",
"role": "Editor" "role": "Editor"
} }
], ]
"windows": {
"nsis": {
"installMode": "currentUser",
"languages": ["English", "Russian"]
}
}
}, },
"plugins": { "plugins": {
"updater": { "updater": {

View file

@ -0,0 +1,5 @@
{
"bundle": {
"targets": ["deb", "rpm", "appimage"]
}
}

View file

@ -0,0 +1,8 @@
{
"bundle": {
"targets": ["app", "dmg"],
"macOS": {
"signingIdentity": "-"
}
}
}

View file

@ -0,0 +1,11 @@
{
"bundle": {
"targets": ["nsis"],
"windows": {
"nsis": {
"installMode": "currentUser",
"languages": ["English", "Russian"]
}
}
}
}