diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 29d18b0..8754f27 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,6 +50,7 @@ jobs: pacman -Syu --noconfirm git jq python libarchive python tests/oma-service-removal.py python tests/upstream-watch.py + python tests/obsidian-upstream.py ./bin/sync-upstream self-test ./bin/sync-rebuilds --self-test ./bin/omarchy-pkgs self-test diff --git a/pkgbuilds/obsidian/.omarchy/package.json b/pkgbuilds/obsidian/.omarchy/package.json new file mode 100644 index 0000000..2a9719d --- /dev/null +++ b/pkgbuilds/obsidian/.omarchy/package.json @@ -0,0 +1,3 @@ +{ + "source": "local" +} diff --git a/pkgbuilds/obsidian/.omarchy/upstream.py b/pkgbuilds/obsidian/.omarchy/upstream.py new file mode 100644 index 0000000..3e93edf --- /dev/null +++ b/pkgbuilds/obsidian/.omarchy/upstream.py @@ -0,0 +1,45 @@ +#!/usr/bin/python3 +"""Report the newest stable ARM desktop installer, ignoring mobile-only tags.""" +import json +import re +import subprocess + + +def select(releases): + if not isinstance(releases, list): + raise ValueError('Expected a GitHub release list') + candidates = [] + for release in releases: + if release.get('draft') or release.get('prerelease'): + continue + match = re.fullmatch(r'v([0-9]+(?:\.[0-9]+)*)', release.get('tag_name', '')) + if not match: + continue + version = match[1] + assets = [a for a in release.get('assets', []) + if a.get('name') == f'obsidian-{version}-arm64.tar.gz'] + if not assets: + continue + if len(assets) != 1: + raise ValueError('Duplicate desktop installer') + candidates.append((tuple(map(int, version.split('.'))), version, release, assets[0])) + if not candidates: + raise ValueError('No stable ARM desktop installer found') + _, version, release, asset = max(candidates, key=lambda row: row[0]) + digest = asset.get('digest') or '' + if not re.fullmatch(r'sha256:[0-9a-f]{64}', digest): + raise ValueError('Selected desktop installer lacks its SHA256 digest') + expected = f'https://github.com/obsidianmd/obsidian-releases/releases/download/v{version}/obsidian-{version}-arm64.tar.gz' + if asset.get('browser_download_url') != expected: + raise ValueError('Unexpected desktop installer URL') + return {'pkgver': version, 'published_at': release['published_at'], + 'sha256sums': {'aarch64': [digest[7:]]}} + + +if __name__ == '__main__': + response = subprocess.check_output([ + 'curl', '--proto', '=https', '--proto-redir', '=https', '-fsSL', + '--retry', '3', '--max-time', '60', + 'https://api.github.com/repos/obsidianmd/obsidian-releases/releases?per_page=100' + ], text=True) + print(json.dumps(select(json.loads(response)))) diff --git a/pkgbuilds/obsidian/.omarchy/upstream.sh b/pkgbuilds/obsidian/.omarchy/upstream.sh new file mode 100644 index 0000000..c422cec --- /dev/null +++ b/pkgbuilds/obsidian/.omarchy/upstream.sh @@ -0,0 +1,3 @@ +#!/bin/bash +set -euo pipefail +python3 "$(dirname "$0")/upstream.py" diff --git a/pkgbuilds/obsidian/PKGBUILD b/pkgbuilds/obsidian/PKGBUILD new file mode 100644 index 0000000..00e8060 --- /dev/null +++ b/pkgbuilds/obsidian/PKGBUILD @@ -0,0 +1,94 @@ +# Maintainer: Shun Li +# Extracted from omacom/omarchy-pkgs PR #240, revision dcef132b2df11bb762e233ea8c6af3110f079b67. + +pkgname=obsidian +pkgver=1.13.7 +pkgrel=2 +pkgdesc='Knowledge base on top of a local folder of plain-text Markdown files' +arch=('aarch64') +url='https://obsidian.md/' +license=('custom') +depends=( + 'alsa-lib' + 'at-spi2-core' + 'cairo' + 'dbus' + 'expat' + 'fontconfig' + 'gcc-libs' + 'glib2' + 'glibc' + 'gtk3' + 'hicolor-icon-theme' + 'libcups' + 'libnotify' + 'libsecret' + 'libx11' + 'libxcb' + 'libxcomposite' + 'libxdamage' + 'libxext' + 'libxfixes' + 'libxkbcommon' + 'libxrandr' + 'mesa' + 'nspr' + 'nss' + 'pango' + 'systemd-libs' + 'xdg-utils' +) +provides=('obsidian') +conflicts=('obsidian-appimage' 'obsidian-bin') +options=('!debug' '!strip') +source=('obsidian.desktop') +source_aarch64=( + "obsidian-${pkgver}-arm64.tar.gz::https://github.com/obsidianmd/obsidian-releases/releases/download/v${pkgver}/obsidian-${pkgver}-arm64.tar.gz" +) +sha256sums=('42ecb332c900a01b2a6db0a70b4cc8c0553e863aee72894b23ed20a6289b131c') +sha256sums_aarch64=('98aac34d1f132a35cf506fc3fa196d595dcdeefdebd44b0cc5faaa7a1a210de2') + +package() { + local source_dir="$srcdir/obsidian-${pkgver}-arm64" + local install_dir="$pkgdir/opt/obsidian" + + install -d "$install_dir" + cp -a --no-preserve=ownership "$source_dir/." "$install_dir/" + + # The official ARM64 archive currently carries two x86_64-only addons. + # Remove only an unusable x86_64 payload; retain it automatically if the + # vendor starts shipping an AArch64 build in a later release. + local addon machine + for addon in \ + resources/app.asar.unpacked/node_modules/btime/binding.node \ + resources/app.asar.unpacked/node_modules/get-fonts/binding.node; do + [[ -f "$install_dir/$addon" ]] || continue + machine=$(readelf -h "$install_dir/$addon" | + sed -n 's/^[[:space:]]*Machine:[[:space:]]*//p') + case "$machine" in + AArch64) ;; + 'Advanced Micro Devices X86-64') rm -f "$install_dir/$addon" ;; + *) error "Unexpected Obsidian addon architecture: $addon ($machine)"; return 1 ;; + esac + done + + find "$install_dir" -type d -exec chmod 755 {} + + find "$install_dir" -type f -exec chmod 644 {} + + chmod 755 \ + "$install_dir/chrome_crashpad_handler" \ + "$install_dir/obsidian" \ + "$install_dir/obsidian-cli" + chmod 4755 "$install_dir/chrome-sandbox" + + install -d "$pkgdir/usr/bin" + ln -s /opt/obsidian/obsidian "$pkgdir/usr/bin/obsidian" + ln -s /opt/obsidian/obsidian-cli "$pkgdir/usr/bin/obsidian-cli" + install -Dm644 "$srcdir/obsidian.desktop" \ + "$pkgdir/usr/share/applications/obsidian.desktop" + install -Dm644 "$install_dir/resources/icon.png" \ + "$pkgdir/usr/share/icons/hicolor/512x512/apps/obsidian.png" + install -Dm644 "$install_dir/LICENSE.electron.txt" \ + "$pkgdir/usr/share/licenses/$pkgname/LICENSE.electron.txt" + install -Dm644 "$install_dir/LICENSES.chromium.html" \ + "$pkgdir/usr/share/licenses/$pkgname/LICENSES.chromium.html" +} diff --git a/pkgbuilds/obsidian/README.md b/pkgbuilds/obsidian/README.md new file mode 100644 index 0000000..4d12c27 --- /dev/null +++ b/pkgbuilds/obsidian/README.md @@ -0,0 +1,11 @@ +# Obsidian on ARM + +This is the canonical `obsidian` package, extracted from omacom/omarchy-pkgs #240 at `dcef132b2df11bb762e233ea8c6af3110f079b67`. It repackages the official ARM64 desktop tarball and preserves the original contributor attribution and Electron/Chromium license files. + +The package owns the installed application bundle and Electron runtime. Obsidian's user-controlled in-app updater may separately update application code; no updater-disabling patch is applied. See https://obsidian.md/help/updates. + +The package-local upstream hook selects stable desktop releases containing the exact ARM64 tarball, ignoring mobile-only tags. Missing SHA256 metadata on the selected desktop release fails rather than silently selecting an older installer. Run `python tests/obsidian-upstream.py` for offline tests. Upstream's scheduled sync opens or updates its normal review PR; maintainers need GitHub PR notifications enabled and should monitor failed sync runs too. + +This package conflicts with `obsidian-appimage` and `obsidian-bin`. It does not automatically remove them or migrate users. Adoption in Omarchy Mac requires a separate change to the existing ARM preinstall/removal mappings. Do not modify vaults or user configuration as part of the package transaction. + +Before this draft is ready, validate clean ARM installation, desktop/CLI paths, native payload architecture and opening/editing a disposable vault in a disposable GUI session. Existing AppImage operation does not validate this tarball package. The two named vendor x86-only addons are removed; any newly introduced foreign executable requires review. diff --git a/pkgbuilds/obsidian/obsidian.desktop b/pkgbuilds/obsidian/obsidian.desktop new file mode 100644 index 0000000..da3429e --- /dev/null +++ b/pkgbuilds/obsidian/obsidian.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=Obsidian +Comment=Knowledge base on top of a local folder of Markdown files +Exec=obsidian %U +Terminal=false +Type=Application +Icon=obsidian +StartupWMClass=obsidian +MimeType=x-scheme-handler/obsidian; +Categories=Office; diff --git a/tests/obsidian-upstream.py b/tests/obsidian-upstream.py new file mode 100644 index 0000000..2638b34 --- /dev/null +++ b/tests/obsidian-upstream.py @@ -0,0 +1,47 @@ +#!/usr/bin/python3 +import importlib.util +from pathlib import Path +import unittest + +path = Path(__file__).resolve().parents[1] / 'pkgbuilds/obsidian/.omarchy/upstream.py' +spec = importlib.util.spec_from_file_location('obsidian_upstream', path) +module = importlib.util.module_from_spec(spec) +spec.loader.exec_module(module) + + +def release(version, **extra): + return dict(tag_name='v' + version, draft=False, prerelease=False, + published_at='2026-09-23T00:00:00Z', assets=[{ + 'name': f'obsidian-{version}-arm64.tar.gz', + 'digest': 'sha256:' + 'a' * 64, + 'browser_download_url': f'https://github.com/obsidianmd/obsidian-releases/releases/download/v{version}/obsidian-{version}-arm64.tar.gz' + }], **extra) + + +class DesktopReleaseTests(unittest.TestCase): + def test_mobile_only_and_preview_ignored(self): + mobile = release('2.0.0'); mobile['assets'] = [{'name': 'Obsidian.apk'}] + preview = release('3.0.0'); preview['prerelease'] = True + draft = release('4.0.0'); draft['draft'] = True + self.assertEqual(module.select([mobile, preview, draft, release('1.13.7')])['pkgver'], '1.13.7') + + def test_numeric_version_order(self): + self.assertEqual(module.select([release('1.9.0'), release('1.13.7')])['pkgver'], '1.13.7') + + def test_bad_newest_digest_fails_without_falling_back(self): + newest = release('2.0.0'); newest['assets'][0]['digest'] = None + with self.assertRaises(ValueError): + module.select([release('1.13.7'), newest]) + + def test_changed_url_rejected(self): + row = release('1.13.7'); row['assets'][0]['browser_download_url'] = 'https://example.org/foreign' + with self.assertRaises(ValueError): module.select([row]) + + def test_missing_or_duplicate_installer_rejected(self): + with self.assertRaises(ValueError): module.select([]) + row = release('1.13.7'); row['assets'] *= 2 + with self.assertRaises(ValueError): module.select([row]) + + +if __name__ == '__main__': + unittest.main()