added users (spsf untangled by astronand)

This commit is contained in:
2026-02-20 23:32:05 -05:00
parent 57b1d46837
commit 0655f2a39e
20 changed files with 2419 additions and 237 deletions

292
Src/Hyperion-bash/bin/help Normal file
View File

@@ -0,0 +1,292 @@
--:Minify:--
-- help: display command reference with paged scrolling
local COMMANDS = {
-- {name, usage, description, {flags...}}
-- flags: {flag, desc}
{
name = "cat",
usage = "cat [file]",
desc = "Print file contents to stdout. Reads from stdin if no file given.",
flags = {}
},
{
name = "cd",
usage = "cd [dir]",
desc = "Change working directory. Use '-' to return to previous directory.",
flags = {}
},
{
name = "clear",
usage = "clear",
desc = "Clear the terminal screen.",
flags = {}
},
{
name = "echo",
usage = "echo [text...]",
desc = "Print arguments to stdout.",
flags = {}
},
{
name = "hfetch",
usage = "hfetch",
desc = "Display system information in a neofetch-style layout.",
flags = {}
},
{
name = "id",
usage = "id [username]",
desc = "Print user identity (uid, gid). Defaults to current user.",
flags = {}
},
{
name = "login",
usage = "login",
desc = "System login prompt. Launched automatically at boot.",
flags = {}
},
{
name = "ls",
usage = "ls [-alh] [dir]",
desc = "List directory contents.",
flags = {
{"-a", "Show hidden files (starting with .)"},
{"-l", "Long format: permissions, owner, size"},
{"-h", "Human-readable file sizes"},
}
},
{
name = "lsusers",
usage = "lsusers",
desc = "List all user accounts with uid, gid, home, and shell.",
flags = {}
},
{
name = "lua",
usage = "lua",
desc = "Interactive Lua REPL prompt.",
flags = {}
},
{
name = "mkdir",
usage = "mkdir <dir>",
desc = "Create a directory.",
flags = {}
},
{
name = "passwd",
usage = "passwd [username]",
desc = "Change a user password. Non-root must verify current password first.",
flags = {}
},
{
name = "ps",
usage = "ps",
desc = "List running tasks with pid, user, name, and status.",
flags = {}
},
{
name = "pwd",
usage = "pwd",
desc = "Print current working directory.",
flags = {}
},
{
name = "su",
usage = "su [username]",
desc = "Switch user. Defaults to root. Root can switch without a password.",
flags = {}
},
{
name = "sudo",
usage = "sudo [-u user] <command> [args...]",
desc = "Run a command as another user (default root). Authenticates as current user.",
flags = {
{"-u user", "Run as the specified user (name or uid)"},
}
},
{
name = "sysdump",
usage = "sysdump",
desc = "List all registered kernel syscalls.",
flags = {}
},
{
name = "useradd",
usage = "useradd [-p pw] [-g gid] [-d home] [-s shell] [-M] <username>",
desc = "Create a new user account.",
flags = {
{"-p pw", "Set password (prompted interactively if omitted)"},
{"-g gid", "Set primary group id"},
{"-d home", "Set home directory (default: /home/username)"},
{"-s shell", "Set login shell (default: /bin/hysh)"},
{"-M", "Do not create home directory"},
}
},
{
name = "userdel",
usage = "userdel [-r] <username>",
desc = "Delete a user account.",
flags = {
{"-r", "Also recursively remove the user's home directory"},
}
},
{
name = "usermod",
usage = "usermod [-l name] [-p pw] [-g gid] [-d home] [-s shell] [-L] [-U] <username>",
desc = "Modify an existing user account.",
flags = {
{"-l name", "Rename the user"},
{"-p pw", "Set new password"},
{"-g gid", "Change primary group id"},
{"-d home", "Change home directory"},
{"-s shell", "Change login shell"},
{"-L", "Lock the account (disable login)"},
{"-U", "Unlock the account"},
}
},
{
name = "whoami",
usage = "whoami",
desc = "Print the current username.",
flags = {}
},
{
name = "yes",
usage = "yes [text]",
desc = "Repeatedly print 'y' (or given text) until interrupted with Ctrl+C.",
flags = {}
},
}
-- Build lines to display
local C_HEAD = 7 -- yellow (section headers)
local C_CMD = 5 -- cyan (command name)
local C_USAGE = 1 -- white (usage line)
local C_DESC = 13 -- grey (description)
local C_FLAG = 3 -- green (flag name)
local C_DIM = 12 -- dark grey
-- Each entry is {text, color}
local lines = {}
local function push(text, col) lines[#lines+1] = {text, col or 1} end
push("HyperionOS Command Reference", C_HEAD)
push(string.rep("=", 50), C_DIM)
push("", 1)
local args = {...}
local filter = args[1] -- optional: help <command>
local function addCmd(cmd)
push(cmd.name, C_CMD)
push(" Usage: " .. cmd.usage, C_USAGE)
push(" " .. cmd.desc, C_DESC)
if #cmd.flags > 0 then
for _, f in ipairs(cmd.flags) do
push(" " .. f[1], C_FLAG)
push(" " .. f[2], C_DESC)
end
end
push("", 1)
end
if filter then
local found = false
for _, cmd in ipairs(COMMANDS) do
if cmd.name == filter then addCmd(cmd); found = true; break end
end
if not found then
push("help: unknown command '" .. filter .. "'", 2)
push("Run 'help' with no arguments for the full list.", C_DESC)
end
else
push("Run 'help <command>' for details on a specific command.", C_DESC)
push("", 1)
for _, cmd in ipairs(COMMANDS) do addCmd(cmd) end
end
-- Pager
local sizeStr = syscall.devctl(1, "size")
local screenW = tonumber(sizeStr:sub(1, sizeStr:find(";")-1)) or 51
local screenH = tonumber(sizeStr:sub(sizeStr:find(";")+1)) or 19
local pageSize = screenH - 2 -- reserve bottom row for status bar
local scroll = 0
local totalLines = #lines
local dirty = true
local function render()
syscall.devctl(1, "clear")
syscall.devctl(1, "spos", 1, 1)
for row = 1, pageSize do
local li = scroll + row
if li <= totalLines then
local text, col = lines[li][1], lines[li][2]
syscall.devctl(1, "sfgc", col)
-- truncate to screen width
if #text > screenW then text = text:sub(1, screenW) end
syscall.write(1, text .. "\n")
else
syscall.write(1, "\n")
end
end
-- status bar
syscall.devctl(1, "sfgc", 16)
syscall.devctl(1, "sbgc", 13)
local pct = math.floor(math.min(100, (scroll + pageSize) / totalLines * 100))
local status = string.format(" help -- line %d/%d (%d%%) [up/down: scroll q: quit] ",
scroll + 1, totalLines, pct)
if #status > screenW then status = status:sub(1, screenW) end
syscall.devctl(1, "spos", 1, screenH)
syscall.write(1, status .. string.rep(" ", screenW - #status))
syscall.devctl(1, "sfgc", 1)
syscall.devctl(1, "sbgc", 16)
dirty = false
end
-- If output fits on one screen, just print without pager
if totalLines <= pageSize then
syscall.devctl(1, "clear")
syscall.devctl(1, "spos", 1, 1)
for _, line in ipairs(lines) do
syscall.devctl(1, "sfgc", line[2])
syscall.write(1, line[1] .. "\n")
end
syscall.devctl(1, "sfgc", 1)
return
end
render()
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
-- idle
elseif ch == "q" or ch == "Q" then
break
elseif ch == "\17" then -- up arrow
if scroll > 0 then
scroll = scroll - 1
dirty = true
end
elseif ch == "\18" then -- down arrow
if scroll + pageSize < totalLines then
scroll = scroll + 1
dirty = true
end
elseif ch == "\19" then -- left / page up (reuse left arrow)
scroll = math.max(0, scroll - pageSize)
dirty = true
elseif ch == "\20" then -- right / page down
scroll = math.min(totalLines - pageSize, scroll + pageSize)
dirty = true
end
if dirty then render() end
end
-- Restore screen
syscall.devctl(1, "clear")
syscall.devctl(1, "spos", 1, 1)
syscall.devctl(1, "sfgc", 1)
syscall.devctl(1, "sbgc", 16)

View File

@@ -0,0 +1,104 @@
--:Minify:--
-- Color indices (sfgc):
-- 1=white, 2=red, 3=green, 4=blue, 5=cyan, 6=magenta, 7=yellow
-- 8=orange, 9=lime, 10=lightcyan, 11=brown, 12=darkgrey, 13=lightgrey, 14=purple, 15=chartreuse, 16=black
local C_LOGO = 5 -- cyan
local C_WHITE = 1 -- white
local C_LABEL = 13 -- light grey (key names)
local C_SEP = 12 -- dark grey (---- separator)
local C_USER = 3 -- green (user@host)
local function c(col) syscall.devctl(1, "sfgc", col) end
local username = syscall.getUsername() or "Unknown"
local hostname = syscall.getHostname() or "Unknown"
local userhost = username .. "@" .. hostname
local function formatUptime(ms)
local s = math.floor(ms / 1000)
local m = math.floor(s / 60)
local h = math.floor(m / 60)
local d = math.floor(h / 24)
s = s % 60; m = m % 60; h = h % 24
local parts = {}
if d > 0 then parts[#parts+1] = d .. "d" end
if h > 0 then parts[#parts+1] = h .. "h" end
if m > 0 then parts[#parts+1] = m .. "m" end
parts[#parts+1] = s .. "s"
return table.concat(parts, " ")
end
local host_str = syscall.getHost() or "Unknown"
local cc_ver = host_str:match("ComputerCraft ([%d%.]+)") or host_str
local info = {
-- {label, value} label=nil means print value as-is (userhost / separator)
{nil, userhost},
{nil, string.rep("-", #userhost)},
{"OS", syscall.version() or "Unknown"},
{"Host", cc_ver},
{"Arch", syscall.arch() or "Unknown"},
{"Uptime", formatUptime(syscall.getUptime() or 0)},
{"Tasks", tostring(#(syscall.getTasks() or {}))},
{"Shell", syscall.getEnviron("SHELL") or "Unknown"},
{"Terminal", "TTY1"},
{"UID", tostring(syscall.getuid())},
{"Packages", "n/a (spm)"},
}
local logo = {
".. *. .. ",
" *= +@* +* ",
" .@#. -@@@= :#@. ",
" =@@+ *@@@# +@@= ",
" %@@%: *@@@# -%@@% ",
" :@@@@+ *@@@# .*@@@@: ",
" :*@@@%- *@@@# -@@@@*: ",
" =%@@#. *@@@# .#@@%= ",
" :=. :*@@= *@@@# =@@+: .=: ",
" %@#=..*# +@@@# #*..=#@# ",
" .@@@@+=# .%@%: #=+@@@@. ",
" .....=# -@= *+...:. ",
" -*%*-@= - =@-*%*- ",
" -@*. -@%. :%@- :*@- ",
" .#@#@* ",
" -#- ",
" ",
}
local lines = math.max(#logo, #info)
for i = 1, lines do
local logo_str = logo[i] or string.rep(" ", 36)
-- print logo segment in cyan
c(C_LOGO)
printInline(logo_str)
-- print separator pipe
c(C_LABEL)
printInline("| ")
-- print info segment
local row = info[i]
if row then
if row[1] == nil and i == 1 then
-- user@host line
c(C_USER)
printInline(row[2])
elseif row[1] == nil and i == 2 then
-- separator line
c(C_SEP)
printInline(row[2])
elseif row[1] then
-- label: value
c(C_LABEL)
printInline(row[1] .. ": ")
c(C_WHITE)
printInline(row[2])
end
end
c(C_WHITE)
print("")
end

299
Src/Hyperion-bash/bin/hysh Normal file
View File

@@ -0,0 +1,299 @@
--:Minify:--
syscall.open("/dev/tty/TTY1","r") --stdin (Device 0)
syscall.open("/dev/tty/TTY1","w") --stdout (Device 1)
syscall.open("/dev/null","w") --stderr (device 2)
local success, errorMsg = xpcall(function()
local fs = require("sys.fs")
syscall.devctl(1,"clear")
syscall.devctl(1,"sfgc",1)
syscall.devctl(1,"spos",1,1)
print("HyperionOS hysh Shell")
local userhost = (syscall.getUsername() or "Unknown").."@"..(syscall.getHostname() or "Unknown")
local commandHistory = {}
local terminate = false
syscall.setEnviron("SHELL","rtbash")
syscall.setEnviron("PATH","/bin/")
syscall.chdir("/")
local oldWD = ""
for i = 1, 16 do
syscall.devctl(1,"sbgc",i)
printInline(" ");
end
print("\n")
syscall.sigcatch(function(sig)
if sig == 1 then
terminate = true
end
end)
local builtinCmds = {}
builtinCmds.cd = function(path)
local cwd = syscall.getcwd()
local dirIn = (path or "")
if dirIn == "-" then
if oldWD == "" then
print("hysh-cd: No previous working directory set.")
else
print(oldWD)
syscall.chdir(oldWD)
oldWD = cwd
end
return
end
local dirInMod = dirIn
if dirIn:sub(1, 1) ~= "/" then dirInMod = cwd .. "/" .. dirIn end
local parts = {}
for part in dirInMod:gmatch("[^/]+") do
if part == ".." then
if #parts > 0 then table.remove(parts) end
elseif part ~= "." and part ~= "" then
table.insert(parts, part)
end
end
local normDir = "/" .. table.concat(parts, "/")
if normDir:sub(#normDir, #normDir) ~= "/" then normDir = normDir .. "/" end
if not fs.isDir(normDir) then
print("hysh-cd: "..dirIn..": No such directory.")
return
end
oldWD = cwd
syscall.chdir(normDir)
end
local function getUserInput()
syscall.devctl(1,"sfgc",3)
syscall.write(1, userhost)
syscall.devctl(1,"sfgc",1)
syscall.write(1, ":")
syscall.devctl(1,"sfgc",10)
syscall.write(1, syscall.getcwd())
syscall.devctl(1,"sfgc",1)
syscall.write(1, "$ ")
local curOffsetStr = syscall.devctl(1, "gpos")
local curOffsetX = tonumber(curOffsetStr:sub(1, curOffsetStr:find(";")-1))
local curOffsetY = tonumber(curOffsetStr:sub(curOffsetStr:find(";")+1))
local input = ""
local blinkState = false
local cursorPos = 1
local history = 0
local dirty = true -- redraw on first iteration
local function redraw()
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
-- text before cursor
syscall.write(1, string.sub(input, 1, cursorPos-1))
-- cursor character (inverted if blinking on)
if blinkState then
syscall.devctl(1,"sfgc",16)
syscall.devctl(1,"sbgc",1)
end
if cursorPos > #input then
syscall.write(1, " ")
else
syscall.write(1, string.sub(input, cursorPos, cursorPos))
end
syscall.devctl(1,"sfgc",1)
syscall.devctl(1,"sbgc",16)
-- text after cursor + trailing space to erase old chars
syscall.write(1, string.sub(input, cursorPos+1) .. " ")
end
while true do
local key = syscall.read(0)
if key and key ~= "" then
if key == "\19" then -- left arrow
if cursorPos > 1 then
cursorPos = cursorPos - 1
dirty = true
end
elseif key == "\20" then -- right arrow
if cursorPos <= #input then
cursorPos = cursorPos + 1
dirty = true
end
elseif key == "\17" then -- up arrow
if history < #commandHistory then
history = history + 1
input = commandHistory[#commandHistory - history + 1]
cursorPos = #input + 1
dirty = true
end
elseif key == "\18" then -- down arrow
if history > 1 then
history = history - 1
input = commandHistory[#commandHistory - history + 1]
cursorPos = #input + 1
dirty = true
elseif history == 1 then
history = 0
input = ""
cursorPos = 1
dirty = true
end
elseif key == "\b" then
if cursorPos > 1 then
input = string.sub(input, 1, cursorPos-2) .. string.sub(input, cursorPos)
cursorPos = cursorPos - 1
dirty = true
end
elseif key == "\n" then
-- redraw cleanly with no cursor highlight before committing
syscall.devctl(1,"sfgc",1)
syscall.devctl(1,"sbgc",16)
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
syscall.write(1, input .. " \n")
return input
else
input = string.sub(input, 1, cursorPos-1) .. key .. string.sub(input, cursorPos)
cursorPos = cursorPos + 1
dirty = true
end
end
-- cursor blink
local curBlink = ((math.floor(syscall.getUptime() / 500) % 2) == 0)
if curBlink ~= blinkState then
blinkState = curBlink
dirty = true
end
if dirty then
redraw()
dirty = false
end
end
end
local function runCommand(command)
do
local func = load("return " .. command, "@equation", "t", {})
if func then
local success, result = pcall(func)
if success and type(result) == "number" then
print(result)
return
end
end
end
terminate = false
local args = string.split(command, " ")
if builtinCmds[args[1]] then
local success, msg = pcall(builtinCmds[args[1]], table.unpack(args, 2))
if not success then
local errSL = string.sub(msg, string.find(msg, "]") + 2)
syscall.devctl(1,"sfgc",2)
printInline(args[1]..": Program runtime error on line ")
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
syscall.devctl(1,"sfgc",1)
print(string.sub(errSL, string.find(errSL, ":") + 1))
end
return
end
local cmdPath = ""
if string.find(args[1], "/") then
if fs.exists(args[1]) then
cmdPath = args[1]
end
else
local paths = string.split(syscall.getEnviron("PATH"), ":")
for _, path in pairs(paths) do
if fs.exists(path..args[1]) then
cmdPath = path..args[1]
break
end
end
end
if cmdPath == "" then
print(args[1]..": Command not found")
return
end
local progName = string.sub(cmdPath, #cmdPath - string.find(string.reverse(cmdPath), "/") + 2)
local text = fs.readAllText(cmdPath)
local program, err = load(text, progName)
if not program then
local errSL = string.sub(err, string.find(err, ":") + 1)
syscall.devctl(1,"sfgc",2)
printInline(progName..": Program load error on line ")
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
syscall.devctl(1,"sfgc",1)
print(string.sub(errSL, string.find(errSL, ":") + 1))
return
end
local proc = syscall.spawn(function(...)
syscall.open("/dev/tty/TTY1","r")
syscall.open("/dev/tty/TTY1","w")
syscall.open("/dev/null","w")
local success, msg = pcall(program, ...)
if not success then
local errSL = string.sub(msg, string.find(msg, ":") + 1)
syscall.devctl(1,"sfgc",2)
printInline(progName..": Program runtime error on line ")
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
syscall.devctl(1,"sfgc",1)
print(string.sub(errSL, string.find(errSL, ":") + 1))
end
end, progName, nil, {table.unpack(args, 2)})
while true do
local exited, code = syscall.collect(proc)
if exited then
if code then
print("\nTask exited with code:\n"..tostring(code))
end
return
end
if terminate then
local success, err = syscall.kill(proc)
if success then
syscall.devctl(1,"sbgc",16)
syscall.devctl(1,"sfgc",2)
print("\nProgram Terminated.")
syscall.devctl(1,"sfgc",1)
end
terminate = false
break
end
sleep(0.05)
end
end
while true do
local command = getUserInput()
if command ~= "" then
if command ~= commandHistory[#commandHistory] then
table.insert(commandHistory, command)
end
runCommand(command)
end
end
--ERROR HANDLING
end, debug.traceback)
if not success then
syscall.log("Error running shell: "..errorMsg, "ERROR")
syscall.devctl(1,"sfgc",2)
syscall.devctl(1,"sbgc",16)
print()
print("Error running shell: ")
print(errorMsg)
end

View File

@@ -0,0 +1,94 @@
--:Minify:--
syscall.open("/dev/tty/TTY1","r")
syscall.open("/dev/tty/TTY1","w")
syscall.open("/dev/null","r")
syscall.devctl(1,"clear")
syscall.devctl(1,"sfgc",1)
syscall.devctl(1,"spos",1,1)
print("HyperionOS hysh Shell")
local str=""
local stopInput=false
local proc=0
local fs=require("sys.fs")
local timeout=false
syscall.setEnviron("SHELL","simpleshell")
printInline("> ")
syscall.sigcatch(function(sig)
if sig==1 then
syscall.kill(proc)
print("Terminated")
printInline("> ")
stopInput=false
end
end)
while true do
if not stopInput then
local input=syscall.read(0)
if input then
if input=="\b" then
if #str>0 then
str=str:sub(1,#str-1)
printInline("\b")
end
elseif input=="\n" then
print("")
stopInput=true
if str == "" then
printInline("> ")
stopInput=false
else
local path=nil
local split=string.split(str, " ")
if fs.exists("/bin/"..split[1]) then
path="/bin/"..split[1]
elseif fs.exists("/bin/"..split[1]..".lua") then
path="/bin/"..split[1]..".lua"
end
if not path then
print("Program not found")
printInline("> ")
stopInput=false
else
local text = fs.readAllText(path)
local program, err = load(text, path)
if not program then
print(err)
printInline("> ")
end
proc = syscall.spawn(function(...)
syscall.open("/dev/tty/TTY1","r")
syscall.open("/dev/tty/TTY1","w")
syscall.open("/dev/null","w")
program(...)
end, path, nil, {table.unpack(split, 2)})
end
str=""
end
else
str=str..input
printInline(input)
end
timeout=false
else
timeout=true
end
else
local exited, code = syscall.collect(proc)
if exited then
if code then
print("\nTask exited with code:\n"..tostring(code))
end
printInline("> ")
stopInput=false
end
timeout=true
end
if timeout then
if stopInput then
sleep(.5)
else
sleep(.05)
end
end
end

19
Src/Hyperion-bash/bin/id Normal file
View File

@@ -0,0 +1,19 @@
--:Minify:--
local args = {...}
local uid
if args[1] then
uid = syscall.auth_getuid(args[1])
if not uid then
print("id: user '" .. args[1] .. "' does not exist")
syscall.exit(1); return
end
else
uid = syscall.getuid()
end
local pwent = syscall.auth_getpasswd(uid)
local name = (pwent and pwent.username) or tostring(uid)
local gid = (pwent and pwent.gid) or uid
print(string.format("uid=%d(%s) gid=%d(%s)", uid, name, gid, name))

178
Src/Hyperion-bash/bin/login Normal file
View File

@@ -0,0 +1,178 @@
--:Minify:--
syscall.open("/dev/tty/TTY1","r") --stdin (fd 0)
syscall.open("/dev/tty/TTY1","w") --stdout (fd 1)
syscall.open("/dev/null","w") --stderr (fd 2)
local fs = require("sys.fs")
local MAX_ATTEMPTS = 3
local function readLine(mask)
local input = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
-- buffer empty, spin
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 = fs.readAllText("/etc/shadow") or ""
if shadow:match("%S") then return end
syscall.devctl(1, "clear")
syscall.devctl(1, "spos", 1, 1)
syscall.devctl(1, "sfgc", 3)
syscall.write(1, "HyperionOS First Boot Setup\n")
syscall.devctl(1, "sfgc", 1)
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", 2)
syscall.write(1, "Passwords do not match. Try again.\n\n")
syscall.devctl(1, "sfgc", 1)
elseif #pw1 < 6 then
syscall.devctl(1, "sfgc", 2)
syscall.write(1, "Password too short (minimum 6 characters).\n\n")
syscall.devctl(1, "sfgc", 1)
else
local ok, err = syscall.auth_setpassword(0, pw1)
if ok then
syscall.devctl(1, "sfgc", 3)
syscall.write(1, "Root password set.\n\n")
syscall.devctl(1, "sfgc", 1)
sleep(0.5)
break
else
syscall.devctl(1, "sfgc", 2)
syscall.write(1, "Error: " .. tostring(err) .. "\n")
syscall.devctl(1, "sfgc", 1)
end
end
end
end
local function spawnShell(username, uid, shell, homedir)
local shellText = fs.readAllText(shell)
if not shellText then
syscall.write(1, "login: shell not found: " .. shell .. "\n")
sleep(2)
return false
end
-- Spawn a wrapper that loads and runs the shell, reporting any error back
-- via exit code channel so we can display it
local errFifo = {}
local proc = syscall.spawn(function()
syscall.setuid(uid)
syscall.chdir(homedir)
syscall.setEnviron("HOME", homedir)
syscall.setEnviron("USER", username)
syscall.setEnviron("SHELL", shell)
syscall.setEnviron("PATH", "/bin/")
local shellFn, loadErr = load(shellText, "@" .. shell)
if not shellFn then
-- Report load error via log and a recognizable exit code
syscall.log("login: shell load error: " .. tostring(loadErr), "ERROR")
syscall.exit(-1)
return
end
local ok, runErr = xpcall(shellFn, debug.traceback)
if not ok then
syscall.log("login: shell runtime error: " .. tostring(runErr), "ERROR")
end
end, username .. ":shell")
while true do
local exited, code = syscall.collect(proc)
if exited then
if code then
syscall.devctl(1, "sfgc", 2)
syscall.write(1, "\nShell exited with code: " .. tostring(code) .. "\n")
syscall.write(1, "(Check /var/log/syslog.log for details)\n")
syscall.devctl(1, "sfgc", 1)
sleep(2)
end
return true
end
sleep(0.1)
end
end
local function doLogin()
syscall.devctl(1, "clear")
syscall.devctl(1, "sfgc", 1)
syscall.devctl(1, "sbgc", 16)
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", 1)
syscall.write(1, "Username: ")
local username = readLine(nil)
if username == "" then goto continue end
syscall.write(1, "Password: ")
local password = readLine("*")
local ok, err = syscall.auth_login(username, password)
if ok then
local uid = syscall.auth_getuid(username)
local pwent = uid and syscall.auth_getpasswd(uid)
local shell = (pwent and pwent.shell) or "/bin/hysh"
local homedir = (pwent and pwent.homedir) or "/"
syscall.devctl(1, "sfgc", 3)
syscall.write(1, "\nWelcome, " .. username .. "!\n")
syscall.devctl(1, "sfgc", 1)
sleep(0.3)
spawnShell(username, uid, shell, homedir)
return -- back to login prompt
else
attempts = attempts + 1
sleep(1)
syscall.devctl(1, "sfgc", 2)
syscall.write(1, "Login incorrect.\n\n")
syscall.devctl(1, "sfgc", 1)
end
::continue::
end
syscall.devctl(1, "sfgc", 2)
syscall.write(1, "Maximum login attempts exceeded.\n")
syscall.devctl(1, "sfgc", 1)
sleep(5)
end
firstBoot()
while true do
doLogin()
end

View File

@@ -0,0 +1,19 @@
--:Minify:--
local users = syscall.auth_listusers()
if not users or #users == 0 then
print("No users found.")
return
end
syscall.devctl(1,"sfgc",13)
print(string.format("%-6s %-6s %-16s %-20s %s", "UID", "GID", "Username", "Home", "Shell"))
print(string.rep("-", 65))
syscall.devctl(1,"sfgc",1)
for _, u in ipairs(users) do
local lock_marker = u.locked and " [locked]" or ""
if u.locked then syscall.devctl(1,"sfgc",2) end
print(string.format("%-6d %-6d %-16s %-20s %s%s",
u.uid, u.gid, u.username, u.homedir, u.shell, lock_marker))
if u.locked then syscall.devctl(1,"sfgc",1) end
end

View File

@@ -1,149 +1,325 @@
syscall.devctl(1,"sfgc",7)
print("HyperionOS Lua prompt.")
print("Call exit() to exit.")
--:Minify:--
local C_PROMPT = 7
local C_CONT = 13
local C_OUT = 5
local C_ERR = 2
local C_KEY = 3
local C_STR = 9
local C_NUM = 10
local C_BOOL = 8
local C_NIL = 12
local C_TABLE = 13
local commandHistory = {}
local luaEnv=setmetatable({
["exit"] = setmetatable({}, {
__tostring = function() return "Call exit() to exit." end,
__call = function() syscall.exit() end,
}),
["_echo"] = function(...)
return ...
end,
},{__index=_ENV})
local function c(col) syscall.devctl(1, "sfgc", col) end
local function w(s) syscall.write(1, tostring(s)) end
local function getUserInput()
syscall.devctl(1,"sfgc",1)
printInline("lua> ")
local curOffsetStr = syscall.devctl(1, "gpos")
local curOffsetX = tonumber(curOffsetStr:sub(1, curOffsetStr:find(";")-1))
local curOffsetY = tonumber(curOffsetStr:sub(curOffsetStr:find(";")+1))
local MAX_DEPTH = 6
local MAX_ENTRIES = 64
local input = ""
local blinkState = false
local cursorPos = 1
local history = 0
local function prettyVal(val, indent, seen)
indent = indent or 0
seen = seen or {}
local t = type(val)
while true do
local key=syscall.read(0)
if key then
if key == "\19" then --TODO: REPLACE WITH LEFT ARROW
if cursorPos > 1 then
cursorPos = cursorPos - 1
end
elseif key == "\20" then --TODO: REPLACE WITH RIGHT ARROW
if cursorPos <= #input then
cursorPos = cursorPos + 1
end
elseif key == "\17" then --TODO: REPLACE WITH UP ARROW
if history < #commandHistory then
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
printInline((" "):rep(#input + 1))
history = history + 1
input = commandHistory[#commandHistory - history + 1]
cursorPos = #input + 1
end
elseif key == "\18" then --TODO: REPLACE WITH DOWN ARROW
if history > 1 then
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
printInline((" "):rep(#input + 1))
history = history - 1
input = commandHistory[#commandHistory - history + 1]
cursorPos = #input + 1
elseif history == 1 then
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
printInline((" "):rep(#input + 1))
history = 0
input = ""
cursorPos = 1
end
elseif key == "\b" then
if cursorPos > 1 then
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
printInline((" "):rep(#input + 1))
input = string.sub(input, 1, cursorPos-2)..string.sub(input, cursorPos)
cursorPos = cursorPos - 1
end
elseif key == "\n" then
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
print(input.." ")
return input
else
input = string.sub(input, 1, cursorPos-1)..key..string.sub(input, cursorPos)
cursorPos = cursorPos + 1
end
local screenSizeStr = syscall.devctl(1, "size")
local sizeX = tonumber(screenSizeStr:sub(1, screenSizeStr:find(";")-1))
local sizeY = tonumber(screenSizeStr:sub(screenSizeStr:find(";")+1))
local totalChars = sizeX * sizeY
local eocCharNum = ((curOffsetY - 1) * sizeX) + curOffsetX + #input
if eocCharNum >= totalChars then
syscall.devctl(1,"spos",sizeX,sizeY)
printInline(" ")
curOffsetY = curOffsetY - 1
end
end
syscall.devctl(1,"spos",curOffsetX,curOffsetY)
printInline(string.sub(input, 1, cursorPos-1))
if blinkState then
syscall.devctl(1,"sfgc",16)
syscall.devctl(1,"sbgc",1)
end
if cursorPos > #input then
printInline(" ")
if t == "nil" then
c(C_NIL); w("nil")
elseif t == "boolean" then
c(C_BOOL); w(tostring(val))
elseif t == "number" then
c(C_NUM)
if val ~= val then
w("nan")
elseif val == math.huge then
w("inf")
elseif val == -math.huge then
w("-inf")
elseif val == math.floor(val) and math.abs(val) < 1e15 then
w(tostring(math.floor(val)))
else
printInline(string.sub(input, cursorPos, cursorPos))
w(tostring(val))
end
syscall.devctl(1,"sfgc",1)
syscall.devctl(1,"sbgc",16)
printInline(string.sub(input, cursorPos+1))
if cursorPos <= #input then
printInline(" ")
elseif t == "string" then
c(C_STR)
local s = string.format("%q", val)
w(s)
elseif t == "function" then
c(C_TABLE); w(tostring(val))
elseif t == "table" then
if seen[val] then
c(C_TABLE); w("<circular " .. tostring(val) .. ">")
return
end
local curBlink = ((math.floor(syscall.getUptime() / 500) % 2) == 0)
if curBlink ~= blinkState then
blinkState = curBlink
if indent >= MAX_DEPTH then
c(C_TABLE); w("<table " .. tostring(val) .. ">")
return
end
seen[val] = true
local pad = string.rep(" ", indent)
local padIn = string.rep(" ", indent + 1)
local arrKeys = {}
local hashKeys = {}
local arrMax = #val
for k in pairs(val) do
if type(k) == "number" and k >= 1 and k <= arrMax and k == math.floor(k) then
arrKeys[#arrKeys+1] = k
else
hashKeys[#hashKeys+1] = k
end
end
table.sort(arrKeys)
table.sort(hashKeys, function(a, b)
local ta, tb = type(a), type(b)
if ta == tb then
if ta == "string" then return a < b end
if ta == "number" then return a < b end
return tostring(a) < tostring(b)
end
return ta < tb
end)
local total = #arrKeys + #hashKeys
if total == 0 then
c(C_TABLE); w("{}")
seen[val] = nil
return
end
c(C_TABLE); w("{\n")
local shown = 0
local function printEntry(k, v, isLast)
shown = shown + 1
if shown > MAX_ENTRIES then return true end
w(padIn)
if type(k) == "number" and arrKeys[k] then
else
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
c(C_KEY); w(k)
else
c(C_TABLE); w("[")
prettyVal(k, indent+1, seen)
c(C_TABLE); w("]")
end
c(C_TABLE); w(" = ")
end
prettyVal(v, indent+1, seen)
c(C_TABLE)
if not isLast then w(",") end
w("\n")
end
for i, k in ipairs(arrKeys) do
w(padIn)
prettyVal(val[k], indent+1, seen)
c(C_TABLE)
if i < total then w(",") end
w("\n")
shown = shown + 1
if shown >= MAX_ENTRIES then
c(C_NIL); w(padIn .. "-- ..." .. (total - shown) .. " more entries\n")
break
end
end
if shown < MAX_ENTRIES then
for i, k in ipairs(hashKeys) do
local isLast = (shown + 1 >= total)
w(padIn)
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
c(C_KEY); w(k)
else
c(C_TABLE); w("[")
prettyVal(k, indent+1, seen)
c(C_TABLE); w("]")
end
c(C_TABLE); w(" = ")
prettyVal(val[k], indent+1, seen)
c(C_TABLE)
shown = shown + 1
if shown < total then w(",") end
w("\n")
if shown >= MAX_ENTRIES then
local rem = total - shown
if rem > 0 then
c(C_NIL); w(padIn .. "-- ..." .. rem .. " more entries\n")
end
break
end
end
end
c(C_TABLE); w(pad .. "}")
seen[val] = nil
else
c(C_TABLE); w(tostring(val))
end
c(1)
end
local function printResults(...)
local n = select("#", ...)
if n == 0 then return end
for i = 1, n do
if i > 1 then c(C_TABLE); w("\t") end
prettyVal(select(i, ...), 0, {})
end
w("\n")
c(1)
end
local luaEnv = setmetatable({}, {__index = _ENV})
luaEnv._G = luaEnv
luaEnv.print = function(...)
local n = select("#", ...)
for i = 1, n do
if i > 1 then w("\t") end
prettyVal(select(i, ...), 0, {})
end
w("\n")
c(1)
end
luaEnv.pp = function(val)
prettyVal(val, 0, {})
w("\n")
c(1)
end
luaEnv.exit = setmetatable({}, {
__tostring = function() return "function: exit()" end,
__call = function() syscall.exit() end,
})
local function compile(code)
local exprFn = load("return " .. code, "@lua", "t", luaEnv)
if exprFn then return exprFn, true end
local stmtFn, err = load(code, "@lua", "t", luaEnv)
return stmtFn, false, err
end
local function isIncomplete(code)
local _, err = load(code, "@lua", "t", luaEnv)
return err and (err:find("<eof>") ~= nil or err:find("'end'") ~= nil
or err:find("'then'") ~= nil or err:find("'until'") ~= nil)
end
local function cleanErr(msg)
return tostring(msg)
:gsub("^%[string .-%]:", "")
:gsub("^@lua:", "")
:gsub("stack traceback:.*", "")
:match("^%s*(.-)%s*$")
end
local function runCode(code)
local func, err = load(code, "@lua", "t", luaEnv)
local isReturn = false
if load("return "..code) then
func, err = load("return _echo("..code.."\n)", "@lua", "t", luaEnv)
isReturn = true
end
if not func then
local errSL = string.sub(err, string.find(err, ":") + 1)
syscall.devctl(1,"sfgc",2)
printInline("@lua: Load error on line ")
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
syscall.devctl(1,"sfgc",1)
print(string.sub(errSL, string.find(errSL, ":") + 1))
local fn, isExpr, err = compile(code)
if not fn then
c(C_ERR); w("[error] "); c(1); w(cleanErr(err) .. "\n")
return
end
local success, msg = xpcall(func, debug.traceback)
if not success then
local errSL = string.sub(msg, string.find(msg, ":") + 1)
syscall.devctl(1,"sfgc",2)
printInline("@lua: Runtime error on line ")
print(string.sub(errSL, 1, string.find(errSL, ":") - 1))
syscall.devctl(1,"sfgc",1)
print(string.sub(errSL, string.find(errSL, ":") + 1))
elseif isReturn then
print(tostring(msg))
local results = table.pack(xpcall(fn, debug.traceback))
local ok = table.remove(results, 1)
results.n = results.n - 1
if not ok then
c(C_ERR); w("[error] "); c(1); w(cleanErr(results[1]) .. "\n")
elseif isExpr and results.n > 0 then
c(C_OUT); w("= ")
printResults(table.unpack(results, 1, results.n))
end
end
while true do
local code = getUserInput()
if code ~= "" then
if code ~= commandHistory[#commandHistory] then
table.insert(commandHistory, code)
end
runCode(code)
local function getUserInput(prompt, history)
c(C_PROMPT); w(prompt); c(1)
local pos = syscall.devctl(1, "gpos")
local ox = tonumber(pos:sub(1, pos:find(";")-1))
local oy = tonumber(pos:sub(pos:find(";")+1))
local input = ""
local cursor = 1
local histIdx = 0
local blink = false
local dirty = true
local function redraw()
syscall.devctl(1, "spos", ox, oy)
w(input:sub(1, cursor-1))
if blink then syscall.devctl(1,"sfgc",16); syscall.devctl(1,"sbgc",1) end
w(cursor > #input and " " or input:sub(cursor, cursor))
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
w(input:sub(cursor+1) .. " ")
dirty = false
end
end
while true do
local key = syscall.read(0)
if key and key ~= "" then
if key == "\19" then
if cursor > 1 then cursor = cursor - 1; dirty = true end
elseif key == "\20" then
if cursor <= #input then cursor = cursor + 1; dirty = true end
elseif key == "\17" then
if history and histIdx < #history then
histIdx = histIdx + 1
input = history[#history - histIdx + 1]
cursor = #input + 1; dirty = true
end
elseif key == "\18" then
if histIdx > 1 then
histIdx = histIdx - 1
input = history[#history - histIdx + 1]
cursor = #input + 1; dirty = true
elseif histIdx == 1 then
histIdx = 0; input = ""; cursor = 1; dirty = true
end
elseif key == "\b" then
if cursor > 1 then
input = input:sub(1, cursor-2) .. input:sub(cursor)
cursor = cursor - 1; dirty = true
end
elseif key == "\n" then
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
syscall.devctl(1,"spos",ox,oy)
w(input .. " \n")
return input
else
input = input:sub(1, cursor-1) .. key .. input:sub(cursor)
cursor = cursor + 1; dirty = true
end
end
local nb = (math.floor(syscall.getUptime() / 500) % 2) == 0
if nb ~= blink then blink = nb; dirty = true end
if dirty then redraw() end
end
end
syscall.devctl(1, "clear")
syscall.devctl(1, "spos", 1, 1)
c(C_PROMPT); w("HyperionOS " .. _VERSION .. "\n")
c(C_NIL)
w("Interactive Lua REPL. exit() to quit.\n\n")
c(1)
local history = {}
while true do
local code = getUserInput("lua> ", history)
if code == "" then goto continue end
while isIncomplete(code) do
code = code .. "\n" .. getUserInput("... ", nil)
end
if code ~= history[#history] then
history[#history+1] = code
end
runCode(code)
::continue::
end

View File

@@ -0,0 +1,80 @@
--:Minify:--
-- passwd: change a user's password
-- Usage: passwd [username] (default: current user)
local args = {...}
local targetName = args[1]
local currentUid = syscall.getuid()
local targetUid
if targetName then
targetUid = syscall.auth_getuid(targetName)
if not targetUid then
print("passwd: user '" .. targetName .. "' does not exist")
syscall.exit(1); return
end
-- Only root can change another user's password
if currentUid ~= 0 and targetUid ~= currentUid then
print("passwd: permission denied")
syscall.exit(1); return
end
else
targetUid = currentUid
targetName = syscall.getUsername(currentUid) or tostring(currentUid)
end
-- Non-root must verify their current password first
if currentUid ~= 0 then
printInline("Current password: ")
local cur = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then syscall.write(1,"\n"); break
elseif ch == "\b" then
if #cur > 0 then cur=cur:sub(1,-2); syscall.write(1,"\b \b") end
else cur=cur..ch; syscall.write(1,"*") end
end
local ok, err = syscall.auth_elevate(targetName, cur)
if not ok then
sleep(1)
print("passwd: authentication failure")
syscall.exit(1); return
end
end
printInline("New password: ")
local pw1 = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then syscall.write(1,"\n"); break
elseif ch == "\b" then
if #pw1 > 0 then pw1=pw1:sub(1,-2); syscall.write(1,"\b \b") end
else pw1=pw1..ch; syscall.write(1,"*") end
end
printInline("Confirm password: ")
local pw2 = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then syscall.write(1,"\n"); break
elseif ch == "\b" then
if #pw2 > 0 then pw2=pw2:sub(1,-2); syscall.write(1,"\b \b") end
else pw2=pw2..ch; syscall.write(1,"*") end
end
if pw1 ~= pw2 then
print("passwd: passwords do not match")
syscall.exit(1); return
end
local ok, err = syscall.auth_setpassword(targetUid, pw1)
if not ok then
print("passwd: " .. tostring(err))
syscall.exit(1); return
end
print("passwd: password updated for '" .. targetName .. "'")

72
Src/Hyperion-bash/bin/su Normal file
View File

@@ -0,0 +1,72 @@
--:Minify:--
local fs = require("sys.fs")
local targetUser = ({...})[1] or "root"
local currentUid = syscall.getuid()
local currentUser = syscall.getUsername(currentUid) or tostring(currentUid)
local targetUid = syscall.auth_getuid(targetUser)
if not targetUid then
print("su: user '" .. targetUser .. "' does not exist")
syscall.exit(1)
return
end
local ok, err
if currentUid == 0 then
ok = true
else
printInline("Password: ")
local pw = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then
syscall.write(1, "\n")
break
elseif ch == "\b" then
if #pw > 0 then pw = pw:sub(1, -2); syscall.write(1, "\b \b") end
else
pw = pw .. ch
syscall.write(1, "*")
end
end
ok, err = syscall.auth_elevate(targetUser, pw)
if not ok then
sleep(1)
print("su: Authentication failure")
syscall.exit(1)
return
end
end
if currentUid == 0 then
syscall.setuid(targetUid)
end
local pwent = syscall.auth_getpasswd(targetUid)
local shell = (pwent and pwent.shell) or "/bin/hysh"
local homedir = (pwent and pwent.homedir) or "/"
syscall.chdir(homedir)
syscall.setEnviron("HOME", homedir)
syscall.setEnviron("USER", targetUser)
syscall.setEnviron("SHELL", shell)
local shellText = fs.readAllText(shell)
if not shellText then
print("su: shell not found: " .. shell)
syscall.exit(1)
return
end
local shellFn, loadErr = load(shellText, "@" .. shell)
if not shellFn then
print("su: cannot load shell: " .. tostring(loadErr))
syscall.exit(1)
return
end
shellFn()

110
Src/Hyperion-bash/bin/sudo Normal file
View File

@@ -0,0 +1,110 @@
--:Minify:--
local fs = require("sys.fs")
local cmdArgs = {...}
local targetUser = "root"
local i = 1
if cmdArgs[i] == "-u" then
i = i + 1
local uarg = cmdArgs[i] or "root"
local numUid = tonumber(uarg)
if numUid then
local pwent = syscall.auth_getpasswd(numUid)
targetUser = (pwent and pwent.username) or uarg
else
targetUser = uarg
end
i = i + 1
end
local cmd = cmdArgs[i]
if not cmd or cmd == "" then
print("usage: sudo [-u user] <command> [args...]")
syscall.exit(1)
return
end
local restArgs = {}
for j = i + 1, #cmdArgs do restArgs[#restArgs + 1] = cmdArgs[j] end
local currentUid = syscall.getuid()
local currentUser = syscall.getUsername(currentUid) or tostring(currentUid)
local targetUid = syscall.auth_getuid(targetUser)
if not targetUid then
print("sudo: user '" .. targetUser .. "' does not exist")
syscall.exit(1)
return
end
if currentUid ~= 0 then
printInline("[sudo] password for " .. currentUser .. ": ")
local pw = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then
syscall.write(1, "\n")
break
elseif ch == "\b" then
if #pw > 0 then pw = pw:sub(1, -2); syscall.write(1, "\b \b") end
else
pw = pw .. ch
syscall.write(1, "*")
end
end
local ok, err = syscall.auth_elevate(currentUser, pw)
if not ok then
sleep(1)
print("sudo: Authentication failure")
syscall.exit(1)
return
end
if targetUid ~= 0 then
syscall.setuid(targetUid)
end
else
if targetUid ~= currentUid then
syscall.setuid(targetUid)
end
end
local cmdPath = ""
if cmd:find("/") then
if fs.exists(cmd) then cmdPath = cmd end
else
local paths = string.split(syscall.getEnviron("PATH") or "/bin/", ":")
for _, p in ipairs(paths) do
local full = p .. cmd
if fs.exists(full) then cmdPath = full; break end
end
end
if cmdPath == "" then
print("sudo: command not found: " .. cmd)
syscall.exit(1)
return
end
local text = fs.readAllText(cmdPath)
local program, loadErr = load(text, "@" .. cmdPath)
if not program then
print("sudo: cannot load " .. cmd .. ": " .. tostring(loadErr))
syscall.exit(1)
return
end
local pwent = syscall.auth_getpasswd(targetUid)
if pwent and pwent.homedir then
syscall.setEnviron("HOME", pwent.homedir)
end
syscall.setEnviron("USER", targetUser)
local ok, err = xpcall(program, debug.traceback, table.unpack(restArgs))
if not ok then
print("sudo: " .. cmd .. ": " .. tostring(err))
syscall.exit(1)
end

View File

@@ -0,0 +1,67 @@
--:Minify:--
local args = {...}
local i = 1
local opt = { createHome = true }
while i <= #args do
local a = args[i]
if a == "-p" then i=i+1; opt.password = args[i]
elseif a == "-g" then i=i+1; opt.gid = tonumber(args[i])
elseif a == "-d" then i=i+1; opt.homedir = args[i]
elseif a == "-s" then i=i+1; opt.shell = args[i]
elseif a == "-M" then opt.createHome = false
elseif a:sub(1,1) ~= "-" then opt.username = a
else print("useradd: unknown option: " .. a); return end
i = i + 1
end
if not opt.username then
print("Usage: useradd [-p password] [-g gid] [-d homedir] [-s shell] [-M] <username>")
syscall.exit(1); return
end
local password = opt.password
if not password then
printInline("New password: ")
password = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then syscall.write(1,"\n"); break
elseif ch == "\b" then
if #password > 0 then password=password:sub(1,-2); syscall.write(1,"\b \b") end
else password=password..ch; syscall.write(1,"*") end
end
printInline("Confirm password: ")
local pw2 = ""
while true do
local ch = syscall.read(0)
if not ch or ch == "" then
elseif ch == "\n" then syscall.write(1,"\n"); break
elseif ch == "\b" then
if #pw2 > 0 then pw2=pw2:sub(1,-2); syscall.write(1,"\b \b") end
else pw2=pw2..ch; syscall.write(1,"*") end
end
if password ~= pw2 then
print("useradd: passwords do not match")
syscall.exit(1); return
end
end
local uid, err = syscall.auth_newuser(
opt.username, password, opt.gid, opt.homedir, opt.shell or "/bin/hysh"
)
if not uid then
print("useradd: " .. tostring(err))
syscall.exit(1); return
end
if opt.createHome then
local home = opt.homedir or ("/home/" .. opt.username)
local ok, e = pcall(syscall.mkdir, home)
if not ok then
print("useradd: warning: could not create home " .. home .. ": " .. tostring(e))
end
end
print("useradd: created user '" .. opt.username .. "' with uid=" .. tostring(uid))

View File

@@ -0,0 +1,49 @@
--:Minify:--
local args = {...}
local removeHome = false
local username = nil
for _, a in ipairs(args) do
if a == "-r" then removeHome = true
elseif a:sub(1,1) ~= "-" then username = a
else print("userdel: unknown option: " .. a); syscall.exit(1); return end
end
if not username then
print("Usage: userdel [-r] <username>")
syscall.exit(1); return
end
local uid = syscall.auth_getuid(username)
if not uid then
print("userdel: user '" .. username .. "' does not exist")
syscall.exit(1); return
end
local pwent = syscall.auth_getpasswd(uid)
local ok, err = syscall.auth_deleteuser(uid)
if not ok then
print("userdel: " .. tostring(err))
syscall.exit(1); return
end
if removeHome and pwent and pwent.homedir then
local fs = require("sys.fs")
local ok2, err2 = pcall(function()
local function rmdir(path)
for _, f in ipairs(fs.list(path) or {}) do
local full = path .. "/" .. f
if fs.isDir(full) then rmdir(full)
else syscall.remove(full) end
end
syscall.remove(path)
end
if fs.exists(pwent.homedir) then rmdir(pwent.homedir) end
end)
if not ok2 then
print("userdel: warning: could not remove home: " .. tostring(err2))
end
end
print("userdel: deleted user '" .. username .. "'")

View File

@@ -0,0 +1,49 @@
--:Minify:--
local args = {...}
local i = 1
local opt = {}
while i <= #args do
local a = args[i]
if a == "-l" then i=i+1; opt.newname = args[i]
elseif a == "-p" then i=i+1; opt.password = args[i]
elseif a == "-g" then i=i+1; opt.gid = tonumber(args[i])
elseif a == "-d" then i=i+1; opt.homedir = args[i]
elseif a == "-s" then i=i+1; opt.shell = args[i]
elseif a == "-L" then opt.lock = true
elseif a == "-U" then opt.unlock = true
elseif a:sub(1,1) ~= "-" then opt.username = a
else print("usermod: unknown option: " .. a); syscall.exit(1); return end
i = i + 1
end
if not opt.username then
print("Usage: usermod [-l newname] [-p password] [-g gid] [-d homedir] [-s shell] [-L] [-U] <username>")
syscall.exit(1); return
end
if opt.lock and opt.unlock then
print("usermod: -L and -U are mutually exclusive")
syscall.exit(1); return
end
local uid = syscall.auth_getuid(opt.username)
if not uid then
print("usermod: user '" .. opt.username .. "' does not exist")
syscall.exit(1); return
end
local function apply(fn, ...)
local ok, err = fn(...)
if not ok then print("usermod: " .. tostring(err)); syscall.exit(1) end
end
if opt.newname then apply(syscall.auth_setusername, uid, opt.newname) end
if opt.password then apply(syscall.auth_setpassword, uid, opt.password) end
if opt.gid then apply(syscall.auth_setgid, uid, opt.gid) end
if opt.homedir then apply(syscall.auth_sethomedir, uid, opt.homedir) end
if opt.shell then apply(syscall.auth_setshell, uid, opt.shell) end
if opt.lock then apply(syscall.auth_lockuser, uid) end
if opt.unlock then apply(syscall.auth_unlockuser, uid) end
print("usermod: updated user '" .. opt.username .. "'")