diff --git a/.gitea/workflows/desktop-release.yml b/.gitea/workflows/desktop-release.yml index 4f72a4b..7c5d7cf 100644 --- a/.gitea/workflows/desktop-release.yml +++ b/.gitea/workflows/desktop-release.yml @@ -245,6 +245,23 @@ jobs: } }' > release-assets/latest.json + windows_sha="$(sha256sum release-assets/fieldlens-print-windows-x64-setup.exe | awk '{print $1}')" + linux_deb_sha="$(sha256sum release-assets/fieldlens-print-linux-x64.deb | awk '{print $1}')" + jq -n \ + --arg version "$version" \ + --arg windows_sha "$windows_sha" \ + --arg linux_deb_sha "$linux_deb_sha" \ + --arg windows_url "https://fieldlens.arrowstack.dev/downloads/fieldlens-print-windows-x64-setup.exe" \ + --arg linux_deb_url "https://fieldlens.arrowstack.dev/downloads/fieldlens-print-linux-x64.deb" \ + '{ + version: $version, + notes: "FieldLens Print desktop update", + downloads: { + "windows-x86_64": {url:$windows_url, sha256:$windows_sha}, + "linux-x86_64-deb": {url:$linux_deb_url, sha256:$linux_deb_sha} + } + }' > release-assets/release.json + - name: Update stable public downloads shell: bash run: | @@ -257,6 +274,7 @@ jobs: cp release-assets/fieldlens-print-linux-x64.AppImage downloads/ cp release-assets/fieldlens-print-linux-x64.AppImage.sig downloads/ cp release-assets/latest.json downloads/latest.json + cp release-assets/release.json downloads/release.json deb_sha="$(sha256sum downloads/fieldlens-print-linux-x64.deb | awk '{print $1}')" sed -E "s/EXPECTED_SHA256=\"[0-9a-f]{64}\"/EXPECTED_SHA256=\"$deb_sha\"/" downloads/install.sh > /tmp/install.sh @@ -322,7 +340,8 @@ jobs: release-assets/fieldlens-print-linux-x64.deb \ release-assets/fieldlens-print-linux-x64.AppImage \ release-assets/fieldlens-print-linux-x64.AppImage.sig \ - release-assets/latest.json + release-assets/latest.json \ + release-assets/release.json do name="$(basename "$file")" existing_id="$(jq -r --arg name "$name" '.[] | select(.name == $name) | .id' /tmp/assets.json | head -n 1)" diff --git a/Dockerfile.builder-linux b/Dockerfile.builder-linux index b0423ea..76987d0 100644 --- a/Dockerfile.builder-linux +++ b/Dockerfile.builder-linux @@ -32,7 +32,10 @@ RUN set -eux; \ deb="$(find src-tauri/target/release/bundle/deb -maxdepth 1 -type f -name '*.deb' -print -quit)"; \ test -n "$deb"; \ cp "$deb" /desktop-artifacts/fieldlens-print-linux-x64.deb; \ - sha256sum /desktop-artifacts/fieldlens-print-linux-x64.deb > /desktop-artifacts/SHA256SUMS + deb_sha="$(sha256sum /desktop-artifacts/fieldlens-print-linux-x64.deb | awk '{print $1}')"; \ + sha256sum /desktop-artifacts/fieldlens-print-linux-x64.deb > /desktop-artifacts/SHA256SUMS; \ + version="$(node -p "require('./package.json').version")"; \ + printf '{\n "version": "%s",\n "notes": "FieldLens Print desktop update",\n "downloads": {\n "linux-x86_64-deb": {\n "url": "https://fieldlens.arrowstack.dev/downloads/fieldlens-print-linux-x64.deb",\n "sha256": "%s"\n }\n }\n}\n' "$version" "$deb_sha" > /desktop-artifacts/release.json FROM nginx:1.27-alpine COPY --from=build /desktop-artifacts /usr/share/nginx/html diff --git a/README.md b/README.md index c1d8453..3665be2 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,13 @@ A push to `main` builds: - `fieldlens-print-windows-x64-setup.exe` - `fieldlens-print-linux-x64.deb` +- signed updater artifacts when the persistent Tauri signing key is available +- `downloads/latest.json` for the signed Tauri updater +- `downloads/release.json` as the stable installer/version feed The workflow publishes them to the Gitea release matching the version in `package.json`. +The desktop app uses the signed Tauri updater where supported and falls back to +`release.json` for installer-based upgrades such as Debian packages. ## Local build diff --git a/downloads/release.json b/downloads/release.json new file mode 100644 index 0000000..562452f --- /dev/null +++ b/downloads/release.json @@ -0,0 +1,14 @@ +{ + "version": "0.1.4", + "notes": "FieldLens Print desktop update", + "downloads": { + "windows-x86_64": { + "url": "https://fieldlens.arrowstack.dev/downloads/fieldlens-print-windows-x64-setup.exe", + "sha256": "3ac9ddb57f44a0078f3e7b8739754901ff22ffd4f217a696910db4fbcd1343eb" + }, + "linux-x86_64-deb": { + "url": "https://fieldlens.arrowstack.dev/downloads/fieldlens-print-linux-x64.deb", + "sha256": "PENDING_REBUILD" + } + } +} diff --git a/package.json b/package.json index 25749a9..65eb9ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fieldlens/print", - "version": "0.1.3", + "version": "0.1.4", "private": true, "type": "module", "scripts": { diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 24719ef..515d092 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fieldlens-print" -version = "0.1.3" +version = "0.1.4" description = "FieldLens print-stream barcode capture companion" authors = ["Arrowstack"] edition = "2021" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c8c28f7..690fb56 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "FieldLens Print", - "version": "0.1.3", + "version": "0.1.4", "identifier": "dev.arrowstack.fieldlens.print", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/main.ts b/src/main.ts index a34b537..9146839 100644 --- a/src/main.ts +++ b/src/main.ts @@ -64,14 +64,31 @@ type LoginStart = { interval: number } +type ManualReleaseDownload = { + url: string + sha256?: string +} + +type ManualRelease = { + version: string + notes?: string + downloads: Record +} + +type ManualUpdate = { + version: string + url: string +} + const app = document.querySelector('#app')! let config: AppConfig let jobs: Job[] = [] let status: Status let loginTimer: number | undefined -let currentVersion = '0.1.3' +let currentVersion = '0.1.4' let bundleType: BundleType | null = null let availableUpdate: Update | null = null +let availableManualUpdate: ManualUpdate | null = null let updateBusy = false let systemPrinters: SystemPrinter[] = [] let virtualPrinterStatus: VirtualPrinterStatus | null = null @@ -404,18 +421,38 @@ async function checkForUpdates(manual = false) { try { checkButton.disabled = true state.textContent = manual ? 'Checking for updates…' : 'Checking quietly for updates…' - const update = await check() - availableUpdate = update + availableUpdate = null + availableManualUpdate = null - if (!update) { - installButton.hidden = true - state.textContent = `FieldLens Print ${currentVersion} is up to date.` + if (bundleType !== BundleType.Deb) { + try { + const update = await check() + availableUpdate = update + + if (update) { + state.textContent = `Version ${update.version} is available.` + installButton.hidden = false + installButton.textContent = 'Download & restart' + return + } + } catch { + // The signed updater feed may not exist yet on a freshly bootstrapped + // install. Fall back to the stable installer feed below. + } + } + + const manualUpdate = await checkManualRelease() + availableManualUpdate = manualUpdate + + if (manualUpdate) { + state.textContent = `Version ${manualUpdate.version} is available.` + installButton.hidden = false + installButton.textContent = 'Open installer' return } - state.textContent = `Version ${update.version} is available.` - installButton.hidden = false - installButton.textContent = bundleType === BundleType.Deb ? 'Open Linux installer' : 'Download & restart' + installButton.hidden = true + state.textContent = `FieldLens Print ${currentVersion} is up to date.` } catch (error) { if (manual) state.textContent = `Update check failed: ${String(error)}` else state.textContent = 'Automatic update check unavailable. You can retry manually.' @@ -425,16 +462,16 @@ async function checkForUpdates(manual = false) { } async function installAvailableUpdate() { - if (!availableUpdate || updateBusy) return + if ((!availableUpdate && !availableManualUpdate) || updateBusy) return const state = el('update-state') const button = el('install-update') const progress = el('update-progress') const bar = progress.querySelector('span') as HTMLSpanElement - if (bundleType === BundleType.Deb) { - state.textContent = 'Debian packages update through the Linux installer.' - await openUrl('https://fieldlens.arrowstack.dev/downloads') + if (availableManualUpdate) { + state.textContent = 'Opening the current FieldLens Print installer…' + await openUrl(availableManualUpdate.url) return } @@ -479,6 +516,42 @@ async function installAvailableUpdate() { } } +function compareVersions(left: string, right: string) { + const a = left.split('.').map((part) => Number.parseInt(part, 10) || 0) + const b = right.split('.').map((part) => Number.parseInt(part, 10) || 0) + const length = Math.max(a.length, b.length) + + for (let index = 0; index < length; index += 1) { + const diff = (a[index] ?? 0) - (b[index] ?? 0) + if (diff !== 0) return diff + } + + return 0 +} + +async function checkManualRelease(): Promise { + const response = await fetch( + 'https://git.arrowstack.dev/Admin/fieldlens-desktop/raw/branch/main/downloads/release.json', + { cache: 'no-store' }, + ) + if (!response.ok) throw new Error(`release feed returned HTTP ${response.status}`) + + const release = await response.json() as ManualRelease + if (!release.version || compareVersions(release.version, currentVersion) <= 0) return null + + const key = bundleType === BundleType.Deb + ? 'linux-x86_64-deb' + : navigator.userAgent.includes('Windows') + ? 'windows-x86_64' + : null + if (!key) return null + + const download = release.downloads?.[key] + if (!download?.url) return null + + return { version: release.version, url: download.url } +} + el('check-update').addEventListener('click', () => { void checkForUpdates(true) }) el('install-update').addEventListener('click', () => { void installAvailableUpdate() })