omegga 1.18.0 → 1.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,6 +42,17 @@ Omegga plugins can do things like:
42
42
 
43
43
  Omegga runs on linux, including the [Windows Subsystem for Linux](docs/install/wsl.md). **Do not install or run it as root**; if `whoami` prints "root", [create a new user](docs/install/linux.md#creating-a-new-user) first.
44
44
 
45
+ Get Omegga on Debian, Ubuntu, Fedora, or Arch with:
46
+
47
+ ```sh
48
+ curl -fsSL https://omegga.brickadia.dev/install.sh | bash
49
+ ```
50
+
51
+ [Omegga `install.sh` Docs](docs/install/linux.md#quick-setup) | [Omegga `install.sh` Source](tools/install.sh)
52
+
53
+ <details>
54
+ <summary>Manual Install for Debian or Ubuntu</summary>
55
+
45
56
  ```sh
46
57
  # update what's installed, then install what omegga needs
47
58
  sudo apt update && sudo apt upgrade
@@ -60,6 +71,8 @@ nvm install 24
60
71
  npm i -g omegga
61
72
  ```
62
73
 
74
+ </details>
75
+
63
76
  Then make a folder for your server and start it:
64
77
 
65
78
  ```sh
@@ -10,17 +10,23 @@ const EventEmitter = require("node:events");
10
10
  const fs = require("node:fs");
11
11
  const path = require("node:path");
12
12
  const node_process = require("node:process");
13
+ const distro = require("../util/distro.js");
13
14
  const readline = require("readline");
14
15
  const stripAnsi = require("strip-ansi");
16
+ const MISSING_LIBRARY_FIX = distro.installHint({
17
+ debian: "libgl1 libglib2.0-0",
18
+ fedora: "mesa-libGL glib2",
19
+ arch: "libglvnd glib2"
20
+ });
15
21
  const knownErrors = [
16
22
  {
17
23
  name: "MISSING_LIBGL",
18
- solution: "apt-get install libgl1-mesa-glx libglib2.0-0",
24
+ solution: MISSING_LIBRARY_FIX,
19
25
  match: /error while loading shared libraries: libGL\.so\.1: cannot open shared object file/
20
26
  },
21
27
  {
22
28
  name: "MISSING_GLIB",
23
- solution: "apt-get install libgl1-mesa-glx libglib2.0-0",
29
+ solution: MISSING_LIBRARY_FIX,
24
30
  match: /error while loading shared libraries: libgthread-2\.0\.so\.0: cannot open shared object file/
25
31
  }
26
32
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sources":["../../src/brickadia/server.ts"],"sourcesContent":["/*\n Brickadia Server Wrapper\n Manages IO with the game server\n*/\n\nimport Logger from '@/logger';\nimport {\n ACTIVE_WORLD_FILE,\n CONFIG_SAVED_DIR,\n GAME_BIN_PATH,\n getOverrideGameBinary,\n getSteamGameDir,\n getSteamInstallDir,\n} from '@/softconfig';\nimport { getGlobalToken } from '@cli/auth';\nimport { type IConfig } from '@config/types';\nimport { checkWsl } from '@util/wsl';\nimport 'colors';\nimport { type ChildProcessWithoutNullStreams, spawn } from 'node:child_process';\nimport EventEmitter from 'node:events';\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { env } from 'node:process';\nimport readline from 'readline';\nimport stripAnsi from 'strip-ansi';\n\n// list of errors that can be solved by yelling at the user\nconst knownErrors: {\n name: string;\n solution?: string;\n match: RegExp;\n message?: string;\n}[] = [\n {\n name: 'MISSING_LIBGL',\n solution: 'apt-get install libgl1-mesa-glx libglib2.0-0',\n match:\n /error while loading shared libraries: libGL\\.so\\.1: cannot open shared object file/,\n },\n {\n name: 'MISSING_GLIB',\n solution: 'apt-get install libgl1-mesa-glx libglib2.0-0',\n match:\n /error while loading shared libraries: libgthread-2\\.0\\.so\\.0: cannot open shared object file/,\n },\n];\n\n/** Start a brickadia server */\nexport default class BrickadiaServer extends EventEmitter {\n #child: ChildProcessWithoutNullStreams | null = null;\n #errInterface: readline.Interface | null = null;\n #outInterface: readline.Interface | null = null;\n\n config: IConfig;\n path: string;\n\n constructor(dataPath: string, config: IConfig) {\n super();\n\n this.config = config;\n // use the data path if it's absolute, otherwise build an absolute path\n this.path =\n path.isAbsolute(dataPath) || dataPath.startsWith('/')\n ? dataPath\n : path.join(process.cwd(), dataPath);\n\n this.lineListener = this.lineListener.bind(this);\n this.errorListener = this.errorListener.bind(this);\n this.exitListener = this.exitListener.bind(this);\n }\n\n getActiveWorldFile(): string {\n return path.join(this.path, ACTIVE_WORLD_FILE);\n }\n\n /** A world specified by the active world file */\n getActiveWorld(): string | null {\n const activeWorldFile = this.getActiveWorldFile();\n if (existsSync(activeWorldFile)) {\n try {\n return readFileSync(activeWorldFile, 'utf8').trim();\n } catch (err) {\n Logger.errorp(\n 'Failed to read active world file',\n activeWorldFile.yellow,\n err,\n );\n return null;\n }\n }\n return null;\n }\n\n /** Set the world to use next startup */\n setActiveWorld(world: string | null): boolean {\n const activeWorldFile = this.getActiveWorldFile();\n if (!world || world === null) {\n if (existsSync(activeWorldFile)) {\n Logger.verbose('Removing active world file', activeWorldFile.yellow);\n unlinkSync(activeWorldFile);\n }\n return true;\n }\n\n if (!this.worldExists(world)) {\n Logger.verbose(\n 'Cannot set active world to',\n world.yellow,\n 'as it does not exist',\n );\n return false;\n }\n\n Logger.verbose('Setting active world to', world.yellow);\n try {\n writeFileSync(activeWorldFile, world, 'utf8');\n return true;\n } catch (err) {\n Logger.errorp(\n 'Failed to write active world file',\n activeWorldFile.yellow,\n err,\n );\n return false;\n }\n }\n\n /** A world specified by the config */\n getConfigWorld(): string | null {\n return this.config?.server?.world ?? null;\n }\n\n /** A world specified by the BRICKADIA_WORLD env variable */\n getEnvWorld(): string | null {\n return env.BRICKADIA_WORLD || null;\n }\n\n /** Check if a world exists */\n worldExists(world: string): boolean {\n const savedDir = this.config?.server?.savedDir ?? CONFIG_SAVED_DIR;\n const worldPath = path.join(this.path, savedDir, 'Worlds', world + '.brdb');\n return existsSync(worldPath);\n }\n\n /** Get the world that will be used next startup */\n getNextWorld(): { source: string; file: string } | null {\n const candidates = [\n { source: 'file', file: this.getActiveWorld() },\n { source: 'config', file: this.getConfigWorld() },\n { source: 'env', file: this.getEnvWorld() },\n ];\n\n return (\n candidates.find(\n (c): c is { source: string; file: string } =>\n !!c.file && this.worldExists(c.file),\n ) ?? null\n );\n }\n\n /**\n * Resolve the path to the game server binary for steam/override installs.\n * Returns null for launcher-managed installs, where the binary lives in a\n * branch directory the launcher owns and isn't known statically here.\n */\n getGameBinaryPath(): string | null {\n const overrideBinary = getOverrideGameBinary();\n if (overrideBinary) return overrideBinary;\n\n // a configured branch means the launcher manages the install, not steam\n const isSteam = !this.config.server.branch;\n if (!isSteam) return null;\n\n const steamBeta = this.config.server.steambeta ?? 'main';\n return path.join(\n getSteamInstallDir(), // steam install directory\n steamBeta, // steam beta branch (or main)\n getSteamGameDir(), // Brickadia\n GAME_BIN_PATH, // path to binary\n );\n }\n\n /**\n * Resolve the directory the game server binary lives in. Null whenever\n * {@link getGameBinaryPath} can't resolve a binary.\n */\n getGameBinaryDir(): string | null {\n const binary = this.getGameBinaryPath();\n return binary && path.dirname(binary);\n }\n\n // start the server child process\n start() {\n const {\n email,\n password,\n token: confToken,\n }: { email?: string; password?: string; token?: string } = this.config\n .credentials || {};\n\n const token = confToken || process.env.BRICKADIA_TOKEN || getGlobalToken();\n\n if (token) {\n Logger.verbose('Starting server with hosting token');\n } else {\n Logger.verbose(\n 'Starting server',\n (!email && !password ? 'without' : 'with').yellow,\n 'credentials',\n );\n }\n\n const isSteam = !this.config.server.branch;\n const steamBeta = this.config.server.steambeta ?? 'main';\n const overrideBinary = getOverrideGameBinary();\n const steamBinary = path.join(\n getSteamInstallDir(), // steam install directory\n steamBeta, // steam beta branch (or main)\n getSteamGameDir(), // Brickadia\n GAME_BIN_PATH, // path to binary\n );\n\n let gameBinary = this.getGameBinaryPath() ?? steamBinary;\n\n if (overrideBinary) {\n if (!existsSync(overrideBinary)) {\n Logger.error(\n 'Override binary',\n overrideBinary.yellow,\n 'does not exist!',\n );\n throw new Error(`Override binary ${overrideBinary} does not exist!`);\n }\n\n Logger.verbose(\n 'Using override binary',\n overrideBinary.yellow,\n 'instead of',\n steamBinary.yellow,\n );\n gameBinary = overrideBinary;\n } else if (isSteam) {\n Logger.verbose('Using steam binary', steamBeta.yellow);\n } else {\n Logger.verbose(\n 'Running',\n (this.config.server.__LOCAL\n ? path.join(__dirname, '../../tools/brickadia.sh')\n : 'brickadia launcher'\n ).yellow,\n );\n if (typeof this.config.server.branch === 'string')\n Logger.verbose('Using branch', this.config.server.branch.yellow);\n }\n\n // handle local launcher support\n const launchArgs = isSteam\n ? [gameBinary]\n : [\n this.config.server.__LOCAL\n ? path.join(__dirname, '../../tools/brickadia.sh')\n : 'brickadia',\n this.config.server.branch && `--branch=${this.config.server.branch}`,\n '--server',\n '--',\n ];\n\n const world = this.getNextWorld();\n if (world) {\n Logger.verbose(\n 'Using world',\n world.file.yellow,\n 'from',\n world.source.yellow,\n );\n } else if (this.config.server.map) {\n Logger.verbose('Using map', this.config.server.map.yellow, 'from config');\n }\n\n const params = [\n '--output=L',\n '--',\n ...launchArgs,\n !world &&\n this.config.server.map &&\n `-Environment=\"${this.config.server.map}\"`,\n world && `-World=\"${world.file}\"`,\n '-NotInstalled',\n '-log',\n // remote file access is on unless explicitly disabled in the config\n this.config.server.remoteFiles === false ? '-NoRemoteFileAccess' : null,\n checkWsl() === 1 ? '-OneThread' : null,\n this.path ? `-UserDir=\"${this.path}\"` : null,\n token ? `-Token=\"${token}\"` : null, // remove token argument if not provided\n !token && email ? `-User=\"${email}\"` : null, // remove email argument if not provided or token is provided\n !token && password ? `-Password=\"${password}\"` : null, // remove password argument if not provided or token is provided\n `-port=\"${this.config.server.port}\"`,\n this.config.server.launchArgs,\n ]\n .filter(arg => typeof arg === 'string')\n .filter(arg => arg.length > 0); // remove unused/empty arguments\n Logger.verbose(\n 'Params for spawn',\n params\n .join(' ')\n .replace(/-User=\".*?\"/, '-User=\"<hidden>\"')\n .replace(/-Password=\".*?\"/, '-Password=\"<hidden>\"')\n .replace(/-Token=\".*?\"/, '-Token=\"<hidden>\"')\n .replace(/-Cookie=\".*?\"/, '-Cookie=\"<hidden>\"')\n .replace(/-Cookie=\\S+/, '-Cookie=<hidden>'),\n );\n\n // Either unbuffer or stdbuf must be used because brickadia's output is buffered\n // this means that multiple lines can be bundled together if the output buffer is not full\n // unfortunately without stdbuf or unbuffer, the output would not happen immediately\n const child = spawn('stdbuf', params);\n this.#child = child;\n\n Logger.verbose('Spawn process', child.pid ?? 'failed'.red);\n\n child.stdin.setDefaultEncoding('utf8');\n this.#outInterface = readline.createInterface({\n input: child.stdout,\n terminal: false,\n });\n this.#errInterface = readline.createInterface({\n input: child.stderr,\n terminal: false,\n });\n this.attachListeners();\n Logger.verbose('Attached listeners');\n }\n\n // write a string to the child process\n write(line: string) {\n if (line.length >= 512) {\n // show a warning\n Logger.warn(\n 'WARNING'.yellow,\n 'The following line was called and is',\n 'longer than allowed limit'.red,\n );\n Logger.warn(line.replace(/\\n$/, ''));\n // throw a fake error to get the line number\n try {\n throw new Error('Console Line Too Long');\n } catch (err) {\n Logger.warn(err);\n }\n return;\n }\n if (this.#child) {\n Logger.verbose('WRITE'.green, line.replace(/\\n$/, ''));\n this.#child.stdin.write(line);\n }\n }\n\n // write a line to the child process\n writeln(line: string) {\n this.write(line + '\\n');\n }\n\n /**\n * pid of the running game process, or null when it isn't running.\n *\n * The child is spawned through `stdbuf`, which execs the game in place, so\n * this is the game's own pid and /proc entry.\n */\n get pid(): number | null {\n return this.#child?.pid ?? null;\n }\n\n // forcibly kills the server\n stop() {\n if (!this.#child) {\n Logger.verbose('Cannot stop server as no subprocess exists');\n return;\n }\n\n Logger.verbose('Forcibly stopping server');\n // kill the process\n this.#child.kill('SIGINT');\n\n // ...kill it again just to make sure it's dead. this signals the pid\n // directly rather than shelling out: slim container images have no\n // `kill` binary, only the shell builtin, and spawn cannot use that.\n this.#child.kill('SIGKILL');\n }\n\n // detaches listeners\n cleanup() {\n if (!this.#child) return;\n\n Logger.verbose('Cleaning up brickadia server');\n\n // detach listener\n this.detachListeners();\n\n this.#child = null;\n this.#outInterface = null;\n this.#errInterface = null;\n }\n\n // attaches proxy event listeners\n attachListeners() {\n this.#outInterface?.on('line', this.lineListener);\n this.#errInterface?.on('line', this.errorListener);\n this.#child?.on('exit', this.exitListener);\n this.#child?.on('close', () => {});\n }\n\n // removes previously attached proxy event listeners\n detachListeners() {\n this.#outInterface?.off('line', this.lineListener);\n this.#errInterface?.off('line', this.errorListener);\n this.#child?.off('exit', this.exitListener);\n this.#child?.removeAllListeners('close');\n }\n\n // -- listeners for basic events (line, err, exit)\n errorListener(line: string) {\n Logger.verbose('ERROR'.red, line);\n this.emit('err', line);\n for (const { match, solution, name, message } of knownErrors) {\n if (line.match(match)) {\n Logger.error(\n `Encountered ${name.red}. ${\n solution ? 'Known fix:\\n ' + solution : message || 'Unknown error.'\n }`,\n );\n }\n }\n }\n\n exitListener(code: number | null, signal: NodeJS.Signals | null) {\n Logger.verbose('Exit listener fired');\n this.emit('closed', code, signal);\n this.cleanup();\n }\n\n lineListener(line: string) {\n this.emit('line', stripAnsi(line));\n }\n}\n"],"names":["ACTIVE_WORLD_FILE","existsSync","readFileSync","Logger","unlinkSync","writeFileSync","env","CONFIG_SAVED_DIR","getOverrideGameBinary","getSteamInstallDir","getSteamGameDir","GAME_BIN_PATH","getGlobalToken","checkWsl","spawn"],"mappings":";;;;;;;;;;;;;;AA2BA,MAAM,cAKA;AAAA,EACJ;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,OACE;AAAA,EAAA;AAAA,EAEJ;AAAA,IACE,MAAM;AAAA,IACN,UAAU;AAAA,IACV,OACE;AAAA,EAAA;AAEN;AAGA,MAAqB,wBAAwB,aAAa;AAAA,EACxD,SAAgD;AAAA,EAChD,gBAA2C;AAAA,EAC3C,gBAA2C;AAAA,EAK3C,YAAY,UAAkB,QAAiB;AAC7C,UAAA;AAEA,SAAK,SAAS;AAEd,SAAK,OACH,KAAK,WAAW,QAAQ,KAAK,SAAS,WAAW,GAAG,IAChD,WACA,KAAK,KAAK,QAAQ,IAAA,GAAO,QAAQ;AAEvC,SAAK,eAAe,KAAK,aAAa,KAAK,IAAI;AAC/C,SAAK,gBAAgB,KAAK,cAAc,KAAK,IAAI;AACjD,SAAK,eAAe,KAAK,aAAa,KAAK,IAAI;AAAA,EACjD;AAAA,EAEA,qBAA6B;AAC3B,WAAO,KAAK,KAAK,KAAK,MAAMA,WAAAA,iBAAiB;AAAA,EAC/C;AAAA;AAAA,EAGA,iBAAgC;AAC9B,UAAM,kBAAkB,KAAK,mBAAA;AAC7B,QAAIC,GAAAA,WAAW,eAAe,GAAG;AAC/B,UAAI;AACF,eAAOC,gBAAa,iBAAiB,MAAM,EAAE,KAAA;AAAA,MAC/C,SAAS,KAAK;AACZC,eAAAA,QAAO;AAAA,UACL;AAAA,UACA,gBAAgB;AAAA,UAChB;AAAA,QAAA;AAEF,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,eAAe,OAA+B;AAC5C,UAAM,kBAAkB,KAAK,mBAAA;AAC7B,QAAI,CAAC,SAAS,UAAU,MAAM;AAC5B,UAAIF,GAAAA,WAAW,eAAe,GAAG;AAC/BE,eAAAA,QAAO,QAAQ,8BAA8B,gBAAgB,MAAM;AACnEC,WAAAA,WAAW,eAAe;AAAA,MAC5B;AACA,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,KAAK,YAAY,KAAK,GAAG;AAC5BD,aAAAA,QAAO;AAAA,QACL;AAAA,QACA,MAAM;AAAA,QACN;AAAA,MAAA;AAEF,aAAO;AAAA,IACT;AAEAA,WAAAA,QAAO,QAAQ,2BAA2B,MAAM,MAAM;AACtD,QAAI;AACFE,uBAAc,iBAAiB,OAAO,MAAM;AAC5C,aAAO;AAAA,IACT,SAAS,KAAK;AACZF,aAAAA,QAAO;AAAA,QACL;AAAA,QACA,gBAAgB;AAAA,QAChB;AAAA,MAAA;AAEF,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGA,iBAAgC;AAC9B,WAAO,KAAK,QAAQ,QAAQ,SAAS;AAAA,EACvC;AAAA;AAAA,EAGA,cAA6B;AAC3B,WAAOG,aAAAA,IAAI,mBAAmB;AAAA,EAChC;AAAA;AAAA,EAGA,YAAY,OAAwB;AAClC,UAAM,WAAW,KAAK,QAAQ,QAAQ,YAAYC,WAAAA;AAClD,UAAM,YAAY,KAAK,KAAK,KAAK,MAAM,UAAU,UAAU,QAAQ,OAAO;AAC1E,WAAON,GAAAA,WAAW,SAAS;AAAA,EAC7B;AAAA;AAAA,EAGA,eAAwD;AACtD,UAAM,aAAa;AAAA,MACjB,EAAE,QAAQ,QAAQ,MAAM,KAAK,iBAAe;AAAA,MAC5C,EAAE,QAAQ,UAAU,MAAM,KAAK,iBAAe;AAAA,MAC9C,EAAE,QAAQ,OAAO,MAAM,KAAK,cAAY;AAAA,IAAE;AAG5C,WACE,WAAW;AAAA,MACT,CAAC,MACC,CAAC,CAAC,EAAE,QAAQ,KAAK,YAAY,EAAE,IAAI;AAAA,IAAA,KAClC;AAAA,EAET;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAmC;AACjC,UAAM,iBAAiBO,WAAAA,sBAAA;AACvB,QAAI,eAAgB,QAAO;AAG3B,UAAM,UAAU,CAAC,KAAK,OAAO,OAAO;AACpC,QAAI,CAAC,QAAS,QAAO;AAErB,UAAM,YAAY,KAAK,OAAO,OAAO,aAAa;AAClD,WAAO,KAAK;AAAA,MACVC,8BAAA;AAAA;AAAA,MACA;AAAA;AAAA,MACAC,2BAAA;AAAA;AAAA,MACAC,WAAAA;AAAAA;AAAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,mBAAkC;AAChC,UAAM,SAAS,KAAK,kBAAA;AACpB,WAAO,UAAU,KAAK,QAAQ,MAAM;AAAA,EACtC;AAAA;AAAA,EAGA,QAAQ;AACN,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,OAAO;AAAA,IAAA,IACkD,KAAK,OAC7D,eAAe,CAAA;AAElB,UAAM,QAAQ,aAAa,QAAQ,IAAI,mBAAmBC,KAAAA,eAAA;AAE1D,QAAI,OAAO;AACTT,aAAAA,QAAO,QAAQ,oCAAoC;AAAA,IACrD,OAAO;AACLA,aAAAA,QAAO;AAAA,QACL;AAAA,SACC,CAAC,SAAS,CAAC,WAAW,YAAY,QAAQ;AAAA,QAC3C;AAAA,MAAA;AAAA,IAEJ;AAEA,UAAM,UAAU,CAAC,KAAK,OAAO,OAAO;AACpC,UAAM,YAAY,KAAK,OAAO,OAAO,aAAa;AAClD,UAAM,iBAAiBK,WAAAA,sBAAA;AACvB,UAAM,cAAc,KAAK;AAAA,MACvBC,8BAAA;AAAA;AAAA,MACA;AAAA;AAAA,MACAC,2BAAA;AAAA;AAAA,MACAC,WAAAA;AAAAA;AAAAA,IAAA;AAGF,QAAI,aAAa,KAAK,kBAAA,KAAuB;AAE7C,QAAI,gBAAgB;AAClB,UAAI,CAACV,GAAAA,WAAW,cAAc,GAAG;AAC/BE,eAAAA,QAAO;AAAA,UACL;AAAA,UACA,eAAe;AAAA,UACf;AAAA,QAAA;AAEF,cAAM,IAAI,MAAM,mBAAmB,cAAc,kBAAkB;AAAA,MACrE;AAEAA,aAAAA,QAAO;AAAA,QACL;AAAA,QACA,eAAe;AAAA,QACf;AAAA,QACA,YAAY;AAAA,MAAA;AAEd,mBAAa;AAAA,IACf,WAAW,SAAS;AAClBA,aAAAA,QAAO,QAAQ,sBAAsB,UAAU,MAAM;AAAA,IACvD,OAAO;AACLA,aAAAA,QAAO;AAAA,QACL;AAAA,SACC,KAAK,OAAO,OAAO,UAChB,KAAK,KAAK,WAAW,0BAA0B,IAC/C,sBACF;AAAA,MAAA;AAEJ,UAAI,OAAO,KAAK,OAAO,OAAO,WAAW;AACvCA,eAAAA,QAAO,QAAQ,gBAAgB,KAAK,OAAO,OAAO,OAAO,MAAM;AAAA,IACnE;AAGA,UAAM,aAAa,UACf,CAAC,UAAU,IACX;AAAA,MACE,KAAK,OAAO,OAAO,UACf,KAAK,KAAK,WAAW,0BAA0B,IAC/C;AAAA,MACJ,KAAK,OAAO,OAAO,UAAU,YAAY,KAAK,OAAO,OAAO,MAAM;AAAA,MAClE;AAAA,MACA;AAAA,IAAA;AAGN,UAAM,QAAQ,KAAK,aAAA;AACnB,QAAI,OAAO;AACTA,aAAAA,QAAO;AAAA,QACL;AAAA,QACA,MAAM,KAAK;AAAA,QACX;AAAA,QACA,MAAM,OAAO;AAAA,MAAA;AAAA,IAEjB,WAAW,KAAK,OAAO,OAAO,KAAK;AACjCA,qBAAO,QAAQ,aAAa,KAAK,OAAO,OAAO,IAAI,QAAQ,aAAa;AAAA,IAC1E;AAEA,UAAM,SAAS;AAAA,MACb;AAAA,MACA;AAAA,MACA,GAAG;AAAA,MACH,CAAC,SACC,KAAK,OAAO,OAAO,OACnB,iBAAiB,KAAK,OAAO,OAAO,GAAG;AAAA,MACzC,SAAS,WAAW,MAAM,IAAI;AAAA,MAC9B;AAAA,MACA;AAAA;AAAA,MAEA,KAAK,OAAO,OAAO,gBAAgB,QAAQ,wBAAwB;AAAA,MACnEU,aAAA,MAAe,IAAI,eAAe;AAAA,MAClC,KAAK,OAAO,aAAa,KAAK,IAAI,MAAM;AAAA,MACxC,QAAQ,WAAW,KAAK,MAAM;AAAA;AAAA,MAC9B,CAAC,SAAS,QAAQ,UAAU,KAAK,MAAM;AAAA;AAAA,MACvC,CAAC,SAAS,WAAW,cAAc,QAAQ,MAAM;AAAA;AAAA,MACjD,UAAU,KAAK,OAAO,OAAO,IAAI;AAAA,MACjC,KAAK,OAAO,OAAO;AAAA,IAAA,EAElB,OAAO,CAAA,QAAO,OAAO,QAAQ,QAAQ,EACrC,OAAO,CAAA,QAAO,IAAI,SAAS,CAAC;AAC/BV,WAAAA,QAAO;AAAA,MACL;AAAA,MACA,OACG,KAAK,GAAG,EACR,QAAQ,eAAe,kBAAkB,EACzC,QAAQ,mBAAmB,sBAAsB,EACjD,QAAQ,gBAAgB,mBAAmB,EAC3C,QAAQ,iBAAiB,oBAAoB,EAC7C,QAAQ,eAAe,kBAAkB;AAAA,IAAA;AAM9C,UAAM,QAAQW,mBAAAA,MAAM,UAAU,MAAM;AACpC,SAAK,SAAS;AAEdX,WAAAA,QAAO,QAAQ,iBAAiB,MAAM,OAAO,SAAS,GAAG;AAEzD,UAAM,MAAM,mBAAmB,MAAM;AACrC,SAAK,gBAAgB,SAAS,gBAAgB;AAAA,MAC5C,OAAO,MAAM;AAAA,MACb,UAAU;AAAA,IAAA,CACX;AACD,SAAK,gBAAgB,SAAS,gBAAgB;AAAA,MAC5C,OAAO,MAAM;AAAA,MACb,UAAU;AAAA,IAAA,CACX;AACD,SAAK,gBAAA;AACLA,WAAAA,QAAO,QAAQ,oBAAoB;AAAA,EACrC;AAAA;AAAA,EAGA,MAAM,MAAc;AAClB,QAAI,KAAK,UAAU,KAAK;AAEtBA,aAAAA,QAAO;AAAA,QACL,UAAU;AAAA,QACV;AAAA,QACA,4BAA4B;AAAA,MAAA;AAE9BA,aAAAA,QAAO,KAAK,KAAK,QAAQ,OAAO,EAAE,CAAC;AAEnC,UAAI;AACF,cAAM,IAAI,MAAM,uBAAuB;AAAA,MACzC,SAAS,KAAK;AACZA,eAAAA,QAAO,KAAK,GAAG;AAAA,MACjB;AACA;AAAA,IACF;AACA,QAAI,KAAK,QAAQ;AACfA,qBAAO,QAAQ,QAAQ,OAAO,KAAK,QAAQ,OAAO,EAAE,CAAC;AACrD,WAAK,OAAO,MAAM,MAAM,IAAI;AAAA,IAC9B;AAAA,EACF;AAAA;AAAA,EAGA,QAAQ,MAAc;AACpB,SAAK,MAAM,OAAO,IAAI;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,MAAqB;AACvB,WAAO,KAAK,QAAQ,OAAO;AAAA,EAC7B;AAAA;AAAA,EAGA,OAAO;AACL,QAAI,CAAC,KAAK,QAAQ;AAChBA,aAAAA,QAAO,QAAQ,4CAA4C;AAC3D;AAAA,IACF;AAEAA,WAAAA,QAAO,QAAQ,0BAA0B;AAEzC,SAAK,OAAO,KAAK,QAAQ;AAKzB,SAAK,OAAO,KAAK,SAAS;AAAA,EAC5B;AAAA;AAAA,EAGA,UAAU;AACR,QAAI,CAAC,KAAK,OAAQ;AAElBA,WAAAA,QAAO,QAAQ,8BAA8B;AAG7C,SAAK,gBAAA;AAEL,SAAK,SAAS;AACd,SAAK,gBAAgB;AACrB,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA,EAGA,kBAAkB;AAChB,SAAK,eAAe,GAAG,QAAQ,KAAK,YAAY;AAChD,SAAK,eAAe,GAAG,QAAQ,KAAK,aAAa;AACjD,SAAK,QAAQ,GAAG,QAAQ,KAAK,YAAY;AACzC,SAAK,QAAQ,GAAG,SAAS,MAAM;AAAA,IAAC,CAAC;AAAA,EACnC;AAAA;AAAA,EAGA,kBAAkB;AAChB,SAAK,eAAe,IAAI,QAAQ,KAAK,YAAY;AACjD,SAAK,eAAe,IAAI,QAAQ,KAAK,aAAa;AAClD,SAAK,QAAQ,IAAI,QAAQ,KAAK,YAAY;AAC1C,SAAK,QAAQ,mBAAmB,OAAO;AAAA,EACzC;AAAA;AAAA,EAGA,cAAc,MAAc;AAC1BA,WAAAA,QAAO,QAAQ,QAAQ,KAAK,IAAI;AAChC,SAAK,KAAK,OAAO,IAAI;AACrB,eAAW,EAAE,OAAO,UAAU,MAAM,QAAA,KAAa,aAAa;AAC5D,UAAI,KAAK,MAAM,KAAK,GAAG;AACrBA,eAAAA,QAAO;AAAA,UACL,eAAe,KAAK,GAAG,KACrB,WAAW,mBAAmB,WAAW,WAAW,gBACtD;AAAA,QAAA;AAAA,MAEJ;AAAA,IACF;AAAA,EACF;AAAA,EAEA,aAAa,MAAqB,QAA+B;AAC/DA,WAAAA,QAAO,QAAQ,qBAAqB;AACpC,SAAK,KAAK,UAAU,MAAM,MAAM;AAChC,SAAK,QAAA;AAAA,EACP;AAAA,EAEA,aAAa,MAAc;AACzB,SAAK,KAAK,QAAQ,UAAU,IAAI,CAAC;AAAA,EACnC;AACF;;"}
1
+ {"version":3,"file":"server.js","sources":["../../src/brickadia/server.ts"],"sourcesContent":["/*\n Brickadia Server Wrapper\n Manages IO with the game server\n*/\n\nimport Logger from '@/logger';\nimport {\n ACTIVE_WORLD_FILE,\n CONFIG_SAVED_DIR,\n GAME_BIN_PATH,\n getOverrideGameBinary,\n getSteamGameDir,\n getSteamInstallDir,\n} from '@/softconfig';\nimport { getGlobalToken } from '@cli/auth';\nimport { type IConfig } from '@config/types';\nimport { checkWsl } from '@util/wsl';\nimport 'colors';\nimport { type ChildProcessWithoutNullStreams, spawn } from 'node:child_process';\nimport EventEmitter from 'node:events';\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { env } from 'node:process';\nimport { installHint } from '@util/distro';\nimport readline from 'readline';\nimport stripAnsi from 'strip-ansi';\n\n/**\n * libgl1-mesa-glx was dropped after Debian 11 and Ubuntu 22.04; libgl1 is what\n * provides libGL.so.1 there now.\n */\nconst MISSING_LIBRARY_FIX = installHint({\n debian: 'libgl1 libglib2.0-0',\n fedora: 'mesa-libGL glib2',\n arch: 'libglvnd glib2',\n});\n\n// list of errors that can be solved by yelling at the user\nconst knownErrors: {\n name: string;\n solution?: string;\n match: RegExp;\n message?: string;\n}[] = [\n {\n name: 'MISSING_LIBGL',\n solution: MISSING_LIBRARY_FIX,\n match:\n /error while loading shared libraries: libGL\\.so\\.1: cannot open shared object file/,\n },\n {\n name: 'MISSING_GLIB',\n solution: MISSING_LIBRARY_FIX,\n match:\n /error while loading shared libraries: libgthread-2\\.0\\.so\\.0: cannot open shared object file/,\n },\n];\n\n/** Start a brickadia server */\nexport default class BrickadiaServer extends EventEmitter {\n #child: ChildProcessWithoutNullStreams | null = null;\n #errInterface: readline.Interface | null = null;\n #outInterface: readline.Interface | null = null;\n\n config: IConfig;\n path: string;\n\n constructor(dataPath: string, config: IConfig) {\n super();\n\n this.config = config;\n // use the data path if it's absolute, otherwise build an absolute path\n this.path =\n path.isAbsolute(dataPath) || dataPath.startsWith('/')\n ? dataPath\n : path.join(process.cwd(), dataPath);\n\n this.lineListener = this.lineListener.bind(this);\n this.errorListener = this.errorListener.bind(this);\n this.exitListener = this.exitListener.bind(this);\n }\n\n getActiveWorldFile(): string {\n return path.join(this.path, ACTIVE_WORLD_FILE);\n }\n\n /** A world specified by the active world file */\n getActiveWorld(): string | null {\n const activeWorldFile = this.getActiveWorldFile();\n if (existsSync(activeWorldFile)) {\n try {\n return readFileSync(activeWorldFile, 'utf8').trim();\n } catch (err) {\n Logger.errorp(\n 'Failed to read active world file',\n activeWorldFile.yellow,\n err,\n );\n return null;\n }\n }\n return null;\n }\n\n /** Set the world to use next startup */\n setActiveWorld(world: string | null): boolean {\n const activeWorldFile = this.getActiveWorldFile();\n if (!world || world === null) {\n if (existsSync(activeWorldFile)) {\n Logger.verbose('Removing active world file', activeWorldFile.yellow);\n unlinkSync(activeWorldFile);\n }\n return true;\n }\n\n if (!this.worldExists(world)) {\n Logger.verbose(\n 'Cannot set active world to',\n world.yellow,\n 'as it does not exist',\n );\n return false;\n }\n\n Logger.verbose('Setting active world to', world.yellow);\n try {\n writeFileSync(activeWorldFile, world, 'utf8');\n return true;\n } catch (err) {\n Logger.errorp(\n 'Failed to write active world file',\n activeWorldFile.yellow,\n err,\n );\n return false;\n }\n }\n\n /** A world specified by the config */\n getConfigWorld(): string | null {\n return this.config?.server?.world ?? null;\n }\n\n /** A world specified by the BRICKADIA_WORLD env variable */\n getEnvWorld(): string | null {\n return env.BRICKADIA_WORLD || null;\n }\n\n /** Check if a world exists */\n worldExists(world: string): boolean {\n const savedDir = this.config?.server?.savedDir ?? CONFIG_SAVED_DIR;\n const worldPath = path.join(this.path, savedDir, 'Worlds', world + '.brdb');\n return existsSync(worldPath);\n }\n\n /** Get the world that will be used next startup */\n getNextWorld(): { source: string; file: string } | null {\n const candidates = [\n { source: 'file', file: this.getActiveWorld() },\n { source: 'config', file: this.getConfigWorld() },\n { source: 'env', file: this.getEnvWorld() },\n ];\n\n return (\n candidates.find(\n (c): c is { source: string; file: string } =>\n !!c.file && this.worldExists(c.file),\n ) ?? null\n );\n }\n\n /**\n * Resolve the path to the game server binary for steam/override installs.\n * Returns null for launcher-managed installs, where the binary lives in a\n * branch directory the launcher owns and isn't known statically here.\n */\n getGameBinaryPath(): string | null {\n const overrideBinary = getOverrideGameBinary();\n if (overrideBinary) return overrideBinary;\n\n // a configured branch means the launcher manages the install, not steam\n const isSteam = !this.config.server.branch;\n if (!isSteam) return null;\n\n const steamBeta = this.config.server.steambeta ?? 'main';\n return path.join(\n getSteamInstallDir(), // steam install directory\n steamBeta, // steam beta branch (or main)\n getSteamGameDir(), // Brickadia\n GAME_BIN_PATH, // path to binary\n );\n }\n\n /**\n * Resolve the directory the game server binary lives in. Null whenever\n * {@link getGameBinaryPath} can't resolve a binary.\n */\n getGameBinaryDir(): string | null {\n const binary = this.getGameBinaryPath();\n return binary && path.dirname(binary);\n }\n\n // start the server child process\n start() {\n const {\n email,\n password,\n token: confToken,\n }: { email?: string; password?: string; token?: string } = this.config\n .credentials || {};\n\n const token = confToken || process.env.BRICKADIA_TOKEN || getGlobalToken();\n\n if (token) {\n Logger.verbose('Starting server with hosting token');\n } else {\n Logger.verbose(\n 'Starting server',\n (!email && !password ? 'without' : 'with').yellow,\n 'credentials',\n );\n }\n\n const isSteam = !this.config.server.branch;\n const steamBeta = this.config.server.steambeta ?? 'main';\n const overrideBinary = getOverrideGameBinary();\n const steamBinary = path.join(\n getSteamInstallDir(), // steam install directory\n steamBeta, // steam beta branch (or main)\n getSteamGameDir(), // Brickadia\n GAME_BIN_PATH, // path to binary\n );\n\n let gameBinary = this.getGameBinaryPath() ?? steamBinary;\n\n if (overrideBinary) {\n if (!existsSync(overrideBinary)) {\n Logger.error(\n 'Override binary',\n overrideBinary.yellow,\n 'does not exist!',\n );\n throw new Error(`Override binary ${overrideBinary} does not exist!`);\n }\n\n Logger.verbose(\n 'Using override binary',\n overrideBinary.yellow,\n 'instead of',\n steamBinary.yellow,\n );\n gameBinary = overrideBinary;\n } else if (isSteam) {\n Logger.verbose('Using steam binary', steamBeta.yellow);\n } else {\n Logger.verbose(\n 'Running',\n (this.config.server.__LOCAL\n ? path.join(__dirname, '../../tools/brickadia.sh')\n : 'brickadia launcher'\n ).yellow,\n );\n if (typeof this.config.server.branch === 'string')\n Logger.verbose('Using branch', this.config.server.branch.yellow);\n }\n\n // handle local launcher support\n const launchArgs = isSteam\n ? [gameBinary]\n : [\n this.config.server.__LOCAL\n ? path.join(__dirname, '../../tools/brickadia.sh')\n : 'brickadia',\n this.config.server.branch && `--branch=${this.config.server.branch}`,\n '--server',\n '--',\n ];\n\n const world = this.getNextWorld();\n if (world) {\n Logger.verbose(\n 'Using world',\n world.file.yellow,\n 'from',\n world.source.yellow,\n );\n } else if (this.config.server.map) {\n Logger.verbose('Using map', this.config.server.map.yellow, 'from config');\n }\n\n const params = [\n '--output=L',\n '--',\n ...launchArgs,\n !world &&\n this.config.server.map &&\n `-Environment=\"${this.config.server.map}\"`,\n world && `-World=\"${world.file}\"`,\n '-NotInstalled',\n '-log',\n // remote file access is on unless explicitly disabled in the config\n this.config.server.remoteFiles === false ? '-NoRemoteFileAccess' : null,\n checkWsl() === 1 ? '-OneThread' : null,\n this.path ? `-UserDir=\"${this.path}\"` : null,\n token ? `-Token=\"${token}\"` : null, // remove token argument if not provided\n !token && email ? `-User=\"${email}\"` : null, // remove email argument if not provided or token is provided\n !token && password ? `-Password=\"${password}\"` : null, // remove password argument if not provided or token is provided\n `-port=\"${this.config.server.port}\"`,\n this.config.server.launchArgs,\n ]\n .filter(arg => typeof arg === 'string')\n .filter(arg => arg.length > 0); // remove unused/empty arguments\n Logger.verbose(\n 'Params for spawn',\n params\n .join(' ')\n .replace(/-User=\".*?\"/, '-User=\"<hidden>\"')\n .replace(/-Password=\".*?\"/, '-Password=\"<hidden>\"')\n .replace(/-Token=\".*?\"/, '-Token=\"<hidden>\"')\n .replace(/-Cookie=\".*?\"/, '-Cookie=\"<hidden>\"')\n .replace(/-Cookie=\\S+/, '-Cookie=<hidden>'),\n );\n\n // Either unbuffer or stdbuf must be used because brickadia's output is buffered\n // this means that multiple lines can be bundled together if the output buffer is not full\n // unfortunately without stdbuf or unbuffer, the output would not happen immediately\n const child = spawn('stdbuf', params);\n this.#child = child;\n\n Logger.verbose('Spawn process', child.pid ?? 'failed'.red);\n\n child.stdin.setDefaultEncoding('utf8');\n this.#outInterface = readline.createInterface({\n input: child.stdout,\n terminal: false,\n });\n this.#errInterface = readline.createInterface({\n input: child.stderr,\n terminal: false,\n });\n this.attachListeners();\n Logger.verbose('Attached listeners');\n }\n\n // write a string to the child process\n write(line: string) {\n if (line.length >= 512) {\n // show a warning\n Logger.warn(\n 'WARNING'.yellow,\n 'The following line was called and is',\n 'longer than allowed limit'.red,\n );\n Logger.warn(line.replace(/\\n$/, ''));\n // throw a fake error to get the line number\n try {\n throw new Error('Console Line Too Long');\n } catch (err) {\n Logger.warn(err);\n }\n return;\n }\n if (this.#child) {\n Logger.verbose('WRITE'.green, line.replace(/\\n$/, ''));\n this.#child.stdin.write(line);\n }\n }\n\n // write a line to the child process\n writeln(line: string) {\n this.write(line + '\\n');\n }\n\n /**\n * pid of the running game process, or null when it isn't running.\n *\n * The child is spawned through `stdbuf`, which execs the game in place, so\n * this is the game's own pid and /proc entry.\n */\n get pid(): number | null {\n return this.#child?.pid ?? null;\n }\n\n // forcibly kills the server\n stop() {\n if (!this.#child) {\n Logger.verbose('Cannot stop server as no subprocess exists');\n return;\n }\n\n Logger.verbose('Forcibly stopping server');\n // kill the process\n this.#child.kill('SIGINT');\n\n // ...kill it again just to make sure it's dead. this signals the pid\n // directly rather than shelling out: slim container images have no\n // `kill` binary, only the shell builtin, and spawn cannot use that.\n this.#child.kill('SIGKILL');\n }\n\n // detaches listeners\n cleanup() {\n if (!this.#child) return;\n\n Logger.verbose('Cleaning up brickadia server');\n\n // detach listener\n this.detachListeners();\n\n this.#child = null;\n this.#outInterface = null;\n this.#errInterface = null;\n }\n\n // attaches proxy event listeners\n attachListeners() {\n this.#outInterface?.on('line', this.lineListener);\n this.#errInterface?.on('line', this.errorListener);\n this.#child?.on('exit', this.exitListener);\n this.#child?.on('close', () => {});\n }\n\n // removes previously attached proxy event listeners\n detachListeners() {\n this.#outInterface?.off('line', this.lineListener);\n this.#errInterface?.off('line', this.errorListener);\n this.#child?.off('exit', this.exitListener);\n this.#child?.removeAllListeners('close');\n }\n\n // -- listeners for basic events (line, err, exit)\n errorListener(line: string) {\n Logger.verbose('ERROR'.red, line);\n this.emit('err', line);\n for (const { match, solution, name, message } of knownErrors) {\n if (line.match(match)) {\n Logger.error(\n `Encountered ${name.red}. ${\n solution ? 'Known fix:\\n ' + solution : message || 'Unknown error.'\n }`,\n );\n }\n }\n }\n\n exitListener(code: number | null, signal: NodeJS.Signals | null) {\n Logger.verbose('Exit listener fired');\n this.emit('closed', code, signal);\n this.cleanup();\n }\n\n lineListener(line: string) {\n this.emit('line', stripAnsi(line));\n }\n}\n"],"names":["installHint","ACTIVE_WORLD_FILE","existsSync","readFileSync","Logger","unlinkSync","writeFileSync","env","CONFIG_SAVED_DIR","getOverrideGameBinary","getSteamInstallDir","getSteamGameDir","GAME_BIN_PATH","getGlobalToken","checkWsl","spawn"],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const fs = require("node:fs");
4
+ const INSTALLERS = {
5
+ debian: { label: "Debian/Ubuntu", command: "sudo apt install" },
6
+ fedora: { label: "Fedora", command: "sudo dnf install" },
7
+ arch: { label: "Arch", command: "sudo pacman -S" }
8
+ };
9
+ function parseDistroFamily(osRelease) {
10
+ const field = (name) => osRelease.match(new RegExp(`^${name}=(.*)$`, "m"))?.[1].replace(/["']/g, "") ?? "";
11
+ const ids = new Set(`${field("ID")} ${field("ID_LIKE")}`.trim().split(/\s+/));
12
+ if (ids.has("debian") || ids.has("ubuntu")) return "debian";
13
+ if (ids.has("fedora") || ids.has("rhel") || ids.has("centos"))
14
+ return "fedora";
15
+ if (ids.has("arch")) return "arch";
16
+ return "unknown";
17
+ }
18
+ function getDistroFamily() {
19
+ try {
20
+ return parseDistroFamily(fs.readFileSync("/etc/os-release", "utf8"));
21
+ } catch {
22
+ return "unknown";
23
+ }
24
+ }
25
+ function installHint(packages, family = getDistroFamily()) {
26
+ if (family !== "unknown") {
27
+ return `${INSTALLERS[family].command} ${packages[family]}`;
28
+ }
29
+ const families = Object.keys(INSTALLERS);
30
+ const width = Math.max(...families.map((f) => INSTALLERS[f].label.length)) + 1;
31
+ return families.map((f, i) => {
32
+ const { label, command } = INSTALLERS[f];
33
+ return `${i ? " " : ""}${`${label}:`.padEnd(width)} ${command} ${packages[f]}`;
34
+ }).join("\n");
35
+ }
36
+ exports.getDistroFamily = getDistroFamily;
37
+ exports.installHint = installHint;
38
+ exports.parseDistroFamily = parseDistroFamily;
39
+ //# sourceMappingURL=distro.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"distro.js","sources":["../../src/util/distro.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\n\nexport type DistroFamily = 'debian' | 'fedora' | 'arch' | 'unknown';\n\n/** The same packages under the name each family's repositories give them. */\nexport type DistroPackages = Record<Exclude<DistroFamily, 'unknown'>, string>;\n\nconst INSTALLERS = {\n debian: { label: 'Debian/Ubuntu', command: 'sudo apt install' },\n fedora: { label: 'Fedora', command: 'sudo dnf install' },\n arch: { label: 'Arch', command: 'sudo pacman -S' },\n} as const;\n\n/**\n * ID_LIKE is how a derivative names its parent, so matching it covers Mint,\n * Pop, Rocky and Manjaro without naming any of them.\n */\nexport function parseDistroFamily(osRelease: string): DistroFamily {\n const field = (name: string) =>\n osRelease\n .match(new RegExp(`^${name}=(.*)$`, 'm'))?.[1]\n .replace(/[\"']/g, '') ?? '';\n const ids = new Set(`${field('ID')} ${field('ID_LIKE')}`.trim().split(/\\s+/));\n\n if (ids.has('debian') || ids.has('ubuntu')) return 'debian';\n if (ids.has('fedora') || ids.has('rhel') || ids.has('centos'))\n return 'fedora';\n if (ids.has('arch')) return 'arch';\n return 'unknown';\n}\n\n/** Unknown when there is no os-release to read, which includes not being linux. */\nexport function getDistroFamily(): DistroFamily {\n try {\n return parseDistroFamily(readFileSync('/etc/os-release', 'utf8'));\n } catch {\n return 'unknown';\n }\n}\n\n/**\n * The one install command that works here. An unrecognised distro gets every\n * command instead, with continuation lines indented to sit under callers that\n * prefix the first.\n */\nexport function installHint(\n packages: DistroPackages,\n family: DistroFamily = getDistroFamily(),\n): string {\n if (family !== 'unknown') {\n return `${INSTALLERS[family].command} ${packages[family]}`;\n }\n\n const families = Object.keys(INSTALLERS) as Exclude<\n DistroFamily,\n 'unknown'\n >[];\n const width = Math.max(...families.map(f => INSTALLERS[f].label.length)) + 1;\n return families\n .map((f, i) => {\n const { label, command } = INSTALLERS[f];\n return `${i ? ' ' : ''}${`${label}:`.padEnd(width)} ${command} ${packages[f]}`;\n })\n .join('\\n');\n}\n"],"names":["readFileSync"],"mappings":";;;AAOA,MAAM,aAAa;AAAA,EACjB,QAAQ,EAAE,OAAO,iBAAiB,SAAS,mBAAA;AAAA,EAC3C,QAAQ,EAAE,OAAO,UAAU,SAAS,mBAAA;AAAA,EACpC,MAAM,EAAE,OAAO,QAAQ,SAAS,iBAAA;AAClC;AAMO,SAAS,kBAAkB,WAAiC;AACjE,QAAM,QAAQ,CAAC,SACb,UACG,MAAM,IAAI,OAAO,IAAI,IAAI,UAAU,GAAG,CAAC,IAAI,CAAC,EAC5C,QAAQ,SAAS,EAAE,KAAK;AAC7B,QAAM,MAAM,IAAI,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,MAAM,SAAS,CAAC,GAAG,KAAA,EAAO,MAAM,KAAK,CAAC;AAE5E,MAAI,IAAI,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,EAAG,QAAO;AACnD,MAAI,IAAI,IAAI,QAAQ,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,IAAI,QAAQ;AAC1D,WAAO;AACT,MAAI,IAAI,IAAI,MAAM,EAAG,QAAO;AAC5B,SAAO;AACT;AAGO,SAAS,kBAAgC;AAC9C,MAAI;AACF,WAAO,kBAAkBA,GAAAA,aAAa,mBAAmB,MAAM,CAAC;AAAA,EAClE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,SAAS,YACd,UACA,SAAuB,mBACf;AACR,MAAI,WAAW,WAAW;AACxB,WAAO,GAAG,WAAW,MAAM,EAAE,OAAO,IAAI,SAAS,MAAM,CAAC;AAAA,EAC1D;AAEA,QAAM,WAAW,OAAO,KAAK,UAAU;AAIvC,QAAM,QAAQ,KAAK,IAAI,GAAG,SAAS,IAAI,CAAA,MAAK,WAAW,CAAC,EAAE,MAAM,MAAM,CAAC,IAAI;AAC3E,SAAO,SACJ,IAAI,CAAC,GAAG,MAAM;AACb,UAAM,EAAE,OAAO,YAAY,WAAW,CAAC;AACvC,WAAO,GAAG,IAAI,OAAO,EAAE,GAAG,GAAG,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,OAAO,IAAI,SAAS,CAAC,CAAC;AAAA,EAC/E,CAAC,EACA,KAAK,IAAI;AACd;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omegga",
3
- "version": "1.18.0",
3
+ "version": "1.18.1",
4
4
  "description": "Brickadia server installer, updater, manager, wrapper, configurator, and automator",
5
5
  "engines": {
6
6
  "node": ">=23.0.0"
@@ -157,4 +157,4 @@
157
157
  "url": "https://github.com/brickadia-community/omegga/issues"
158
158
  },
159
159
  "homepage": "https://github.com/brickadia-community/omegga#readme"
160
- }
160
+ }
@@ -9,8 +9,12 @@ FILE=$LAUNCHER_PATH/$TAR_FILE
9
9
  BINARY_PATH=$LAUNCHER_PATH/brickadia-launcher/main-brickadia-launcher
10
10
 
11
11
  if ! [[ $(which tar) && $(which wget) && $(which xz) ]]; then
12
- echo ">! Missing dependencies, please run:" >&2
13
- echo " apt-get install wget tar xz-utils" >&2
12
+ # the package holding xz is the only name that moves: xz-utils on Debian and
13
+ # Ubuntu, xz everywhere else
14
+ echo ">! Missing dependencies. Install wget, tar and xz:" >&2
15
+ echo " Debian/Ubuntu: sudo apt-get install wget tar xz-utils" >&2
16
+ echo " Fedora: sudo dnf install wget tar xz" >&2
17
+ echo " Arch: sudo pacman -S wget tar xz" >&2
14
18
  echo
15
19
  exit 1
16
20
  fi;
@@ -30,22 +30,43 @@ fi
30
30
 
31
31
  has_tar=$(which tar)
32
32
  has_wget=$(which wget)
33
- has_lib32gcc="yes" # sorry anyone not running arch/debian
34
- lib32gcc_dep="lib32gcc-s1"
35
- pkg_manager="apt-get install"
36
33
 
37
- if [[ -f /etc/arch-release ]]; then
38
- # if lib32-gcc-libs is installed in arch linux
39
- lib32gcc_dep="lib32-gcc-libs"
40
- pkg_manager="pacman -S"
41
- has_lib32gcc=$(pacman -Q lib32-gcc-libs >/dev/null 2>&1 && echo "yes" || echo "no")
42
- elif [[ -f /etc/debian_version ]] || [[ -f /etc/lsb-release ]]; then
43
- # if lib32gcc-s1 exists in debian/ubuntu
44
- lib32gcc_dep="lib32gcc-s1"
45
- pkg_manager="apt-get install"
46
- has_lib32gcc=$(dpkg -s lib32gcc-s1 >/dev/null 2>&1 && echo "yes" || echo "no")
34
+ # steamcmd is a 32-bit binary, so it needs 32-bit gcc libraries under whatever
35
+ # name and package manager this distro uses. ID_LIKE covers the derivatives.
36
+ distro=""
37
+ if [[ -r /etc/os-release ]]; then
38
+ . /etc/os-release
39
+ case " ${ID:-} ${ID_LIKE:-} " in
40
+ *" arch "*) distro="arch" ;;
41
+ *" fedora "*|*" rhel "*|*" centos "*) distro="fedora" ;;
42
+ *" debian "*|*" ubuntu "*) distro="debian" ;;
43
+ esac
47
44
  fi
