import stuff I'm using on Secondcraft
This commit is contained in:
parent
776e19df41
commit
fa612224d6
238
bbs/OpenOS/bin/bbs.lua
Normal file
238
bbs/OpenOS/bin/bbs.lua
Normal file
@ -0,0 +1,238 @@
|
||||
local serial = require "serialization"
|
||||
local event = require "event"
|
||||
local rpc = require "rpc"
|
||||
local _, unicode = pcall(require, "unicode")
|
||||
|
||||
local cfg = {}
|
||||
local status = ""
|
||||
local bboard
|
||||
local ssub = (unicode or {}).sub or string.sub
|
||||
|
||||
local function loadConfig()
|
||||
local f = io.open("/etc/bbs.cfg","rb")
|
||||
if not f then return false end
|
||||
cfg = serial.unserialize(f:read("*a"))
|
||||
f:close()
|
||||
return true
|
||||
end
|
||||
local function saveConfig()
|
||||
local f = io.open("/etc/bbs.cfg","wb")
|
||||
if not f then return false end
|
||||
f:write(serial.serialize(cfg))
|
||||
f:close()
|
||||
return true
|
||||
end
|
||||
|
||||
-- figure out terminal geometry
|
||||
|
||||
local function getCursor()
|
||||
io.write("\27[6n")
|
||||
local b = ""
|
||||
repeat
|
||||
b = b .. io.read(1)
|
||||
until b:match("\27%[%d+;%d+R")
|
||||
local y,x = b:match("\27%[(%d+);(%d+)R")
|
||||
return tonumber(x), tonumber(y)
|
||||
end
|
||||
io.write("\27[999;999H")
|
||||
local mx, my = getCursor()
|
||||
print(mx, my)
|
||||
|
||||
local function setStatus(str)
|
||||
local ox, oy = getCursor()
|
||||
status = str
|
||||
io.write(string.format("\27[%d;1H\27[0m\27[7m\27[2K%s\27[0m\27[%i;%iH", my, status, oy, ox))
|
||||
end
|
||||
|
||||
local function menu(items, state)
|
||||
local state = state or {1,1}
|
||||
local i, si = state[1], state[2]
|
||||
while true do
|
||||
if si > i - 3 then
|
||||
si = math.max(1, i-5)
|
||||
elseif i > (si+my) - 6 then
|
||||
si = math.min(i - (my-3) + 5, #items - (my - 3))
|
||||
end
|
||||
i = math.max(1,math.min(#items, i))
|
||||
print(string.format("\27[H\27[0m\27[7m\27[2K%s\27[0m", items.header or ""))
|
||||
for j = si, (si+my)-3 do
|
||||
print((i==j and "\27[7m" or "\27[0m") .. "\27[2K" .. (items[j] or ""):sub(1,mx))
|
||||
end
|
||||
setStatus(status)
|
||||
local _,_,ch,co = event.pull(60,"key_down")
|
||||
ch=string.char(ch or 0)
|
||||
if co == 208 then -- down
|
||||
i = i + 1
|
||||
elseif co == 200 then -- up
|
||||
i = i - 1
|
||||
elseif co == 201 then -- pgdn
|
||||
i = i - (my - 2)
|
||||
elseif co == 209 then -- pgdn
|
||||
i = i + my - 2
|
||||
else
|
||||
return i, ch, co, {i, si}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function popup(str,title)
|
||||
local len = (unicode or {}).len or string.len
|
||||
if title then
|
||||
title=string.format("[%s]",title or "")
|
||||
else
|
||||
title = ""
|
||||
end
|
||||
local width, height, content = 0, 0, {}
|
||||
for line in str:gmatch("[^\n]*") do
|
||||
height = height + 1
|
||||
width = math.max(width,len(line))
|
||||
content[#content+1] = line
|
||||
end
|
||||
if width < 1 or height < 1 then return false end
|
||||
local startx,starty = ((mx//2)-(width//2))-2, ((my//2)-(height//2))-2
|
||||
io.write(string.format("\27[%d;%dH╒═%s%s╕",starty,startx,title,("═"):rep((width+1)-len(title))))
|
||||
for k,v in pairs(content) do
|
||||
io.write(string.format("\27[%d;%dH│ %s%s │",starty+k,startx,v,(" "):rep(width-len(v))))
|
||||
end
|
||||
io.write(string.format("\27[%d;%dH┕%s┙",starty+1+#content,startx,("━"):rep(width+2)))
|
||||
end
|
||||
|
||||
local function query(str, default)
|
||||
local rv
|
||||
io.write(string.format(str, default or "no default"))
|
||||
repeat
|
||||
rv = io.read()
|
||||
rv = #rv > 0 and rv or default or ""
|
||||
until #rv > 0
|
||||
return rv
|
||||
end
|
||||
|
||||
local function help()
|
||||
popup([[
|
||||
[↑][↓] Move 1 entry
|
||||
[PgUp][PgDn] Move 1 page
|
||||
[Enter] Select item
|
||||
|
||||
[c] Compose new post
|
||||
[r] Reply to post
|
||||
[t] Thread view
|
||||
|
||||
[q] Exit
|
||||
|
||||
Press any key to continue...]],"Usage")
|
||||
event.pull("key_down")
|
||||
end
|
||||
|
||||
local function compose(board, post)
|
||||
local post = post or {}
|
||||
io.write("\27[2J\27[H")
|
||||
post.author = cfg.handle
|
||||
post.subject = query("Subject [%s]? ", post.subject)
|
||||
fn = os.tmpname()
|
||||
os.execute(string.format("edit '%s'",fn))
|
||||
f = io.open(fn, "rb")
|
||||
if f then
|
||||
post.content = f:read("*a")
|
||||
f:close()
|
||||
os.execute(string.format("rm '%s'", fn))
|
||||
print("Posting...")
|
||||
bboard.post(board,post)
|
||||
else
|
||||
print("Post empty, cancelled.")
|
||||
end
|
||||
end
|
||||
|
||||
local function viewPost(board,id)
|
||||
io.write("\27[2J\27[H")
|
||||
post = bboard.getPost(board, id)
|
||||
fn = os.tmpname()
|
||||
f = io.open(fn, "wb")
|
||||
f:write(string.format("Author: %s\nSubject: %s\n", post.author, post.subject))
|
||||
f:write(post.content)
|
||||
f:close()
|
||||
os.execute(string.format("less '%s'", fn))
|
||||
os.execute(string.format("rm '%s'", fn))
|
||||
end
|
||||
|
||||
local function threadView(board,id)
|
||||
local run = true
|
||||
local i, ch, co, st
|
||||
local list = bboard.getThread(board, id)
|
||||
while run do
|
||||
local vl = {header=string.format("%4s %15s %s", "ID", "Author", "Subject")}
|
||||
status = string.format("[%s] Thread: %s - %i items", cfg.handle, list[1].subject, #list)
|
||||
for k,v in pairs(list) do
|
||||
vl[k] = string.format("%4d %15s %s", v.id, v.author:sub(1,15), v.subject):sub(1,mx)
|
||||
end
|
||||
i, ch, co, st = menu(vl, st)
|
||||
if ch == "q" then -- quit
|
||||
run = false
|
||||
elseif co == 28 then -- enter, view
|
||||
viewPost(board, list[i].id)
|
||||
elseif ch == "r" then -- reply
|
||||
local prev = list[i]
|
||||
compose(board, {replyTo=prev.id, subject="Re:"..prev.subject})
|
||||
list = bboard.list(board)
|
||||
elseif ch == "h" or ch == "?" then
|
||||
help()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function boardView(name)
|
||||
local run = true
|
||||
local i, ch, co, st, fn, f, post
|
||||
local list = bboard.list(name)
|
||||
while run do
|
||||
status = string.format("[%s] %s/%s - %i items", cfg.handle, cfg.server, name, #list)
|
||||
local vl = {header=string.format("%4s %15s %s", "ID", "Author", "Subject")}
|
||||
for k,v in pairs(list) do
|
||||
vl[k] = string.format("%4d %15s %s", v.id, v.author:sub(1,15), v.subject):sub(1,mx)
|
||||
end
|
||||
i, ch, co, st = menu(vl, st)
|
||||
if ch == "q" then -- quit
|
||||
run = false
|
||||
elseif co == 28 then -- enter, view
|
||||
viewPost(name, list[i].id)
|
||||
elseif ch == "c" then -- compose
|
||||
compose(name)
|
||||
list = bboard.list(name)
|
||||
elseif ch == "r" then -- reply
|
||||
local prev = list[i]
|
||||
compose(name, {replyTo=prev.id, subject="Re:"..prev.subject})
|
||||
list = bboard.list(name)
|
||||
elseif ch == "t" then -- thread view
|
||||
threadView(name,list[i].id)
|
||||
elseif ch == "h" or ch == "?" then
|
||||
help()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
io.write("\27[2J\27[H")
|
||||
setStatus("Loading config")
|
||||
loadConfig()
|
||||
setStatus("Basic setup...")
|
||||
cfg.server = query("Server [%s]?", cfg.server)
|
||||
cfg.handle = query("Username? [%s]?", cfg.handle)
|
||||
setStatus("Saving config")
|
||||
saveConfig()
|
||||
|
||||
setStatus("Connecting...")
|
||||
bboard = rpc.proxy(cfg.server, "bbs_")
|
||||
|
||||
local run = true
|
||||
local i, ch, co, st
|
||||
while run do
|
||||
local list = bboard.boards()
|
||||
setStatus(string.format("Connected to %s - %i boards",cfg.server,#list))
|
||||
list.header = "Choose a board..."
|
||||
i, ch, co, st = menu(list, st)
|
||||
if ch == "q" then
|
||||
run = false
|
||||
elseif co == 28 then
|
||||
boardView(list[i])
|
||||
elseif ch == "h" or ch == "?" then
|
||||
help()
|
||||
end
|
||||
end
|
17
bbs/OpenOS/etc/rc.d/bbsd.lua
Normal file
17
bbs/OpenOS/etc/rc.d/bbsd.lua
Normal file
@ -0,0 +1,17 @@
|
||||
local bboard = require "bboard"
|
||||
local rpc = require "rpc"
|
||||
function start()
|
||||
for k,v in pairs(bboard) do
|
||||
if type(v) == "function" then
|
||||
rpc.register("bbs_"..k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
function stop()
|
||||
if not rpc.unregister then return false end
|
||||
for k,v in pairs(bboard) do
|
||||
rpc.unregister("bbs_"..k)
|
||||
end
|
||||
end
|
||||
|
||||
return {start=start, stop=stop}
|
140
bbs/OpenOS/lib/bboard.lua
Normal file
140
bbs/OpenOS/lib/bboard.lua
Normal file
@ -0,0 +1,140 @@
|
||||
local serial = require "serialization"
|
||||
local fs = require "filesystem"
|
||||
local bboard = {}
|
||||
|
||||
local PSYCHOS = _OSVERSION:sub(1,8)=="PsychOS"
|
||||
|
||||
local f = io.open(PSYCHOS and "/boot/cfg/bboard.cfg" or "/etc/bboard.cfg", "rb")
|
||||
if f then
|
||||
bboard.cfg = serial.unserialize(f:read("*a"))
|
||||
f:close()
|
||||
else
|
||||
bboard.cfg = {
|
||||
path = PSYCHOS and "/boot/bboard" or "/var/spool/bboard",
|
||||
}
|
||||
end
|
||||
|
||||
local BOARDPATH = string.format("%s/%%s", bboard.cfg.path)
|
||||
local POSTPATH = string.format("%s/%%i", BOARDPATH)
|
||||
|
||||
local function sanitise(str)
|
||||
return str:gsub("/","")
|
||||
end
|
||||
|
||||
local flist = PSYCHOS and fs.list or function(path)
|
||||
local rt = {}
|
||||
for file in fs.list(path) do
|
||||
rt[#rt+1] = file
|
||||
end
|
||||
return rt
|
||||
end
|
||||
|
||||
function bboard.boards()
|
||||
local rt = {}
|
||||
for _, file in ipairs(flist(bboard.cfg.path)) do
|
||||
if fs.isDirectory(string.format(BOARDPATH, file)) then
|
||||
rt[#rt+1] = sanitise(file)
|
||||
end
|
||||
end
|
||||
return rt
|
||||
end
|
||||
|
||||
function bboard.list(board, start, limit, trunc)
|
||||
assert(type(board) == "string", "invalid board")
|
||||
local rt = {}
|
||||
local ft = flist(string.format(BOARDPATH, board))
|
||||
local limit, start = math.max(limit or math.huge, 50), #ft
|
||||
table.sort(ft, function(a,b) return a>b end)
|
||||
for k,v in ipairs(ft) do
|
||||
if tonumber(v) and tonumber(v) <= start then
|
||||
rt[#rt+1] = bboard.getPostMeta(board, v, trunc)
|
||||
rt[#rt].id = tonumber(v)
|
||||
end
|
||||
if #rt >= limit then break end
|
||||
end
|
||||
return rt
|
||||
end
|
||||
|
||||
function bboard.getReplies(board, id, max, skip, trunc)
|
||||
assert(type(board) == "string", "invalid board")
|
||||
assert(type(id) == "number", "invalid post ID")
|
||||
skip=skip or 0
|
||||
local max = tonumber((bboard.list(board, nil, 1)[1] or {}).id or 0)
|
||||
local rt = {}
|
||||
for i = id, max do
|
||||
if skip < 1 then
|
||||
local post = bboard.getPost(board, i)
|
||||
if post.replyTo == id then
|
||||
rt[#rt+1] = bboard.getPostMeta(board,i,trunc)
|
||||
end
|
||||
if #rt >= math.max(trunc or math.huge, 50) then break end
|
||||
end
|
||||
skip = skip - 1
|
||||
end
|
||||
return rt
|
||||
end
|
||||
|
||||
function bboard.getAllReplies(board, id, max, skip, trunc)
|
||||
assert(type(board) == "string", "invalid board")
|
||||
assert(type(id) == "number", "invalid post ID")
|
||||
skip=skip or 0
|
||||
local max = tonumber((bboard.list(board, nil, 1)[1] or {}).id or 0)
|
||||
local ids,rt = {[id]=true}, {}
|
||||
for i = id, max do
|
||||
if skip < 1 then
|
||||
local post = bboard.getPost(board, i)
|
||||
if ids[post.replyTo] then
|
||||
ids[i], rt[#rt+1] = true, bboard.getPostMeta(board,i,trunc)
|
||||
end
|
||||
if #rt >= math.max(trunc or math.huge, 50) then break end
|
||||
end
|
||||
skip = skip - 1
|
||||
end
|
||||
return rt
|
||||
end
|
||||
|
||||
function bboard.getThread(board,id,max,skip,trunc)
|
||||
assert(type(board) == "string", "invalid board")
|
||||
assert(type(id) == "number", "invalid post ID")
|
||||
skip=skip or 0
|
||||
local op
|
||||
repeat
|
||||
op = bboard.getPostMeta(board, id)
|
||||
id = op.replyTo or id
|
||||
until not op.replyTo
|
||||
return {op, table.unpack(bboard.getAllReplies(board,id,max,skip,trunc))}
|
||||
end
|
||||
|
||||
function bboard.getPost(board,id)
|
||||
board, id = sanitise(board), tonumber(id)
|
||||
assert(type(board) == "string", "invalid board")
|
||||
assert(type(id) == "number", "invalid post ID")
|
||||
local f, e = io.open(string.format(POSTPATH, board, id), "rb")
|
||||
assert(f, e)
|
||||
local rt = serial.unserialize(f:read("*a"))
|
||||
f:close()
|
||||
rt.date = fs.lastModified(string.format(POSTPATH, board, id))
|
||||
rt.id = id
|
||||
return rt
|
||||
end
|
||||
function bboard.getPostMeta(board,id,trunc)
|
||||
local rt = bboard.getPost(board,id)
|
||||
rt.content = rt.content:match("^[^\n]+"):sub(1,math.min(trunc or 80, 160))
|
||||
return rt
|
||||
end
|
||||
|
||||
function bboard.post(board,post)
|
||||
assert(type(board) == "string", "invalid board")
|
||||
assert(type(post.author) == "string", "invalid author")
|
||||
assert(type(post.subject) == "string" and #post.subject <= 40, "invalid subject")
|
||||
assert(type(post.content) == "string", "no content")
|
||||
post.author = string.format("%s@%s", post.author, os.getenv("RPC_CLIENT") or "localhost")
|
||||
post.id = tonumber((bboard.list(board, nil, 1)[1] or {}).id or 0)+1
|
||||
local f, e = io.open(string.format(POSTPATH, board, post.id), "wb")
|
||||
assert(f, e)
|
||||
f:write(serial.serialize(post))
|
||||
f:close()
|
||||
return true
|
||||
end
|
||||
|
||||
return bboard
|
111
livefdd/bin/livefdc.lua
Normal file
111
livefdd/bin/livefdc.lua
Normal file
@ -0,0 +1,111 @@
|
||||
local serial = require "serialization"
|
||||
local fs = require "filesystem"
|
||||
|
||||
local tA = {...}
|
||||
|
||||
if #tA < 8 then
|
||||
print([[livefdc requires 8 arguments, in this order:
|
||||
- path to clean OpenOS installer
|
||||
- path to repoinstaller-compatible disk
|
||||
- comma-separated list of packages to install in the base system
|
||||
- comma-separated list of packages to archive for live use
|
||||
- comma-separated list of packages to archive for constrained use
|
||||
- comma-separated list of extra packages to be unpacked when there is space
|
||||
- comma-separated list of paths to move into the extra archive
|
||||
- output path
|
||||
]])
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
local function parsecs(str)
|
||||
local rt = {}
|
||||
for w in str:gmatch("[^,]+") do
|
||||
rt[#rt+1] = w
|
||||
end
|
||||
return rt
|
||||
end
|
||||
|
||||
local oospath = fs.canonical(tA[1])
|
||||
local pkgpath = fs.canonical(tA[2])
|
||||
local basepkg, livepkg, instpkg, extpkg = parsecs(tA[3]), parsecs(tA[4]), parsecs(tA[5]), parsecs(tA[6])
|
||||
local archivepaths = parsecs(tA[7])
|
||||
local outpath = fs.canonical(tA[8])
|
||||
|
||||
local f = io.open(pkgpath .. "/master/programs.cfg", "rb")
|
||||
local pkgs = serial.unserialize(f:read("*a"))
|
||||
f:close()
|
||||
local ipkgs,opkgs = {}, {}
|
||||
|
||||
local function run(cmd)
|
||||
print(cmd)
|
||||
os.execute(cmd)
|
||||
end
|
||||
local function mkdir(path)
|
||||
print("mkdir",path)
|
||||
fs.makeDirectory(path)
|
||||
end
|
||||
local function copy(from,to)
|
||||
print("copy",from,to)
|
||||
fs.copy(from,to)
|
||||
end
|
||||
local function move(from,to)
|
||||
print("move",from,to)
|
||||
fs.rename(from,to)
|
||||
end
|
||||
|
||||
local function installPkg(pkgname, dest)
|
||||
local pkg, opkg = pkgs[pkgname], {}
|
||||
assert(pkg, "package not available")
|
||||
print(string.format("Installing %s to %s", pkgname, dest))
|
||||
for k,v in pairs(pkg.files or {}) do
|
||||
v = v:match("^//") and v:sub(2) or fs.concat("/usr", v)
|
||||
mkdir(fs.concat(dest,v))
|
||||
copy(pkgpath .. "/" .. k,fs.concat(dest,v,fs.name(k)))
|
||||
opkg[k] = fs.concat(v,fs.name(k))
|
||||
end
|
||||
for k,v in pairs(pkg.dependencies or {}) do
|
||||
if k:match("^https?://") then
|
||||
v = v:match("^//") and v:sub(2) or fs.concat("/usr", v)
|
||||
mkdir(fs.concat(dest,v))
|
||||
copy(fs.concat(pkgpath, "external", k:match("^https://(.+)$")), fs.concat(dest,v,fs.name(k)))
|
||||
opkg[k] = fs.concat(v,fs.name(k))
|
||||
elseif not ipkgs[k] then
|
||||
installPkg(k,dest)
|
||||
end
|
||||
end
|
||||
ipkgs[pkgname] = pkg
|
||||
opkgs[pkgname] = opkg
|
||||
return true
|
||||
end
|
||||
|
||||
mkdir(outpath)
|
||||
run(string.format("cp -rv '%s' '%s/img'",oospath,outpath))
|
||||
|
||||
for k,v in ipairs(basepkg) do
|
||||
installPkg(v, fs.concat(outpath, "img"))
|
||||
end
|
||||
for k,v in ipairs(livepkg) do
|
||||
installPkg(v, fs.concat(outpath, "live"))
|
||||
end
|
||||
for k,v in ipairs(instpkg) do
|
||||
installPkg(v, fs.concat(outpath, "inst"))
|
||||
end
|
||||
for k,v in ipairs(extpkg) do
|
||||
installPkg(v, fs.concat(outpath, "extra"))
|
||||
end
|
||||
|
||||
for k,v in ipairs(archivepaths) do
|
||||
mkdir(fs.concat(outpath, "extra", fs.path(v)))
|
||||
move(fs.concat(outpath, "img", v), fs.concat(outpath, "extra", v))
|
||||
end
|
||||
|
||||
local f = io.open(fs.concat(outpath, "img", "/etc/opdata.svd"), "wb")
|
||||
f:write(serial.serialize(opkgs))
|
||||
f:close()
|
||||
|
||||
local opwd = os.getenv("PWD")
|
||||
for k,v in ipairs({"live","inst","extra"}) do
|
||||
os.setenv("PWD", fs.concat(outpath, v))
|
||||
run(string.format("mtar -czv '%s/img/.%s.mtar.lss' *", outpath, v))
|
||||
end
|
||||
os.setenv("PWD",opwd)
|
52
livefdd/etc/rc.d/livefdd.lua
Normal file
52
livefdd/etc/rc.d/livefdd.lua
Normal file
@ -0,0 +1,52 @@
|
||||
function start()
|
||||
|
||||
local fs = require "filesystem"
|
||||
local rootfs = fs.get("/")
|
||||
local free = rootfs.spaceTotal() - rootfs.spaceUsed()
|
||||
local function unpack(from,to)
|
||||
if not fs.exists(from) then return false end
|
||||
io.write(string.format("Unpacking %s to %s... ", from, to))
|
||||
local opwd = os.getenv("PWD")
|
||||
os.setenv("PWD", to)
|
||||
os.execute(string.format("mtar -xz '%s'",from))
|
||||
os.setenv("PWD", opwd)
|
||||
print("Done!")
|
||||
end
|
||||
local function link(from)
|
||||
from=fs.canonical(from)
|
||||
local dt = {from}
|
||||
for _, dir in ipairs(dt) do
|
||||
for file in fs.list(dir) do
|
||||
local fp = fs.concat(dir,file)
|
||||
if file:match("/$") then
|
||||
dt[#dt+1] = fp
|
||||
else
|
||||
local lt = fp:sub(#from+1)
|
||||
while not fs.isDirectory(fs.path(lt)) and not fs.isLink(fs.path(lt)) do
|
||||
lt = fs.path(lt)
|
||||
end
|
||||
fs.link(fs.concat(from,lt), lt)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if rootfs.isReadOnly() then -- live floppy
|
||||
print("\27[2J\27[HPreparing live environment...")
|
||||
fs.makeDirectory("/tmp/live")
|
||||
unpack("/.live.mtar.lss","/tmp/live")
|
||||
link("/tmp/live")
|
||||
elseif not rootfs.isReadOnly() and free < 2^17 then -- installed on small media
|
||||
print("\27[2J\27[HPreparing constrained environment...")
|
||||
fs.makeDirectory("/tmp/live")
|
||||
unpack("/.live.mtar.lss","/tmp/live")
|
||||
unpack("/.inst.mtar.lss","/tmp/live")
|
||||
link("/tmp/live")
|
||||
else -- extract and deactivate
|
||||
print("\27[2J\27[HFinalising installation...")
|
||||
unpack("/.live.mtar.lss","/")
|
||||
unpack("/.inst.mtar.lss","/")
|
||||
unpack("/.extra.mtar.lss","/")
|
||||
os.execute("rc livefdd disable")
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue
Block a user