/ install.lua
install.lua
1 --[[ 2 ToasterGen Spin 3 4 Copyright (C) 2025 Clifton Toaster Reid <cliftontreid@duck.com> 5 6 This library is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Lesser General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 ]] 11 12 -- Configuration 13 local CONFIG = { 14 GITHUB_USER = "cliftontoaster-reid", 15 GITHUB_REPO = "cc-roulette", 16 TOOLS_DIR = "/tools", 17 ROULETTE_DIR = "/tools/roulette", 18 ARCHIVE_DIR = "/tools/cc-archive", 19 TEMP_DIR = "/tmp", 20 LOG_FILE = "/tools/roulette-install.log", 21 LOG_LEVEL = "DEBUG", -- DEBUG, INFO, WARNING, ERROR 22 ARCHIVE_FILES = { 23 "LibDeflate.lua", 24 "ar.lua", 25 "archive.lua", 26 "arlib.lua", 27 "gzip.lua", 28 "muxzcat.lua", 29 "tar.lua", 30 "unxz.lua", 31 }, 32 VERSION_FILE = "/tools/roulette/version", 33 } 34 35 local Logger = require("src.log") 36 local semver = require("src.semver") 37 38 local args = { ... } 39 local forceDev = false 40 local forceDel = false 41 local quietRun = false 42 local logLevel = CONFIG.LOG_LEVEL 43 for i, arg in ipairs(args) do 44 if arg == "--dev" then 45 forceDev = true 46 elseif arg == "--del" then 47 forceDel = true 48 elseif arg == "--quiet" then 49 quietRun = true 50 elseif arg == "--log-level" then 51 logLevel = args[i + 1] or logLevel 52 end 53 end 54 55 Logger.setLogLevel(logLevel) 56 57 --- Fetches the releases for a given GitHub repository. 58 --- @param githubUser string The GitHub username of the repository owner. 59 --- @param githubRepo string The GitHub repository name. 60 --- @param logger Logger The logger to use for output. 61 --- @return Release[]|nil The releases for the repository, or nil if an error occurred. 62 local function getReleases(githubUser, githubRepo, logger) 63 local url = string.format("https://api.github.com/repos/%s/%s/releases", githubUser, githubRepo) 64 logger.info("Fetching releases from GitHub API...") 65 local response = http.get(url) 66 if not response then 67 logger.error("Failed to fetch releases from GitHub API") 68 return nil 69 end 70 local data = response.readAll() 71 response.close() 72 logger.debug("Received " .. #data .. " bytes of release data") 73 return textutils.unserializeJSON(data) 74 end 75 76 -- Utility functions 77 local function printHeader(text) 78 local oldColor = term.getTextColor() 79 term.setTextColor(colors.cyan) 80 print("======================================") 81 print(text) 82 print("======================================") 83 term.setTextColor(oldColor) 84 Logger.debug("Header displayed: " .. text) 85 end 86 87 local function printFooter() 88 local oldColor = term.getTextColor() 89 term.setTextColor(colors.cyan) 90 print("======================================") 91 term.setTextColor(oldColor) 92 Logger.debug("Footer displayed") 93 end 94 95 local function ensureDirectory(path) 96 if not fs.exists(path) then 97 fs.makeDir(path) 98 Logger.debug("Created directory: " .. path) 99 return true 100 end 101 Logger.debug("Directory already exists: " .. path) 102 return false 103 end 104 105 local function downloadFile(url, path, binary) 106 Logger.debug("Downloading file from " .. url .. " to " .. path) 107 local response = http.get(url, nil, binary) 108 if not response then 109 Logger.error("Failed to download from " .. url) 110 return false, "Failed to download from " .. url 111 end 112 113 local data = response.readAll() 114 response.close() 115 116 local file = fs.open(path, "wb") 117 file.write(data) 118 file.close() 119 120 Logger.debug("Successfully downloaded file to " .. path) 121 return true 122 end 123 124 local function downloadCCArchive() 125 if fs.exists(CONFIG.ARCHIVE_DIR) then 126 Logger.info("CC-Archive already exists at " .. CONFIG.ARCHIVE_DIR) 127 return true 128 end 129 130 Logger.info("Downloading CC-Archive...") 131 ensureDirectory(CONFIG.TOOLS_DIR) 132 ensureDirectory(CONFIG.ARCHIVE_DIR) 133 134 local baseUrl = "https://github.com/MCJack123/CC-Archive/raw/refs/heads/master/" 135 local totalFiles = #CONFIG.ARCHIVE_FILES 136 137 for i, file in ipairs(CONFIG.ARCHIVE_FILES) do 138 Logger.info(string.format("[%d/%d] Downloading %s", i, totalFiles, file)) 139 local success, err = downloadFile(baseUrl .. file, CONFIG.ARCHIVE_DIR .. "/" .. file) 140 if not success then 141 Logger.error("Failed to download " .. file .. ": " .. error) 142 error(err) 143 end 144 end 145 146 Logger.success("Successfully downloaded all CC-Archive components") 147 return true 148 end 149 150 local function downloadAndExtractRelease(release) 151 Logger.info("Starting download of release " .. release.tag_name) 152 ensureDirectory(CONFIG.TEMP_DIR) 153 154 local tempFile = CONFIG.TEMP_DIR .. "/release.tar.gz" 155 if fs.exists(tempFile) then 156 Logger.debug("Removing existing temp file: " .. tempFile) 157 fs.delete(tempFile) 158 end 159 160 Logger.info("Downloading release archive...") 161 local success, err = downloadFile(release.tarball_url, tempFile, true) 162 if not success then 163 Logger.error("Failed to download release archive: " .. err) 164 error(err) 165 end 166 167 Logger.info("Extracting release files...") 168 ensureDirectory(CONFIG.ROULETTE_DIR) 169 170 Logger.debug("Running tar command to extract files") 171 shell.run(CONFIG.ARCHIVE_DIR .. "/tar.lua", "xzf", tempFile, "-C", CONFIG.ROULETTE_DIR) 172 173 -- Handle nested directory 174 local files = fs.list(CONFIG.ROULETTE_DIR) 175 if #files == 1 and fs.isDir(CONFIG.ROULETTE_DIR .. "/" .. files[1]) then 176 local folder = files[1] 177 local folderPath = CONFIG.ROULETTE_DIR .. "/" .. folder 178 179 Logger.info("Organizing extracted files from nested directory: " .. folder) 180 local fileCount = 0 181 for _, file in ipairs(fs.list(folderPath)) do 182 fs.move(folderPath .. "/" .. file, CONFIG.ROULETTE_DIR .. "/" .. file) 183 fileCount = fileCount + 1 184 end 185 186 Logger.debug("Moved " .. fileCount .. " files to target directory") 187 Logger.debug("Removing temporary directory: " .. folderPath) 188 fs.delete(folderPath) 189 end 190 191 Logger.success("Release extraction completed successfully") 192 return true 193 end 194 195 local function displayReleaseSummary(release) 196 printHeader("Release Summary for " .. CONFIG.GITHUB_REPO) 197 198 Logger.info("Version: " .. release.tag_name) 199 Logger.info("Name: " .. (release.name or "Unnamed")) 200 Logger.info("Published: " .. (release.published_at or release.created_at or "Unknown")) 201 202 local status = release.draft and "Draft" or (release.prerelease and "Pre-release" or "Stable") 203 Logger.info("Status: " .. status) 204 Logger.info("Assets: " .. #release.assets) 205 206 if release.body then 207 Logger.info("Description:") 208 local shortDesc = string.sub(release.body, 1, 100) 209 if #release.body > 100 then 210 shortDesc = shortDesc .. "..." 211 end 212 Logger.info(shortDesc) 213 end 214 215 printFooter() 216 end 217 218 -- Main execution 219 local function main() 220 if forceDel then 221 Logger.info("Force deletion (--del) detected. Removing entire tools directory: " .. CONFIG.TOOLS_DIR) 222 if fs.exists(CONFIG.TOOLS_DIR) then 223 fs.delete(CONFIG.TOOLS_DIR) 224 Logger.success("Successfully removed " .. CONFIG.TOOLS_DIR) 225 else 226 Logger.warning("No tools directory found at " .. CONFIG.TOOLS_DIR) 227 end 228 end 229 230 local update = false 231 232 Logger.info("Starting installation of " .. CONFIG.GITHUB_REPO .. "...") 233 234 -- If the --dev flag is present, force install from the provided URL 235 if forceDev then 236 Logger.info("Force installation (--dev) detected. Forcing installation from dev release.") 237 238 local latest = { 239 tag_name = "vDEV", 240 tarball_url = "https://github.com/cliftontoaster-reid/cc-roulette/archive/main.tar.gz", 241 } 242 243 if fs.exists(CONFIG.ROULETTE_DIR) then 244 Logger.info("Removing existing installation due to --dev flag") 245 fs.delete(CONFIG.ROULETTE_DIR) 246 end 247 248 downloadCCArchive() 249 downloadAndExtractRelease(latest) 250 251 printHeader("Installation complete") 252 Logger.success("Installation (dev) of " .. CONFIG.GITHUB_REPO .. " completed successfully") 253 254 local versionFile = fs.open(CONFIG.VERSION_FILE, "w") 255 versionFile.writeLine(latest.tag_name:sub(2)) 256 versionFile.close() 257 258 local config = require("tools.config") 259 if config.askYesNo("Would you like to configure the program now?") then 260 local dev = config.askOption("What device would you like to configure?", { "Table", "Server" }) 261 if dev == "Table" then 262 config.configTable() 263 elseif dev == "Server" then 264 error("Server configuration not yet implemented, we apologize for the inconvenience") 265 end 266 end 267 268 Logger.debug("Exiting the program (dev mode)...") 269 return true 270 end 271 272 local releases = getReleases(CONFIG.GITHUB_USER, CONFIG.GITHUB_REPO, Logger) 273 274 if not releases or #releases == 0 then 275 Logger.error("No releases found for repository") 276 return 277 end 278 279 local latest = releases[1] 280 Logger.info("Found latest release: " .. latest.tag_name) 281 282 displayReleaseSummary(latest) 283 284 if fs.exists(CONFIG.ROULETTE_DIR) then 285 update = true 286 Logger.info("Existing installation found at " .. CONFIG.ROULETTE_DIR) 287 288 local versionFile = fs.open(CONFIG.VERSION_FILE, "r") 289 if versionFile then 290 local currentVersion = versionFile.readLine() 291 versionFile.close() 292 293 local cVer = semver.parse(currentVersion) 294 local lVer = semver.parse(latest.tag_name:sub(2)) 295 296 Logger.debug("Installed version: " .. currentVersion) 297 Logger.debug("Latest version: " .. latest.tag_name) 298 if not cVer or not lVer or semver.ge(cVer, lVer) then 299 Logger.success("Installed version is up to date") 300 return 301 else 302 Logger.info("Updating from version " .. currentVersion .. " to " .. latest.tag_name) 303 fs.delete(CONFIG.ROULETTE_DIR) 304 end 305 else 306 Logger.warning("Failed to read version file, continuing with installation") 307 fs.delete(CONFIG.ROULETTE_DIR) 308 end 309 end 310 311 downloadCCArchive() 312 downloadAndExtractRelease(latest) 313 314 printHeader("Installation complete") 315 Logger.success("Installation of " .. CONFIG.GITHUB_REPO .. " completed successfully") 316 317 local versionFile = fs.open(CONFIG.VERSION_FILE, "w") 318 versionFile.writeLine(latest.tag_name:sub(2)) 319 versionFile.close() 320 321 local config = require("tools.config") 322 323 if not update or not quietRun then 324 if config.askYesNo("Would you like to configure the program now?") then 325 local dev = config.askOption("What device would you like to configure?", { "Table", "Server" }) 326 if dev == "Table" then 327 config.configTable() 328 elseif dev == "Server" then 329 error("Server configuration not yet implemented, we apologize for the inconvenience") 330 end 331 end 332 end 333 334 Logger.debug("Exiting the program...") 335 return true 336 end 337 338 -- Run the program 339 main()