2022-12-23 15:43:56 +01:00
|
|
|
const { log } = require("../src/util");
|
|
|
|
const childProcess = require("child_process");
|
2023-02-05 10:45:36 +01:00
|
|
|
const fs = require("fs");
|
2023-02-05 11:01:54 +01:00
|
|
|
const mysql = require("mysql2");
|
2022-12-23 15:43:56 +01:00
|
|
|
|
2023-02-05 10:45:36 +01:00
|
|
|
/**
|
|
|
|
* It is only used inside the docker container
|
|
|
|
*/
|
2022-12-23 15:43:56 +01:00
|
|
|
class EmbeddedMariaDB {
|
|
|
|
|
2023-02-05 10:45:36 +01:00
|
|
|
static instance = null;
|
2022-12-23 15:43:56 +01:00
|
|
|
|
2023-02-05 10:45:36 +01:00
|
|
|
exec = "mariadbd";
|
2022-12-23 15:43:56 +01:00
|
|
|
|
2023-02-05 10:45:36 +01:00
|
|
|
mariadbDataDir = "/app/data/mariadb";
|
|
|
|
|
2024-11-05 13:25:08 +01:00
|
|
|
runDir = "/app/data/run";
|
2023-02-05 10:45:36 +01:00
|
|
|
|
2024-11-05 13:25:08 +01:00
|
|
|
socketPath = this.runDir + "/mariadb.sock";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The username to connect to the MariaDB
|
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
username = null;
|
2023-02-05 10:45:36 +01:00
|
|
|
|
2023-09-07 09:42:44 +02:00
|
|
|
/**
|
|
|
|
* @type {ChildProcessWithoutNullStreams}
|
|
|
|
* @private
|
|
|
|
*/
|
2023-02-05 10:45:36 +01:00
|
|
|
childProcess = null;
|
|
|
|
running = false;
|
|
|
|
|
|
|
|
started = false;
|
|
|
|
|
|
|
|
/**
|
2023-09-07 09:42:44 +02:00
|
|
|
* @returns {EmbeddedMariaDB} The singleton instance
|
2023-02-05 10:45:36 +01:00
|
|
|
*/
|
|
|
|
static getInstance() {
|
|
|
|
if (!EmbeddedMariaDB.instance) {
|
|
|
|
EmbeddedMariaDB.instance = new EmbeddedMariaDB();
|
|
|
|
}
|
|
|
|
return EmbeddedMariaDB.instance;
|
2022-12-23 15:43:56 +01:00
|
|
|
}
|
|
|
|
|
2023-08-11 16:29:45 +02:00
|
|
|
/**
|
2023-09-07 09:42:44 +02:00
|
|
|
* @returns {boolean} If the singleton instance is created
|
2023-08-11 16:29:45 +02:00
|
|
|
*/
|
2023-02-05 10:45:36 +01:00
|
|
|
static hasInstance() {
|
|
|
|
return !!EmbeddedMariaDB.instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-09-07 09:42:44 +02:00
|
|
|
* Start the embedded MariaDB
|
2024-11-05 13:25:08 +01:00
|
|
|
* @throws {Error} If the current user is not "node" or "root"
|
2023-09-07 09:42:44 +02:00
|
|
|
* @returns {Promise<void>|void} A promise that resolves when the MariaDB is started or void if it is already started
|
2023-02-05 10:45:36 +01:00
|
|
|
*/
|
|
|
|
start() {
|
2024-11-05 13:25:08 +01:00
|
|
|
// Check if the current user is "node" or "root"
|
|
|
|
this.username = require("os").userInfo().username;
|
|
|
|
if (this.username !== "node" && this.username !== "root") {
|
|
|
|
throw new Error("Embedded Mariadb supports only 'node' or 'root' user, but the current user is: " + this.username);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.initDB();
|
|
|
|
|
|
|
|
this.startChildProcess();
|
|
|
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
let interval = setInterval(() => {
|
|
|
|
if (this.started) {
|
|
|
|
clearInterval(interval);
|
|
|
|
resolve();
|
|
|
|
} else {
|
|
|
|
log.info("mariadb", "Waiting for Embedded MariaDB to start...");
|
|
|
|
}
|
|
|
|
}, 1000);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start the child process
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
startChildProcess() {
|
2022-12-23 15:43:56 +01:00
|
|
|
if (this.childProcess) {
|
2023-02-05 10:45:36 +01:00
|
|
|
log.info("mariadb", "Already started");
|
2022-12-23 15:43:56 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.running = true;
|
2023-02-05 10:45:36 +01:00
|
|
|
log.info("mariadb", "Starting Embedded MariaDB");
|
|
|
|
this.childProcess = childProcess.spawn(this.exec, [
|
|
|
|
"--user=node",
|
|
|
|
"--datadir=" + this.mariadbDataDir,
|
|
|
|
`--socket=${this.socketPath}`,
|
|
|
|
`--pid-file=${this.runDir}/mysqld.pid`,
|
2024-11-05 13:25:08 +01:00
|
|
|
// Don't add the following option, the mariadb will not report message to the console, which affects initDBAfterStarted()
|
|
|
|
// "--log-error=" + `${this.mariadbDataDir}/mariadb-error.log`,
|
2023-02-05 10:45:36 +01:00
|
|
|
]);
|
2022-12-23 15:43:56 +01:00
|
|
|
|
|
|
|
this.childProcess.on("close", (code) => {
|
|
|
|
this.running = false;
|
|
|
|
this.childProcess = null;
|
2023-02-05 10:45:36 +01:00
|
|
|
this.started = false;
|
|
|
|
log.info("mariadb", "Stopped Embedded MariaDB: " + code);
|
|
|
|
|
|
|
|
if (code !== 0) {
|
2024-11-05 13:25:08 +01:00
|
|
|
log.error("mariadb", "Try to restart Embedded MariaDB as it is not stopped by user");
|
|
|
|
this.startChildProcess();
|
2023-02-05 10:45:36 +01:00
|
|
|
}
|
2022-12-23 15:43:56 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
this.childProcess.on("error", (err) => {
|
|
|
|
if (err.code === "ENOENT") {
|
2023-02-05 10:45:36 +01:00
|
|
|
log.error("mariadb", `Embedded MariaDB: ${this.exec} is not found`);
|
2022-12-23 15:43:56 +01:00
|
|
|
} else {
|
2023-02-05 10:45:36 +01:00
|
|
|
log.error("mariadb", err);
|
2022-12-23 15:43:56 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-02-05 10:45:36 +01:00
|
|
|
let handler = (data) => {
|
2024-11-05 13:25:08 +01:00
|
|
|
log.info("mariadb", data.toString("utf-8"));
|
2023-02-05 10:45:36 +01:00
|
|
|
if (data.toString("utf-8").includes("ready for connections")) {
|
2023-02-05 11:01:54 +01:00
|
|
|
this.initDBAfterStarted();
|
2023-02-05 10:45:36 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.childProcess.stdout.on("data", handler);
|
|
|
|
this.childProcess.stderr.on("data", handler);
|
2022-12-23 15:43:56 +01:00
|
|
|
}
|
|
|
|
|
2023-08-11 16:29:45 +02:00
|
|
|
/**
|
2023-09-07 09:42:44 +02:00
|
|
|
* Stop all the child processes
|
|
|
|
* @returns {void}
|
2023-08-11 16:29:45 +02:00
|
|
|
*/
|
2023-02-05 10:45:36 +01:00
|
|
|
stop() {
|
2022-12-23 15:43:56 +01:00
|
|
|
if (this.childProcess) {
|
|
|
|
this.childProcess.kill("SIGINT");
|
|
|
|
this.childProcess = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-11 16:29:45 +02:00
|
|
|
/**
|
2023-09-07 09:42:44 +02:00
|
|
|
* Install MariaDB if it is not installed and make sure the `runDir` directory exists
|
|
|
|
* @returns {void}
|
2023-08-11 16:29:45 +02:00
|
|
|
*/
|
2023-02-05 11:01:54 +01:00
|
|
|
initDB() {
|
|
|
|
if (!fs.existsSync(this.mariadbDataDir)) {
|
|
|
|
log.info("mariadb", `Embedded MariaDB: ${this.mariadbDataDir} is not found, create one now.`);
|
|
|
|
fs.mkdirSync(this.mariadbDataDir, {
|
|
|
|
recursive: true,
|
|
|
|
});
|
|
|
|
|
2024-11-05 13:25:08 +01:00
|
|
|
let result = childProcess.spawnSync("mariadb-install-db", [
|
2023-02-05 11:01:54 +01:00
|
|
|
"--user=node",
|
2024-11-05 13:25:08 +01:00
|
|
|
"--auth-root-socket-user=node",
|
|
|
|
"--datadir=" + this.mariadbDataDir,
|
|
|
|
"--auth-root-authentication-method=socket",
|
2023-02-05 11:01:54 +01:00
|
|
|
]);
|
|
|
|
|
|
|
|
if (result.status !== 0) {
|
|
|
|
let error = result.stderr.toString("utf-8");
|
|
|
|
log.error("mariadb", error);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
log.info("mariadb", "Embedded MariaDB: mysql_install_db done:" + result.stdout.toString("utf-8"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-05 13:25:08 +01:00
|
|
|
// Check the owner of the mariadb directory, and change it if necessary
|
|
|
|
let stat = fs.statSync(this.mariadbDataDir);
|
|
|
|
if (stat.uid !== 1000 || stat.gid !== 1000) {
|
|
|
|
fs.chownSync(this.mariadbDataDir, 1000, 1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the permission of the mariadb directory, and change it if it is not 755
|
|
|
|
if (stat.mode !== 0o755) {
|
|
|
|
fs.chmodSync(this.mariadbDataDir, 0o755);
|
|
|
|
}
|
|
|
|
|
2023-02-05 11:01:54 +01:00
|
|
|
if (!fs.existsSync(this.runDir)) {
|
|
|
|
log.info("mariadb", `Embedded MariaDB: ${this.runDir} is not found, create one now.`);
|
|
|
|
fs.mkdirSync(this.runDir, {
|
|
|
|
recursive: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-11-05 13:25:08 +01:00
|
|
|
stat = fs.statSync(this.runDir);
|
|
|
|
if (stat.uid !== 1000 || stat.gid !== 1000) {
|
|
|
|
fs.chownSync(this.runDir, 1000, 1000);
|
|
|
|
}
|
|
|
|
if (stat.mode !== 0o755) {
|
|
|
|
fs.chmodSync(this.runDir, 0o755);
|
|
|
|
}
|
2023-02-05 11:01:54 +01:00
|
|
|
}
|
|
|
|
|
2023-08-11 16:29:45 +02:00
|
|
|
/**
|
2023-09-07 09:42:44 +02:00
|
|
|
* Initialise the "kuma" database in mariadb if it does not exist
|
|
|
|
* @returns {Promise<void>}
|
2023-08-11 16:29:45 +02:00
|
|
|
*/
|
2023-02-05 11:01:54 +01:00
|
|
|
async initDBAfterStarted() {
|
|
|
|
const connection = mysql.createConnection({
|
|
|
|
socketPath: this.socketPath,
|
2024-11-05 13:25:08 +01:00
|
|
|
user: this.username,
|
2023-02-05 11:01:54 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
let result = await connection.execute("CREATE DATABASE IF NOT EXISTS `kuma`");
|
|
|
|
log.debug("mariadb", "CREATE DATABASE: " + JSON.stringify(result));
|
|
|
|
|
|
|
|
log.info("mariadb", "Embedded MariaDB is ready for connections");
|
|
|
|
this.started = true;
|
|
|
|
}
|
|
|
|
|
2022-12-23 15:43:56 +01:00
|
|
|
}
|
2023-02-05 10:45:36 +01:00
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
EmbeddedMariaDB,
|
|
|
|
};
|