| … | … | @@ -24,6 +24,7 @@ import http from 'bare-http1' |
| 24 | 24 | import fs from 'bare-fs' |
| 25 | 25 | import path from 'bare-path' |
| 26 | 26 | import os from 'bare-os' |
| 27 | import subprocess from 'bare-subprocess' |
| 27 | 28 | import Corestore from 'corestore' |
| 28 | 29 | import Hyperbee from 'hyperbee' |
| 29 | 30 | import Hyperdrive from 'hyperdrive' |
| … | … | @@ -71,35 +72,42 @@ process.on('unhandledRejection', (reason) => { |
| 71 | 72 | console.error('[WARN] Unhandled rejection:', reason && reason.message ? reason.message : reason) |
| 72 | 73 | }) |
| 73 | 74 | |
| 74 | | // OTA update bootstrap — pear-runtime checks for updates when `upgrade` key is in package.json |
| 75 | // Background OTA via pgh — content-addressed store, NO Hyperdrive/blobs. |
| 76 | // `pgh app update <name>` resolves the latest release from package.json's |
| 77 | // pgh.upgrade, fetches any new tree closure into the content store, and |
| 78 | // ATOMICALLY materializes it into the install dir (scratch build + swap), |
| 79 | // safe to run while the server is live; the next start picks up the new tree. |
| 80 | // Replaces the former pear-runtime Hyperdrive updater, which kept an unbounded |
| 81 | // append-only blob corestore under ~/.config/hypernote/pear-runtime. |
| 75 | 82 | const isPear = typeof Pear !== 'undefined' |
| 76 | | if (!isPear) { |
| 83 | if (!isPear && !process.argv.includes('--no-updates')) { |
| 77 | 84 | try { |
| 78 | 85 | const appDir = path.dirname(new URL(import.meta.url).pathname) |
| 79 | 86 | const pkg = JSON.parse(fs.readFileSync(path.join(appDir, 'package.json'), 'utf-8')) |
| 80 | | if (pkg.upgrade) { |
| 81 | | import('pear-' + 'runtime').then(({ default: PearRuntime }) => { |
| 82 | | const runtime = new PearRuntime({ |
| 83 | | dir: path.join(os.homedir(), '.config', 'hypernote'), |
| 84 | | app: appDir, |
| 85 | | version: pkg.version || '0.2.0', |
| 86 | | upgrade: pkg.upgrade |
| 87 | | }) |
| 88 | | runtime.on('updated', async () => { |
| 89 | | console.log('[OTA] Update available, applying...') |
| 90 | | try { |
| 91 | | await runtime.applyUpdate() |
| 92 | | if (runtime.applied) { |
| 93 | | console.log('[OTA] Update applied — exiting for restart') |
| 94 | | if (globalThis._hnServer) try { await globalThis._hnServer.close() } catch {} |
| 95 | | if (globalThis._hnHttpServer) try { globalThis._hnHttpServer.close() } catch {} |
| 96 | | setTimeout(() => process.exit(0), 1000) |
| 97 | | } |
| 98 | | } catch (err) { |
| 99 | | console.error('[OTA] Failed to apply:', err.message) |
| 100 | | } |
| 87 | const upgrade = pkg.pgh && pkg.pgh.upgrade |
| 88 | if (upgrade) { |
| 89 | const appName = (pkg.pgh && pkg.pgh.bin) || pkg.name || 'hypernote' |
| 90 | // Resolve the pgh CLI: prefer ~/.local/bin/pgh, else the store-installed |
| 91 | // bare runtime + entry. Bail if pgh isn't on this machine. |
| 92 | let argv = null |
| 93 | const launcher = path.join(os.homedir(), '.local', 'bin', 'pgh') |
| 94 | if (fs.existsSync(launcher)) argv = [launcher] |
| 95 | else { |
| 96 | const storeRoot = process.env.PGH_STORE_ROOT || path.join(os.homedir(), '.local', 'share', 'store') |
| 97 | const bareBin = path.join(storeRoot, 'apps', 'pgh', 'runtime', 'bare') |
| 98 | const entry = path.join(storeRoot, 'apps', 'pgh', 'bin', 'pgh.bare.js') |
| 99 | if (fs.existsSync(bareBin) && fs.existsSync(entry)) argv = [bareBin, entry] |
| 100 | } |
| 101 | if (argv) { |
| 102 | // Detached + best-effort: never blocks the server. pgh's atomic |
| 103 | // materialize leaves the live install intact if interrupted. |
| 104 | const proc = subprocess.spawn(argv[0], [...argv.slice(1), 'app', 'update', appName], { |
| 105 | stdio: 'ignore', |
| 106 | detached: true |
| 101 | 107 | }) |
| 102 | | }).catch(() => {}) |
| 108 | proc.on('error', () => {}) |
| 109 | if (proc.unref) proc.unref() |
| 110 | } |
| 103 | 111 | } |
| 104 | 112 | } catch {} |
| 105 | 113 | } |