neonucleus/data/OpenOS/lib/core/install_basics.lua
IonutParau 687cfebd00 testing version of LuaBIOS and OpenOS
people were having issues getting them to work so now we promote consistency
2025-06-28 20:41:49 +02:00

232 lines
6.7 KiB
Lua

local computer = require("computer")
local shell = require("shell")
local fs = require("filesystem")
local args, options = shell.parse(...)
if options.help then
io.write([[Usage: install [OPTION]...
--from=ADDR install filesystem at ADDR
default: builds list of
candidates and prompts user
--to=ADDR same as --from but for target
--fromDir=PATH install PATH from source
--root=PATH same as --fromDir but target
--toDir=PATH same as --root
-u, --update update files interactively
--label override label from .prop
--nosetlabel do not label target
--nosetboot do not use target for boot
--noreboot do not reboot after install
]])
return nil -- exit success
end
local utils_path = "/lib/core/install_utils.lua"
local utils
local rootfs = fs.get("/")
if not rootfs then
io.stderr:write("no root filesystem, aborting\n")
os.exit(1)
end
local label = args[1]
options.label = label
local source_filter = options.from
local source_filter_dev
if source_filter then
local from_path = shell.resolve(source_filter)
if fs.isDirectory(from_path) then
source_filter_dev = fs.get(from_path)
source_filter = source_filter_dev.address
options.from = from_path
end
end
local target_filter = options.to
local target_filter_dev
if target_filter then
local to_path = shell.resolve(target_filter)
if fs.isDirectory(target_filter) then
target_filter_dev = fs.get(to_path)
target_filter = target_filter_dev.address
options.to = to_path
end
end
local sources = {}
local targets = {}
-- tmpfs is not a candidate unless it is specified
local comps = require("component").list("filesystem")
local devices = {}
-- not all mounts are components, only use components
for dev, path in fs.mounts() do
if comps[dev.address] then
local known = devices[dev]
devices[dev] = known and #known < #path and known or path
end
end
local dev_dev = fs.get("/dev")
devices[dev_dev == rootfs or dev_dev] = nil
local tmpAddress = computer.tmpAddress()
for dev, path in pairs(devices) do
local address = dev.address
local install_path = dev == target_filter_dev and options.to or path
local specified = target_filter and address:find(target_filter, 1, true) == 1
if dev.isReadOnly() then
if specified then
io.stderr:write("Cannot install to " .. options.to .. ", it is read only\n")
os.exit(1)
end
elseif
specified or
not (source_filter and address:find(source_filter, 1, true) == 1) and -- specified for source
not target_filter and
address ~= tmpAddress
then
table.insert(targets, {dev = dev, path = install_path, specified = specified})
end
end
local target = targets[1]
-- if there is only 1 target, the source selection cannot include it
if #targets == 1 then
devices[targets[1].dev] = nil
end
for dev, path in pairs(devices) do
local address = dev.address
local install_path = dev == source_filter_dev and options.from or path
local specified = source_filter and address:find(source_filter, 1, true) == 1
if
fs.list(install_path)() and
(specified or
not source_filter and address ~= tmpAddress and not (address == rootfs.address and not rootfs.isReadOnly()))
then
local prop = {}
local prop_path = path .. "/.prop"
local prop_file = fs.open(prop_path)
if prop_file then
local prop_data = prop_file:read(math.maxinteger or math.huge)
prop_file:close()
local prop_load = load("return " .. prop_data)
prop = prop_load and prop_load()
if not prop then
io.stderr:write("Ignoring " .. path .. " due to malformed prop file\n")
prop = {ignore = true}
end
end
if not prop.ignore then
if not label or label:lower() == (prop.label or dev.getLabel() or ""):lower() then
table.insert(sources, {dev = dev, path = install_path, prop = prop, specified = specified})
end
end
end
end
-- Ask the user to select a source
local source = sources[1]
if #sources ~= 1 then
utils = loadfile(utils_path, "bt", _G)
source = utils("select", "sources", options, sources)
end
if not source then
return
end
options = {
from = source.path .. "/",
fromDir = fs.canonical(options.fromDir or source.prop.fromDir or ""),
root = fs.canonical(options.root or options.toDir or source.prop.root or ""),
update = options.update or options.u,
label = source.prop.label or label,
setlabel = not (options.nosetlabel or options.nolabelset) and source.prop.setlabel,
setboot = not (options.nosetboot or options.noboot) and source.prop.setboot,
reboot = not options.noreboot and source.prop.reboot
}
local source_display = options.label or source.dev.getLabel() or source.path
-- Remove the source from the target options
for index, entry in ipairs(targets) do
if entry.dev == source.dev then
table.remove(targets, index)
target = targets[1]
end
end
-- Ask the user to select a target
if #targets ~= 1 then
if #sources == 1 then
io.write(source_display, " selected for install\n")
end
utils = utils or loadfile(utils_path, "bt", _G)
target = utils("select", "targets", options, targets)
end
if not target then
return
end
options.to = target.path .. "/"
local function resolveFrom(path)
return fs.concat(options.from, options.fromDir) .. "/" .. path
end
local fullTargetPath = fs.concat(options.to, options.root)
local transfer_args = {
{
{resolveFrom("."), fullTargetPath},
{
cmd = "cp",
r = true, v = true, x = true, u = options.update, i = options.update,
skip = {resolveFrom(".prop")},
}
}
}
if source.prop.noclobber and #source.prop.noclobber > 0 then
local noclobber_opts = {cmd = "cp", v = true, n = true}
for _, noclobber in ipairs(source.prop.noclobber or {}) do
local noclobberFrom = resolveFrom(noclobber)
local noclobberTo = fs.concat(fullTargetPath, noclobber)
table.insert(transfer_args[1][2].skip, noclobberFrom)
table.insert(transfer_args, {{noclobberFrom, noclobberTo}, noclobber_opts})
end
end
local special_target = ""
if #targets > 1 or target_filter or source_filter then
special_target = " to " .. transfer_args[1][1][2]
end
io.write("Install " .. source_display .. special_target .. "? [Y/n] ")
if not ((io.read() or "n") .. "y"):match("^%s*[Yy]") then
io.write("Installation cancelled\n")
os.exit()
end
local installer_path = options.from .. "/.install"
if fs.exists(installer_path) then
local installer, reason = loadfile(installer_path, "bt", setmetatable({install = options}, {__index = _G}))
if not installer then
io.stderr:write("installer failed to load: " .. tostring(reason) .. "\n")
os.exit(1)
end
os.exit(installer())
end
options.cp_args = transfer_args
options.target = target
return options