controlpanel/lua/main.tl

165 lines
4.5 KiB
Plaintext

local json = require("json")
local Framebuffer = require("framebuffer")
local Ringbuffer = require("ringbuffer")
local Socket = require("socket")
local auth = require("auth")
local ENDPOINT <const> = auth.server:gsub("http", "ws", 1) .. "/ipmi/computer/" .. auth.id .. "/ws"
local HEADERS <const> = { ["Authorization"] = "Bearer " .. auth.token }
print("[MAIN] Init")
local socket = Socket.new(ENDPOINT, HEADERS)
-- Set up framebuffer capture and statusline
print("[MAIN] Setup framebuffer")
local prev_term = term.current()
local orig_native = term.native
local buffer = Framebuffer.wrap(orig_native())
term.native = function(): term.Redirect
return buffer.target
end
local width, height = term.getSize()
local top_line = window.create(buffer.target, 1, 1, width, 1)
local main_view = window.create(buffer.target, 1, 2, width, height - 1)
term.redirect(main_view as term.Redirect)
local function set_bar(text: string, fg: string | nil, bg: string | nil)
fg = fg or ("9"):rep(text:len())
bg = bg or ("f"):rep(text:len())
top_line.clear()
top_line.setCursorPos(1,1)
top_line.blit(text, fg, bg)
main_view.restoreCursor()
end
-- Create tasks
local bar_codes: { Socket.State: {string} } = {
["reset"] = {"[WS] RST", "78870111"},
["error"] = {"[WS] ERR", "78870EEE"},
["ok"] = {"[WS] OK\x03", "78870DD5"},
["viewer_connected"] = {"[WS] CON", "78870999"},
}
socket:on_state_change(function(new_state: Socket.State)
set_bar(table.unpack(bar_codes[new_state]))
end)
local ws_task = coroutine.create(function()
while true do
if socket:is_bad_state() then
socket:reconnect()
end
sleep(1)
end
end)
local report_task = coroutine.create(function()
local last_report = -1.0
while true do
local now = os.clock()
local interval = (socket.state == "viewer_connected") and 0.05 or 1
if now - last_report >= interval then
local message = json.encode({
screen = buffer.serialize()
})
socket:send(message)
last_report = now
end
sleep(0) -- until next gametick
end
end)
-- basically parallel.waitForAny
local record Task
coro: thread
filter: string | nil
end
local background_tasks: {Task} = {
{coro = ws_task},
{coro = report_task},
}
local shell_task: Task = {
coro = coroutine.create(function() shell.run("shell") end)
}
local function handle_event(e: table, pid: integer)
if e[1] == "terminate" then return end
local task = background_tasks[pid]
if task.filter == nil or task.filter == e[1] then
local ok, param = coroutine.resume(task.coro, table.unpack(e as {any}))
if not ok then
term.native = orig_native
term.redirect(term.native())
term.clear()
term.setCursorPos(1,1)
print(("OMEGABIG OOF @ PID %d"):format(pid))
error(param, 0)
else
task.filter = param as string
end
end
end
local event_queue: Ringbuffer.Ringbuffer<table> = Ringbuffer.new(64)
event_queue:push({n = 0})
local shell_deaths: {any} = {}
local shell_running = true
while shell_running do
local e: table
if not event_queue:is_empty() then
e = event_queue:pop() as table
else
e = table.pack(os.pullEventRaw())
end
if e[1] == "websocket_message" and e[2] == ENDPOINT then
local payload = json.decode(e[3] as string) as table
if payload["type"] == "push_event" then
event_queue:push(payload["event"] as table)
elseif payload["type"] == "viewer_connect" then
socket:signal_viewer_connect(true)
elseif payload["type"] == "viewer_disconnect" then
socket:signal_viewer_connect(false)
end
else
for pid = 1, #background_tasks do
handle_event(e, pid)
end
if shell_task.filter == nil or shell_task.filter == e[1] or e[1] == "terminate" then
local ok, param = coroutine.resume(shell_task.coro, table.unpack(e as {any}))
if not ok then
-- shell died i guess?
table.insert(shell_deaths, param)
else
shell_task.filter = param as string
end
end
if coroutine.status(shell_task.coro) == "dead" then
shell_running = false
end
end
end
socket:close()
term.native = orig_native
term.redirect(prev_term)
term.clear()
term.setCursorPos(1,1)
for i = 1, #shell_deaths do
print(shell_deaths[i])
end