lostcity-engine/tools/server/setup.ts

520 lines
12 KiB
TypeScript

import child_process from 'child_process';
import fs from 'fs';
import { confirm, input, number, password, select } from '@inquirer/prompts';
// ----
function setWebPort(port: number) {
fs.appendFileSync('.env', `WEB_PORT=${port}\n`);
}
function setNodeId(id: number) {
fs.appendFileSync('.env', `NODE_ID=${id + 9}\n`);
}
function setNodePort(port: number) {
fs.appendFileSync('.env', `NODE_PORT=${port}\n`);
}
function setNodeMembers(state: boolean) {
fs.appendFileSync('.env', `NODE_MEMBERS=${state}\n`);
}
function setNodeXpRate(rate: number) {
fs.appendFileSync('.env', `NODE_XPRATE=${rate}\n`);
}
function setNodeProduction(state: boolean) {
fs.appendFileSync('.env', `NODE_PRODUCTION=${state}\n`);
fs.appendFileSync('.env', `NODE_DEBUG=${!state}\n`);
}
function setLoginServer(state: boolean, host?: string, port?: number) {
if (host && port) {
fs.appendFileSync('.env', `LOGIN_SERVER=${state}\nLOGIN_HOST=${host}\nLOGIN_PORT=${port}\n`);
} else {
fs.appendFileSync('.env', `LOGIN_SERVER=${state}\n`);
}
}
function setFriendServer(state: boolean, host?: string, port?: number) {
if (host && port) {
fs.appendFileSync('.env', `FRIEND_SERVER=${state}\nFRIEND_HOST=${host}\nFRIEND_PORT=${port}\n`);
} else {
fs.appendFileSync('.env', `FRIEND_SERVER=${state}\n`);
}
}
function setLoggerServer(state: boolean, host?: string, port?: number) {
if (host && port) {
fs.appendFileSync('.env', `LOGGER_SERVER=${state}\nLOGGER_HOST=${host}\nLOGGER_PORT=${port}\n`);
} else {
fs.appendFileSync('.env', `LOGGER_SERVER=${state}\n`);
}
}
function setLocalSupportServers() {
setLoginServer(true, 'localhost', 43500);
setFriendServer(true, 'localhost', 45099);
setLoggerServer(true, 'localhost', 43501);
}
function setDbBackend(backend: 'sqlite' | 'mysql') {
fs.appendFileSync('.env', `DB_BACKEND=${backend}\n`);
}
function setDatabase(host: string, port: number, name: string, user: string, pass: string) {
fs.appendFileSync('.env', `DATABASE_URL=mysql://${user}:${pass}@${host}:${port}/${name}\n`);
fs.appendFileSync('.env', `DB_HOST=${host}\nDB_PORT=${port}\nDB_NAME=${name}\nDB_USER=${user}\nDB_PASS=${pass}\n`);
}
function setWebsiteRegistration(state: boolean) {
fs.appendFileSync('.env', `WEBSITE_REGISTRATION=${state}\n`);
}
// ----
async function promptWebPort() {
const port = await number({
message: 'Set http port',
default: 80,
required: true
});
setWebPort(port!);
}
async function promptNodeId() {
const id = await number({
message: 'Set world ID',
default: 1,
required: true
});
setNodeId(id!);
}
async function promptNodePort() {
const port = await number({
message: 'Set world port',
default: 43594,
required: true
});
setNodePort(port!);
}
async function promptNodeMembers() {
const choice = await confirm({
message: 'Enable members content',
default: true
});
setNodeMembers(choice);
}
async function promptNodeXpRate() {
const rate = await number({
message: 'Set world XP rate',
default: 1,
required: true
});
setNodeXpRate(rate!);
}
async function promptNodeProduction() {
const choice = await confirm({
message: 'Enable production mode',
default: false
});
setNodeProduction(choice);
}
async function promptLogin() {
const choice = await confirm({
message: 'Do you want to use a login server to provide authentication?',
default: true
});
if (choice) {
const host = await input({
message: 'Host address',
default: 'localhost'
});
const port = await number({
message: 'Host port',
default: 43500,
required: true
});
setLoginServer(true, host, port!);
} else {
setLoginServer(false);
}
}
async function promptFriend() {
const choice = await confirm({
message: 'Do you want to use a friend server to allow PMing?',
default: true
});
if (choice) {
const host = await input({
message: 'Host address',
default: 'localhost'
});
const port = await number({
message: 'Host port',
default: 45099,
required: true
});
setFriendServer(true, host, port!);
} else {
setFriendServer(false);
}
}
async function promptLogger() {
const choice = await confirm({
message: 'Do you want to use a logger server to log player sessions?',
default: true
});
if (choice) {
const host = await input({
message: 'Host address',
default: 'localhost'
});
const port = await number({
message: 'Host port',
default: 43501,
required: true
});
setLoggerServer(true, host, port!);
} else {
setLoggerServer(false);
}
}
async function promptDatabase() {
const host = await input({
message: 'Database host address',
default: 'localhost'
});
const port = await number({
message: 'Database host port',
default: 3306,
required: true
});
const name = await input({
message: 'Database name',
default: 'lostcity'
});
const user = await input({
message: 'Database user account',
default: 'lostcity'
});
const pass = await password({
message: 'Database user password'
});
setDatabase(host, port!, name, user, pass);
}
async function promptWebsiteRegistration() {
const autoregister = await confirm({
message: 'Do you want to automatically register accounts when they attempt to log in?',
default: true
});
setWebsiteRegistration(!autoregister);
}
// ----
async function startup() {
while (true) {
const choices = [];
if (fs.existsSync('data/pack')) {
// quickstart script should run this before starting the server. exits and continues starting the world
choices.push({
name: 'Continue startup',
value: 'continue'
});
}
choices.push({
name: 'Set up as a development world',
description: 'Game server only, using sqlite',
value: 'configure-dev'
});
choices.push({
name: 'Set up as a full development stack',
description: 'Includes login, friend, and logger servers',
value: 'configure-dev-stack'
});
choices.push({
name: 'Set up as a single world',
value: 'configure-local'
});
choices.push({
name: 'Set up as part of a multi-world infrastructure',
value: 'configure-prod'
});
choices.push({
name: 'Advanced options',
value: 'advanced'
});
const action = await select({
message: 'What would you like to do?',
choices
});
switch (action) {
case 'continue': {
process.exit(0);
break;
}
case 'configure-dev': {
await configureDev();
break;
}
case 'configure-dev-stack': {
await configureDevStack();
break;
}
case 'configure-local': {
await configureSingle();
break;
}
case 'configure-prod': {
await configureMulti();
break;
}
case 'advanced': {
await advancedOptions();
break;
}
}
}
}
async function configureDev() {
// we don't actually have to do anything because it's good OOTB :)
fs.copyFileSync('.env.example', '.env');
process.exit(0);
}
async function configureDevStack() {
fs.copyFileSync('.env.example', '.env');
fs.appendFileSync('.env', '\n## SETUP SCRIPT\n');
setWebsiteRegistration(false);
setNodeProduction(false);
const backend = await select({
message: 'Choose a database backend',
choices: [
{
name: 'SQLite',
value: 'sqlite'
},
{
name: 'MySQL',
value: 'mysql'
}
]
});
if (backend === 'sqlite') {
setDbBackend('sqlite');
} else if (backend === 'mysql') {
setDbBackend('mysql');
await promptDatabase();
} else {
console.error('Invalid database backend');
process.exit(1);
}
setLocalSupportServers();
fs.appendFileSync('.env', 'EASY_STARTUP=true\n');
if (backend === 'sqlite') {
child_process.execSync('bun run sqlite:migrate', {
stdio: 'inherit'
});
} else if (backend === 'mysql') {
child_process.execSync('bun run db:migrate', {
stdio: 'inherit'
});
}
process.exit(0);
}
async function configureSingle() {
fs.copyFileSync('.env.example', '.env');
fs.appendFileSync('.env', '\n## SETUP SCRIPT\n');
setNodeProduction(true);
await promptNodeId();
await promptNodeXpRate();
await promptNodeMembers();
fs.appendFileSync('.env', 'DB_BACKEND=sqlite\n');
await promptWebsiteRegistration();
setLocalSupportServers();
fs.appendFileSync('.env', 'EASY_STARTUP=true\n');
child_process.execSync('bun run sqlite:migrate', {
stdio: 'inherit'
});
process.exit(0);
}
async function configureMulti() {
fs.copyFileSync('.env.example', '.env');
fs.appendFileSync('.env', '\n## SETUP SCRIPT\n');
setWebsiteRegistration(true);
setNodeProduction(true);
await promptNodeId();
await promptNodeXpRate();
await promptNodeMembers();
setDbBackend('mysql');
await promptDatabase();
await promptLogin();
await promptWebsiteRegistration();
await promptFriend();
await promptLogger();
child_process.execSync('bun run db:migrate', {
stdio: 'inherit'
});
process.exit(0);
}
async function advancedOptions() {
const advanced = await select({
message: 'Advanced options',
pageSize: 24,
choices: [
{
name: 'Go back',
value: 'back'
},
{
name: 'Set http port',
value: 'web_port'
},
{
name: 'Set world ID',
value: 'node_id'
},
{
name: 'Set world port',
value: 'node_port'
},
{
name: 'Disable members content',
value: 'node_members'
},
{
name: 'Set world XP rate',
value: 'node_xprate'
},
{
name: 'Enable production mode',
value: 'node_production'
},
{
name: 'Configure login server',
value: 'login'
},
{
name: 'Configure friend server',
value: 'friend'
},
{
name: 'Configure logger server',
value: 'logger'
},
{
name: 'Configure database connection',
value: 'database'
}
]
});
switch (advanced) {
case 'web_port': {
await promptWebPort();
break;
}
case 'node_id': {
await promptNodeId();
break;
}
case 'node_port': {
await promptNodePort();
break;
}
case 'node_members': {
await promptNodeMembers();
break;
}
case 'node_xprate': {
await promptNodeXpRate();
break;
}
case 'node_production': {
await promptNodeProduction();
break;
}
case 'login': {
await promptLogin();
break;
}
case 'friend': {
await promptFriend();
break;
}
case 'logger': {
await promptLogger();
break;
}
case 'database': {
await promptDatabase();
break;
}
}
}
try {
await startup();
} catch (_) {
// no-op
}