284 lines
8.3 KiB
Luau
284 lines
8.3 KiB
Luau
noctalia.setUpdateInterval(5000)
|
|
|
|
-- Helper functions
|
|
function tr(key)
|
|
return noctalia.tr(key)
|
|
end
|
|
|
|
function cfg(key)
|
|
return noctalia.getConfig(key)
|
|
end
|
|
|
|
function getState(key)
|
|
return noctalia.state.get(key)
|
|
end
|
|
|
|
function setState(key, val)
|
|
noctalia.state.set(key, val)
|
|
end
|
|
|
|
function watchState(key, cb)
|
|
noctalia.state.watch(key, cb)
|
|
end
|
|
|
|
function killWithPidFile(file)
|
|
if noctalia.fileExists(file) then
|
|
local pid = noctalia.string.trim(noctalia.readFile(file))
|
|
|
|
if type(pid) == "string" and pid ~= "" then
|
|
noctalia.runAsync(`pkill -9 -P {pid} && kill -9 {pid}`)
|
|
end
|
|
noctalia.removeFile(file)
|
|
end
|
|
end
|
|
|
|
-- Vars
|
|
local branch = cfg("branch")
|
|
local updateCheckInterval = cfg("check_interval")
|
|
local checkDurationThreshold = cfg("check_duration_threshold")
|
|
local systemStatsCheckInterval = cfg("system_stats_check_interval")
|
|
local stateDir = "/tmp/nix-monitor"
|
|
local remoteHashFile = stateDir .. "/remoteHash"
|
|
local storeSizeFile = stateDir .. "/storeSize"
|
|
local closureSizeFile = stateDir .. "/closureSize"
|
|
local remotePidFile = stateDir .. "/remoteHash.pid"
|
|
local storePidFile = stateDir .. "/storeSize.pid"
|
|
local closurePidFile = stateDir .. "/closureSize.pid"
|
|
local showUpdateAvailableNotification = true
|
|
|
|
local localHashCmd = "nixos-version --hash 2> /dev/null | cut -c -7"
|
|
local remoteHashCmd = "git ls-remote https://github.com/NixOS/nixpkgs " .. branch .. " 2>/dev/null | cut -c 1-7"
|
|
local nixosGenerationsCmd = "nixos-rebuild list-generations | tail -n +2 | wc -l"
|
|
local nixosCurrentGenCmd = "nixos-rebuild list-generations | grep True | awk '{print $1}'"
|
|
local hmGenerationsCmd = "home-manager generations | wc -l"
|
|
local hmCurrentGenCmd = "home-manager generations | grep '(current)' | awk '{print $5}'"
|
|
local storeSizeCmd = "du -s --block-size=1GiB /nix/store | awk '\{print $1 \" GiB\"\}'"
|
|
local closureSizeCmd = "nix path-info --closure-size --human-readable /run/current-system | awk '\{print $2, $3\}'"
|
|
|
|
-- State inits
|
|
setState("lastLocalCheckTime", os.time())
|
|
setState("lastRemoteCheckRunTime", os.time())
|
|
setState("lastRemoteCheckTime", os.time())
|
|
setState("lastRemoteCheckTimeStr", "n/a")
|
|
setState("localHash", "n/a")
|
|
setState("remoteHash", "n/a")
|
|
setState("isRemoteCheckRunning", false)
|
|
setState("isUpdateAvailable", false)
|
|
setState("lastGenerationsCheckTime", 0)
|
|
setState("isSystemStatsCheckRunning", false)
|
|
setState("lastSystemStatsCheckTime", 0)
|
|
setState("hasHomeManager", noctalia.commandExists("home-manager"))
|
|
setState("nixosGenerations", "•••")
|
|
setState("nixosCurrentGen", "•••")
|
|
setState("hmGenerations", "•••")
|
|
setState("hmCurrentGen", "•••")
|
|
setState("storeSize", "•••")
|
|
setState("closureSize", "•••")
|
|
|
|
noctalia.mkdirAll(stateDir)
|
|
noctalia.writeFile(remoteHashFile, "")
|
|
|
|
-- Local check
|
|
function checkLocalHash()
|
|
setState("lastLocalCheckTime", os.time())
|
|
noctalia.runAsync(localHashCmd, function(res)
|
|
if res.exitCode == 0 then
|
|
setState("localHash", noctalia.string.trim(res.stdout))
|
|
end
|
|
end)
|
|
end
|
|
|
|
-- Remote check
|
|
--
|
|
-- Currently it is writing to a file because when I use the runAsync with
|
|
-- the callback, it is timed out
|
|
function checkRemoteHash(delay: number?)
|
|
local delay = delay or 0
|
|
|
|
if getState("isRemoteCheckRunning") then
|
|
return
|
|
end
|
|
|
|
setState("isRemoteCheckRunning", true)
|
|
setState("lastRemoteCheckRunTime", os.time())
|
|
|
|
if cfg("show_update_check_notification") then
|
|
noctalia.notify(tr("notification.checking_update"))
|
|
end
|
|
|
|
noctalia.writeFile(remoteHashFile, "")
|
|
noctalia.runAsync(`sleep {delay} && {remoteHashCmd} > {remoteHashFile} & echo $! > {remotePidFile}`)
|
|
end
|
|
|
|
-- Check if the remoteHash check command is finished by reading the file
|
|
function checkRemoteStatus()
|
|
local remoteHash = noctalia.string.trim(noctalia.readFile(remoteHashFile))
|
|
|
|
if type(remoteHash) == "string" and remoteHash ~= "" then
|
|
setState("isRemoteCheckRunning", false)
|
|
setState("remoteHash", remoteHash)
|
|
onRemoteHashCheckCompleted()
|
|
end
|
|
end
|
|
|
|
function onRemoteHashCheckCompleted()
|
|
if cfg("show_update_check_notification") then
|
|
noctalia.notify(tr("notification.update_check_completed"))
|
|
end
|
|
|
|
setState("lastRemoteCheckTime", os.time())
|
|
|
|
local date = noctalia.formatTime("%Y-%m-%d %H:%M:%S")
|
|
setState("lastRemoteCheckTimeStr", date)
|
|
|
|
noctalia.removeFile(remotePidFile)
|
|
end
|
|
|
|
function cancelRemoteCheck()
|
|
setState("isRemoteCheckRunning", false)
|
|
killWithPidFile(remotePidFile)
|
|
end
|
|
|
|
-- Generation check
|
|
function checkGenerations()
|
|
setState("lastGenerationsCheckTime", os.time())
|
|
|
|
noctalia.runAsync(nixosGenerationsCmd, function(result)
|
|
setState("nixosGenerations", result.stdout)
|
|
end)
|
|
|
|
noctalia.runAsync(nixosCurrentGenCmd, function(result)
|
|
setState("nixosCurrentGen", result.stdout)
|
|
end)
|
|
|
|
if getState("hasHomeManager") then
|
|
noctalia.runAsync(hmGenerationsCmd, function(result)
|
|
setState("hmGenerations", result.stdout)
|
|
end)
|
|
|
|
noctalia.runAsync(hmCurrentGenCmd, function(result)
|
|
setState("hmCurrentGen", result.stdout)
|
|
end)
|
|
end
|
|
end
|
|
|
|
-- System stats check
|
|
function checkSystemStats()
|
|
if getState("isSystemStatsCheckRunning") then
|
|
return
|
|
end
|
|
|
|
setState("isSystemStatsCheckRunning", true)
|
|
setState("lastSystemStatsCheckTime", os.time())
|
|
|
|
-- Sleep 0 to trigger noctalia calling /bin/sh
|
|
noctalia.runAsync(`sleep 0 && {storeSizeCmd} > {storeSizeFile} & echo $! > {storePidFile}`)
|
|
noctalia.runAsync(`sleep 0 && {closureSizeCmd} > {closureSizeFile} & echo $! > {closurePidFile}`)
|
|
end
|
|
|
|
function checkSystemStatsFile()
|
|
local storeSize = noctalia.string.trim(noctalia.readFile(storeSizeFile))
|
|
local closureSize = noctalia.string.trim(noctalia.readFile(closureSizeFile))
|
|
|
|
if typeof(storeSize) == "string" and storeSize ~= "" then
|
|
setState("storeSize", storeSize)
|
|
end
|
|
|
|
if typeof(closureSize) == "string" and closureSize ~= "" then
|
|
setState("closureSize", closureSize)
|
|
end
|
|
|
|
local success = typeof(storeSize) == "string" and storeSize ~= "" and
|
|
typeof(closureSize) == "string" and closureSize ~= ""
|
|
|
|
if success then
|
|
setState("isSystemStatsCheckRunning", false)
|
|
noctalia.removeFile(storePidFile)
|
|
noctalia.removeFile(closurePidFile)
|
|
end
|
|
end
|
|
|
|
function cancelSystemCheck()
|
|
setState("isSystemStatsCheckRunning", false)
|
|
killWithPidFile(storePidFile)
|
|
killWithPidFile(closurePidFile)
|
|
end
|
|
|
|
-- Init
|
|
checkLocalHash()
|
|
checkRemoteHash(10)
|
|
|
|
-- Command watcher
|
|
watchState("command", function(command)
|
|
if command == "check" then
|
|
checkLocalHash()
|
|
cancelRemoteCheck()
|
|
checkRemoteHash()
|
|
checkGenerations()
|
|
checkSystemStats()
|
|
end
|
|
|
|
if command == "cancel" then
|
|
cancelRemoteCheck()
|
|
cancelSystemCheck()
|
|
end
|
|
end)
|
|
|
|
--
|
|
-- Hooks
|
|
--
|
|
|
|
function update()
|
|
-- Check local hash every 10 seconds
|
|
if (os.time() - getState("lastLocalCheckTime") >= 10) then
|
|
checkLocalHash()
|
|
end
|
|
|
|
-- Remote check
|
|
if (os.time() - getState("lastRemoteCheckTime")) >= 60 * updateCheckInterval then
|
|
checkRemoteHash()
|
|
end
|
|
|
|
if getState("isRemoteCheckRunning") then
|
|
checkRemoteStatus()
|
|
|
|
-- Remote check timeout
|
|
if (os.time() - getState("lastRemoteCheckRunTime")) >= 60 * checkDurationThreshold then
|
|
cancelRemoteCheck()
|
|
end
|
|
end
|
|
|
|
if getState("localHash") ~= getState("remoteHash") and
|
|
getState("localHash") ~= "n/a" and getState("remoteHash") ~= "n/a"
|
|
then
|
|
setState("isUpdateAvailable", true)
|
|
|
|
if cfg("show_update_available_notification") and showUpdateAvailableNotification then
|
|
noctalia.notify(tr("notification.update_is_available"))
|
|
showUpdateAvailableNotification = false
|
|
end
|
|
else
|
|
setState("isUpdateAvailable", false)
|
|
showUpdateAvailableNotification = true
|
|
end
|
|
|
|
-- Generations check
|
|
if (os.time() - getState("lastGenerationsCheckTime")) >= 20 then
|
|
checkGenerations()
|
|
end
|
|
|
|
-- System stats check
|
|
if (os.time() - getState("lastSystemStatsCheckTime")) >= 60 * systemStatsCheckInterval then
|
|
checkSystemStats()
|
|
end
|
|
|
|
if getState("isSystemStatsCheckRunning") then
|
|
checkSystemStatsFile()
|
|
end
|
|
end
|
|
|
|
function onExit(signal)
|
|
cancelRemoteCheck()
|
|
cancelSystemCheck()
|
|
end
|