cc-libs/install-ccpm.lua

256 lines
6.8 KiB
Lua

local _VERSION = '5.1.0';
local REPO_BASE = 'https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/';
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 function printUsage()
print('install-ccpm usage:');
print();
print('\t\twget run <install-url>');
print('\t\twget run <install-url> --beta');
print('\t\twget run <install-url> --stable');
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 confirmBeta()
print();
print('You are about to install the BETA branch (next).');
print('Beta builds may be unstable. Continue? (y/N)');
write('> ');
local answer = read();
if not answer then return false; end
answer = answer:lower();
return answer == 'y' or answer == 'yes';
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(branch)
return fetchJson(REPO_BASE .. branch .. '/manifest.json');
end
local function fetchDescriptor(branch, pkg)
return fetchJson(REPO_BASE .. branch .. '/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(branch, 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(branch, 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(branch)
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 = branch;
found = true;
end
end
if not found then
table.insert(cfg.registries, 1, { name = DEFAULT_REGISTRY_NAME, type = 'gitea', branch = branch });
end
writeJsonFile(LOCAL_CONFIG_PATH, cfg);
end
local rawArgs = table.pack(...);
local forceBeta, forceStable = false, false;
for i = 1, rawArgs.n do
local a = rawArgs[i];
if a == 'version' or a == '-version' or a == '--version' then
print('install-ccpm v' .. _VERSION);
return;
elseif a == 'help' or a == '-help' or a == '--help' then
printUsage();
return;
elseif a == '--beta' or a == '-beta' then
forceBeta = true;
elseif a == '--stable' or a == '-stable' then
forceStable = true;
elseif a ~= nil and a ~= '' then
printUsage();
return;
end
end
local localManifest = readJsonFile(LOCAL_MANIFEST_PATH);
local localBranch = localManifest and localManifest.branch or nil;
local branch;
if forceBeta then
branch = 'next';
if localBranch ~= 'next' then
if not confirmBeta() then
print('Aborted.');
return;
end
end
elseif forceStable then
branch = 'master';
else
branch = localBranch or 'master';
end
print('Fetching manifest from branch: ' .. branch);
local manifest = fetchManifest(branch);
if not manifest then
print('Failed to fetch or parse manifest.json from ' .. branch);
return;
end
local requested = { 'trapos-core' };
local resolved, resolveErr = resolvePackages(branch, requested);
if not resolved then
print('Failed to resolve packages: ' .. resolveErr);
return;
end
local REPO_PREFIX = REPO_BASE .. branch .. '/';
-- 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);
shell.execute('wget', REPO_PREFIX .. 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 {},
};
end
-- Aggregated OS state for motd/servers/upgrade (unchanged consumers).
writeJsonFile(LOCAL_MANIFEST_PATH, {
name = manifest.name or 'TrapOS',
version = manifest.version or '?',
branch = branch,
files = allFiles,
autostart = autostart,
});
writeJsonFile(LOCAL_LOCK_PATH, { packages = lockPackages });
seedCcpmConfig(branch);
ensureProgramsPath();
print();
print('=> ccpm installed (branch: ' .. branch .. ')');
print('=> Default registry: ' .. DEFAULT_REGISTRY_NAME);
print('=> Run: ccpm update');
print('=> Run: ccpm install trapos');
if fs.exists('/startup/servers.lua') then
shell.execute('/startup/servers.lua');
end
shell.setDir(previousDir);