Compare commits

...

48 Commits

Author SHA1 Message Date
8c8221f789 wrong repo oopsies 2025-06-02 00:31:44 +00:00
af8d46154c Add App Store.lua 2025-06-02 00:31:15 +00:00
ebf3f8a35a :3 2024-08-26 00:18:19 +03:00
63704f0447 :3 2024-08-26 00:18:11 +03:00
59dc5edde5 :3 2024-08-25 23:56:48 +03:00
4df6073f9c :3 2024-08-25 23:52:04 +03:00
dfa9e305ab :3 2024-08-25 23:48:10 +03:00
85dac18494 :3 added bindings for bootloader lib 2024-08-25 23:44:09 +03:00
2b744a5813 added exports to modules 2024-08-25 23:31:35 +03:00
ffa88f010c :3 2024-08-25 23:21:31 +03:00
51b6b7fc95 :3 2024-08-25 23:19:12 +03:00
efc78c87b7 :3 2024-08-25 23:16:54 +03:00
d99a1ee5bb :3 2024-08-25 23:15:37 +03:00
1c6af0a1f9 :3 2024-08-25 23:11:50 +03:00
f0e24ab135 :3 2024-08-25 23:10:04 +03:00
b96de1a029 :3 2024-08-25 23:06:47 +03:00
7e5b7503d2 :3 2024-08-25 23:05:38 +03:00
cfc3374897 :3 2024-08-25 23:01:19 +03:00
3e1fd08fb8 :3 2024-08-25 22:58:18 +03:00
818412da9e :3 2024-08-25 22:56:32 +03:00
6900a19205 :3 2024-08-25 22:45:26 +03:00
f83565a402 :3 2024-08-25 22:41:49 +03:00
f41e343970 :3 2024-08-25 22:39:46 +03:00
4161160ab6 :3 2024-08-25 22:35:33 +03:00
5df9f33c2e :3 2024-08-25 22:18:45 +03:00
dc1a26cd4e :3 2024-08-25 22:17:46 +03:00
f4d109583b :3 2024-08-25 22:16:08 +03:00
216aaeed0e :3 2024-08-25 22:14:51 +03:00
b1e225d2c4 :3 2024-08-25 22:12:53 +03:00
bb8aceb832 :3 2024-08-25 22:10:22 +03:00
009d9d6071 :3 2024-08-25 22:08:59 +03:00
c31606c5bc :3 2024-08-25 22:06:55 +03:00
a569de2b4f :3 2024-08-25 22:06:02 +03:00
a9e9200e6e :3 2024-08-25 22:04:43 +03:00
4f0509d30e :3 2024-08-25 22:03:37 +03:00
b7ad80b226 We didnt start the fire 2024-08-25 21:30:33 +03:00
68422b9343 Merge branch 'main' of https://git.mcorangehq.xyz/xomf/keypadOS 2024-08-24 12:59:41 -04:00
dcc0a3e687 :3 2024-08-24 12:59:30 -04:00
9b48f2adcb update rom 2024-08-20 22:19:45 -04:00
9d8398565b how did we get here? 2024-08-21 02:17:19 +00:00
7292f271fb nvm XD 2024-08-18 12:12:48 -04:00
0e6fa358a7 testing color 2024-08-18 12:12:00 -04:00
20495931d0 :3 2024-08-18 12:09:22 -04:00
1e170263fd purple :3 2024-08-18 11:59:14 -04:00
7ce154ed44 more error resistance 2024-08-18 11:50:07 -04:00
4c2197919a ;-; 2024-08-18 15:09:33 +03:00
5eb4a9edb7 :3 2024-08-18 15:03:36 +03:00
87fb39b9bd change default code, implement true hashes 2024-08-18 15:02:01 +03:00
12 changed files with 1116 additions and 529 deletions

23
bindings.lua Normal file
View File

@@ -0,0 +1,23 @@
--- @class Exports
--- @field log Logger
--- @field updater Updater
--- @field notifier Notifier
--- @field json JsonParser
--- @class Logger
--- @field error fun(...)
--- @field warn fun(...)
--- @field info fun(...)
--- @field debug fun(...)
--- @class Updater
--- @field addEntry fun(self: Updater, path: string, branch: string, url: string)
--- @class JsonParser
--- @field decode fun(s: string): table
--- @field encode fun(s: table): string
--- @class Notifier
--- @field notify fun(priority: "1"|"2"|"3"|"4"|"5", body: string)

