Last active
January 25, 2018 00:58
-
-
Save vifino/b95713dcdf174ba96aa743264b107ff2 to your computer and use it in GitHub Desktop.
pam_lua-based dynamic system motd.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env lua | |
-- pam_lua-based dynamic MOTD. | |
-- Rewritten from scratch, again. | |
-- (Maybe also because it was a bit filthy...) | |
-- Still kinda bad, cause it executes programs... | |
-- But hey, it Works For Me(tm)! | |
-- | |
-- To use: | |
-- Install pam_lua (https://github.com/vifino/pam_lua). | |
-- OPEN A ROOT SESSION RIGHT NOW. TMUX AS ROOT, TOO. | |
-- Add `session optional pam_lua.so script=/path/to/motd.lua` to some PAM file, like system or login. | |
-- Login and hope it works. In case it doesn't, revert the change, check system logs and tell me. | |
-- Configs | |
local OS = 'freebsd' | |
local filters = "box-unicode clippy" | |
local minlen = 40 | |
-- Try not to fuck up anything after here. | |
-- Localize some functions | |
local tonumber, tostring = tonumber, tostring | |
local fmt = string.format | |
local min, max, floor, log = math.min, math.max, math.floor, math.log | |
local strep = string.rep | |
local popen = io.popen | |
-- Helpers | |
local function pad(str, n, c) | |
c = c or ' ' | |
return str .. strep(c, n - #str) | |
end | |
local function sizefmt(s) | |
local C = "B" | |
if s >= 0x40000000 then | |
s = s / 0x40000000 | |
C = "G" | |
elseif s >= 0x100000 then | |
s = s / 0x100000 | |
C = "M" | |
elseif s >= 1024 then | |
s = s / 0x400 | |
C = "K" | |
end | |
local d = 10 ^ (floor(log(s) / log(10)) - 2) | |
return tostring(floor(s / d) * d)..C | |
end | |
local function exec(prog) | |
local f = popen(prog) | |
local c = f:read('*all') | |
f:close() | |
return c | |
end | |
local function oneexec(prog) | |
local r = exec(prog) | |
return r:match("^([^\r\n]+)") | |
end | |
-- Hacky way to parse sysctl. Yay. | |
local sysctl_pattern = "^([%w._-]) = (.-)$" | |
if OS == 'freebsd' then sysctl_pattern = "^([%w._-]+): (.-)$" end | |
local function sysctl_parse(sysctl) | |
local cmd = 'sysctl -a' | |
if sysctl then cmd = 'sysctl -- '.. sysctl end | |
local res = {} | |
local output = exec(cmd) | |
local lastname | |
for line in output:gmatch("[^\r\n]+") do | |
local name, val = line:match(sysctl_pattern) | |
if not val then | |
res[lastname] = res[lastname] .. line .. '\n' | |
else | |
res[name] = val | |
lastname = name | |
end | |
end | |
return res | |
end | |
local sysctl = {} | |
setmetatable(sysctl, {__index=function(t, k) | |
local vals = sysctl_parse(k) | |
for nk, nv in pairs(vals) do | |
local v = nv | |
local numb = tonumber(nv) | |
if numb then v = numb end | |
rawset(t, nk, v) | |
end | |
return t[k] or true | |
end}) | |
-- Content | |
local content={} | |
local function addl(...) content[#content+1] = fmt(...) end | |
-- Basics | |
local hostname = oneexec("hostname") | |
addl(os.date("%H:%M %p, %A, the %x.")) | |
-- Uptime | |
local up, users = oneexec("uptime"):match("up +(.-), +([0-9]+) user") | |
addl("Uptime: %s", up) | |
addl("Users: %s users", users) | |
addl("") | |
-- Memory | |
local mem_used, mem_avail, mem_total | |
if OS == "freebsd" then | |
mem_total = sysctl["hw.physmem"] | |
mem_avail = (sysctl["vm.stats.vm.v_inactive_count"] + sysctl["vm.stats.vm.v_cache_count"] + | |
sysctl["vm.stats.vm.v_free_count"] | |
) * sysctl["hw.pagesize"] | |
mem_used = mem_total - mem_avail | |
else | |
-- TODO: make it work on linux too. | |
end | |
addl("Memory: %s/%s used (%.2f%%)", sizefmt(mem_used), sizefmt(mem_total), (mem_used/mem_total) * 100) | |
-- Formatting! | |
local formatters = {} | |
formatters.none = function(linet) return linet end | |
-- Boxes!! | |
-- UL, UR, DL, DR, BU, BD, BL, BR, HL, HR | |
local boxes = { | |
plain = {'+', '+', '+', '+', '-', '_', '|', '|', '[', ']'}, | |
curved = {'/', '\\', '\\', '/', '-', '_', '|', '|', '<', '>'}, | |
unicode = {'┌', '┐', '└', '┘', '─', '─', '│', '│', '<', '>'}, | |
} | |
for name, C in pairs(boxes) do | |
local C1, C2, C3, C4, C5, C6, C7, C8, C9, C10 = C[1], C[2], C[3], C[4], C[5], C[6], C[7], C[8], C[9], C[10] | |
formatters["box-"..name] = function(linet) | |
local maxn = max(#hostname + 3, minlen or 0) | |
for i=1, #linet do | |
maxn = max(maxn, #linet[i]) | |
end | |
local rett = { | |
[1] = C1 .. C5 .. C9 .. hostname .. C10 .. strep(C5, maxn - 3 - #hostname) .. C2 | |
} | |
for i=1, #linet do | |
local str = linet[i] | |
rett[i+1] = C7 .. pad(str, maxn) .. C8 | |
end | |
rett[#rett+1] = C3 .. strep(C6, maxn) .. C4 | |
return rett | |
end | |
end | |
-- Ascii art stuffs. Mostly clippy. | |
local artsies = {} | |
artsies.clippy = [[ | |
__ | |
/ \ | |
| | | |
|O O/ | |
|| || / | |
|\_/| / | |
\___/ <-- | |
]] | |
for name, ascii in pairs(artsies) do | |
local asciimax = 0 | |
local asciit = {} | |
local n = 1 | |
for line in ascii:gmatch("[^\r\n]+") do | |
asciimax = max(asciimax, #line) | |
asciit[n] = line | |
n = n + 1 | |
end | |
local asciilen = n-1 | |
formatters[name] = function(linet) | |
local outb = {} | |
local numo = #linet | |
local startpos = 1 | |
local offset = 0 | |
if numo < asciilen then | |
offset = asciilen - numo | |
elseif numo > asciilen then | |
startpos = numo - asciilen + 1 | |
end | |
local padding = strep(" ", asciimax) | |
for i=1, max(numo + offset, startpos + asciilen) - 1 do | |
local line = linet[i-offset] or "" | |
if i >= startpos then | |
outb[i] = pad(asciit[i - startpos + 1], asciimax) .. line | |
else | |
outb[i] = padding .. line | |
end | |
end | |
return outb | |
end | |
end | |
-- Output | |
local outt = content | |
for filter in filters:gmatch("[^ ]+") do | |
outt = formatters[filter](outt) | |
end | |
local out = "" | |
for i=1, #outt do | |
out = out .. outt[i] .. '\n' | |
end | |
if pam then | |
if pam.handler == "open_session" then | |
if pam.flag.silent then return pam.ret.ignore end | |
pam.info(out) | |
end | |
return pam.ret.ignore | |
end | |
io.write(out) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment