OverviewFilesCommitsIssuesDocsReleasesPeers

godot-keet commits / cadf855

Put entire app in by-arch so pear-runtime swap updates everything
pear-runtime mirrors by-arch/<host>/app/<name> and atomically swaps it with this.app via fsx.swap. Previously only binary+pck+so were in by-arch, so index.js/daemon/node_modules never got OTA'd. Now the entire app (binary, pck, index.js, daemon, node_modules) is in by-arch. Set this.app to the app root directory so the swap replaces everything atomically. No custom file sync needed — the Hyperdrive + pear-runtime handle it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cadf855296f0b0e7e6b52ef152b9a4b3ac89374a
by plankatron <fleeky@gmail.com> · 2026-02-22T01:11:56.000Z

Changed files (2) +14 -73

.pear-git/ci.yaml +12 -7
……@@ -46,9 +46,10 @@ steps:
4646 - name: tune-rocksdb-memory
4747 run: sed -i 's/maxWriteBufferNumber = 2/maxWriteBufferNumber = 1/' node_modules/rocksdb-native/lib/column-family.js && sed -i 's/maxOpenFiles = -1/maxOpenFiles = 256/' node_modules/rocksdb-native/lib/state.js
4848 - name: layout-by-arch
49 # pear-runtime uses which-runtime → Bare.arch which returns Node-style arch names
50 # (x64, arm64) NOT uname -m names (x86_64, aarch64). Must match exactly or
51 # fsx.swap fails with ENOENT because the mirror prefix doesn't find any files.
49 # pear-runtime mirrors by-arch/<host>/app/<name> and atomically swaps it with
50 # the app directory. The ENTIRE app goes here — binary, pck, index.js, daemon,
51 # node_modules — so a single swap updates everything. No custom file sync needed.
52 # Arch names must match Bare.arch (x64, arm64), NOT uname -m (x86_64, aarch64).
5253 run: |
5354 RAW=$(uname -m)
5455 case "$RAW" in
……@@ -57,7 +58,11 @@ steps:
5758 *) ARCH="$RAW" ;;
5859 esac
5960 HOST="linux-${ARCH}"
60 mkdir -p "by-arch/${HOST}/app/godot-keet"
61 cp build/linux/godot-keet/godot-keet "by-arch/${HOST}/app/godot-keet/"
62 cp build/linux/godot-keet/godot-keet.pck "by-arch/${HOST}/app/godot-keet/" 2>/dev/null; true
63 cp build/linux/godot-keet/*.so "by-arch/${HOST}/app/godot-keet/" 2>/dev/null; true
61 TARGET="by-arch/${HOST}/app/godot-keet"
62 mkdir -p "$TARGET/build/linux/godot-keet"
63 cp build/linux/godot-keet/godot-keet "$TARGET/build/linux/godot-keet/"
64 cp build/linux/godot-keet/godot-keet.pck "$TARGET/build/linux/godot-keet/" 2>/dev/null; true
65 cp build/linux/godot-keet/*.so "$TARGET/build/linux/godot-keet/" 2>/dev/null; true
66 cp index.js package.json version.txt "$TARGET/"
67 cp -r daemon "$TARGET/"
68 cp -r node_modules "$TARGET/"
index.js +2 -66
……@@ -140,7 +140,7 @@ async function initPearRuntime () {
140140
141141 const runtime = new PearRuntime({
142142 dir,
143 app: path.join(appDir, 'build', getPlatformDir(), 'godot-keet'),
143 app: appDir,
144144 version: runtimeVersion,
145145 upgrade: pkg.upgrade
146146 })
……@@ -153,82 +153,18 @@ async function initPearRuntime () {
153153 if (data.key) console.log(`[launcher] ${data.key}`)
154154 })
155155
156 // Mutex: only one 'updated' handler runs at a time to prevent race conditions.
157 // Multiple 'updated' events fire as Hyperdrive replicates — without this lock,
158 // concurrent async handlers can write version files without an actual swap.
159 let applying = false
160
161156 runtime.on('updated', async () => {
162 if (applying || runtime.applied) return
163 applying = true
157 if (runtime.applied) return
164158
165159 console.log('[launcher] Update ready — applying...')
166160
167161 try {
168162 await runtime.applyUpdate()
169
170163 if (runtime.applied) {
171 // Binary swap succeeded (by-arch). Sync only the app files that change
172 // between deploys — NOT all of node_modules (thousands of static files).
173 // pear-runtime only swaps by-arch/, so without this, launcher/daemon
174 // changes would require a full reinstall.
175 const appDir = getAppDir()
176 const co = runtime.drive.checkout(runtime.length)
177 let updated = 0
178
179 // Only sync files that actually change between deploys:
180 // - Top-level app files (index.js, package.json, version.txt)
181 // - daemon/ (our app code)
182 // - node_modules/keet-daemon/ (our modified daemon)
183 // - node_modules/pear-runtime/ (CI-patched)
184 const syncPrefixes = [
185 '/index.js',
186 '/package.json',
187 '/version.txt',
188 '/daemon/',
189 '/node_modules/keet-daemon/',
190 '/node_modules/pear-runtime/'
191 ]
192
193 try {
194 for (const prefix of syncPrefixes) {
195 if (!prefix.endsWith('/')) {
196 // Single file
197 const data = await co.get(prefix)
198 if (data) {
199 const dst = path.join(appDir, prefix)
200 fs.writeFileSync(dst, data)
201 console.log(`[launcher] synced ${prefix}`)
202 updated++
203 }
204 continue
205 }
206 // Directory — list entries under prefix
207 for await (const entry of co.list(prefix)) {
208 const key = entry.key || entry
209 if (typeof key !== 'string') continue
210 const data = await co.get(key)
211 if (data) {
212 const dst = path.join(appDir, key)
213 fs.mkdirSync(path.dirname(dst), { recursive: true })
214 fs.writeFileSync(dst, data)
215 updated++
216 }
217 }
218 if (updated > 0) console.log(`[launcher] synced ${prefix} (${updated} files so far)`)
219 }
220 console.log(`[launcher] Synced ${updated} app files from Hyperdrive`)
221 } catch (err) {
222 console.error('[launcher] App file sync partial:', err.message)
223 }
224
225 await co.close()
226164 console.log('[launcher] Update applied — restart for new version')
227165 }
228166 } catch (err) {
229167 console.error('[launcher] Failed to apply update:', err.message)
230 } finally {
231 applying = false
232168 }
233169 })
234170