local _VERSION = '5.0.1'; local REPO_BASE = 'https://raw.githubusercontent.com/guillaumearm/cc-libs/'; 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 '); print('\t\twget run --beta'); print('\t\twget run --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 = 'github'; r.branch = branch; found = true; end end if not found then table.insert(cfg.registries, 1, { name = DEFAULT_REGISTRY_NAME, type = 'github', 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);