View File

@@ -1,235 +0,0 @@
local __BUNDLER_FILES = {}
local __DEFAULT_IMPORT = require
local require = function(path)
if __BUNDLER_FILES[path] then
return __BUNDLER_FILES[path]()
else
return __DEFAULT_IMPORT(path)
end
end
local KEYPADOS_UPDATE_HASH = "ThZGemtZHshNpaflGvUHcJoZ"rawset(__BUNDLER_FILES, "updater.lua", function ()
local utils = require("utils.lua")
local config = require("config.lua")
local LAST_USED = os.time()
local mod = {}
local function checkForUpdate()
local current_time = os.time()
local difference = math.abs(current_time - LAST_USED)
if difference > .5 then
print("INFO: Updating! (diff: " .. tostring(difference) .. ")")
local update_code_request = http.get(config.release_url .. "?x=" .. tostring(math.random(11111111, 99999999))) -- I HATE CACHE REEEEEEEEEEEEEEEEE
if update_code_request then
local update_code_text = update_code_request.readAll()
if update_code_text then
if string.find(update_code_text, "^local __BUNDLER_FILES = {}") then
if fs.exists("backup.lua") then
fs.delete("backup.lua")
end
fs.copy("startup.lua", "backup.lua")
if not string.find(update_code_text, KEYPADOS_UPDATE_HASH) then
local file = fs.open("startup.lua", "w")
file.write(update_code_text)
file.close()
os.reboot()
else
print("Nothing changed, not updating.")
LAST_USED = os.time()
return
end
else
print("Bad file download (core)")
end
else
print("Bad mem read (core)")
end
else
print("Bad download (core)")
end
end
end
function mod.UpdateChecker()
while true do
checkForUpdate()
sleep(1)
end
end
function mod.GetBasalt()
if fs.exists("basalt.lua") then
utils.MonPrint("Basalt found!")
else
utils.MonPrint("Downloading basalt...")
local basalt_code = http.get(config.release_url).readAll()
utils.MonPrint("Installing basalt...")
local file = fs.open("basalt.lua", "w")
if not file then
utils.MonPrint("failed to get basalt")
sleep(60)
os.reboot()
end
file.write(utils.Cast(basalt_code))
file.close()
utils.MonPrint("Rebooting...")
os.reboot()
end
end
return mod
end)
rawset(__BUNDLER_FILES, "config.lua", function ()
local config = {
correctPin = "42169",
release_url = "https://git.mcorangehq.xyz/xomf/keypadOS/raw/branch/main/keypadOS.lua",
basalt_url = "https://raw.githubusercontent.com/Pyroxenium/Basalt/master/docs/versions/latest.lua",
config_path = "keypadOS.config.lua"
}
local DEFAULT_CONFIG = "return {\n" ..
" group=\"default\",\n" ..
"}\n"
function config.ReadConfig()
if not fs.exists(config.config_path) then
local f = fs.open(config.config_path, "w")
f.write(DEFAULT_CONFIG)
f.close();
end
local cfg = require(config.config_path)
return cfg
end
return config
end)
rawset(__BUNDLER_FILES, "ui.lua", function ()
local utils = require("utils.lua")
local basalt = require("basalt")
local config = require("config.lua")
local updater = require("updater.lua")
local monitor = utils.Cast(peripheral.find("monitor"))
local drive = utils.Cast(peripheral.find("drive"))
local mod = {}
local function resetEverything(ui)
sleep(2)
ui.pin = ""
ui.pinLabel:setText("")
redstone.setOutput("front", false)
ui.enterButton:setBackground(colors.blue)
end
local function unlockDoor(ui)
if drive.isDiskPresent() then
if drive.getDiskLabel() == config.correctPin then
ui.pin = config.correctPin
drive:ejectDisk()
end
end
basalt.debug("test")
if ui.pin == config.correctPin then
ui.enterButton:setBackground(colors.green)
ui.pinLabel:setText("Welcome")
redstone.setOutput("front", true)
if drive.isDiskPresent() then
if drive.getDiskLabel() == nil then
drive.setDiskLabel(config.correctPin)
ui.pinLabel:setText("Crd set")
drive:ejectDisk()
end
end
else
ui.pinLabel:setText("Wrong")
ui.enterButton:setBackground(colors.red)
end
ui.main:addThread():start(function()
resetEverything(ui)
end)
end
local function addToPin(ui, i)
if #ui.pin >= 5 then
return
end
ui.pin = ui.pin .. tostring(i)
ui.pinLabel:setText(ui.pin)
end
function mod.InitUi()
local ui = {
pin = "",
main = basalt.addMonitor(),
}
ui.main:setMonitor(monitor)
ui.pinLabel = ui.main:addLabel()
:setText("")
:setFontSize(1)
ui.enterButton = ui.main:addButton()
:setText(">>>>")
:setBackground(colors.blue)
:setPosition(6,2)
:setSize(1.5,3.2)
:onClick(function()
unlockDoor(ui)
end)
local btnX = 1
local btnY = 2
ui.main:addButton()
:setPosition(1, 5)
:setText("0")
:setSize(6,1)
:onClick(function()
addToPin(ui, 0)
end)
for i = 1, 9 do
ui.main:addButton()
:setPosition(btnX, btnY)
:setText(tostring(i))
:setSize(2,1)
:onClick(function()
addToPin(ui, i)
end)
btnX = btnX + 2
if btnX >= 6 then
btnY = btnY + 1
btnX = 1
end
end
local status, err = pcall(function ()
parallel.waitForAll(basalt.autoUpdate, updater.UpdateChecker)
end)
if not status and err ~= "Terminated" then
print("Error detected: " .. err)
http.post("https://ntfy.sh/keypadOS_alerts", err, {Priority = "urgent"}) --exposed ntfy url no spam me pls
sleep(5)
utils.MonReset(0.5)
fs.delete("basalt.lua")
fs.delete("startup.lua")
fs.copy("backup.lua", "startup.lua")
os.reboot()
end
end
return mod
end)
rawset(__BUNDLER_FILES, "utils.lua", function ()
local utils = {}
function utils.Cast(object)
return object
end
local MONITOR = utils.Cast(peripheral.find("monitor"))
local MONITOR_Y = 1
function utils.MonPrint(text)
MONITOR.setCursorPos(1,MONITOR_Y)
MONITOR.write(text)
MONITOR_Y = MONITOR_Y + 1
end
function utils.MonReset(scale)
MONITOR.clear()
MONITOR.setTextScale(scale)
end
return utils;
end)
rawset(__BUNDLER_FILES, "main.lua", function ()
local utils = require("utils.lua")
local updater = require("updater.lua")
local main = {}
KEYPADOS_VERSION = "4.0"
function main.Main()
utils.MonReset(0.5)
updater.GetBasalt()
utils.MonPrint("keypadOS v" .. KEYPADOS_VERSION)
utils.MonReset(1)
require("ui.lua").InitUi()
end
return main
end)
require("main.lua").Main()

