MtnMCG

NDISK RAID10 migrate

Dec 22nd, 2025 (edited)
55
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.87 KB | None | 0 0
  1. -- ndisk_migrate_raid10.lua
  2. -- One-time migration script to upgrade existing files to RAID10
  3. -- Uses SAFE copy-first, delete-last approach like defrag
  4.  
  5. local modem = peripheral.find("modem")
  6. if not modem then error("No modem found") end
  7. modem.open(5)
  8.  
  9. -- Load NDisk secret for authentication
  10. local NDISK_SECRET = nil
  11. if fs.exists(".ndisk_secret") then
  12. local f = fs.open(".ndisk_secret", "r")
  13. NDISK_SECRET = f.readAll()
  14. f.close()
  15. end
  16.  
  17. local REQUEST_TIMEOUT = 2
  18.  
  19. -- ============================================
  20. -- HELPERS
  21. -- ============================================
  22.  
  23. local function log(msg)
  24. print("[" .. os.date("%H:%M:%S") .. "] " .. msg)
  25. end
  26.  
  27. local function logSuccess(msg)
  28. term.setTextColor(colors.lime)
  29. log(msg)
  30. term.setTextColor(colors.white)
  31. end
  32.  
  33. local function logError(msg)
  34. term.setTextColor(colors.red)
  35. log("ERROR: " .. msg)
  36. term.setTextColor(colors.white)
  37. end
  38.  
  39. local function confirm(msg)
  40. term.setTextColor(colors.yellow)
  41. write(msg .. " [y/N]: ")
  42. term.setTextColor(colors.white)
  43. local input = read()
  44. return input:lower() == "y" or input:lower() == "yes"
  45. end
  46.  
  47. local function generateRequestId()
  48. return os.getComputerID() .. "_" .. os.epoch("utc") .. "_" .. math.random(10000)
  49. end
  50.  
  51. local function sendRequest(nodeId, request)
  52. request.targetNode = nodeId
  53. request.secret = NDISK_SECRET -- Include auth secret
  54. modem.transmit(5, 5, request)
  55. local timer = os.startTimer(REQUEST_TIMEOUT)
  56. while true do
  57. local event, p1, p2, p3, p4 = os.pullEvent()
  58. if event == "modem_message" and type(p4) == "table" and p4.id == request.id then
  59. return p4.response
  60. elseif event == "timer" and p1 == timer then
  61. return nil
  62. end
  63. end
  64. end
  65.  
  66. -- ============================================
  67. -- MAIN
  68. -- ============================================
  69.  
  70. local function main()
  71. term.clear()
  72. term.setCursorPos(1, 1)
  73. term.setTextColor(colors.cyan)
  74. print("========================================")
  75. print(" NDisk RAID10 Migration Tool ")
  76. print("========================================")
  77. term.setTextColor(colors.white)
  78. print("")
  79.  
  80. term.setTextColor(colors.orange)
  81. print("SAFE MODE: Copy-first, delete-last")
  82. print("Original data only deleted after verification.")
  83. term.setTextColor(colors.white)
  84. print("")
  85.  
  86. if not confirm("Have you stopped the main server?") then
  87. print("Aborted. Stop the server first.")
  88. return
  89. end
  90.  
  91. print("")
  92. log("Loading ndisk API...")
  93.  
  94. local ndisk = dofile("ndisk_api.lua")
  95. ndisk.init()
  96.  
  97. local raidStatus = ndisk.getRaidStatus()
  98. print("")
  99. print("RAID Status:")
  100. print(" Nodes: " .. raidStatus.nodeCount)
  101. print(" Mode: " .. raidStatus.mode)
  102. print(" Max Failures: " .. raidStatus.maxFailures)
  103. print("")
  104.  
  105. if raidStatus.nodeCount < 2 then
  106. logError("Need at least 2 nodes for RAID10!")
  107. return
  108. end
  109.  
  110. -- Load raw chunk map to track old locations
  111. local oldChunkMap = {}
  112. if fs.exists("chunklocation.txt") then
  113. local file = fs.open("chunklocation.txt", "r")
  114. for line in file.readLine do
  115. local parts = {}
  116. for part in line:gmatch("%S+") do
  117. table.insert(parts, part)
  118. end
  119. if #parts >= 3 then
  120. local filename = parts[1]
  121. local chunkMatch = parts[2]:match("chunk(%d+)")
  122. local chunkNum = chunkMatch and tonumber(chunkMatch) or nil
  123. local primaryId = tonumber(parts[3])
  124. local mirrorId = parts[4] and tonumber(parts[4]) or nil
  125.  
  126. -- Skip malformed lines
  127. if filename and chunkNum and primaryId then
  128. oldChunkMap[filename] = oldChunkMap[filename] or {}
  129. oldChunkMap[filename][chunkNum] = {primary = primaryId, mirror = mirrorId}
  130. end
  131. end
  132. end
  133. file.close()
  134. end
  135.  
  136. local files = {}
  137. for path, _ in pairs(oldChunkMap) do
  138. table.insert(files, path)
  139. end
  140. table.sort(files)
  141.  
  142. log("Found " .. #files .. " files to migrate")
  143. print("")
  144.  
  145. -- Backup chunk map
  146. if fs.exists("chunklocation.txt") then
  147. if fs.exists("chunklocation.txt.premigrate") then
  148. fs.delete("chunklocation.txt.premigrate")
  149. end
  150. fs.copy("chunklocation.txt", "chunklocation.txt.premigrate")
  151. log("Backed up chunklocation.txt")
  152. end
  153.  
  154. if not confirm("Start RAID10 migration?") then
  155. print("Aborted.")
  156. return
  157. end
  158.  
  159. print("")
  160. local migrated = 0
  161. local failed = 0
  162. local skipped = 0
  163.  
  164. for i, path in ipairs(files) do
  165. local progress = math.floor((i / #files) * 100)
  166. term.clearLine()
  167. term.setCursorPos(1, select(2, term.getCursorPos()))
  168. write("[" .. progress .. "%] " .. path)
  169.  
  170. -- Store old chunk locations for this file
  171. local oldChunks = {}
  172. if oldChunkMap[path] then
  173. for k, v in pairs(oldChunkMap[path]) do
  174. oldChunks[k] = {primary = v.primary, mirror = v.mirror}
  175. end
  176. end
  177.  
  178. -- Step 1: Read file content
  179. local content, readErr = ndisk.read(path)
  180. if not content then
  181. failed = failed + 1
  182. print("")
  183. logError(path .. ": read failed - " .. (readErr or "unknown"))
  184. else
  185. -- Step 2: Write with RAID10 (creates new chunks)
  186. local success, writeErr = ndisk.write(path, content)
  187. if not success then
  188. failed = failed + 1
  189. print("")
  190. logError(path .. ": write failed - " .. (writeErr or "unknown"))
  191. else
  192. -- Step 3: Verify by reading back
  193. local verifyContent, verifyErr = ndisk.read(path)
  194. if verifyContent ~= content then
  195. failed = failed + 1
  196. print("")
  197. logError(path .. ": verification failed")
  198. else
  199. -- Step 4: Success! Now delete orphaned old chunks
  200. -- Get new chunk locations from chunk map
  201. local newUsedNodes = {}
  202. if fs.exists("chunklocation.txt") then
  203. local file = fs.open("chunklocation.txt", "r")
  204. for line in file.readLine do
  205. local parts = {}
  206. for part in line:gmatch("%S+") do
  207. table.insert(parts, part)
  208. end
  209. if parts[1] == path and #parts >= 3 then
  210. local nodeId = tonumber(parts[3])
  211. local mirrorId = parts[4] and tonumber(parts[4]) or nil
  212. if nodeId then newUsedNodes[nodeId] = true end
  213. if mirrorId then newUsedNodes[mirrorId] = true end
  214. end
  215. end
  216. file.close()
  217. end
  218.  
  219. -- Delete old chunks on nodes no longer used
  220. for chunkNum, info in pairs(oldChunks) do
  221. if info.primary and not newUsedNodes[info.primary] then
  222. sendRequest(info.primary, {
  223. id = generateRequestId(),
  224. action = "delete",
  225. path = path,
  226. chunk = chunkNum
  227. })
  228. end
  229. if info.mirror and info.mirror ~= info.primary and not newUsedNodes[info.mirror] then
  230. sendRequest(info.mirror, {
  231. id = generateRequestId(),
  232. action = "delete",
  233. path = path,
  234. chunk = chunkNum
  235. })
  236. end
  237. end
  238.  
  239. migrated = migrated + 1
  240. end
  241. end
  242. end
  243. end
  244.  
  245. print("")
  246. print("")
  247. print("========================================")
  248. print(" Migration Complete")
  249. print("========================================")
  250. print("")
  251. logSuccess("Files migrated: " .. migrated)
  252. if failed > 0 then
  253. logError("Files failed: " .. failed)
  254. end
  255. print("")
  256. print("Pre-migration backup: chunklocation.txt.premigrate")
  257. print("You can now restart the main server.")
  258. end
  259.  
  260. main()
  261.  
Advertisement
Add Comment
Please, Sign In to add comment