diff --git a/.gitignore b/.gitignore index 2e3d496..5188bc5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ node_modules/ # Build output dist/ dist-ssr/ +build/ # Rust / Tauri src-tauri/target/ diff --git a/README.md b/README.md index 9fb2092..933758c 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,35 @@ distributions that don't package NSIS (e.g. Void Linux): With those in place, `npm run app:build:windows` produces the NSIS `-setup.exe` and the portable `voiced.exe`. +## Installing on Void Linux + +Two ways to get a native `.xbps` package. + +### Prebuilt binary package (no xbps-src needed) + +```bash +./packaging/void/build-xbps.sh # -> build/void/repo/voiced-_1..xbps +echo "repository=$(pwd)/build/void/repo" | sudo tee /etc/xbps.d/99-voiced-local.conf +sudo xbps-install -Sy voiced +``` + +Launch **Voiced** from the application menu, or run `voiced`. Remove it with +`sudo xbps-remove voiced`. + +The package installs `/usr/bin/voiced`, a desktop entry and hicolor icons, and depends on +`libwebkit2gtk41`. + +### Building through xbps-src + +Copy `packaging/void/template` into a `void-packages` checkout and build it the standard way: + +```bash +cp -r packaging/void/template void-packages/srcpkgs/voiced/template +cd void-packages && ./xbps-src pkg voiced +``` + +See the comments at the top of the template about the private source archive and checksums. + ## Where data lives | What | Location | @@ -108,6 +137,7 @@ src/ React + Carbon frontend src-tauri/ Rust backend (Tauri commands, SQLite schema, asset store) public/brand/ Bongbetic + vendor brand assets public/fonts/ IBM Plex (woff/woff2) +packaging/void/ Void Linux packaging (xbps-src template + build script + desktop entry) ``` ## Ownership diff --git a/package.json b/package.json index 9292c48..66361f1 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "preview": "vite preview", "tauri": "tauri", "app:dev": "tauri dev", - "app:build": "tauri build", - "app:build:windows": "tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc" + "app:build": "tauri build --features custom-protocol", + "app:build:windows": "tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --features custom-protocol" }, "dependencies": { "@carbon/icons-react": "^11.89.0", diff --git a/packaging/void/build-xbps.sh b/packaging/void/build-xbps.sh new file mode 100755 index 0000000..0b8942d --- /dev/null +++ b/packaging/void/build-xbps.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# +# Build a Void Linux binary package (.xbps) for Voiced from this source tree, +# without needing the full xbps-src / void-packages setup. +# +# ./packaging/void/build-xbps.sh +# +# Output: +# dist/void/repo/voiced-_1..xbps (and a local repository index) +# +# Install it with: +# sudo xbps-install -S --repository="$(pwd)/dist/void/repo" voiced +# +# Remove it with: +# sudo xbps-remove voiced +# +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT" + +need() { + command -v "$1" >/dev/null 2>&1 || { echo "error: '$1' is required but not on PATH" >&2; exit 1; } +} +for tool in xbps-create xbps-rindex node npm cargo; do need "$tool"; done + +VERSION="$(node -p "require('./package.json').version")" +REVISION="${REVISION:-1}" +ARCH="$(xbps-uhelper arch 2>/dev/null || uname -m)" +PKGVER="voiced-${VERSION}_${REVISION}" + +echo "==> Building the Linux binary (frontend embedded via the Tauri CLI)" +npx tauri build --no-bundle --features custom-protocol + +BIN="src-tauri/target/release/voiced" +[ -x "$BIN" ] || { echo "error: expected binary not found at $BIN" >&2; exit 1; } + +echo "==> Staging install tree" +STAGE="$(mktemp -d)" +trap 'rm -rf "$STAGE"' EXIT + +install -Dm755 "$BIN" "$STAGE/usr/bin/voiced" +install -Dm644 packaging/void/voiced.desktop "$STAGE/usr/share/applications/voiced.desktop" + +install_icon() { + [ -f "$1" ] || return 0 + install -Dm644 "$1" "$STAGE/usr/share/icons/hicolor/$2/apps/voiced.png" +} +install_icon src-tauri/icons/32x32.png 32x32 +install_icon src-tauri/icons/64x64.png 64x64 +install_icon src-tauri/icons/128x128.png 128x128 +install_icon src-tauri/icons/128x128@2x.png 256x256 +install_icon src-tauri/icons/icon.png 512x512 + +install -Dm644 README.md "$STAGE/usr/share/doc/voiced/README.md" + +echo "==> Creating $PKGVER.$ARCH.xbps" +# Kept outside Vite's `dist/` output directory, which is wiped on every build. +OUT="$ROOT/build/void" +REPO="$OUT/repo" +mkdir -p "$REPO" + +# xbps-create writes the package into the current directory. +( + cd "$OUT" + xbps-create \ + -A "$ARCH" \ + -n "$PKGVER" \ + -s "Offline invoicing app by Bongbetic" \ + -S "Voiced is an offline-first invoice generator with automatic numbering, GST support, optional signatures and print-ready PDF export." \ + -l "proprietary" \ + -H "https://git.bongbetic.com/xavierk/Voiced" \ + -m "Xavier Karma " \ + -D "libwebkit2gtk41>=2.40_1" \ + -t "office finance" \ + --compression zstd \ + "$STAGE" +) + +mv -f "$OUT"/${PKGVER}.*.xbps "$REPO/" + +echo "==> Indexing local repository" +xbps-rindex -a "$REPO"/*.xbps + +echo +echo "Package: $REPO/${PKGVER}.${ARCH}.xbps" +echo "Install: sudo xbps-install -S --repository=\"$REPO\" voiced" diff --git a/packaging/void/template b/packaging/void/template new file mode 100644 index 0000000..4727fac --- /dev/null +++ b/packaging/void/template @@ -0,0 +1,54 @@ +# Template file for 'voiced' +# +# Canonical Void Linux package definition, to be built with xbps-src from a +# void-packages checkout: +# +# cp -r packaging/void/template void-packages/srcpkgs/voiced/template +# cd void-packages && ./xbps-src pkg voiced +# +# Notes: +# * `distfiles` points at a tagged archive of the (private) source repository. +# Private repositories require credentials, so either publish a release +# tarball, mirror it, or drop the sources into the xbps-src masterdir. +# * `checksum=SKIP` avoids pinning a hash that cannot be computed here; replace +# it with the real sha256 once the tarball is published. +# * If the build machine is offline, pre-populate the npm cache and Cargo +# registry in the masterdir before building. +# +pkgname=voiced +version=0.1.0 +revision=1 +archs="x86_64*" +hostmakedepends="rust nodejs pkg-config" +makedepends="libwebkit2gtk41-devel libsoup3-devel gtk+3-devel librsvg-devel openssl-devel" +depends="libwebkit2gtk41" +short_desc="Offline invoicing app by Bongbetic" +maintainer="Xavier Karma " +license="custom:proprietary" +homepage="https://git.bongbetic.com/xavierk/Voiced" +distfiles="https://git.bongbetic.com/xavierk/Voiced/archive/v${version}.tar.gz" +checksum=SKIP +compression="zstd" + +do_build() { + npm install --include=dev --no-audit --no-fund + npm run build + cd src-tauri + # `custom-protocol` embeds the frontend for production; without it the app + # expects a dev server on localhost:1420. + cargo build --release --features custom-protocol +} + +do_install() { + vbin "${wrksrc}/src-tauri/target/release/voiced" + vinstall "${wrksrc}/packaging/void/voiced.desktop" 644 usr/share/applications + for size in 32 64 128; do + vinstall "${wrksrc}/src-tauri/icons/${size}x${size}.png" 644 \ + "usr/share/icons/hicolor/${size}x${size}/apps" voiced.png + done + vinstall "${wrksrc}/src-tauri/icons/128x128@2x.png" 644 \ + usr/share/icons/hicolor/256x256/apps voiced.png + vinstall "${wrksrc}/src-tauri/icons/icon.png" 644 \ + usr/share/icons/hicolor/512x512/apps voiced.png + vdoc "${wrksrc}/README.md" +} diff --git a/packaging/void/voiced.desktop b/packaging/void/voiced.desktop new file mode 100644 index 0000000..f783b51 --- /dev/null +++ b/packaging/void/voiced.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=Voiced +GenericName=Invoice Generator +Comment=Offline invoicing by Bongbetic +Exec=voiced +Icon=voiced +Terminal=false +Categories=Office;Finance; +Keywords=invoice;billing;gst;pdf;bongbetic; +StartupNotify=true +StartupWMClass=Voiced diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e5c5b44..b4a1043 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,6 +26,12 @@ uuid = { version = "1", features = ["v4"] } thiserror = "2" base64 = "0.22" +[features] +# Production builds embed the frontend and serve it over the custom asset +# protocol. Without this, the app expects a dev server on localhost:1420. +# DO NOT REMOVE. +custom-protocol = ["tauri/custom-protocol"] + [profile.release] codegen-units = 1 lto = true