diff --git a/pkgbuilds/granola/.omarchy/package.json b/pkgbuilds/granola/.omarchy/package.json new file mode 100644 index 000000000..bd2155e59 --- /dev/null +++ b/pkgbuilds/granola/.omarchy/package.json @@ -0,0 +1,13 @@ +{ + "source": "local", + "release_ring": "fast", + "upstream": { + "watch": { + "json": "https://api.granola.ai/v1/get-versions", + "path": "production" + } + }, + "origin": { + "aur": "granola" + } +} diff --git a/pkgbuilds/granola/PKGBUILD b/pkgbuilds/granola/PKGBUILD new file mode 100644 index 000000000..22ceebcc1 --- /dev/null +++ b/pkgbuilds/granola/PKGBUILD @@ -0,0 +1,180 @@ +# Maintainer: Shawn Yeager +# +# Repackages the official Granola macOS .dmg as a native Linux app, using +# distro electron44. Conversion follows https://github.com/tirtha4/Granola-for-Linux +# (and the AUR granola recipe derived from it). x86_64 only: the native +# module is built for linux-x64. +# +# Source is the versioned CloudFront URL Granola documents as the static CDN +# target of api.granola.ai/v1/download-latest. There is no GitHub release. +# Version discovery uses api.granola.ai/v1/get-versions. +# +# Format: shfmt -ln bash -i 2 -d PKGBUILD + +pkgname=granola +pkgver=7.568.0 +pkgrel=1 +pkgdesc="AI notepad for meetings, repackaged from the official macOS build" +arch=('x86_64') +url="https://www.granola.ai" +license=('LicenseRef-proprietary') +_electron=electron44 +# Must match the installed $_electron package (pacman -Q $_electron). +_elver=44.3.0 +# Must match the fork version inside the .dmg. +_bs3ver=12.9.0 +depends=( + "$_electron" + 'libstdc++' + 'glibc' + 'sh' + 'desktop-file-utils' + 'xdg-utils' +) +conflicts=('granola-wine') +makedepends=( + '7zip' + 'gcc' + 'make' + 'nodejs' + 'node-gyp' + 'python' +) +options=('!debug') +source=( + "granola-$pkgver.dmg::https://dr2v7l5emb758.cloudfront.net/$pkgver/Granola-$pkgver-mac-universal.dmg" + "https://registry.npmjs.org/better-sqlite3-multiple-ciphers/-/better-sqlite3-multiple-ciphers-$_bs3ver.tgz" + "https://electronjs.org/headers/v$_elver/node-v$_elver-headers.tar.gz" + "granola.desktop" +) +noextract=( + "granola-$pkgver.dmg" + "node-v$_elver-headers.tar.gz" +) +sha256sums=('29fabbacdbad860be46e434b39fdbbf424982affed39f93e0e47899532784b73' + 'ad8ceb2cfe687e0c106547fdd281f0d20b40688200d04f40bb163afd1f102609' + 'ce3ef7604c6a19f6c8e018a771919cacf4a85cea56ddfaa850335908fea83377' + '20c443a00fcba28a00292aab36eda376af28e2b6d2f63e083221edc1af7e3f18') + +_res="Granola/Granola.app/Contents/Resources" +_bs3rel="app.asar.unpacked/node_modules/better-sqlite3-multiple-ciphers" + +_7z() { + if command -v 7zz >/dev/null; then + 7zz "$@" + else + 7z "$@" + fi +} + +prepare() { + local dmg="$srcdir/granola-$pkgver.dmg" + + local sysver + sysver="$(&2 + return 1 + fi + + _7z e "$dmg" \ + "Granola/Granola.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Info.plist" \ + -ofw -y >/dev/null || { echo "cannot read the .dmg" >&2; return 1; } + local dmgel + dmgel="$(grep -A1 CFBundleVersion fw/Info.plist | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)" + if [[ "${dmgel%%.*}" != "${_electron#electron}" ]]; then + echo ".dmg bundles Electron $dmgel, set _electron=electron${dmgel%%.*} and depends to match" >&2 + return 1 + fi + + _7z x "$dmg" "$_res/app.asar" "$_res/app.asar.unpacked" "$_res/icons" \ + -opayload -y >/dev/null + + local bs3="payload/$_res/$_bs3rel" + local realver + realver="$(node -p "require('$srcdir/$bs3/package.json').version")" + if [[ "$realver" != "$_bs3ver" ]]; then + echo "bundled better-sqlite3-multiple-ciphers is $realver, set _bs3ver=$realver and run updpkgsums" >&2 + return 1 + fi + cp package/binding.gyp "$bs3/" + + # api.granola.ai answers 500 to any request carrying platform=linux, + # including the sign-in URL. The app maps darwin->macOS and win32->Windows + # and passes anything else through verbatim; rewrite that fallback so Linux + # reports Windows. The replacement is padded to the same byte length + # because the asar header records file offsets. + python - "payload/$_res/app.asar" <<'PYEOF' +import sys, pathlib +p = pathlib.Path(sys.argv[1]); data = p.read_bytes(); total = 0 +for pat in (b'?`Windows`:window.electron.platform', b'?`Windows`:process.platform'): + rep = b'?`Windows`:`Windows`'.ljust(len(pat)) + total += data.count(pat) + data = data.replace(pat, rep) +if total == 0: + sys.exit("no platform fallback found; Granola's bundler output changed") +p.write_bytes(data) +print(f"patched {total} platform fallback(s)") +PYEOF +} + +build() { + cd "payload/$_res/$_bs3rel" || exit 1 + node-gyp rebuild --release --runtime=electron --target="$_elver" --arch=x64 \ + --dist-url=https://electronjs.org/headers \ + --tarball="$srcdir/node-v$_elver-headers.tar.gz" \ + --devdir="$srcdir/gyp-devdir" +} + +check() { + local bs3="$srcdir/payload/$_res/$_bs3rel" + local db="$srcdir/smoke.db" + rm -f "$db" + ELECTRON_RUN_AS_NODE=1 NODE_PATH="$srcdir/payload/$_res/app.asar/node_modules" \ + "/usr/lib/$_electron/electron" -e " + const Database = require('$bs3/lib/index.js'); + const db = new Database('$db'); + db.pragma(\"cipher='sqlcipher'\"); + db.pragma(\"key='smoketest'\"); + db.exec('CREATE TABLE t(a)'); + let fired = false; + db.updateHook(() => { fired = true; }); + db.prepare('INSERT INTO t VALUES (1)').run(); + if (db.prepare('SELECT count(*) c FROM t').get().c !== 1) throw new Error('insert failed'); + if (!fired) throw new Error('updateHook did not fire'); + db.close(); + " + rm -f "$db" +} + +package() { + install -Dm644 "payload/$_res/app.asar" "$pkgdir/usr/lib/granola/app.asar" + cp -r "payload/$_res/app.asar.unpacked" "$pkgdir/usr/lib/granola/app.asar.unpacked" + + # Keep only the built binaries from the module tree; drop the C++ source, + # sqlite amalgamation, and node-gyp intermediates. + local bs3="$pkgdir/usr/lib/granola/$_bs3rel" + install -Dm755 "$bs3"/build/Release/*.node -t "$srcdir/keep" + rm -rf "$bs3/build" "$bs3/deps" "$bs3/src" "$bs3/binding.gyp" + install -Dm755 "$srcdir/keep"/*.node -t "$bs3/build/Release" + + # icon.png is a black "g|" wordmark on a transparent canvas. mac-icon.png + # is the lime squircle Granola actually ships. + if [[ -f "payload/$_res/icons/mac-icon.png" ]]; then + install -Dm644 "payload/$_res/icons/mac-icon.png" "$pkgdir/usr/share/pixmaps/granola.png" + else + install -Dm644 "payload/$_res/icons/icon.png" "$pkgdir/usr/share/pixmaps/granola.png" + fi + install -Dm644 granola.desktop "$pkgdir/usr/share/applications/granola.desktop" + + install -Dm755 /dev/stdin "$pkgdir/usr/bin/granola" <