OverviewFilesCommitsIssuesDocsReleasesPeers

pgh-hello commits / ef9d9a3

Initial — pgh-hello test app for the OTA demo (P.10 Phase P.11e)
Minimum pgh-installable app: - Prints version + pid + path - Declares upgrade source via pgh: {} in package.json - Optionally wires up pgh-runtime for OTA polling when environment provides PGH_TOPIC, PGH_CURRENT_TREE_HASH, PGH_STORE_ROOT - Otherwise sleeps forever (trivial happy path) Deps: pgh + pgh-runtime (via file:../). Target flow for the OTA demo: 1. pgh publish on tabertron — writes .pgh-lock.json + release entry 2. pgh app install on stortron — fetches tree + content, drops launcher 3. Start pgh-hello on stortron — prints v0.1.0, starts OTA poll 4. Bump version on tabertron + publish 5. stortron's pgh-runtime picks it up within a polling cycle Smoke-tested: `node index.js` runs, prints the expected lines, OTA cleanly disables when env vars are absent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ef9d9a302b247770b4d32367a327bd7f2ecebdb5
by plankatron <fleeky@gmail.com> · 2026-04-18T18:03:11.000Z

Changed files (4) +181 -0

.gitignore +3 -0
……@@ -0,0 +1,3 @@
1node_modules/
2*.log
3.DS_Store
index.js +91 -0
……@@ -0,0 +1,91 @@
1#!/usr/bin/env node
2/**
3 * pgh-hello — minimal test app for the pgh OTA demo.
4 *
5 * Prints its version and sleeps. If PghRuntime has access to a
6 * fetcher + store (typically supplied by the pgh install machinery
7 * via env vars), it polls for updates in the background and logs
8 * when a new tree is materialized.
9 */
10
11import fs from 'fs'
12import path from 'path'
13import { fileURLToPath } from 'url'
14
15const HERE = path.dirname(fileURLToPath(import.meta.url))
16const pkg = JSON.parse(fs.readFileSync(path.join(HERE, 'package.json'), 'utf8'))
17
18console.log('pgh-hello v' + pkg.version)
19console.log(' pid: ' + process.pid)
20console.log(' path: ' + HERE)
21
22if (pkg.pgh && pkg.pgh.upgrade) {
23 console.log(' upgrade source: ' + pkg.pgh.upgrade)
24}
25
26// Optional: wire up OTA via pgh-runtime if environment provides the
27// necessary handles. Environment contract:
28// PGH_CURRENT_TREE_HASH — tree hash pgh-hello was installed at
29// PGH_STORE_ROOT — path to the pgh tree store
30// PGH_TOPIC — 32-byte hex Hyperswarm topic to join
31//
32// This is an OPT-IN integration; if any env var is missing, pgh-hello
33// just sleeps.
34
35async function startOTA () {
36 const topic = process.env.PGH_TOPIC
37 const currentTreeHash = process.env.PGH_CURRENT_TREE_HASH
38 const storeRoot = process.env.PGH_STORE_ROOT
39
40 if (!topic || !currentTreeHash || !storeRoot) {
41 console.log(' OTA: disabled (missing PGH_TOPIC / PGH_CURRENT_TREE_HASH / PGH_STORE_ROOT)')
42 return null
43 }
44
45 try {
46 const { PghRuntime } = await import('pgh-runtime')
47 const { TreeStore } = await import('pgh/store')
48 const { NetworkFetcher } = await import('pgh/network-fetcher')
49
50 const store = new TreeStore({ root: storeRoot })
51 store.init()
52
53 const fetcher = new NetworkFetcher({ topic, timeout: 15000 })
54 await fetcher.ready()
55
56 const runtime = new PghRuntime({
57 name: pkg.name,
58 currentTreeHash,
59 fetcher,
60 store,
61 interval: 15 * 1000, // 15s for demo
62 startImmediately: true
63 })
64
65 runtime.on('updating', (release) => {
66 console.log(' OTA updating → ' + release.version + ' (tree ' + release.treeHash.slice(0, 12) + ')')
67 })
68 runtime.on('updated', (info) => {
69 console.log(' OTA updated — tree ready at ' + info.newTreeHash.slice(0, 12))
70 console.log(' restart pgh-hello to apply (or pgh app rollback to revert)')
71 })
72 runtime.on('error', (err) => {
73 console.error(' OTA error: ' + (err.message || err))
74 })
75
76 await runtime.start()
77 console.log(' OTA: enabled, polling ' + topic.slice(0, 12) + '... every 15s')
78 return runtime
79 } catch (err) {
80 console.error(' OTA: failed to start — ' + err.message)
81 return null
82 }
83}
84
85startOTA().then(() => {
86 // Sleep forever
87 setInterval(() => {}, 1 << 30)
88})
89
90process.on('SIGINT', () => { console.log('\npgh-hello shutting down'); process.exit(0) })
91process.on('SIGTERM', () => { process.exit(0) })
package-lock.json +63 -0
……@@ -0,0 +1,63 @@
1{
2 "name": "pgh-hello",
3 "version": "0.1.0",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "pgh-hello",
9 "version": "0.1.0",
10 "license": "AGPL-3.0",
11 "dependencies": {
12 "pgh": "file:../pgh",
13 "pgh-runtime": "file:../pgh-runtime"
14 },
15 "bin": {
16 "pgh-hello": "index.js"
17 }
18 },
19 "../pgh": {
20 "version": "0.1.0",
21 "license": "AGPL-3.0",
22 "dependencies": {
23 "b4a": "^1.6.0",
24 "blind-pairing": "^2.3.1",
25 "corestore": "^7.0.0",
26 "hyper-overlay": "file:../hyper-overlay",
27 "hyperbee": "^2.27.3",
28 "hypercore": "^11.0.0",
29 "hypercore-crypto": "^3.0.0",
30 "hyperswarm": "^4.17.0",
31 "pgh-runtime": "file:../pgh-runtime",
32 "protomux": "^3.0.0",
33 "sodium-native": "^5.0.0"
34 },
35 "bin": {
36 "pgh": "bin/pgh"
37 },
38 "devDependencies": {
39 "@hyperswarm/testnet": "^3.0.0",
40 "brittle": "^3.0.0"
41 }
42 },
43 "../pgh-runtime": {
44 "version": "0.1.0",
45 "license": "AGPL-3.0",
46 "dependencies": {
47 "hyper-overlay": "file:../hyper-overlay"
48 },
49 "devDependencies": {
50 "brittle": "^3.0.0",
51 "hypercore-crypto": "^3.0.0"
52 }
53 },
54 "node_modules/pgh": {
55 "resolved": "../pgh",
56 "link": true
57 },
58 "node_modules/pgh-runtime": {
59 "resolved": "../pgh-runtime",
60 "link": true
61 }
62 }
63}
package.json +24 -0
……@@ -0,0 +1,24 @@
1{
2 "name": "pgh-hello",
3 "version": "0.1.0",
4 "description": "Minimal test app for the pgh OTA demo — prints version and sleeps",
5 "type": "module",
6 "main": "index.js",
7 "bin": {
8 "pgh-hello": "./index.js"
9 },
10 "scripts": {
11 "start": "node index.js"
12 },
13 "license": "AGPL-3.0",
14 "pgh": {
15 "bin": "pgh-hello",
16 "entrypoint": "index.js",
17 "upgrade": "pgh://fleeky/pgh-hello",
18 "type": "terminal"
19 },
20 "dependencies": {
21 "pgh": "file:../pgh",
22 "pgh-runtime": "file:../pgh-runtime"
23 }
24}