const path = require("path"); const fs = require("fs"); const { stdout, exit } = require("process"); const readline = require("readline"); const { execIn } = require("./utils.js"); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const data = { env: [], config: { register: {} }, extra_pkgs: [] }; let rights = []; process.on("SIGINT", function () { console.log("Caught interrupt signal"); process.exit(); }); console.log("Welcome to Fosscord!"); console.log("Please remember this is pre-release software!"); console.log("We will guide you through some important setup steps."); console.log(); if (fs.existsSync("package-lock.json")) fs.rmSync("package-lock.json"); if (fs.existsSync("yarn.lock")) fs.rmSync("yarn.lock"); async function main() { printTitle("Step 1: Database setup"); console.log("1. PostgreSQL (recommended)"); console.log("2. MariaDB/MySQL"); console.log("3. SQLite (not recommended, but good for a simple test)"); while (!data.db) { let answer = await ask("Please select a database type: "); if (answer == "1") { data.db = "postgres"; data.extra_pkgs.push("pg"); } else if (answer == "2") { data.db = "mariadb"; data.extra_pkgs.push("mysql2"); } else if (answer == "3") { data.db = "sqlite"; data.extra_pkgs.push("sqlite3"); } else { console.log("Invalid choice!"); } } printTitle("Step 2: Database credentials"); if (data.db != "sqlite") { console.log("Please enter your database credentials."); console.log("You can leave the password field empty if you don't want to set a password."); console.log(); while (!data.db_host) { data.db_host = await ask("Host: "); } while (!data.db_port) { data.db_port = await ask("Port: "); } while (!data.db_user) { data.db_user = await ask("Username: "); } while (!data.db_pass) { data.db_pass = await ask("Password: "); } while (!data.db_name) { data.db_name = await ask("Database name: "); } } else { console.log("SQLite does not use credentials..."); } printTitle("Step 3: Domain setup"); console.log("Please enter your domain."); console.log("You can leave the port field empty if you don't want to set a port."); console.log(); data.domain = await ask("Domain (default=localhost): "); if (!data.domain) data.domain = "localhost"; else data.ssl = /y?/i.test(await ask("SSL/HTTPS (Y/n): ")); data.port = await ask("Port (default=3001): "); if (!data.port) data.port = "3001"; if (data.db != "sqlite") data.env.push(`DATABASE=${data.db}://${data.db_user}:${data.db_pass}@${data.db_host}:${data.db_port}/${data.db_name}`); data.env.push(`PORT=${data.port}`); data.env.push("THREADS=1"); printTitle("Step 4: Default rights"); console.log("Please enter the default rights for new users."); console.log("Valid rights are: none, discord, full, custom."); console.log(); let lines = fs.readFileSync(path.join(__dirname, "..", "src", "util", "util", "Rights.ts")).toString(); let lines2 = lines.split("\n"); let lines3 = lines2.filter((y) => y.includes(": BitFlag(")); let lines4 = lines3.map((x) => x.split("//")[0].trim()); let maxRights = 0n; lines4.forEach((x) => { maxRights += eval(`rights.${x.replace(":", " = ").replace(",", ";")}`); }); discordRights = maxRights; discordRights -= rights.SEND_BACKDATED_EVENTS; discordRights -= rights.MANAGE_GUILD_DIRECTORY; discordRights -= rights.CREDITABLE; discordRights -= rights.BYPASS_RATE_LIMITS; discordRights -= rights.ADD_MEMBERS; discordRights -= rights.MANAGE_RATE_LIMITS; discordRights -= rights.OPERATOR; data.default_rights = await ask("Rights (default=none): "); if (!data.default_rights || data.defaultRights == "none") data.config.register.defaultRights = "0"; else if (data.default_rights == "discord") data.config.register.defaultRights = discordRights.toString(); else if (data.default_rights == "full") data.config.register.defaultRights = maxRights.toString(); else if (data.default_rights == "custom") data.config.register.defaultRights = (await askRights()).toString(); if (data.domain != "localhost") data.config = { cdn: { endpointPrivate: `http://localhost:${data.port}`, endpointPublic: `${data.ssl ? "https" : "http"}://${data.domain}:${data.port}` }, gateway: { endpointPrivate: `ws://localhost:${data.port}`, endpointPublic: `${data.ssl ? "wss" : "ws"}://${data.domain}:${data.port}` }, ...data.config }; printTitle("Step 5: extra options"); if (/y?/i.test(await ask("Use fast BCrypt implementation (requires a compiler) (Y/n): "))) data.extra_pkgs.push("bcrypt"); if (/y?/.test(await ask("Enable support for widgets (requires compiler, known to fail on some ARM devices.) (Y/n): "))) data.extra_pkgs.push("canvas"); printTitle("Step 6: finalizing..."); //save console.log("==> Writing .env..."); fs.writeFileSync(".env", data.env.join("\n")); console.log("==> Writing initial.json"); fs.writeFileSync("initial.json", JSON.stringify(data.config, (space = 4))); //install packages... console.log("==> Installing packages..."); console.log(" ==> Ensuring yarn is up to date (v3, not v1)..."); execIn("npx yarn set version stable", process.cwd()); console.log(" ==> Installing base packages"); execIn("npx --yes yarn install", process.cwd(), { stdio: "inherit" }); console.log(` ==> Installing extra packages: ${data.extra_pkgs.join(", ")}...`); execIn(`npx --yes yarn add -O ${data.extra_pkgs.join(" ")}`, process.cwd(), { stdio: "inherit" }); console.log("==> Building..."); execIn("npx --yes yarn run build", process.cwd(), { stdio: "inherit" }); printTitle("Step 6: run your instance!"); console.log("Installation is complete!"); console.log("You can now start your instance by running 'npm run start:bundle'!"); exit(0); } main(); async function askRights() { let w = 0; let brights = { ...eval(`rights`) }; Object.keys(rights).forEach((x) => { brights[x] = false; let str = `[x] ${Object.keys(rights).length}: ${x}`; if (str.length > w) w = str.length; }); let resp = ""; let selectedRights = 0n; while (resp != "q") { selectedRights = 0n; Object.keys(brights).forEach((x) => { if (brights[x]) selectedRights += rights[x]; }); console.clear(); printTitle("Step 4: Default rights"); printTitle(`Current rights: ${selectedRights} (0b${selectedRights.toString(2)}, 0x${selectedRights.toString(16)})`); let xpos = 0; Object.keys(rights).forEach((x) => { let str = `[${brights[x] ? "X" : " "}] ${Object.keys(rights).indexOf(x)}: ${x}`.padEnd(w + 1, " "); if (xpos + str.length > stdout.columns) { console.log(); xpos = 0; } stdout.write(str); xpos += str.length; }); console.log(); resp = await ask("Enter an option, or q to exit: "); if (/\d{1,}/.test(resp) && resp < Object.keys(rights).length && resp > -1) { brights[Object.keys(brights)[parseInt(resp)]] ^= true; } } return selectedRights; } async function askRight(right) { let answer = await ask(`${right}: `); if (answer == "y") return true; else if (answer == "n") return false; else return askRight(right); } function printTitle(input) { let width = stdout.columns / 2 - 1; //40 console.log(); console.log("-".repeat(width - input.length / 2), input, "-".repeat(width - input.length / 2)); console.log(); } async function ask(question) { return new Promise((resolve, reject) => { return rl.question(question, (answer) => { resolve(answer); }); }).catch((err) => { console.log(err); }); } function BitFlag(int) { return 1n << BigInt(int); }