#!/opt/lnxall_app/bin/lua local invoker = require "invoker" local hal = require "lnxhal" local module = {} local LOG_DIR = "/var/log/compressed" local LOG_MAX_AGE = 14 * 24 * 60 * 60 local SSD_FULL_PERCENT = 92 function module.checkplat() local cpuinfo = hal.cpuinfo_get() return type(cpuinfo) == "table" and cpuinfo.arch == hal.ARCH_AARCH64 end function module.category() return "存储媒介" end function module.description() return "检查固态硬盘" end function module.detailed() return "检查 nvme0n1 设备、挂载和空间,并扫描近期内核日志中的介质错误。" end function module.suggestion() return table.concat({ "1. ls /dev/nvme0n1 : 如果输出为空且断电重启后恢复,可能是已知 SSD 主控问题。", "2. 如果 /dev/nvme0n1 存在但 df -h 查不到 /mnt/ssd,请检查挂载。", "3. nvme smart-log /dev/nvme0n1 查看健康信息。", "4. dmesg | grep -i -E 'ext4|critical medium error'.", }, "\n") end local function shell_quote(value) return "'" .. string.gsub(value, "'", "'\\''") .. "'" end local function is_buster() local release = invoker.readfile("/etc/os-release") return type(release) == "string" and string.find(release, "Debian", 1, true) ~= nil and string.find(release, "buster", 1, true) ~= nil end local function get_usage() local _, output = invoker.invoke(invoker.OUTPUT, "df", "-h") if type(output) ~= "string" then return -1, nil, false end local percent = -1 local usage_line for line in string.gmatch(output, "[^\r\n]+") do if string.find(line, "nvme0n1", 1, true) then local value = tonumber(string.match(line, "(%d+)%%")) if value then percent = value end usage_line = line end end return percent, usage_line, percent >= SSD_FULL_PERCENT end local function has_recent_storage_error() local logs = invoker.glob(LOG_DIR .. "/kern*.gz") if not logs then return false, "not found" end local now = os.time() for _, logfile in ipairs(logs) do local ftype, _, _, _, mtime = invoker.statfile(logfile) if ftype == "regular" and type(mtime) == "number" and now - mtime <= LOG_MAX_AGE then local command = "gzip -d -c " .. shell_quote(logfile) .. " | grep -e 'critical medium error' -e 'EXT4-fs error'" local status, output = invoker.invoke(invoker.OUTPUT, "sh", "-c", command) if status == 0 and type(output) == "string" and #output > 0 then return true end end end return false end function module.step() local not_mounted = is_buster() and not invoker.mountpoint("/mnt/ssd") local no_nvme0n1 if not invoker.statfile("/dev/nvme0n1") then no_nvme0n1 = "SSD 主控可能存在问题,断电重启可恢复" end local has_nvme0n2 = invoker.statfile("/dev/nvme0n2") ~= nil local usage_percent, usage, almost_full = get_usage() local result = { ["not mounted"] = not_mounted, ["no nvme0n1"] = no_nvme0n1, ["has nvme0n2"] = has_nvme0n2, ["almost full"] = almost_full, ["usage percent"] = usage_percent, usage = usage, } if not_mounted or no_nvme0n1 or has_nvme0n2 or almost_full then return result, true end local has_log_error, log_status = has_recent_storage_error() if log_status then result["kernel logs"] = log_status end if has_log_error then result["EXT4 ERROR"] = true return result, true end return result, false end return module