Skip to content

Instantly share code, notes, and snippets.

@vifino
Last active January 25, 2018 00:58
Show Gist options
  • Save vifino/b95713dcdf174ba96aa743264b107ff2 to your computer and use it in GitHub Desktop.
Save vifino/b95713dcdf174ba96aa743264b107ff2 to your computer and use it in GitHub Desktop.
pam_lua-based dynamic system motd.
#!/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