| … | … | @@ -27,7 +27,11 @@ export const usage = `pgh repo — browse repos served by the daemon |
| 27 | 27 | pgh repo grant-writer <name> --peer <url> [--role owner|writer] |
| 28 | 28 | Admin: grant a peer push authority |
| 29 | 29 | pgh repo local-key <name> Print this daemon's tracker local key |
| 30 | | pgh repo revoke <name> <userId> Revoke a writer` |
| 30 | pgh repo revoke <name> <userId> Revoke a writer |
| 31 | pgh repo resync <name> [--admit-from <owner-url>] |
| 32 | Heal a stuck tracker: clean re-join in |
| 33 | a fresh corestore (+ auto-admit if the |
| 34 | owner url is given)` |
| 31 | 35 | |
| 32 | 36 | export async function run (client, args) { |
| 33 | 37 | const sub = args[0] |
| … | … | @@ -182,6 +186,32 @@ export async function run (client, args) { |
| 182 | 186 | return client.call('repo-add-writer', { name, userId, writerKey: peerKey.localKey, role }) |
| 183 | 187 | } |
| 184 | 188 | |
| 189 | // Clean RE-JOIN of a stuck/frozen per-repo tracker into a FRESH corestore |
| 190 | // (repo-resync RPC → forceResync). Recovers a standalone peer whose tracker |
| 191 | // can't reconcile a group manifest migration (crash-loops or silently freezes) |
| 192 | // — a fresh namespace does NOT heal it (same primaryKey-derived local writer), |
| 193 | // only a fresh corestore does. The re-join is READ-ONLY until admitted; with |
| 194 | // --admit-from <owner-url> the peer auto-requests admission of its new local |
| 195 | // key from the tracker owner, who auto-approves iff this peer already holds |
| 196 | // write authority (repo-request-writer). One command = heal + participate. |
| 197 | if (sub === 'resync') { |
| 198 | const name = args[1] |
| 199 | if (!name) throw new Error('usage: pgh repo resync <name> [--admit-from <owner-url>]') |
| 200 | const res = await client.call('repo-resync', { repo: name }) |
| 201 | if (res && res.error) return res |
| 202 | const ai = args.indexOf('--admit-from') |
| 203 | const owner = ai !== -1 ? args[ai + 1] : null |
| 204 | if (!owner || !res || !res.localKey) return res |
| 205 | const pc = await PghPeerClient.connect({ url: owner }) |
| 206 | let admission |
| 207 | try { |
| 208 | admission = await pc.call('repo-request-writer', { name, writerKey: res.localKey }) |
| 209 | } finally { |
| 210 | await pc.close() |
| 211 | } |
| 212 | return { ...res, admission } |
| 213 | } |
| 214 | |
| 185 | 215 | if (sub === 'revoke') { |
| 186 | 216 | const name = args[1] |
| 187 | 217 | const userId = args[2] |