finished http implementation working on spm and liveboot env

This commit is contained in:
2026-07-23 14:33:16 -04:00
parent f7b64c11b7
commit 49c9e5e37c
130 changed files with 1128 additions and 635 deletions
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Astronand
+7
View File
@@ -0,0 +1,7 @@
local http=require("http")
local ok,err = xpcall(http.get, debug.traceback, "http://localhost:8000/spm.json")
if not ok then
print(err)
else
print(err.body)
end
+47 -66
View File
@@ -26,8 +26,7 @@ local function parseUrl(url)
end
local function buildRequest(req)
local host, path =
parseUrl(req.url)
local host, path = parseUrl(req.url)
if not host then
return nil, path
@@ -75,73 +74,39 @@ local function buildRequest(req)
return table.concat(out, "\r\n")
end
local function parseResponse(raw)
local headerEnd =
raw:find(
"\r\n\r\n",
1,
true
)
if not headerEnd then
return nil, "EBADMSG"
local function parseResponse(response)
local headerBlock, body = response:match("^(.-)\r\n\r\n(.*)$")
if not headerBlock then
return nil, "invalid HTTP response"
end
local headerPart =
raw:sub(1, headerEnd - 1)
local body =
raw:sub(headerEnd + 4)
local lines = {}
for line in headerPart:gmatch("[^\r\n]+") do
lines[#lines + 1] = line
end
local _, code, msg =
lines[1]:match(
"^(HTTP/%S+)%s+(%d+)%s*(.*)$"
)
local headers = {}
for i = 2, #lines do
local k, v =
lines[i]:match(
"^([^:]+):%s*(.*)$"
)
-- Parse status line
local statusLine, rest = headerBlock:match("^(.-)\r\n(.*)$")
if not statusLine then
statusLine = headerBlock
rest = ""
end
if k then
headers[k:lower()] = v
local version, code, message = statusLine:match("^(HTTP/%d+%.%d+)%s+(%d+)%s+(.*)$")
-- Parse headers
for line in (rest .. "\r\n"):gmatch("(.-)\r\n") do
local key, value = line:match("^([^:]+):%s*(.*)$")
if key then
headers[key:lower()] = value
end
end
return {
code = tonumber(code),
message = msg,
headers = headers,
body = body
code=tonumber(code),
message=message,
headers=headers,
body=body
}
end
local function readAll(fd)
local out = ""
while true do
local chunk =
syscall.read(fd, 4096)
if not chunk or chunk == "" then
break
end
out = out .. chunk
end
return out
end
function http.request(req)
local raw, err =
buildRequest(req)
@@ -151,34 +116,50 @@ function http.request(req)
end
local fd =
syscall.socket(0, 0)
syscall.socket()
if not fd then
return nil, "ESOCKET"
end
local ok, err =
syscall.connect(fd, req.url)
local host, path = parseUrl(req.url)
if not host then
syscall.close(fd)
return nil, path
end
local proto = req.url:match("^(https?://)")
local base = proto .. host
local ok, err = syscall.connect(fd, base)
if not ok then
syscall.close(fd)
return nil, err
end
local ok2, err2 =
syscall.write(fd, raw)
local ok2, err2 = syscall.write(fd, raw)
if not ok2 then
syscall.close(fd)
return nil, err2
end
local resp =
readAll(fd)
local out = {}
while true do
local chunk = syscall.read(fd, 4096)
if chunk == "" then
break
end
out[#out+1] = chunk
end
syscall.close(fd)
return parseResponse(resp)
return parseResponse(table.concat(out))
end
function http.get(url, headers)
+1
View File
@@ -0,0 +1 @@
Core library for Hyperion
+1
View File
@@ -0,0 +1 @@
1.0.0
+3
View File
@@ -0,0 +1,3 @@
Hyperion
Astronand
GHXX (Advanced Computers)
+1
View File
@@ -0,0 +1 @@
firmware for Advanced Computers
+1
View File
@@ -0,0 +1 @@
1.0.0
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Astronand
@@ -0,0 +1 @@
firmware for CraftOS PC
+1
View File
@@ -0,0 +1 @@
1.0.0
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Astronand
@@ -9,25 +9,15 @@ kernel.cct.httpqueue = kernel.cct.httpqueue or {}
kernel.cct.httpresponse = kernel.cct.httpresponse or {}
kernel.cct.httperror = kernel.cct.httperror or {}
local function parseRawRequest(raw)
local sepLen = 4
local headerEnd =
raw:find("\r\n\r\n", 1, true)
if not headerEnd then
headerEnd =
raw:find("\n\n", 1, true)
sepLen = 2
end
local function parseRawRequest(raw, address)
local headerEnd = raw:find("\r\n\r\n", 1, true)
local headerPart
local body = ""
if headerEnd then
headerPart = raw:sub(1, headerEnd - 1)
body = raw:sub(headerEnd + sepLen)
body = raw:sub(headerEnd + 4)
else
headerPart = raw
end
@@ -42,37 +32,25 @@ local function parseRawRequest(raw)
return nil, "EINVAL"
end
local method, path =
lines[1]:match("^(%S+)%s+(%S+)")
if not method or not path then
return nil, "EBADMSG"
local split=string.split(lines[1], " ")
if #split~=3 then
return nil, "EINVAL"
end
local method, path, ver = table.unpack(split)
local headers = {}
for i = 2, #lines do
local k, v =
lines[i]:match("^([^:]+):%s*(.*)$")
local k, v = lines[i]:match("^([^:]+):%s*(.*)$")
if k then
headers[k] = v
end
end
local host = headers.Host or headers.host
if not host and not path:match("^https?://") then
return nil, "EHOSTUNREACH"
end
local url
if path:match("^https?://") then
url = path
else
url = "http://" .. host .. path
if path:sub(1,1)~="/" then
return nil, "EINVAL"
end
local url = address..path
local req = {
url = url,
@@ -84,7 +62,7 @@ local function parseRawRequest(raw)
req.body = body
end
return req
return req, url
end
local function buildResponse(resp)
@@ -93,37 +71,19 @@ local function buildResponse(resp)
end
local code, msg = resp.getResponseCode()
local headers =
resp:getResponseHeaders()
local body =
resp:readAll() or ""
local headers = resp.getResponseHeaders()
local body = resp.readAll() or ""
resp.close()
local out = {
"HTTP/1.1 " ..
tostring(code) ..
" " ..
tostring(msg)
tostring(msg or "")
}
local hasLength = false
for k, v in pairs(headers or {}) do
if k:lower() == "content-length" then
hasLength = true
end
out[#out + 1] =
tostring(k) ..
": " ..
tostring(v)
end
if not hasLength then
out[#out + 1] =
"Content-Length: " ..
tostring(#body)
for i,v in pairs(headers) do
out[#out+1] = i..": "..v
end
out[#out + 1] = ""
@@ -139,155 +99,91 @@ function handler.connect(fd, address)
return nil, "EBADF"
end
fdo.socket.rbuf = ""
fdo.socket.closed = false
fdo.socket.httpid = nil
fdo.handle.write = function(raw)
local req, err =
parseRawRequest(raw)
local req, url = parseRawRequest(raw, address)
if not req then
return nil, err
return nil, url
end
local id =
tostring(kernel.uuid())
fdo.socket.httpid = id
kernel.cct.httpqueue[id] = true
local ok, err =
http.request(req, id)
kernel.cct.httpqueue[url] = true
local ok, err = http.request(req)
if not ok then
kernel.cct.httpqueue[id] = nil
kernel.cct.httpqueue[url] = nil
return nil, err
end
if fdo.socket.active then
fdo.socket.content=nil
fdo.socket.file.close()
fdo.socket.file=nil
end
fdo.socket.active=true
fdo.socket.url=url
return true
end
fdo.handle.read = function(count)
count = count or 4096
local sock = fdo.socket
if #sock.rbuf > 0 then
local out =
sock.rbuf:sub(1, count)
sock.rbuf =
sock.rbuf:sub(count + 1)
return out
if not fdo.socket.active then
return
end
local id = sock.httpid
if not id then
return ""
if not kernel.cct.httpqueue[fdo.socket.url] then
fdo.socket.file=kernel.sfile(buildResponse(kernel.cct.httpresponse[fdo.socket.url]))
kernel.cct.httpqueue[fdo.socket.url]=nil
kernel.cct.httpresponse[fdo.socket.url]=nil
end
local function finish(resp)
sock.rbuf =
buildResponse(resp) or ""
local out =
sock.rbuf:sub(1, count)
sock.rbuf =
sock.rbuf:sub(count + 1)
return out
end
if kernel.cct.httpresponse[id] then
local resp =
kernel.cct.httpresponse[id]
kernel.cct.httpresponse[id] = nil
kernel.cct.httpqueue[id] = nil
return finish(resp)
end
if kernel.cct.httperror[id] then
local err =
kernel.cct.httperror[id]
kernel.cct.httperror[id] = nil
kernel.cct.httpqueue[id] = nil
return nil, err
end
kernel.currentTask.status = "D"
local coro
coro = function()
if kernel.cct.httpresponse[id] then
local resp =
kernel.cct.httpresponse[id]
kernel.cct.httpresponse[id] = nil
kernel.cct.httpqueue[id] = nil
kernel.asyncReturn(
finish(resp)
)
return
end
if kernel.cct.httperror[id] then
local err =
kernel.cct.httperror[id]
kernel.cct.httperror[id] = nil
kernel.cct.httpqueue[id] = nil
kernel.asyncReturn(
nil,
err
)
return
end
coroutine.yield()
end
kernel.currentTask.ksh =
coroutine.create(function()
local ok, err =
xpcall(
coro,
debug.traceback
)
if not ok then
kernel.asyncReturn(
nil,
err
)
if not fdo.socket.file then
kernel.currentTask.io=function()
if not kernel.cct.httpqueue[fdo.socket.url] then
fdo.socket.file=kernel.sfile(buildResponse(kernel.cct.httpresponse[fdo.socket.url]))
kernel.cct.httpqueue[fdo.socket.url]=nil
kernel.cct.httpresponse[fdo.socket.url]=nil
return fdo.socket.file.read(count)
end
end)
end
kernel.currentTask.status="D"
return
else
return fdo.socket.file.read(count)
end
end
fdo.handle.seek = function(whence, offset)
if not fdo.socket.active then
return
end
if not kernel.cct.httpqueue[fdo.socket.url] then
fdo.socket.file=kernel.sfile(buildResponse(kernel.cct.httpresponse[fdo.socket.url]))
kernel.cct.httpqueue[fdo.socket.url]=nil
kernel.cct.httpresponse[fdo.socket.url]=nil
end
if not fdo.socket.file then
kernel.currentTask.io=function()
if not kernel.cct.httpqueue[fdo.socket.url] then
fdo.socket.file=kernel.sfile(buildResponse(kernel.cct.httpresponse[fdo.socket.url]))
kernel.cct.httpqueue[fdo.socket.url]=nil
kernel.cct.httpresponse[fdo.socket.url]=nil
return fdo.socket.file.seek(whence, offset)
end
end
kernel.currentTask.status="D"
return
else
return fdo.socket.file.seek(whence, offset)
end
end
fdo.handle.close = function()
fdo.socket.closed = true
local id =
fdo.socket.httpid
if id then
kernel.cct.httpqueue[id] = nil
kernel.cct.httpresponse[id] = nil
kernel.cct.httperror[id] = nil
end
fdo.socket.file.close()
fdo.socket.active=false
kernel.cct.httpqueue[fdo.socket.url] = nil
kernel.cct.httpresponse[fdo.socket.url] = nil
kernel.cct.httperror[fdo.socket.url] = nil
return true
end
@@ -74,11 +74,16 @@ kernel.processes.cctdeamon = function()
kernel.cct.fifo.push("T")
end
elseif eventType == "http_success" then
kernel.cct.httpqueue[event[2]]=nil
kernel.cct.httpresponse[event[2]]=event[3]
if kernel.cct.httpqueue[event[2]] then
kernel.cct.httpqueue[event[2]]=nil
kernel.cct.httpresponse[event[2]]=event[3]
end
elseif eventType == "http_failure" then
kernel.cct.httpqueue[event[2]]=nil
kernel.cct.httperror[event[2]]=event[3]
if kernel.cct.httpqueue[event[2]] then
kernel.cct.httpqueue[event[2]]=nil
kernel.cct.httperror[event[2]]=event[3]
kernel.cct.httpresponse[event[2]]=event[4]
end
end
timeout = false
@@ -0,0 +1 @@
firmware for ComputerCraft Tweaked
+1
View File
@@ -0,0 +1 @@
1.3.0
+1
View File
@@ -0,0 +1 @@
Hyperion
+1
View File
@@ -0,0 +1 @@
firmware for Opencomputers
+1
View File
@@ -0,0 +1 @@
0.1.0
+3
View File
@@ -0,0 +1,3 @@
Hyperion
Astronand
Spsf
+96
View File
@@ -193,6 +193,100 @@ function kernel.newUUID()
return uuid
end
function kernel.sfile(str, writeable)
local buf = {str or ""}
local pos = 1
local closed = false
local function content()
return table.concat(buf)
end
local function set_content(s)
buf = {s}
end
local function flush()
str=content()
end
local file = {}
function file.read(n)
assert(not closed, "file is closed")
local s = content()
local len = #s
if not n then
local out = s:sub(pos)
pos = len + 1
return out
end
local out = s:sub(pos, pos + n - 1)
pos = pos + #out
return out
end
if writeable then
function file.write(data)
assert(not closed, "file is closed")
local s = content()
local before = s:sub(1, pos - 1)
local after = s:sub(pos + #data)
set_content(before .. data .. after)
pos = pos + #data
return true
end
end
function file.seek(whence, offset)
assert(not closed, "file is closed")
local s = content()
local len = #s
whence = whence or "cur"
offset = offset or 0
if whence == "set" then
pos = offset + 1
elseif whence == "cur" then
pos = pos + offset
elseif whence == "end" then
pos = len + offset + 1
else
error("invalid whence")
end
if pos < 1 then pos = 1 end
if pos > len + 1 then pos = len + 1 end
return pos - 1
end
function file.close()
assert(not closed, "file is closed")
flush()
closed = true
end
function file.flush()
assert(not closed, "file is closed")
flush()
end
function file.content()
return str
end
return file
end
kernel.syscalls={}
local modules={[0]={}}
for i=0, 100 do
@@ -297,6 +391,7 @@ kernel.syscalls["halt"]=function()
end
end
kernel.saveLog()
kernel.log("Running modules")
for _,p in ipairs(modules) do
for _,v in ipairs(p) do
@@ -310,6 +405,7 @@ for _,p in ipairs(modules) do
local status, err = xpcall(func,debug.traceback, kernel)
if not status then kernel.panic("ModuRunErr: "..tostring(err)) end
if kernel.config.showModLoad then kernel.log("Loaded module "..v, "DBUG", 0x00FFFF) end
if kernel.config.moreLogsaves then kernel.saveLog() end
end
end
@@ -9,55 +9,39 @@ kernel.reqcache = cache
local function require(module, ...)
if cache[module] then return cache[module].ret end
kernel.currentTask.status = "D"
local args = {...}
kernel.currentTask.ksh = coroutine.create(function()
local coro = function()
for _, path in ipairs(kernel.searchpaths) do
local filepath = path:gsub("?", module)
if kernel.vfs.exists(filepath) then
if kernel.vfs.type(filepath) == "directory" then
filepath = filepath .. "/init.lua"
if kernel.vfs.type(filepath) == "directory" then
kernel.asyncReturn(false, "Module not found: "..module)
return
end
end
local fd = kernel.vfs.open(filepath, "r")
local chunks = {}
while true do
local chunk = kernel.vfs.read(fd, 4096)
if not chunk or chunk == "" then break end
chunks[#chunks + 1] = chunk
coroutine.yield()
end
kernel.vfs.close(fd)
local data = table.concat(chunks)
local func, err = load(data, "@"..module, "t", kernel._U)
if not func then
kernel.asyncReturn(false, "Error loading module "..module..": "..tostring(err))
return
end
local ok, ret = xpcall(func, debug.traceback, table.unpack(args))
if not ok then
kernel.asyncReturn(false, "Error running module "..module..": "..tostring(ret))
return
end
cache[module] = {ret = ret, expires = kernel.EFI:getEpochMs() + 120000}
kernel.asyncReturn(true, ret)
return
for _, path in ipairs(kernel.searchpaths) do
local filepath = path:gsub("?", module)
if kernel.vfs.exists(filepath) then
if kernel.vfs.type(filepath) == "directory" then
filepath = filepath .. "/init.lua"
if kernel.vfs.type(filepath) == "directory" then
error("Module not found: "..module)
end
coroutine.yield()
end
kernel.asyncReturn(false, "Module not found: "..module)
local fd = kernel.vfs.open(filepath, "r")
local chunks = {}
while true do
local chunk = kernel.vfs.read(fd, 4096)
if not chunk or chunk == "" then break end
chunks[#chunks + 1] = chunk
end
kernel.vfs.close(fd)
local data = table.concat(chunks)
local func, err = load(data, "@"..module, "t", kernel._U)
if not func then
error("Error loading module "..module..": "..tostring(err))
end
local ok, ret = xpcall(func, debug.traceback, table.unpack(args))
if not ok then
error("Error running module "..module..": "..tostring(ret))
end
cache[module] = {ret = ret, expires = kernel.EFI:getEpochMs() + 120000}
return ret
end
local status, err = xpcall(coro, debug.traceback)
if not status then
kernel.asyncReturn(false, "Error in require coroutine for module "..module..": "..tostring(err))
end
end)
coroutine.yield()
end
error("Module not found: "..module)
end
kernel.syscalls["require"] = require
_G.require = function(...) return syscall.require(...) end
_G.require = function(...) return require(...) end
@@ -217,7 +217,7 @@ data[".meta"]=strFile(buildMeta({
log="/var/log/syslog.log"
}))
if kernel.EFI:getEEPROM() then
if kernel.EFI.getEEPROM then
function data.eeprom(op, mode)
if op=="type" then
return "character device"
@@ -255,7 +255,7 @@ if kernel.EFI:getEEPROM() then
end
end
if kernel.EFI:getNvram() then
if kernel.EFI.getNvram then
function data.nvram(op, mode)
if op=="type" then
return "character device"
@@ -22,7 +22,7 @@ local function getHandler(address)
end
end
function socket.socket(domain, socktype)
function socket.socket()
local fd = kernel.vfs.newfd({
handle = {},
@@ -54,7 +54,7 @@ function socket.socket(domain, socktype)
local fdo = kernel.currentTask.fd[fd]
fdo.handle.read = function()
return ""
return nil, "ENOTCONN"
end
fdo.handle.write = function()
@@ -396,22 +396,16 @@ function kernel.main()
end
if task.status == "D" then
if task.ksh then
coroutine.resume(task.ksh)
if coroutine.status(task.ksh) == "dead" then
task.ksh = nil
if task.status == "D" then
task.status = "R"
end
if kernel.config.debugSyscalls then
kernel.log("Task " .. task.pid .. " IO wait completed", "DBUG", 0x00FFFF)
local sysret = task.syscallReturn
for i = 2, #sysret do
local v = type(sysret[i]) == "table" and table.serialize(sysret[i]) or tostring(sysret[i])
kernel.log(" retval[" .. (i-1) .. "] = " .. v, "DBUG", 0x00FFFF)
end
end
if task.io then
local ret = {xpcall(task.io, debug.traceback)}
if not ret[1] then
task.syscallReturn = {false, table.unpack(ret, 2)}
task.status = "R"
task.io=nil
elseif #ret>1 then
task.syscallReturn = {true, table.unpack(ret, 2)}
task.status = "R"
task.io=nil
end
end
end
@@ -25,6 +25,7 @@ kernel.tasks["1"] = {
else
kernel.panic("Init system exited: " .. tostring(err))
end
kernel.saveLog()
end),
name = "sysinit",
@@ -1,10 +0,0 @@
--:Minify:--
local kernel = ...
if not kernel.vfs.exists("/root") then kernel.vfs.mkdir("/root") end
kernel.processes.login = function()
local ok, err = pcall(kernel.hpv.execspawn, "/bin/login", "login")
if not ok then
kernel.log("Failed to exec /bin/login: " .. tostring(err), "ERROR", 0xFF0000)
end
end
+1
View File
@@ -0,0 +1 @@
Hyperion kernel package
+1
View File
@@ -0,0 +1 @@
1.5.0
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Lua
+1
View File
@@ -0,0 +1 @@
Bit32 package for Hyperion
+1
View File
@@ -0,0 +1 @@
1.0.0
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Astronand
+1
View File
@@ -0,0 +1 @@
blake2s package for Hyperion
+1
View File
@@ -0,0 +1 @@
1.0.0
+3
View File
@@ -0,0 +1,3 @@
Hyperion
Astronand
Spsf
@@ -0,0 +1,169 @@
--:Minify:--
syscall.open("/dev/input/keyboard1", "r") --stdin (fd 0)
syscall.open("/dev/tty/1", "w") --stdout (fd 1)
syscall.open("/dev/null", "w") --stderr (fd 2)
local MAX_ATTEMPTS = 3
local function readLine(mask)
local input = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then
syscall.write(1, "\n")
return input
elseif ch == "\b" then
if #input > 0 then
input = input:sub(1, -2)
syscall.write(1, "\b \b")
end
else
input = input .. ch
syscall.write(1, mask or ch)
end
end
end
local function firstBoot()
local shadow = ""
local _fd, _fderr = pcall(function()
local fd = syscall.open("/etc/shadow", "r")
shadow = syscall.read(fd, 65535) or ""
syscall.close(fd)
end)
if shadow:match("%S") then return end
syscall.devctl(1, "clear")
syscall.devctl(1, "spos", 1, 1)
syscall.devctl(1, "sfgc", 0x00FF00)
syscall.write(1, "HyperionOS First Boot Setup\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
syscall.write(1, "No root password is set. Please create one now.\n\n")
while true do
syscall.write(1, "New root password: ")
local pw1 = readLine("*")
syscall.write(1, "Confirm password: ")
local pw2 = readLine("*")
if pw1 ~= pw2 then
syscall.devctl(1, "sfgc", 0xFF0000)
syscall.write(1, "Passwords do not match. Try again.\n\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
elseif #pw1 < 6 then
syscall.devctl(1, "sfgc", 0xFF0000)
syscall.write(1, "Password too short (minimum 6 characters).\n\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
else
local ok, err = syscall.setpassword(0, pw1)
if ok then
syscall.devctl(1, "sfgc", 0x00FF00)
syscall.write(1, "Root password set.\n\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
sleep(0.5)
break
else
syscall.devctl(1, "sfgc", 0xFF0000)
syscall.write(1, "Error: " .. tostring(err) .. "\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
end
end
end
end
local function spawnShell(username, uid, shell, homedir)
local existsOk, existsErr = pcall(syscall.exists, shell)
if not existsOk or not existsErr then
syscall.write(1, "login: shell not found: " .. shell .. "\n")
sleep(2)
return false
end
local accessOk, accessErr = pcall(syscall.access, shell, "rx")
syscall.setEnviron("HOME", homedir)
syscall.setEnviron("USER", username)
syscall.setEnviron("SHELL", shell)
syscall.setEnviron("PATH", "/bin/")
local setuidOk, setuidErr = pcall(syscall.setuid, uid)
if not setuidOk then
syscall.write(1, "login: setuid failed: " .. tostring(setuidErr) .. "\n")
sleep(2)
return false
end
local chdirOk, chdirErr = pcall(syscall.chdir, homedir)
if not chdirOk then
pcall(syscall.chdir, "/")
end
local ok, err = pcall(syscall.execspawn, shell, username .. ":shell")
if not ok then
syscall.write(1, "login: failed to launch shell: " .. tostring(err) .. "\n")
sleep(2)
return false
end
syscall.exit(0)
end
local function doLogin()
syscall.devctl(1, "clear")
syscall.devctl(1, "sfgc", 0xFFFFFF)
syscall.devctl(1, "sbgc", 0x000000)
syscall.devctl(1, "spos", 1, 1)
local hostname = syscall.getHostname() or "hyperion"
syscall.write(1, "HyperionOS\n")
syscall.write(1, hostname .. " login\n\n")
local attempts = 0
while attempts < MAX_ATTEMPTS do
syscall.devctl(1, "sfgc", 0xFFFFFF)
syscall.write(1, "Username: ")
local username = readLine(nil)
if username ~= "" then
syscall.write(1, "Password: ")
local password = readLine("*")
local uid = syscall.getuidbyname(username)
local ok, err = syscall.login(uid, password)
if ok then
local uid = syscall.getuid()
local pwent = syscall.getpasswd(uid)
local shell = (pwent and pwent.shell) or "/bin/hysh"
local homedir = (pwent and pwent.homedir) or "/"
syscall.devctl(1, "sfgc", 0x00FF00)
syscall.write(1, "\nWelcome, " .. username .. "!\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
sleep(0.3)
spawnShell(username, uid, shell, homedir)
return -- back to login prompt
else
attempts = attempts + 1
sleep(1)
syscall.devctl(1, "sfgc", 0xFF0000)
syscall.write(1, "Login incorrect.\n\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
end
end
end
syscall.devctl(1, "sfgc", 0xFF0000)
syscall.write(1, "Maximum login attempts exceeded.\n")
syscall.devctl(1, "sfgc", 0xFFFFFF)
sleep(5)
end
firstBoot()
while true do
doLogin()
end
+1
View File
@@ -0,0 +1 @@
core utilities for user management
+1
View File
@@ -0,0 +1 @@
1.0.0
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Haoqian He
+1
View File
@@ -0,0 +1 @@
Libdeflate for Hyperion
+1
View File
@@ -0,0 +1 @@
1.0.0-release
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Astronand
+1
View File
@@ -0,0 +1 @@
package providing kernel support for framebuffers and graphics
+1
View File
@@ -0,0 +1 @@
1.0.0
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Spsf
+1
View File
@@ -0,0 +1 @@
Official Hyperion shell package
+1
View File
@@ -0,0 +1 @@
1.3.0
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Astronand
+1
View File
@@ -0,0 +1 @@
ini parser
+1
View File
@@ -0,0 +1 @@
1.0.0
-12
View File
@@ -1,12 +0,0 @@
[
"Hyperion-kernel",
"Hyperion-core",
"hysh",
"lua",
"micro",
"sysinit",
"bit32",
"json",
"muxzcat",
"spm"
]
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Astronand
+1
View File
@@ -0,0 +1 @@
Hyperion os live installer
+1
View File
@@ -0,0 +1 @@
1.0.0
+2
View File
@@ -0,0 +1,2 @@
Hyperion
rxi
+1
View File
@@ -0,0 +1 @@
JSON library for Hyperion
+1
View File
@@ -0,0 +1 @@
1.0.0
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Spsf
+1
View File
@@ -0,0 +1 @@
Interactive lua shell
+1
View File
@@ -0,0 +1 @@
1.2.0
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Spsf
+1
View File
@@ -0,0 +1 @@
micro text editor
+1
View File
@@ -0,0 +1 @@
1.0.0
+3
View File
@@ -0,0 +1,3 @@
Hyperion
Mark Langen
Astronand (added goto support)
+1
View File
@@ -0,0 +1 @@
lua minifier
+1
View File
@@ -0,0 +1 @@
1.0.0
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Astronand
+1
View File
@@ -0,0 +1 @@
Proportional Integral Derivative library
+1
View File
@@ -0,0 +1 @@
1.0.0
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Astronand
+65 -3
View File
@@ -1,6 +1,68 @@
local args={...}
local json=require("json")
local http=require("http")
local rootRepo="https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/main/spm/spm.src"
-- local test repo
local rootRepo="http://localhost:8000/spm.json"
--local rootRepo="https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/main/spm.json"
local json = require("json")
local http = require("http")
local deflate = require("deflate")
local function checkRepo(repo)
local resp = http.get(repo)
if resp.code ~= 200 then
return false
end
local repojson = json.decode(resp.body)
if not repojson then
return false
end
return true, repojson
end
local function getRepos(root)
local repos = {}
local visited = {}
local queue = {root}
local head = 1
while head <= #queue do
local url = queue[head]
head = head + 1
if not visited[url] then
visited[url] = true
local ok, repo = checkRepo(url)
if ok then
table.insert(repos, {
url = url,
data = repo
})
if repo.refs then
for _, ref in ipairs(repo.refs) do
if not visited[ref] then
queue[#queue + 1] = ref
end
end
end
end
end
end
return repos
end
local function getPkg(name)
end
if args[1] == "install" then
local pkg=getPkg(args[2])
elseif args[1] == "sync" then
error("not implemented")
elseif args[1] == "remove" then
error("not implemented")
end
+1
View File
@@ -0,0 +1 @@
Simple Package Manager
+1
View File
@@ -0,0 +1 @@
1.0.0
+2
View File
@@ -0,0 +1,2 @@
Hyperion
Astronand
+25 -22
View File
@@ -1,8 +1,9 @@
--:Minify:--
local kernel=...
local fs=require("fs")
kernel.log("Sysinit started...")
kernel.saveLog()
local fs=require("fs")
kernel.log("Loaded fs")
for i,v in pairs(kernel.processes) do
kernel.log("Spawning kernel task "..i)
@@ -16,30 +17,32 @@ for i,v in pairs(kernel.processes) do
end, i)
end
if not fs.exists("/bin/startup") then
fs.mkdir("/bin/startup")
-- config file path setup
if not fs.exists("/etc/sysinit/") then
kernel.log("Creating conf path")
fs.mkdir("/etc/sysinit/")
fs.mkdir("/etc/sysinit/autorun/")
fs.mkdir("/etc/sysinit/system/")
fs.mkdir("/etc/sysinit/user/")
end
local files = fs.list("/bin/startup")
if not files then error("Failed to list /bin/startup") end
local files = fs.list("/etc/sysinit/autorun/")
if not files then error("Failed to list autorun") end
for i,v in ipairs(files) do
if v:sub(-4) == ".lua" then
local filepath = "/bin/startup/" .. v
kernel.log("Executing startup script: " .. filepath)
local startupFunc, err = load(fs.readAllText(filepath), "@" .. filepath)
if not startupFunc then
kernel.log("Error loading startup script '" .. filepath .. "': " .. err, "ERROR", 0xFF0000)
else
syscall.spawn(function()
syscall.setuid(1)
local status, err = pcall(startupFunc)
if not status then
kernel.log("Error executing startup script '" .. filepath .. "': " .. err, "ERROR", 0xFF0000)
else
kernel.log("Successfully executed startup script: " .. filepath)
end
end, "startup:" .. v)
end
local filepath = "/etc/sysinit/autorun/" .. v
kernel.log("Executing startup script: " .. filepath)
local startupFunc, err = load(fs.readAllText(filepath), "@" .. filepath)
if not startupFunc then
kernel.log("Error loading startup script '" .. filepath .. "': " .. err, "ERROR", 0xFF0000)
else
syscall.spawn(function()
local status, err = pcall(startupFunc)
if not status then
kernel.log("Error executing startup script '" .. filepath .. "': " .. err, "ERROR", 0xFF0000)
else
kernel.log("Successfully executed startup script: " .. filepath)
end
end, "startup:" .. v)
end
end
kernel.saveLog()
+1
View File
@@ -0,0 +1 @@
System init package
+1
View File
@@ -0,0 +1 @@
1.1.0
+5
View File
@@ -0,0 +1,5 @@
Hyperion
Astronand
pts@fazekas.hu
JackMacWindows
hanzhao
+1
View File
@@ -0,0 +1 @@
XZ compression library
+1
View File
@@ -0,0 +1 @@
1.0.0
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env python3
import hashlib
import json
import shutil
import tarfile
from pathlib import Path
ROOT = Path(__file__).resolve().parent
SOURCE = ROOT / "Src"
PACKAGE_ROOT = ROOT / "packages"
PACKAGE_DIR = PACKAGE_ROOT / "raw"
# main repo
#API_ROOT = "https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/main"
# local repo
API_ROOT = "http://localhost:8000"
DEFAULT_VERSION = "0.0.0"
DEFAULT_DESCRIPTION = "No description provided"
DEFAULT_AUTHORS = []
DEFAULT_DEPENDENCIES = []
def debug(message):
print(f"[COMPILE] {message}", flush=True)
def sha256(path):
hasher = hashlib.sha256()
with open(path, "rb") as file:
while chunk := file.read(1024 * 1024):
hasher.update(chunk)
return hasher.hexdigest()
def exclude(info):
blocked = (".git", "__pycache__", ".pyc")
if any(item in info.name.split("/") for item in blocked):
return None
return info
def read_file(folder, names, default):
for name in names:
file = folder / name
if file.exists():
return file.read_text(encoding="utf-8").strip()
return default
def read_authors(folder):
authors_file = folder / "authors.txt"
if not authors_file.exists():
return DEFAULT_AUTHORS
authors = []
for line in authors_file.read_text(encoding="utf-8").splitlines():
line = line.strip()
if line:
authors.append(line)
return authors if authors else DEFAULT_AUTHORS
def read_dependencies(folder):
dependencies_file = folder / "dependencies.txt"
if not dependencies_file.exists():
return DEFAULT_DEPENDENCIES
dependencies = []
for line in dependencies_file.read_text(encoding="utf-8").splitlines():
line = line.strip()
if line:
dependencies.append(line)
return dependencies if dependencies else DEFAULT_DEPENDENCIES
def create_package(folder):
name = folder.name
tarball = PACKAGE_DIR / f"{name}.tar.gz"
pkg_file = PACKAGE_ROOT / f"{name}.pkg"
if tarball.exists():
debug(f"Replacing {tarball.name}")
tarball.unlink()
debug(f"Packing {name}")
with tarfile.open(tarball, "w:gz") as archive:
archive.add(folder, arcname=name, filter=exclude)
package_hash = sha256(tarball)
metadata = {
"id": name,
"description": read_file(
folder,
["description.md", "description.txt"],
DEFAULT_DESCRIPTION,
),
"authors": read_authors(folder),
"dependencies": read_dependencies(folder),
"version": read_file(
folder,
["version.txt"],
DEFAULT_VERSION,
),
"hash": package_hash,
"tarball": f"{API_ROOT}/packages/raw/{name}.tar.xz",
}
with open(pkg_file, "w", encoding="utf-8") as file:
json.dump(metadata, file, indent=4)
file.write("\n")
debug(f"Generated {pkg_file.name}")
debug(f"{name}: {package_hash}")
return tarball
def load_spm():
spm_file = ROOT / "spm.json"
if not spm_file.exists():
return {
"refs": {},
"maintainers": [],
"packages": {}
}
with open(spm_file, "r", encoding="utf-8") as file:
data = json.load(file)
data.setdefault("refs", {})
data.setdefault("maintainers", [])
data.setdefault("packages", {})
return data
def update_spm():
spm = load_spm()
packages = {}
for pkg in sorted(PACKAGE_ROOT.glob("*.pkg")):
with open(pkg, "r", encoding="utf-8") as file:
metadata = json.load(file)
packages[metadata["id"]] = f"{API_ROOT}/packages/{pkg.name}"
spm["packages"] = packages
with open(ROOT / "spm.json", "w", encoding="utf-8") as file:
json.dump(spm, file, indent=4)
file.write("\n")
debug("Generated spm.json")
def main():
if not SOURCE.exists():
debug("Src directory missing")
return 1
if PACKAGE_ROOT.exists():
shutil.rmtree(PACKAGE_ROOT)
PACKAGE_DIR.mkdir(parents=True, exist_ok=True)
for folder in sorted(SOURCE.iterdir()):
if folder.is_dir():
create_package(folder)
update_spm()
debug("Complete")
return 0
if __name__ == "__main__":
raise SystemExit(main())
-233
View File
@@ -1,233 +0,0 @@
term.clear()
term.setTextColor(colors.white)
term.setCursorPos(1,1)
print("HyperionOS CCT Installer //")
local w,h = term.getSize()
local exitall,releasename,packagelist=false,"",{}
local function printc(...)
local args={...}
for i=1, #args do
if type(args[i]) == "number" then
term.setTextColor(args[i])
else
term.write(args[i])
end
end
print("")
end
local function download(url)
printc(colors.gray, "[ ", colors.green, " FETCH ", colors.gray, " ] ", colors.white, url)
local data,err=http.get(url)
if not data then error(err) end
local text=data.readAll()
data.close()
return text
end
local function writeFile(path, data)
printc(colors.gray, "[ ", colors.green, "WRITING", colors.gray, " ] ", colors.white, path)
local file=fs.open(path, "w")
file.write(data)
file.close()
end
print("Installing JSON to /hyinstall/json...")
writeFile("/hyinstall/json",download("https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/main/misc/json"))
print("Loading JSON...")
local json=loadfile("/hyinstall/json")()
local function printTitle()
term.clear()
term.setCursorPos(1,1)
term.setTextColor(colors.white)
print("HyperionOS CCT Installer //")
print("Orange is prerelease...")
term.setCursorPos(1,h-1)
print("DESCLAIMER: This will wipe your system on install...")
term.write("Bspc: Back | Enter: execute | Tab: description")
end
local function pc(text, y, c)
local x=(w/2)-#text/2
term.setTextColor(c)
term.setCursorPos(x, y)
term.write(text)
end
local function drawMenu(menu, sel)
local y=(h/2)-#menu/2
for i,v in ipairs(menu) do
if i==sel then
pc("[ "..v.name.." ]", y+i, v.color)
else
pc(v.name, y+i, v.color)
end
end
end
local function desc(description)
term.clear()
term.setTextColor(colors.white)
term.setCursorPos(1,1)
print("HyperionOS CCT Installer //\n")
print(description)
term.setCursorPos(1,h)
term.write("Press enter to continue...")
while not exitall do
local _,key = os.pullEvent("key")
if key==keys.enter then
break
end
end
end
local function menu(m)
local exit,sel=false,1
while not exit and not exitall do
printTitle()
drawMenu(m, sel)
local _,key = os.pullEvent("key")
if key==keys.down then
if sel<#m then
sel=sel+1
end
elseif key==keys.up then
if sel>1 then
sel=sel-1
end
elseif key==keys.enter then
m[sel].func()
elseif key==keys.tab then
desc(m[sel].desc)
elseif key==keys.backspace then
exit=true
end
end
end
local releases,page={},1
while true do
local raw=download("https://git.astronand.dev/api/v1/repos/Hyperion/HyperionOS/releases?page="..tostring(page).."&limit=1")
if raw=="[]\n" then
break
end
releases[page]=json.decode(raw)[1]
page=page+1
end
local function makePage(start, num)
local m={}
for i=start, num do
if not releases[i] then break end
local release=releases[i]
m[#m+1]={
name=release.name,
desc=release.body,
color=release.prerelease and colors.orange or colors.white,
func=function()
term.clear()
term.setCursorPos(1,1)
local data=download("https://git.astronand.dev/Hyperion/HyperionOS/raw/tag/"..release.tag_name.."/Src/install.json")
if not data then
term.clear()
term.setCursorPos(1,1)
print("Unable to find package list for ver "..release.tag_name..":\n"..err)
sleep(3)
return
end
releasename=release.tag_name
packagelist=json.decode(data)
exitall=true
end
}
end
if #releases>start+num then
m[#m+1]={
name="Next",
desc="Next menu",
color=colors.cyan,
func=function()
local menudata=makePage(start+num, num)
menu(menudata)
end
}
end
return m
end
printTitle()
menu(makePage(1,5))
if not exitall then error("Exited") end
term.clear()
term.setCursorPos(1,1)
term.setTextColor(colors.white)
term.write("Formating disk in ")
for i=5, 1, -1 do
term.write(tostring(i))
sleep(.25)
for v=1, 3 do
term.write(".")
sleep(.25)
end
end
print("")
local function delDir(dir)
printc(colors.gray, "[ ", colors.green, " LSDIR ", colors.gray, " ] ", colors.white, dir)
local list=fs.list(dir)
printc(colors.gray, "[ ", colors.red, " DELET ", colors.gray, " ] ", colors.white, dir)
for i=1, #list do
if fs.isDir(dir..list[i]) then
if dir..list[i] ~= "/rom" then
delDir(dir..list[i].."/")
fs.delete(dir..list[i])
end
else
fs.delete(dir..list[i])
printc(colors.gray, "[ ", colors.red, " DELET ", colors.gray, " ] ", colors.white, dir..list[i])
end
sleep(.01)
end
sleep(.15)
end
delDir("/")
print("")
printc(colors.cyan, " \\\\Installing//")
print("")
printc(colors.white, "Getting package list...")
local function installdir(path, dir, pkg)
local data=json.decode(download("https://git.astronand.dev/api/v1/repos/Hyperion/HyperionOS/contents/prod/"..pkg..dir.."?ref="..releasename))
if not data then error("Uh Oh: JSON decode error...") end
for i=1, #data do
local entry=data[i]
if entry.type == "dir" then
installdir(path, dir..entry.name.."/", pkg)
elseif entry.type == "file" then
writeFile(path..dir..entry.name, download(entry.download_url))
else
error("Uh Oh: unknown entrytype "..entry.path)
end
sleep(.01)
end
sleep(.15)
end
local function installpkg(pkg)
installdir("/$", "/", pkg)
end
for i=1, #packagelist do
installpkg(packagelist[i])
end
installpkg("Hyperion-firmware-cct")
fs.copy("/$/boot/cct/eeprom", "/startup.lua")
printc(colors.white, "Rebooting...")
sleep(1)
os.reboot()
+12
View File
@@ -0,0 +1,12 @@
{
"id": "Hyperion-core",
"description": "Core library for Hyperion",
"authors": [
"Hyperion",
"Astronand"
],
"dependencies": [],
"version": "1.0.0",
"hash": "e83a8dc42a871c1e16cd56c214cf0ca4bb0079432dc8bd198abbe11d6cf31d4e",
"tarball": "http://localhost:8000/packages/raw/Hyperion-core.tar.xz"
}
+13
View File
@@ -0,0 +1,13 @@
{
"id": "Hyperion-firmware-ac",
"description": "firmware for Advanced Computers",
"authors": [
"Hyperion",
"Astronand",
"GHXX (Advanced Computers)"
],
"dependencies": [],
"version": "1.0.0",
"hash": "f64be830fc02394eeb9c049a528ed47b97430c6682a577c789d16303dcaad6fc",
"tarball": "http://localhost:8000/packages/raw/Hyperion-firmware-ac.tar.xz"
}
+12
View File
@@ -0,0 +1,12 @@
{
"id": "Hyperion-firmware-ccpc",
"description": "firmware for CraftOS PC",
"authors": [
"Hyperion",
"Astronand"
],
"dependencies": [],
"version": "1.0.0",
"hash": "f3f030d6f6faa137f9950f0ce4ccc598a9b29c729380c100c920fad5aa254076",
"tarball": "http://localhost:8000/packages/raw/Hyperion-firmware-ccpc.tar.xz"
}
+12
View File
@@ -0,0 +1,12 @@
{
"id": "Hyperion-firmware-cct",
"description": "firmware for ComputerCraft Tweaked",
"authors": [
"Hyperion",
"Astronand"
],
"dependencies": [],
"version": "1.3.0",
"hash": "dd69c26ba6f46acb99d6f839474fc50283524bd99df7f67a5d300a749af86dde",
"tarball": "http://localhost:8000/packages/raw/Hyperion-firmware-cct.tar.xz"
}
+11
View File
@@ -0,0 +1,11 @@
{
"id": "Hyperion-firmware-oc",
"description": "firmware for Opencomputers",
"authors": [
"Hyperion"
],
"dependencies": [],
"version": "0.1.0",
"hash": "8b1bbff272dd07f0e5023c90007ec8d3a84f5b29a59449a75894319c86ee10cd",
"tarball": "http://localhost:8000/packages/raw/Hyperion-firmware-oc.tar.xz"
}
+13
View File
@@ -0,0 +1,13 @@
{
"id": "Hyperion-kernel",
"description": "Hyperion kernel package",
"authors": [
"Hyperion",
"Astronand",
"Spsf"
],
"dependencies": [],
"version": "1.5.0",
"hash": "a17552597f1a4e661804bcf23d26e19664cb6f75cb3058357603df59b033155a",
"tarball": "http://localhost:8000/packages/raw/Hyperion-kernel.tar.xz"
}
+12
View File
@@ -0,0 +1,12 @@
{
"id": "bit32",
"description": "Bit32 package for Hyperion",
"authors": [
"Hyperion",
"Lua"
],
"dependencies": [],
"version": "1.0.0",
"hash": "9bc32da8ab97f899e85faeb47064433ef4a167e45924c0e583d30f901551bdf5",
"tarball": "http://localhost:8000/packages/raw/bit32.tar.xz"
}
+12
View File
@@ -0,0 +1,12 @@
{
"id": "blake2s",
"description": "blake2s package for Hyperion",
"authors": [
"Hyperion",
"Astronand"
],
"dependencies": [],
"version": "1.0.0",
"hash": "c32e2bbeba5d8609f720bce59fad0505d490ee22a6e54b4d830a524e609ac415",
"tarball": "http://localhost:8000/packages/raw/blake2s.tar.xz"
}
+13
View File
@@ -0,0 +1,13 @@
{
"id": "coreutils",
"description": "core utilities for user management",
"authors": [
"Hyperion",
"Astronand",
"Spsf"
],
"dependencies": [],
"version": "1.0.0",
"hash": "c52244c7fd2ca96780ed684327ebb55872757a4f6c8d90e18367ec4e0d8f10ad",
"tarball": "http://localhost:8000/packages/raw/coreutils.tar.xz"
}
+12
View File
@@ -0,0 +1,12 @@
{
"id": "deflate",
"description": "Libdeflate for Hyperion",
"authors": [
"Hyperion",
"Haoqian He"
],
"dependencies": [],
"version": "1.0.0-release",
"hash": "43a3df0e6da39aa97c0ee0ce4217e9acffadba4178d03dae5c66bf4627147644",
"tarball": "http://localhost:8000/packages/raw/deflate.tar.xz"
}
+12
View File
@@ -0,0 +1,12 @@
{
"id": "gfxterm",
"description": "package providing kernel support for framebuffers and graphics",
"authors": [
"Hyperion",
"Astronand"
],
"dependencies": [],
"version": "1.0.0",
"hash": "88eb9f9bb7bb86fb7752601c67e4ab62e88fb7562057a83c101aaf7ffc3ccdc0",
"tarball": "http://localhost:8000/packages/raw/gfxterm.tar.xz"
}
+12
View File
@@ -0,0 +1,12 @@
{
"id": "hysh",
"description": "Official Hyperion shell package",
"authors": [
"Hyperion",
"Spsf"
],
"dependencies": [],
"version": "1.3.0",
"hash": "d0baaeb889eeaa38a9f100c4954b8f30c36c220ba73d8b8fa878e0fcaeaadb15",
"tarball": "http://localhost:8000/packages/raw/hysh.tar.xz"
}
+12
View File
@@ -0,0 +1,12 @@
{
"id": "iniparse",
"description": "ini parser",
"authors": [
"Hyperion",
"Astronand"
],
"dependencies": [],
"version": "1.0.0",
"hash": "f26f900027066dc2f498a4ccb8306f48c93210e7869b11f4aef9900f2e00e066",
"tarball": "http://localhost:8000/packages/raw/iniparse.tar.xz"
}
+12
View File
@@ -0,0 +1,12 @@
{
"id": "installer",
"description": "Hyperion os live installer",
"authors": [
"Hyperion",
"Astronand"
],
"dependencies": [],
"version": "1.0.0",
"hash": "8623a44260062d0066aecc683b45c2ea9e6f438fd74cef050e7bcac7daa33a4f",
"tarball": "http://localhost:8000/packages/raw/installer.tar.xz"
}
+12
View File
@@ -0,0 +1,12 @@
{
"id": "json",
"description": "JSON library for Hyperion",
"authors": [
"Hyperion",
"rxi"
],
"dependencies": [],
"version": "1.0.0",
"hash": "8344035d687f5cafb8c918d6bddd2ba5a7d6c2025aa43def84b942a614583282",
"tarball": "http://localhost:8000/packages/raw/json.tar.xz"
}

Some files were not shown because too many files have changed in this diff Show More