🌳
pt0/ipfsF/verifyMfsReplicationAI.mts
6type MfsEntry = {cid: any, name: string, type: number | string}
8const listMfsEntries = async (client: ReturnType<typeof getIpfsClient>, mfsRoot: string): Promise<MfsEntry[]> => {
9 const entries: MfsEntry[] = []
10 for await (const entry of client.files.ls(mfsRoot)) {
11 entries.push(entry as any)
12 }
13 return entries
16export const verifyMfsReplication = async ({rootCids, mfsParamsMap}: {
17 rootCids: MfsRootCidEntry[]
18 mfsParamsMap: Record<string, {mfsRoot: string, kvKey: string}>
19}) => {
20 const targets = await getPinTargets()
21 const primaryClient = getIpfsClient()
22 const result = {
23 totalRoots: 0,
24 synced: 0,
25 healed: 0,
26 mismatches: 0,
27 perRoot: {} as Record<string, {primaryCid: string, synced: string[], healed: string[], mismatches: string[]}>,
28 }
30 for (const {label, cid} of rootCids) {
31 const bareLabel = label.split('/').pop()!
32 const mfsParam = mfsParamsMap[bareLabel]
33 if (!mfsParam) continue
35 result.totalRoots++
36 const entry = {primaryCid: cid, synced: [] as string[], healed: [] as string[], mismatches: [] as string[]}
37 result.perRoot[label] = entry
39 let primaryEntries: MfsEntry[]
40 try {
41 primaryEntries = await listMfsEntries(primaryClient, mfsParam.mfsRoot)
42 } catch (err) {
43 betLog('mfsVerifyPrimaryLsFail', {label, mfsRoot: mfsParam.mfsRoot, errMsg: (err as Error).message})
44 continue
45 }
47 for (const t of targets) {
48 if (t.client === primaryClient) continue
50 let secondaryEntries: MfsEntry[]
51 try {
52 secondaryEntries = await listMfsEntries(t.client, mfsParam.mfsRoot)
53 } catch {
54 secondaryEntries = []
55 }
57 const secondaryNames = new Set(secondaryEntries.filter(e => e.type === 'file').map(e => e.name))
58 const missingEntries = primaryEntries.filter(e => e.type === 'file' && !secondaryNames.has(e.name))
60 if (missingEntries.length > 0) {
61 try {
62 await t.client.files.mkdir(mfsParam.mfsRoot, {parents: true}).catch(() => {})
63 for (const me of missingEntries) {
64 const mfsPath = `${mfsParam.mfsRoot}/${me.name}`
65 const meCid = me.cid.toString()
66 await t.client.files.cp(`/ipfs/${meCid}`, mfsPath).catch((err: Error) => betLog('mfsVerifyCpFail', {label, host: t.host, mfsPath, errMsg: err.message}))
67 }
68 entry.healed.push(t.host)
69 result.healed++
70 betLog('mfsVerifyHealed', {label, host: t.host, copiedCount: missingEntries.length})
71 } catch (err) {
72 betLog('mfsVerifyHealFail', {label, host: t.host, errMsg: (err as Error).message})
73 }
74 }
76 try {
77 const stat = await t.client.files.stat(mfsParam.mfsRoot)
78 const secondaryRootCid = stat.cid.toString()
79 if (secondaryRootCid === cid) {
80 entry.synced.push(t.host)
81 } else {
82 betLog('mfsVerifyRebuildRoot', {label, host: t.host, primaryCid: cid, oldCid: secondaryRootCid})
83 await t.client.files.rm(mfsParam.mfsRoot, {recursive: true}).catch((err: Error) => betLog('mfsVerifyRmFail', {label, host: t.host, errMsg: err.message}))
84 await t.client.files.cp(`/ipfs/${cid}`, mfsParam.mfsRoot).catch((err: Error) => betLog('mfsVerifyRebuildCpFail', {label, host: t.host, errMsg: err.message}))
85 entry.synced.push(t.host)
86 }
87 } catch (err) {
88 betLog('mfsVerifyStatFail', {label, host: t.host, errMsg: (err as Error).message})
89 }
90 }
92 if (entry.mismatches.length === 0 && entry.healed.length === 0) result.synced++
93 }
95 if (result.mismatches > 0) await noThrowNotifErr('mfsReplicationMismatch', result)
96 betLog('mfsVerifyResult', result)
97 return result