241 lines
6.6 KiB
Lua
241 lines
6.6 KiB
Lua
local _VERSION = '6.1.0';
|
|
|
|
local REPO_BASE = 'https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/master/';
|
|
local LOCAL_STATE_DIR = '/trapos';
|
|
local LOCAL_MANIFEST_PATH = '/trapos/manifest.json';
|
|
local LOCAL_CONFIG_PATH = '/trapos/ccpm.json';
|
|
local LOCAL_LOCK_PATH = '/trapos/ccpm.lock.json';
|
|
local DEFAULT_REGISTRY_NAME = 'guillaumearm/cc-libs';
|
|
local INSTALL_BRANCH = 'master';
|
|
|
|
local function printUsage()
|
|
print('install-trapos usage:');
|
|
print();
|
|
print('\t\twget run <install-url>');
|
|
print('\t\twget run <install-url> --cpm-only');
|
|
end
|
|
|
|
local function readJsonFile(path)
|
|
if not fs.exists(path) then return nil; end
|
|
local f = fs.open(path, 'r');
|
|
if not f then return nil; end
|
|
local data = f.readAll();
|
|
f.close();
|
|
if not data or data == '' then return nil; end
|
|
return textutils.unserializeJSON(data);
|
|
end
|
|
|
|
local function writeJsonFile(path, value)
|
|
fs.makeDir(LOCAL_STATE_DIR);
|
|
local f = fs.open(path, 'w');
|
|
if not f then return false; end
|
|
f.write(textutils.serializeJSON(value));
|
|
f.close();
|
|
return true;
|
|
end
|
|
|
|
local function fetchJson(url)
|
|
local response = http.get(url);
|
|
if not response then return nil end
|
|
local body = response.readAll();
|
|
response.close();
|
|
if not body or body == '' then return nil end
|
|
return textutils.unserializeJSON(body);
|
|
end
|
|
|
|
local function fetchManifest()
|
|
return fetchJson(REPO_BASE .. 'manifest.json');
|
|
end
|
|
|
|
local function fetchDescriptor(pkg)
|
|
return fetchJson(REPO_BASE .. 'packages/' .. pkg .. '/ccpm.json');
|
|
end
|
|
|
|
local function ensureProgramsPath()
|
|
local current = shell.path();
|
|
for entry in string.gmatch(current, '[^:]+') do
|
|
if entry == '/programs' then return; end
|
|
end
|
|
if current == '' then
|
|
shell.setPath('/programs');
|
|
return;
|
|
end
|
|
shell.setPath(current .. ':/programs');
|
|
end
|
|
|
|
-- Resolve a list of package names + their dependencies into install order
|
|
-- (deps first). Returns an ordered list of descriptors or nil, err.
|
|
local function resolvePackages(names)
|
|
local ordered = {};
|
|
local state = {}; -- name -> 'visiting' | 'done'
|
|
|
|
local function visit(name)
|
|
if state[name] == 'done' then return true end
|
|
if state[name] == 'visiting' then
|
|
return false, 'dependency cycle detected at ' .. name;
|
|
end
|
|
state[name] = 'visiting';
|
|
local desc = fetchDescriptor(name);
|
|
if not desc then
|
|
return false, 'package not found: ' .. name;
|
|
end
|
|
for _, dep in ipairs(desc.dependencies or {}) do
|
|
local ok, err = visit(dep);
|
|
if not ok then return false, err end
|
|
end
|
|
state[name] = 'done';
|
|
ordered[#ordered + 1] = desc;
|
|
return true;
|
|
end
|
|
|
|
for _, name in ipairs(names) do
|
|
local ok, err = visit(name);
|
|
if not ok then return nil, err end
|
|
end
|
|
return ordered;
|
|
end
|
|
|
|
-- Seed/refresh the default ccpm registry so it tracks the install branch.
|
|
local function seedCcpmConfig()
|
|
local cfg = readJsonFile(LOCAL_CONFIG_PATH) or { registries = {} };
|
|
cfg.registries = cfg.registries or {};
|
|
local found = false;
|
|
for _, r in ipairs(cfg.registries) do
|
|
if r.name == DEFAULT_REGISTRY_NAME then
|
|
r.type = 'gitea';
|
|
r.branch = INSTALL_BRANCH;
|
|
found = true;
|
|
end
|
|
end
|
|
if not found then
|
|
table.insert(cfg.registries, 1, { name = DEFAULT_REGISTRY_NAME, type = 'gitea', branch = INSTALL_BRANCH });
|
|
end
|
|
writeJsonFile(LOCAL_CONFIG_PATH, cfg);
|
|
end
|
|
|
|
local rawArgs = table.pack(...);
|
|
local cpmOnly = false;
|
|
|
|
for i = 1, rawArgs.n do
|
|
local a = rawArgs[i];
|
|
if a == 'version' or a == '-version' or a == '--version' then
|
|
print('install-trapos v' .. _VERSION);
|
|
return;
|
|
elseif a == 'help' or a == '-help' or a == '--help' then
|
|
printUsage();
|
|
return;
|
|
elseif a == '--cpm-only' or a == '-cpm-only' then
|
|
cpmOnly = true;
|
|
elseif a ~= nil and a ~= '' then
|
|
printUsage();
|
|
return;
|
|
end
|
|
end
|
|
|
|
print('Fetching manifest from branch: ' .. INSTALL_BRANCH);
|
|
local manifest = fetchManifest();
|
|
if not manifest then
|
|
print('Failed to fetch or parse manifest.json from ' .. INSTALL_BRANCH);
|
|
return;
|
|
end
|
|
|
|
local requested = { 'trapos-core' };
|
|
local explicitBootstrap = {};
|
|
if cpmOnly then
|
|
for _, name in ipairs(requested) do
|
|
explicitBootstrap[name] = true;
|
|
end
|
|
end
|
|
|
|
local resolved, resolveErr = resolvePackages(requested);
|
|
if not resolved then
|
|
print('Failed to resolve packages: ' .. resolveErr);
|
|
return;
|
|
end
|
|
|
|
-- Legacy file cleanup (pre-manifest installs).
|
|
fs.delete('ping-server.lua');
|
|
fs.delete('ping.lua');
|
|
fs.delete('cube.lua');
|
|
fs.delete('router.lua');
|
|
fs.delete('servers/cube-startup.lua');
|
|
fs.delete('programs/cube.lua');
|
|
fs.delete('programs/goo.lua');
|
|
fs.delete('servers/cube-server.lua');
|
|
fs.delete('servers/cube-boot.lua');
|
|
|
|
local previousDir = shell.dir();
|
|
shell.setDir('/');
|
|
|
|
fs.makeDir('/programs');
|
|
fs.makeDir('/apis');
|
|
fs.makeDir('/startup');
|
|
fs.makeDir('/servers');
|
|
fs.makeDir(LOCAL_STATE_DIR);
|
|
|
|
local allFiles = {};
|
|
local seenFile = {};
|
|
local autostart = {};
|
|
local seenAuto = {};
|
|
local lockPackages = {};
|
|
|
|
for _, desc in ipairs(resolved) do
|
|
for _, filePath in ipairs(desc.files or {}) do
|
|
if not seenFile[filePath] then
|
|
seenFile[filePath] = true;
|
|
allFiles[#allFiles + 1] = filePath;
|
|
fs.delete(filePath);
|
|
-- Sources live under packages/<pkg>/ in the repo but install flat, so
|
|
-- fetch from the package path and write to the flat destination.
|
|
shell.execute('wget', REPO_BASE .. 'packages/' .. desc.name .. '/' .. filePath, filePath);
|
|
end
|
|
end
|
|
for _, srv in ipairs(desc.autostart or {}) do
|
|
if not seenAuto[srv] then
|
|
seenAuto[srv] = true;
|
|
autostart[#autostart + 1] = srv;
|
|
end
|
|
end
|
|
lockPackages[desc.name] = {
|
|
version = desc.version,
|
|
registry = DEFAULT_REGISTRY_NAME,
|
|
files = desc.files or {},
|
|
dependencies = desc.dependencies or {},
|
|
autostart = desc.autostart or {},
|
|
explicit = explicitBootstrap[desc.name] == true,
|
|
};
|
|
end
|
|
|
|
-- Aggregated OS state for motd/servers/upgrade (unchanged consumers).
|
|
writeJsonFile(LOCAL_MANIFEST_PATH, {
|
|
name = manifest.name or 'TrapOS',
|
|
version = manifest.version or '?',
|
|
branch = INSTALL_BRANCH,
|
|
files = allFiles,
|
|
autostart = autostart,
|
|
});
|
|
|
|
writeJsonFile(LOCAL_LOCK_PATH, { packages = lockPackages });
|
|
seedCcpmConfig();
|
|
ensureProgramsPath();
|
|
|
|
print();
|
|
print('=> ccpm installed (branch: ' .. INSTALL_BRANCH .. ')');
|
|
print('=> Default registry: ' .. DEFAULT_REGISTRY_NAME);
|
|
|
|
if cpmOnly then
|
|
print('=> Run: ccpm update');
|
|
print('=> Run: ccpm install trapos');
|
|
shell.setDir(previousDir);
|
|
return;
|
|
end
|
|
|
|
-- Full install: sync the registry and install the TrapOS meta-package.
|
|
shell.execute('ccpm', 'update');
|
|
shell.execute('ccpm', 'install', 'trapos');
|
|
shell.execute('trapos-postinstall');
|
|
shell.setDir(previousDir);
|
|
print();
|
|
print('=> Rebooting...');
|
|
os.reboot();
|