256 lines
6.8 KiB
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);
|