Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- ndisk_migrate_raid10.lua
- -- One-time migration script to upgrade existing files to RAID10
- -- Uses SAFE copy-first, delete-last approach like defrag
- local modem = peripheral.find("modem")
- if not modem then error("No modem found") end
- modem.open(5)
- -- Load NDisk secret for authentication
- local NDISK_SECRET = nil
- if fs.exists(".ndisk_secret") then
- local f = fs.open(".ndisk_secret", "r")
- NDISK_SECRET = f.readAll()
- f.close()
- end
- local REQUEST_TIMEOUT = 2
- -- ============================================
- -- HELPERS
- -- ============================================
- local function log(msg)
- print("[" .. os.date("%H:%M:%S") .. "] " .. msg)
- end
- local function logSuccess(msg)
- term.setTextColor(colors.lime)
- log(msg)
- term.setTextColor(colors.white)
- end
- local function logError(msg)
- term.setTextColor(colors.red)
- log("ERROR: " .. msg)
- term.setTextColor(colors.white)
- end
- local function confirm(msg)
- term.setTextColor(colors.yellow)
- write(msg .. " [y/N]: ")
- term.setTextColor(colors.white)
- local input = read()
- return input:lower() == "y" or input:lower() == "yes"
- end
- local function generateRequestId()
- return os.getComputerID() .. "_" .. os.epoch("utc") .. "_" .. math.random(10000)
- end
- local function sendRequest(nodeId, request)
- request.targetNode = nodeId
- request.secret = NDISK_SECRET -- Include auth secret
- modem.transmit(5, 5, request)
- local timer = os.startTimer(REQUEST_TIMEOUT)
- while true do
- local event, p1, p2, p3, p4 = os.pullEvent()
- if event == "modem_message" and type(p4) == "table" and p4.id == request.id then
- return p4.response
- elseif event == "timer" and p1 == timer then
- return nil
- end
- end
- end
- -- ============================================
- -- MAIN
- -- ============================================
- local function main()
- term.clear()
- term.setCursorPos(1, 1)
- term.setTextColor(colors.cyan)
- print("========================================")
- print(" NDisk RAID10 Migration Tool ")
- print("========================================")
- term.setTextColor(colors.white)
- print("")
- term.setTextColor(colors.orange)
- print("SAFE MODE: Copy-first, delete-last")
- print("Original data only deleted after verification.")
- term.setTextColor(colors.white)
- print("")
- if not confirm("Have you stopped the main server?") then
- print("Aborted. Stop the server first.")
- return
- end
- print("")
- log("Loading ndisk API...")
- local ndisk = dofile("ndisk_api.lua")
- ndisk.init()
- local raidStatus = ndisk.getRaidStatus()
- print("")
- print("RAID Status:")
- print(" Nodes: " .. raidStatus.nodeCount)
- print(" Mode: " .. raidStatus.mode)
- print(" Max Failures: " .. raidStatus.maxFailures)
- print("")
- if raidStatus.nodeCount < 2 then
- logError("Need at least 2 nodes for RAID10!")
- return
- end
- -- Load raw chunk map to track old locations
- local oldChunkMap = {}
- if fs.exists("chunklocation.txt") then
- local file = fs.open("chunklocation.txt", "r")
- for line in file.readLine do
- local parts = {}
- for part in line:gmatch("%S+") do
- table.insert(parts, part)
- end
- if #parts >= 3 then
- local filename = parts[1]
- local chunkMatch = parts[2]:match("chunk(%d+)")
- local chunkNum = chunkMatch and tonumber(chunkMatch) or nil
- local primaryId = tonumber(parts[3])
- local mirrorId = parts[4] and tonumber(parts[4]) or nil
- -- Skip malformed lines
- if filename and chunkNum and primaryId then
- oldChunkMap[filename] = oldChunkMap[filename] or {}
- oldChunkMap[filename][chunkNum] = {primary = primaryId, mirror = mirrorId}
- end
- end
- end
- file.close()
- end
- local files = {}
- for path, _ in pairs(oldChunkMap) do
- table.insert(files, path)
- end
- table.sort(files)
- log("Found " .. #files .. " files to migrate")
- print("")
- -- Backup chunk map
- if fs.exists("chunklocation.txt") then
- if fs.exists("chunklocation.txt.premigrate") then
- fs.delete("chunklocation.txt.premigrate")
- end
- fs.copy("chunklocation.txt", "chunklocation.txt.premigrate")
- log("Backed up chunklocation.txt")
- end
- if not confirm("Start RAID10 migration?") then
- print("Aborted.")
- return
- end
- print("")
- local migrated = 0
- local failed = 0
- local skipped = 0
- for i, path in ipairs(files) do
- local progress = math.floor((i / #files) * 100)
- term.clearLine()
- term.setCursorPos(1, select(2, term.getCursorPos()))
- write("[" .. progress .. "%] " .. path)
- -- Store old chunk locations for this file
- local oldChunks = {}
- if oldChunkMap[path] then
- for k, v in pairs(oldChunkMap[path]) do
- oldChunks[k] = {primary = v.primary, mirror = v.mirror}
- end
- end
- -- Step 1: Read file content
- local content, readErr = ndisk.read(path)
- if not content then
- failed = failed + 1
- print("")
- logError(path .. ": read failed - " .. (readErr or "unknown"))
- else
- -- Step 2: Write with RAID10 (creates new chunks)
- local success, writeErr = ndisk.write(path, content)
- if not success then
- failed = failed + 1
- print("")
- logError(path .. ": write failed - " .. (writeErr or "unknown"))
- else
- -- Step 3: Verify by reading back
- local verifyContent, verifyErr = ndisk.read(path)
- if verifyContent ~= content then
- failed = failed + 1
- print("")
- logError(path .. ": verification failed")
- else
- -- Step 4: Success! Now delete orphaned old chunks
- -- Get new chunk locations from chunk map
- local newUsedNodes = {}
- if fs.exists("chunklocation.txt") then
- local file = fs.open("chunklocation.txt", "r")
- for line in file.readLine do
- local parts = {}
- for part in line:gmatch("%S+") do
- table.insert(parts, part)
- end
- if parts[1] == path and #parts >= 3 then
- local nodeId = tonumber(parts[3])
- local mirrorId = parts[4] and tonumber(parts[4]) or nil
- if nodeId then newUsedNodes[nodeId] = true end
- if mirrorId then newUsedNodes[mirrorId] = true end
- end
- end
- file.close()
- end
- -- Delete old chunks on nodes no longer used
- for chunkNum, info in pairs(oldChunks) do
- if info.primary and not newUsedNodes[info.primary] then
- sendRequest(info.primary, {
- id = generateRequestId(),
- action = "delete",
- path = path,
- chunk = chunkNum
- })
- end
- if info.mirror and info.mirror ~= info.primary and not newUsedNodes[info.mirror] then
- sendRequest(info.mirror, {
- id = generateRequestId(),
- action = "delete",
- path = path,
- chunk = chunkNum
- })
- end
- end
- migrated = migrated + 1
- end
- end
- end
- end
- print("")
- print("")
- print("========================================")
- print(" Migration Complete")
- print("========================================")
- print("")
- logSuccess("Files migrated: " .. migrated)
- if failed > 0 then
- logError("Files failed: " .. failed)
- end
- print("")
- print("Pre-migration backup: chunklocation.txt.premigrate")
- print("You can now restart the main server.")
- end
- main()
Advertisement
Add Comment
Please, Sign In to add comment