OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-note commits / 459fe4f

Add migrate verify/cleanup CLI commands for v3 media migration
Two-step post-migration workflow: - `migrate verify`: walks all notes, checks every media hash exists in MediaStore (including legacy mappings), verifies blob readability, sets migration:v3-verified marker on success. - `migrate cleanup`: requires verified marker, removes .pre-v3-backup directory and clears old Hyperdrive /media/ files from local drives. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
459fe4fc2c7c3dd2582e73550f6f9275a604372d
by plankatron <fleeky@gmail.com> · 2026-03-19T17:18:35.000Z

Changed files (1) +153 -0

hypernote-server.js +153 -0
……@@ -1033,10 +1033,163 @@ if (command === 'serve' || !command) {
10331033 })
10341034 console.log(result.token)
10351035 await store.close()
1036} else if (command === 'migrate') {
1037 const subCommand = args[1] // 'verify' or 'cleanup'
1038
1039 let storage = null
1040 for (let i = 0; i < args.length; i++) {
1041 if (args[i].startsWith('--storage=')) storage = args[i].split('=')[1]
1042 else if (args[i] === '--storage' && args[i + 1]) storage = args[i + 1]
1043 }
1044
1045 const locatorPath = StorageLocator.read()
1046 storage = storage || locatorPath || HypernoteServer.resolveDefaultStorage()
1047
1048 const store = new Corestore(storage)
1049 const registryCore = store.get({ name: 'registry' })
1050 const registry = new Hyperbee(registryCore, { keyEncoding: 'utf-8', valueEncoding: 'json' })
1051 await registry.ready()
1052
1053 const { extractMediaHashes } = await import('./lib/multiwriter/notebook-apply.js')
1054
1055 const mediaStore = new MediaStore(store)
1056 await mediaStore.ready()
1057
1058 const driveManager = new DriveManager(store, registry)
1059 const noteManager = new NoteManager(driveManager)
1060
1061 if (subCommand === 'verify') {
1062 // Check migration marker
1063 const marker = await registry.get('migration:v3-media-store')
1064 if (!marker) {
1065 console.log('No v3 media migration found. Run the server first to trigger migration.')
1066 await store.close()
1067 process.exit(1)
1068 }
1069 console.log('Migration marker:', JSON.stringify(marker.value, null, 2))
1070
1071 // Walk all notes, extract media hashes, verify each exists
1072 let totalHashes = 0
1073 let found = 0
1074 let missing = 0
1075 const missingList = []
1076
1077 const notebooks = await noteManager.listNotebooks()
1078 for (const nb of notebooks) {
1079 const notes = await noteManager.listNotes(nb.slug)
1080 for (const note of notes) {
1081 const hashes = extractMediaHashes(note.content || '')
1082 for (const hash of hashes) {
1083 totalHashes++
1084 const exists = await mediaStore.has(hash)
1085 if (exists) {
1086 found++
1087 } else {
1088 // Check legacy mapping
1089 const legacy = await mediaStore._index.get('legacy:' + hash)
1090 if (legacy && legacy.value && legacy.value.hash) {
1091 const legacyExists = await mediaStore.has(legacy.value.hash)
1092 if (legacyExists) { found++; continue }
1093 }
1094 missing++
1095 missingList.push({ notebook: nb.slug, note: note.slug || note.id, hash })
1096 }
1097 }
1098 }
1099 }
1100
1101 // Also verify MediaStore blobs are readable
1102 const allBlobs = await mediaStore.listAll()
1103 let readable = 0
1104 let unreadable = 0
1105 for (const blob of allBlobs) {
1106 try {
1107 const buf = await mediaStore.get(blob.hash)
1108 if (buf && buf.length > 0) readable++
1109 else unreadable++
1110 } catch {
1111 unreadable++
1112 }
1113 }
1114
1115 console.log('\n=== Migration Verification ===')
1116 console.log('Media references in notes: ' + totalHashes)
1117 console.log(' Found in MediaStore: ' + found)
1118 console.log(' Missing: ' + missing)
1119 console.log('Blobs in MediaStore: ' + allBlobs.length)
1120 console.log(' Readable: ' + readable)
1121 console.log(' Unreadable: ' + unreadable)
1122
1123 if (missing > 0) {
1124 console.log('\nMissing media:')
1125 for (const m of missingList) {
1126 console.log(' ' + m.notebook + '/' + m.note + ' → ' + m.hash)
1127 }
1128 console.log('\nVerification FAILED. Do NOT clean up yet.')
1129 } else {
1130 // Set verified marker
1131 await registry.put('migration:v3-verified', { verifiedAt: Date.now(), totalHashes, blobs: allBlobs.length })
1132 console.log('\nVerification PASSED. Safe to run: bare hypernote-server.js migrate cleanup')
1133 }
1134
1135 await store.close()
1136
1137 } else if (subCommand === 'cleanup') {
1138 // Check verified marker
1139 const verified = await registry.get('migration:v3-verified')
1140 if (!verified) {
1141 console.log('Migration not verified. Run "migrate verify" first.')
1142 await store.close()
1143 process.exit(1)
1144 }
1145
1146 // Delete backup directory
1147 const backupPath = storage + '.pre-v3-backup'
1148 try {
1149 await fs.promises.access(backupPath)
1150 console.log('Removing backup at ' + backupPath + '...')
1151 await fs.promises.rm(backupPath, { recursive: true, force: true })
1152 console.log('Backup removed.')
1153 } catch {
1154 console.log('No backup found at ' + backupPath)
1155 }
1156
1157 // Clear old Hyperdrive /media/ files
1158 let cleared = 0
1159 const drives = await driveManager.listDrives()
1160 for (const entry of drives) {
1161 if (!entry.isLocal) continue
1162 const drive = await driveManager.getDrive(entry.slug)
1163 if (!drive) continue
1164 try {
1165 const files = []
1166 for await (const f of drive.readdir('/media/')) files.push(f)
1167 for (const f of files) {
1168 try {
1169 await drive.clear('/media/' + f)
1170 cleared++
1171 } catch {}
1172 }
1173 } catch {}
1174 }
1175
1176 console.log('Cleared ' + cleared + ' old Hyperdrive media files.')
1177 console.log('Cleanup complete.')
1178
1179 await store.close()
1180
1181 } else {
1182 console.log('Usage:')
1183 console.log(' bare hypernote-server.js migrate verify [--storage=path]')
1184 console.log(' bare hypernote-server.js migrate cleanup [--storage=path]')
1185 process.exit(1)
1186 }
10361187} else {
10371188 console.log('Usage:')
10381189 console.log(' bare hypernote-server.js [serve] [port] [--storage=path] [--network]')
10391190 console.log(' bare hypernote-server.js promote <publicKey|username> [--storage=path]')
10401191 console.log(' bare hypernote-server.js new-token [--storage=path]')
1192 console.log(' bare hypernote-server.js migrate verify [--storage=path]')
1193 console.log(' bare hypernote-server.js migrate cleanup [--storage=path]')
10411194 process.exit(1)
10421195}