470
rtmc.lua Normal file
View File

@@ -0,0 +1,470 @@
local __BUNDLER_FILES = {}
local __VERSION = "1.0.0d"
local __DEFAULT_IMPORT = require
local require = function(path)
if __BUNDLER_FILES[path] then
return __BUNDLER_FILES[path]()
elseif __BUNDLER_FILES[path .. ".lua"] then
return __BUNDLER_FILES[path .. ".lua"]()
else
return __DEFAULT_IMPORT(path)
end
end
rawset(__BUNDLER_FILES, "main", function ()
local updater = require("updater");
local log = require("log");
local notifier = require("notifier")
local json = require("json")
local MODULE_NAME = "keypadOS";
local MODULE_URL = "https://git.mcorangehq.xyz/xomf/keypadOS/raw/branch/keypad/keypadOS.lua";
if not __VERSION then
__VERSION = "DEV";
end
local function updaterLoop(upd)
while true do
sleep(1)
upd:checkAndUpdateAll();
end
end
local function _start()
log.info("Starting bootloader (" .. __VERSION .. ")");
local upd = updater.new();
upd:addEntry(MODULE_NAME .. ".lua", "keypad", MODULE_URL);
local lib_exports = {
log = log,
updater = upd,
notifier = notifier,
json = json
};
parallel.waitForAny(
function()
updaterLoop(upd)
end,
function ()
require(MODULE_NAME).main(lib_exports);
end
);
end
local mod = {};
mod.main = _start;
return mod;
end)
rawset(__BUNDLER_FILES, "log", function ()
local log = {};
log.__index = log;
function log.error(...)
print("[ERROR] " .. ...);
end
function log.warn(...)
print("[WARN] " .. ...);
end
function log.info(...)
print("[INFO] " .. ...);
end
function log.debug(...)
print("[DEBUG] " .. ...);
end
return log;
end)
rawset(__BUNDLER_FILES, "json", function ()
local json = { _version = "0.1.2" }
local encode
local escape_char_map = {
[ "\\" ] = "\\",
[ "\"" ] = "\"",
[ "\b" ] = "b",
[ "\f" ] = "f",
[ "\n" ] = "n",
[ "\r" ] = "r",
[ "\t" ] = "t",
}
local escape_char_map_inv = { [ "/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
if stack[val] then error("circular reference") end
stack[val] = true
if rawget(val, 1) ~= nil or next(val) == nil then
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(1, 4), 16 )
local n2 = tonumber( s:sub(7, 10), 16 )
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local res = ""
local j = i + 1
local k = j
while j <= #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
elseif x == 92 then -- `\`: Escape
res = res .. str:sub(k, j - 1)
j = j + 1
local c = str:sub(j, j)
if c == "u" then
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
or str:match("^%x%x%x%x", j + 1)
or decode_error(str, j - 1, "invalid unicode escape in string")
res = res .. parse_unicode_escape(hex)
j = j + #hex
else
if not escape_chars[c] then
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
end
res = res .. escape_char_map_inv[c]
end
k = j + 1
elseif x == 34 then -- `"`: End of string
res = res .. str:sub(k, j - 1)
return res, j + 1
end
j = j + 1
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
if str:sub(i, i) == "]" then
i = i + 1
break
end
x, i = parse(str, i)
res[n] = x
n = n + 1
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
if str:sub(i, i) == "}" then
i = i + 1
break
end
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
val, i = parse(str, i)
res[key] = val
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end
return json
end)
rawset(__BUNDLER_FILES, "updater", function ()
local log = require("log");
local json = require("json");
local notifier = require("notifier");
local _;
local updater = {};
updater.__index = updater;
function updater.new()
local c = setmetatable({}, updater);
c.last_check = os.time();
c.threshold = ((20*60)/24/60/60) * 5;
c.api_url = "https://git.mcorangehq.xyz/api/v1/repos/xomf/keypadOS";
c.curr_commit_hash = "";
c.updated_files = {};
c:addEntry("startup.lua", "main", "https://git.mcorangehq.xyz/xomf/keypadOS/raw/branch/main/rtmc.lua");
return c
end
function updater:addEntry(path, branch, url)
self.updated_files[path] = {
branch = branch,
url = url
};
self:update(path, url);
end
function updater:checkAndUpdateAll()
local updated = false;
for path, entry in pairs(self.updated_files) do
if self:check(entry.branch) then
self:update(path, entry.url);
updated = true;
end
end
if updated then
log.info("Rebooting!");
sleep(3);
os.reboot();
end
end
function updater:check(branch)
local curr = os.time();
if math.abs(curr - self.last_check) < self.threshold then
if self.curr_commit_hash ~= "" then
return false;
end
end
self.last_check = curr;
local req, rerr = http.get(self.api_url .. "/commits?sha="..branch);
if not req then
log.error("Updater:check: Could not send request: " .. rerr);
return false;
end
local body, berr = req.readAll();
if not body then
log.error("Updater:check: Could not get body of request: " .. berr);
return false;
end
local data = json.decode(body);
if self.curr_commit_hash == "" then
log.debug("No commit hash found, setting");
self.curr_commit_hash = data[1].sha;
elseif data[1].sha ~= self.curr_commit_hash then
log.debug("Commit hash doesnt match, probbably an update");
return true;
end
if data[1].hash == self.curr_commit_hash then
log.debug("Commit hash matches");
log.debug(data[1].sha .. " (old) => (new) " .. self.curr_commit_hash);
end
write(".");
return false;
end
function updater:update(path, url)
local req, rerr = http.get(url .. "?x=" .. tostring(math.random(0,69420)));
if not req then
log.error("Updater:update: Could not send request for update: " .. rerr);
return;
end
local body, berr = req.readAll();
if not body then
log.error("Updater:update: Could not get file for update: " .. berr);
return;
end
if fs.exists(path .. ".bak") then
fs.delete(path .. ".bak");
end
if fs.exists(path) then
fs.copy(path, path..".bak");
fs.delete(path);
end
local fd = fs.open(path, "w");
fd.write(body);
fd.close();
local notif = "Computer #" .. tostring( os.getComputerID() ) .. " updating";
notifier.notify("5", notif);
log.debug("New update written to disk (" .. path .. ")");
end
return updater;
end)
rawset(__BUNDLER_FILES, "notifier", function ()
local notifier = {};
notifier.__index = notifier;
function notifier.notify(priority, body)
local headers = {
["Priority"] = priority;
};
http.post("https://ntfy.sh/keypadOS_alerts", body, headers);
end
return notifier;
end)
if pcall(debug.getlocal, 4, 1) then
local exports = {};
for k, v in pairs(__BUNDLER_FILES) do
if k ~= "main" then
exports[k] = v();
end
end
return exports;
else
require("main").main()
end

View File

@@ -1,28 +0,0 @@
local config = {
correctPin = "42169",
release_url = "https://git.mcorangehq.xyz/xomf/keypadOS/raw/branch/main/keypadOS.lua",
basalt_url = "https://raw.githubusercontent.com/Pyroxenium/Basalt/master/docs/versions/latest.lua",
ntfy_url = "https://ntfy.sh/keypadOS_alerts",
config_path = "keypadOS.config.lua"
}
local DEFAULT_CONFIG = "return {\n" ..
" group=\"default\",\n" ..
"}\n"
--- @class Config
--- @field group string
--- Read current configs, create if doesnt exist
---@return Config
function config.ReadConfig()
if not fs.exists(config.config_path) then
local f = fs.open(config.config_path, "w")
f.write(DEFAULT_CONFIG)
f.close();
end
local cfg = require(config.config_path)
return cfg
end
return config

389
src/json.lua Normal file
View File

@@ -0,0 +1,389 @@
--
-- json.lua
--
-- Copyright (c) 2020 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--
--- @class JsonParser
local json = { _version = "0.1.2" }
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\",
[ "\"" ] = "\"",
[ "\b" ] = "b",
[ "\f" ] = "f",
[ "\n" ] = "n",
[ "\r" ] = "r",
[ "\t" ] = "t",
}
local escape_char_map_inv = { [ "/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if rawget(val, 1) ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(1, 4), 16 )
local n2 = tonumber( s:sub(7, 10), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local res = ""
local j = i + 1
local k = j
while j <= #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
elseif x == 92 then -- `\`: Escape
res = res .. str:sub(k, j - 1)
j = j + 1
local c = str:sub(j, j)
if c == "u" then
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
or str:match("^%x%x%x%x", j + 1)
or decode_error(str, j - 1, "invalid unicode escape in string")
res = res .. parse_unicode_escape(hex)
j = j + #hex
else
if not escape_chars[c] then
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
end
res = res .. escape_char_map_inv[c]
end
k = j + 1
elseif x == 34 then -- `"`: End of string
res = res .. str:sub(k, j - 1)
return res, j + 1
end
j = j + 1
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end
return json

19
src/log.lua Normal file
View File

@@ -0,0 +1,19 @@
--- @class Logger
local log = {};
log.__index = log;
function log.error(...)
print("[ERROR] " .. ...);
end
function log.warn(...)
print("[WARN] " .. ...);
end
function log.info(...)
print("[INFO] " .. ...);
end
function log.debug(...)
print("[DEBUG] " .. ...);
end
return log;

View File

@@ -1,20 +1,50 @@
-- keycardOS "bootloader", has no access to basalt local updater = require("updater");
-- intended for checking for updates, and automatically updating basalt if it is missing local log = require("log");
local utils = require("utils.lua") local notifier = require("notifier")
local updater = require("updater.lua") local json = require("json")
local main = {}
local MODULE_NAME = "keypadOS";
local MODULE_URL = "https://git.mcorangehq.xyz/xomf/keypadOS/raw/branch/keypad/keypadOS.lua";
KEYPADOS_VERSION = "4.0" if not __VERSION then
__VERSION = "DEV";
function main.Main()
utils.MonReset(0.5)
updater.GetBasalt()
utils.MonPrint("keypadOS v" .. KEYPADOS_VERSION)
utils.MonReset(1)
require("ui.lua").InitUi()
end end
return main
--- @param upd Updater
local function updaterLoop(upd)
while true do
sleep(1)
upd:checkAndUpdateAll();
end
end
local function _start()
log.info("Starting bootloader (" .. __VERSION .. ")");
local upd = updater.new();
upd:addEntry(MODULE_NAME .. ".lua", "keypad", MODULE_URL);
--- @type Exports
local lib_exports = {
log = log,
updater = upd,
notifier = notifier,
json = json
};
parallel.waitForAny(
function()
updaterLoop(upd)
end,
function ()
require(MODULE_NAME).main(lib_exports);
end
);
end
local mod = {};
mod.main = _start;
return mod;

16
src/notifier.lua Normal file
View File

@@ -0,0 +1,16 @@
--- @class Notifier
local notifier = {};
notifier.__index = notifier;
--- @param priority "1"|"2"|"3"|"4"|"5"
--- @param body string
function notifier.notify(priority, body)
local headers = {
["Priority"] = priority;
};
http.post("https://ntfy.sh/keypadOS_alerts", body, headers);
end
return notifier;

View File

@@ -1,135 +0,0 @@
local utils = require("utils.lua")
local basalt = require("basalt")
local config = require("config.lua")
local updater = require("updater.lua")
--- @type Monitor
local monitor = utils.Cast(peripheral.find("monitor"))
--- @type drive
local drive = utils.Cast(peripheral.find("drive"))
local mod = {}
--- @class Ui
--- @field pin string
--- @field main any
--- @field pinLabel any
--- @field enterButton any
--- @field resetEverything function
--- @field unlockDoor function
--- @field addToPin function
--- @param ui Ui
local function resetEverything(ui)
sleep(2)
ui.pin = ""
ui.pinLabel:setText("")
redstone.setOutput("front", false)
ui.enterButton:setBackground(colors.blue)
end
--- @param ui Ui
local function unlockDoor(ui)
if drive.isDiskPresent() then
if drive.getDiskLabel() == config.correctPin then
ui.pin = config.correctPin
drive:ejectDisk()
end
end
basalt.debug("test")
if ui.pin == config.correctPin then
ui.enterButton:setBackground(colors.green)
ui.pinLabel:setText("Welcome")
redstone.setOutput("front", true)
if drive.isDiskPresent() then
if drive.getDiskLabel() == nil then
drive.setDiskLabel(config.correctPin)
ui.pinLabel:setText("Crd set")
drive:ejectDisk()
end
end
else
ui.pinLabel:setText("Wrong")
ui.enterButton:setBackground(colors.red)
end
ui.main:addThread():start(function()
resetEverything(ui)
end)
end
--- @param ui Ui
local function addToPin(ui, i)
if #ui.pin >= 5 then
return
end
ui.pin = ui.pin .. tostring(i)
ui.pinLabel:setText(ui.pin)
end
function mod.InitUi()
local ui = {
pin = "",
main = basalt.addMonitor(),
}
ui.main:setMonitor(monitor)
ui.pinLabel = ui.main:addLabel()
:setText("")
:setFontSize(1)
ui.enterButton = ui.main:addButton()
:setText(">>>>")
:setBackground(colors.blue)
:setPosition(6,2)
:setSize(1.5,3.2)
:onClick(function()
unlockDoor(ui)
end)
local btnX = 1
local btnY = 2
ui.main:addButton()
:setPosition(1, 5)
:setText("0")
:setSize(6,1)
:onClick(function()
addToPin(ui, 0)
end)
for i = 1, 9 do
ui.main:addButton()
:setPosition(btnX, btnY)
:setText(tostring(i))
:setSize(2,1)
:onClick(function()
addToPin(ui, i)
end)
btnX = btnX + 2
if btnX >= 6 then
btnY = btnY + 1
btnX = 1
end
end
local status, err = pcall(function ()
parallel.waitForAll(basalt.autoUpdate, updater.UpdateChecker)
end)
if not status and err ~= "Terminated" then
print("Error detected: " .. err)
http.post(config.ntfy_url, err, {Priority = "urgent"}) --exposed ntfy url no spam me pls
sleep(5)
utils.MonReset(0.5)
fs.delete("basalt.lua")
fs.delete("startup.lua")
fs.copy("backup.lua", "startup.lua")
os.reboot()
end
end
return mod

View File

@@ -1,83 +1,121 @@
local utils = require("utils.lua") local log = require("log");
local config = require("config.lua") local json = require("json");
local LAST_USED = os.time() local notifier = require("notifier");
local mod = {} --- @class UpdaterEntry
--- @field url string
--- @field branch string
local _;
-- For lsp, shouldnt ever be check for true --- @class Updater
if not __UPDATE_HASH then --- @field last_check integer
__UPDATE_HASH = "WHATTHEFUCK"; --- @field threshold integer in hours (mc time)
--- @field api_url string
--- @field curr_commit_hash string
--- @field updated_files table<string, UpdaterEntry> {path: {url, branch}}
local updater = {};
updater.__index = updater;
function updater.new()
local c = setmetatable({}, updater);
c.last_check = os.time();
c.threshold = ((20*60)/24/60/60) * 5;
c.api_url = "https://git.mcorangehq.xyz/api/v1/repos/xomf/keypadOS";
c.curr_commit_hash = "";
c.updated_files = {};
c:addEntry("startup.lua", "main", "https://git.mcorangehq.xyz/xomf/keypadOS/raw/branch/main/rtmc.lua");
return c
end end
local function checkForUpdate() function updater:addEntry(path, branch, url)
local current_time = os.time() self.updated_files[path] = {
local difference = math.abs(current_time - LAST_USED) branch = branch,
-- print("INFO: Checking for update... difference: " .. tostring(difference)) url = url
--its been considerable time since the keypad was interacted with };
--therefore it's time to force an update down the users throat (microsoft moment) self:update(path, url);
if difference > .5 then
print("INFO: Updating! (diff: " .. tostring(difference) .. ")")
local update_code_request = http.get(config.release_url .. "?x=" .. tostring(math.random(11111111, 99999999))) -- I HATE CACHE REEEEEEEEEEEEEEEEE
if update_code_request then
local update_code_text = update_code_request.readAll()
if update_code_text then
if string.find(update_code_text, "^local __BUNDLER_FILES = {}") then
-- Make backup
if fs.exists("backup.lua") then
fs.delete("backup.lua")
end end
fs.copy("startup.lua", "backup.lua")
if not string.find(update_code_text, __UPDATE_HASH) then function updater:checkAndUpdateAll()
local file = fs.open("startup.lua", "w") local updated = false;
file.write(update_code_text) for path, entry in pairs(self.updated_files) do
file.close() if self:check(entry.branch) then
os.reboot() self:update(path, entry.url);
else updated = true;
print("Nothing changed, not updating.")
LAST_USED = os.time()
return
end end
else
print("Bad file download (core)")
end
else
print("Bad mem read (core)")
end
else
print("Bad download (core)")
end end
if updated then
log.info("Rebooting!");
sleep(3);
os.reboot();
end end
end end
function mod.UpdateChecker() function updater:check(branch)
while true do local curr = os.time();
checkForUpdate() if math.abs(curr - self.last_check) < self.threshold then
sleep(1) if self.curr_commit_hash ~= "" then
return false;
end end
end end
self.last_check = curr;
function mod.GetBasalt() local req, rerr = http.get(self.api_url .. "/commits?sha="..branch);
if fs.exists("basalt.lua") then if not req then
utils.MonPrint("Basalt found!") log.error("Updater:check: Could not send request: " .. rerr);
else return false;
utils.MonPrint("Downloading basalt...")
local basalt_code = http.get(config.release_url).readAll()
utils.MonPrint("Installing basalt...")
local file = fs.open("basalt.lua", "w")
if not file then
utils.MonPrint("failed to get basalt")
sleep(60)
os.reboot()
end end
file.write(utils.Cast(basalt_code)) local body, berr = req.readAll();
file.close() if not body then
log.error("Updater:check: Could not get body of request: " .. berr);
utils.MonPrint("Rebooting...") return false;
os.reboot()
end end
local data = json.decode(body);
if self.curr_commit_hash == "" then
log.debug("No commit hash found, setting");
self.curr_commit_hash = data[1].sha;
elseif data[1].sha ~= self.curr_commit_hash then
log.debug("Commit hash doesnt match, probbably an update");
return true;
end end
return mod if data[1].hash == self.curr_commit_hash then
log.debug("Commit hash matches");
log.debug(data[1].sha .. " (old) => (new) " .. self.curr_commit_hash);
end
write(".");
return false;
end
function updater:update(path, url)
local req, rerr = http.get(url .. "?x=" .. tostring(math.random(0,69420)));
if not req then
log.error("Updater:update: Could not send request for update: " .. rerr);
return;
end
local body, berr = req.readAll();
if not body then
log.error("Updater:update: Could not get file for update: " .. berr);
return;
end
if fs.exists(path .. ".bak") then
fs.delete(path .. ".bak");
end
if fs.exists(path) then
fs.copy(path, path..".bak");
fs.delete(path);
end
local fd = fs.open(path, "w");
fd.write(body);
fd.close();
local notif = "Computer #" .. tostring( os.getComputerID() ) .. " updating";
notifier.notify("5", notif);
log.debug("New update written to disk (" .. path .. ")");
end
return updater;

View File

@@ -1,26 +0,0 @@
local utils = {}
-- Type coersion for lsp
---@generic T
---@param object any
---@return T
function utils.Cast(object)
return object
end
--- @type Monitor
local MONITOR = utils.Cast(peripheral.find("monitor"))
local MONITOR_Y = 1
function utils.MonPrint(text)
MONITOR.setCursorPos(1,MONITOR_Y)
MONITOR.write(text)
MONITOR_Y = MONITOR_Y + 1
end
function utils.MonReset(scale)
MONITOR.clear()
MONITOR.setTextScale(scale)
end
return utils;

70
x.py
View File

@@ -1,22 +1,20 @@
#!/usr/bin/python #!/usr/bin/python
import string OUTPUT="rtmc.lua";
import random
UPDATE_ID= ''.join(random.choices(string.ascii_letters, k=24))
OUTPUT="keypadOS.lua";
FILES= [ FILES= [
"updater.lua",
"config.lua",
"ui.lua",
"utils.lua",
"main.lua", "main.lua",
"log.lua",
"json.lua",
"updater.lua",
"notifier.lua"
] ]
VERSION="1.0.0d"
MINIMISE=True MINIMISE=True
def read_file(p: str) -> str: def read_file(p: str) -> str:
buf = ""; buf = "";
with open("src/"+p, "r", encoding="utf-8") as f: with open("src/"+p, "r", encoding="utf-8") as f:
buf += f"rawset(__BUNDLER_FILES, \"{p}\", function ()\n"; buf += f"rawset(__BUNDLER_FILES, \"{p.replace(".lua", "")}\", function ()\n";
for line in f.readlines(): for line in f.readlines():
buf += " " + line; buf += " " + line;
buf += "\nend)"; buf += "\nend)";
@@ -36,29 +34,57 @@ def minimise(buf: str) -> str:
newbuf += line + "\n"; newbuf += line + "\n";
return newbuf; return newbuf;
# def get_hash(buf: str) -> str:
# hasher = hashlib.sha1();
#
# hasher.update(bytes(buf, "utf-8"));
# return hasher.hexdigest();
REQ_HEADER=f"""local __BUNDLER_FILES = {{}}
local __VERSION = "{VERSION}"
local __DEFAULT_IMPORT = require
local require = function(path)
if __BUNDLER_FILES[path] then
return __BUNDLER_FILES[path]()
elseif __BUNDLER_FILES[path .. ".lua"] then
return __BUNDLER_FILES[path .. ".lua"]()
else
return __DEFAULT_IMPORT(path)
end
end
"""
REQ_FOOTER="""
if pcall(debug.getlocal, 4, 1) then
local exports = {};
for k, v in pairs(__BUNDLER_FILES) do
if k ~= "main" then
exports[k] = v();
end
end
return exports;
else
require("main").main()
end
"""
def main(): def main():
buf = "" buf = ""
buf += "local __UPDATE_HASH = \"" + {UPDATE_ID} + "\""; buf += REQ_HEADER;
buf += "local __BUNDLER_FILES = {}\n"; # buf += "local __UPDATE_HASH = __BUNDLER_REPLACE_HASH__\n";
buf += "local __DEFAULT_IMPORT = require\n";
buf += "local require = function(path)\n";
buf += " if __BUNDLER_FILES[path] then\n";
buf += " return __BUNDLER_FILES[path]()\n";
buf += " else\n";
buf += " return __DEFAULT_IMPORT(path)\n";
buf += " end\n";
buf += "end\n";
for file in FILES: for file in FILES:
print(f"=== FILE: {file}"); print(f"=== FILE: {file}");
buf += read_file(file); buf += read_file(file);
print(f"=== UPDATE HASH: {UPDATE_ID}")
buf += "require(\"main.lua\").Main()\n"; buf += REQ_FOOTER;
if MINIMISE: if MINIMISE:
buf = minimise(buf); buf = minimise(buf);
#update_hash = get_hash(buf);
# buf = buf.replace("__BUNDLER_REPLACE_HASH__", f"\"{update_hash}\"");
#print(f"=== UPDATE HASH: {update_hash}")
with open(OUTPUT, "w", encoding="utf-8") as f: with open(OUTPUT, "w", encoding="utf-8") as f:
f.write(buf); f.write(buf);
print("DONE"); print("DONE");