|
| 1 | +import fs from "fs"; |
| 2 | +import path from "path"; |
| 3 | +import { fileURLToPath } from "url"; |
| 4 | +import consola from "consola"; |
| 5 | +import { TServer } from "./server"; |
| 6 | +import { version } from "./package.json"; |
| 7 | +import open from "open"; |
| 8 | +import { exec } from "child_process"; |
| 9 | + |
| 10 | +consola.info("Bootstrapping TerbiumOS [v" + version + "]"); |
| 11 | + |
| 12 | +export default async function Bootstrap() { |
| 13 | + const args = process.argv |
| 14 | + await BuildApps(); |
| 15 | + await CreateAppsPaths(); |
| 16 | + if (!fs.existsSync(".env")) await CreateEnv(); |
| 17 | + await Updater(); |
| 18 | + consola.success("TerbiumOS bootstrapped successfully"); |
| 19 | + if (!(args.includes("--apps-only") || args.includes("--dev"))) { |
| 20 | + TServer(); |
| 21 | + } |
| 22 | +} |
| 23 | + |
| 24 | +export async function BuildApps() { |
| 25 | + consola.start("Building apps..."); |
| 26 | + const __dirname = path.dirname(fileURLToPath(import.meta.url)), |
| 27 | + baseDir = path.join(__dirname, "./public/apps"), |
| 28 | + outputDir = path.join(__dirname, "./src"), |
| 29 | + outputJsonPath = path.join(outputDir, "apps.json"), |
| 30 | + result: { name: string; config: any }[] = []; |
| 31 | + function scanDirectory(dir: string) { |
| 32 | + fs.readdirSync(dir, { withFileTypes: true }).forEach((i) => { |
| 33 | + if (i.isDirectory()) { |
| 34 | + const indexFilePath = path.join(dir, i.name, "index.json"); |
| 35 | + if (fs.existsSync(indexFilePath)) { |
| 36 | + try { |
| 37 | + const data = JSON.parse(fs.readFileSync(indexFilePath, "utf-8")); |
| 38 | + if (data.name && data.config) { |
| 39 | + if (data.name !== "Browser") { |
| 40 | + data.config.src = data.config.src.replace(`/apps/${data.name.toLowerCase()}.tapp/`, `/fs/apps/system/${data.name.toLowerCase()}.tapp/`); |
| 41 | + data.config.icon = data.config.icon.replace(`/apps/${data.name.toLowerCase()}.tapp/`, `/fs/apps/system/${data.name.toLowerCase()}.tapp/`); |
| 42 | + } else { |
| 43 | + return; |
| 44 | + } |
| 45 | + result.push({ name: data.name, config: data.config }); |
| 46 | + } |
| 47 | + } catch (t) { |
| 48 | + consola.error(`Error parsing ${indexFilePath}:`, t.message); |
| 49 | + } |
| 50 | + } |
| 51 | + } |
| 52 | + }); |
| 53 | + } |
| 54 | + scanDirectory(baseDir), |
| 55 | + fs.existsSync(path.join(__dirname, "./build")) || fs.mkdirSync(path.join(__dirname, "./build")), |
| 56 | + fs.existsSync(outputJsonPath) || fs.writeFileSync(outputJsonPath, "[]", "utf-8"), |
| 57 | + fs.writeFileSync(outputJsonPath, JSON.stringify(result, null, 2), "utf-8"), |
| 58 | + consola.success(`Aggregated JSON saved to ${outputJsonPath}`); |
| 59 | + exec("git rev-parse HEAD", (error, stdout, stderr) => { |
| 60 | + if (error || stderr) { |
| 61 | + consola.error("Failed to get git commit hash"); |
| 62 | + } else { |
| 63 | + const hash = stdout.trim(); |
| 64 | + exec("git remote get-url origin", (remoteError, remoteStdout, remoteStderr) => { |
| 65 | + const repoUrl = remoteStdout.trim(); |
| 66 | + const data = { hash, repository: repoUrl.replace("https://github.com/", "") }; |
| 67 | + if (remoteError || remoteStderr) { |
| 68 | + consola.error("Failed to get repository URL"); |
| 69 | + fs.writeFileSync(path.join(__dirname, "./src/hash.json"), JSON.stringify({ hash: null, repository: null}, null, 2), "utf-8"); |
| 70 | + } else { |
| 71 | + fs.writeFileSync(path.join(__dirname, "./src/hash.json"), JSON.stringify(data, null, 2), "utf-8"); |
| 72 | + consola.success(`Git hash and repo saved to ${path.join(__dirname, "./src/hash.json")}`); |
| 73 | + } |
| 74 | + }); |
| 75 | + } |
| 76 | + }); |
| 77 | +} |
| 78 | + |
| 79 | +export async function CreateAppsPaths() { |
| 80 | + interface Apps { |
| 81 | + [appName: string]: (string | { [path: string]: string[] })[]; |
| 82 | + } |
| 83 | + consola.start("Creating apps paths..."); |
| 84 | + |
| 85 | + const __dirname = path.dirname(fileURLToPath(import.meta.url)), |
| 86 | + baseDir = path.join(__dirname, "./public/apps"), |
| 87 | + outputDir = path.join(__dirname, "./src"), |
| 88 | + outputJsonPath = path.join(outputDir, "installer.json"), |
| 89 | + output: string[] = []; |
| 90 | + |
| 91 | + function collectPaths(dir: string, base: string = dir): void { |
| 92 | + const files: fs.Dirent[] = fs.readdirSync(dir, { withFileTypes: true }); |
| 93 | + files.forEach((file: fs.Dirent) => { |
| 94 | + const fullPath = path.join(dir, file.name); |
| 95 | + const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, "/"); |
| 96 | + if (file.isDirectory()) { |
| 97 | + output.push(relativePath + "/"); |
| 98 | + collectPaths(fullPath, base); |
| 99 | + } else { |
| 100 | + output.push(relativePath); |
| 101 | + } |
| 102 | + }); |
| 103 | + } |
| 104 | + |
| 105 | + const accmp: string[] = []; |
| 106 | + fs.readdirSync(baseDir, { withFileTypes: true }).forEach(app => { |
| 107 | + if (app.isDirectory() && app.name.toLocaleLowerCase().endsWith(".tapp") && !app.name.toLowerCase().includes("browser.tapp")) { |
| 108 | + const appPath = path.join(baseDir, app.name); |
| 109 | + if (app.name.toLowerCase() === "settings.tapp") { |
| 110 | + collectPaths(appPath); |
| 111 | + if (fs.existsSync(path.join(appPath, "accounts"))) { |
| 112 | + collectPaths(path.join(appPath, "accounts")); |
| 113 | + accmp.push(...output.splice(output.indexOf("settings.tapp/accounts/"))); |
| 114 | + } |
| 115 | + } else { |
| 116 | + collectPaths(appPath); |
| 117 | + } |
| 118 | + } |
| 119 | + }); |
| 120 | + |
| 121 | + output.push(...accmp); |
| 122 | + fs.writeFileSync(outputJsonPath, JSON.stringify(output, null, 2), "utf-8"); |
| 123 | + consola.success(`Installer JSON saved to ${outputJsonPath}`); |
| 124 | +} |
| 125 | + |
| 126 | +export async function CreateEnv() { |
| 127 | + const port = await consola.prompt("Enter a port for the server to run on (3000): ", { |
| 128 | + type: "text", |
| 129 | + default: "3000", |
| 130 | + placeholder: "3000", |
| 131 | + cancel: "default", |
| 132 | + }) || 3000; |
| 133 | + const masqr = await consola.prompt("Enable Masqr? (no): ", { |
| 134 | + type: "text", |
| 135 | + default: "false", |
| 136 | + placeholder: "no", |
| 137 | + cancel: "default", |
| 138 | + }); |
| 139 | + if(masqr === "no" || masqr === "false" || masqr === "n") { |
| 140 | + fs.writeFileSync(".env", `MASQR=${false}\nPORT=${port}`); |
| 141 | + } else { |
| 142 | + const licenseServer = await consola.prompt("Enter the masqr license server URL: ") || ""; |
| 143 | + const whitelist = await consola.prompt("Enter a comma separated array of domains to whitelist (Ex: ['https://balls.com', 'https://tomp.app']): ") || []; |
| 144 | + fs.writeFileSync(".env", `MASQR=${true}\nPORT=${port}\nLICENSE_SERVER_URL=${licenseServer}\nWHITELISTED_DOMAINS=${whitelist}\n`); |
| 145 | + } |
| 146 | + consola.success("Environment file created"); |
| 147 | +} |
| 148 | + |
| 149 | +export async function Updater() { |
| 150 | + consola.start("Checking for updates..."); |
| 151 | + exec("git remote get-url origin", async (remoteError, remoteStdout, remoteStderr) => { |
| 152 | + if (remoteError || remoteStderr) { |
| 153 | + consola.error("Failed to get local repository URL"); |
| 154 | + return; |
| 155 | + } |
| 156 | + const repo = `https://raw.githubusercontent.com/${remoteStdout.trim().replace("https://github.com/", "")}/refs/heads/main/package.json` || "https://raw.githubusercontent.com/TerbiumOS/web-react/refs/heads/main/package.json"; |
| 157 | + try { |
| 158 | + const response = await fetch(repo); |
| 159 | + const ver = (await response.json()).version; |
| 160 | + if (ver !== version) { |
| 161 | + const res = await consola.prompt(`A new version of Terbium is available. Would you like to download it? (New Version: ${ver}, Current: ${version})`, { |
| 162 | + type: "confirm" |
| 163 | + }); |
| 164 | + if (res) { |
| 165 | + open(`${remoteStdout.trim()}/releases/latest`); |
| 166 | + return; |
| 167 | + } else return; |
| 168 | + } else { |
| 169 | + consola.success("Terbium is up to date"); |
| 170 | + } |
| 171 | + } catch (e) { |
| 172 | + consola.error(`Failed to check for updates, ${e}`); |
| 173 | + } |
| 174 | + }); |
| 175 | +} |
| 176 | + |
| 177 | +Bootstrap(); |
0 commit comments