48
45
 
46
+ case "$distro" in
47
+ arch)
48
+ lib32gcc_dep="lib32-gcc-libs"
49
+ pkg_manager="pacman -S"
50
+ has_lib32gcc=$(pacman -Q lib32-gcc-libs >/dev/null 2>&1 && echo "yes" || echo "no")
51
+ ;;
52
+ fedora)
53
+ lib32gcc_dep="glibc.i686 libgcc.i686 libstdc++.i686"
54
+ pkg_manager="dnf install"
55
+ has_lib32gcc=$(rpm -q libgcc.i686 >/dev/null 2>&1 && rpm -q libstdc++.i686 >/dev/null 2>&1 && echo "yes" || echo "no")
56
+ ;;
57
+ debian)
58
+ lib32gcc_dep="lib32gcc-s1"
59
+ pkg_manager="apt-get install"
60
+ has_lib32gcc=$(dpkg -s lib32gcc-s1 >/dev/null 2>&1 && echo "yes" || echo "no")
61
+ ;;
62
+ *)
63
+ # An unrecognised distro gets the benefit of the doubt rather than a wrong
64
+ # package name: steamcmd will say so itself if the libraries are missing.
65
+ lib32gcc_dep=""
66
+ pkg_manager="<your package manager>"
67
+ has_lib32gcc="yes"
68
+ ;;
69
+ esac
49
70
 
