Compare commits

...

2 Commits

Author SHA1 Message Date
78f637d8e9 add package filtering to instgen 2023-11-14 19:47:07 +10:00
fa612224d6 import stuff I'm using on Secondcraft 2023-11-14 19:46:42 +10:00
6 changed files with 564 additions and 0 deletions

238
bbs/OpenOS/bin/bbs.lua Normal file
View 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

View 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
View 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
View 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)

View 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

View File

@ -69,6 +69,12 @@ wget(src,pcfgname)
local programs = parsecfg(pcfgname) local programs = parsecfg(pcfgname)
os.execute("rm '"..pcfgname.."'") os.execute("rm '"..pcfgname.."'")
if tArgs[3] then
for k,v in pairs(programs) do
if not filter[k] then programs[k] = nil end
end
end
local dlfiles = {} local dlfiles = {}
for k,v in pairs(programs) do for k,v in pairs(programs) do
for l,m in pairs(v.files or {}) do for l,m in pairs(v.files or {}) do