50
71
  if ! [[ $has_tar && $has_wget && $has_lib32gcc == "yes" ]]; then
51
72
  wget_dep="wget "
@@ -55,11 +76,34 @@ if ! [[ $has_tar && $has_wget && $has_lib32gcc == "yes" ]]; then
55
76
  if [[ $has_lib32gcc == "yes" ]]; then lib32gcc_dep=""; fi
56
77
 
57
78
  echo ">! Missing dependencies, please run:" >&2
58
- echo " sudo $pkg_manager $wget_dep$tar_dep_$lib32gcc_dep" >&2
79
+ echo " sudo $pkg_manager $wget_dep$tar_dep$lib32gcc_dep" >&2
59
80
  echo
60
81
  exit 1
61
82
  fi;
62
83
 
84
+ # steamcmd's own OpenSSL reads the trust store from this one path and reports an
85
+ # empty one as being offline, which sends people looking at their network for a
86
+ # missing symlink. Fedora ships no bundle file at any legacy path.
87
+ #
88
+ # Stopping here rather than warning: steamcmd retries instead of giving up when
89
+ # it cannot verify anything, so carrying on looks like omegga hanging on first
90
+ # start.
91
+ CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
92
+ EXTRACTED_BUNDLE=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
93
+ if [[ ! -e $CA_BUNDLE ]]; then
94
+ echo ">! $CA_BUNDLE is missing. steamcmd reads CA certificates from that" >&2
95
+ echo ">! path, and without it every download fails as 'Steamcmd needs to be" >&2
96
+ echo ">! online to update'." >&2
97
+ if [[ -f $EXTRACTED_BUNDLE ]]; then
98
+ echo ">! Point it at the bundle this system already has:" >&2
99
+ echo " sudo ln -s $EXTRACTED_BUNDLE $CA_BUNDLE" >&2
100
+ echo >&2
101
+ exit 1
102
+ fi
103
+ echo ">! Install your distro's ca-certificates package." >&2
104
+ echo >&2
105
+ fi
106
+
63
107
  if [[ -f $STEAMCMD ]]; then
64
108
  echo ">> Steamcmd already installed, checking for updates..."
65
109
  $STEAMCMD +login anonymous +quit