Skip to content

Commit

Permalink
Database Setup Page (#2738)
Browse files Browse the repository at this point in the history
* WIP

* WIP: Database setup process

* Add database setup page
  • Loading branch information
louislam authored Feb 11, 2023
1 parent db4663d commit e4183ee
Show file tree
Hide file tree
Showing 16 changed files with 516 additions and 62 deletions.
2 changes: 1 addition & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/.idea
/node_modules
/data
/data*
/cypress
/out
/test
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dist-ssr

/data
!/data/.gitkeep
/data*
.vscode

/private
Expand Down
2 changes: 1 addition & 1 deletion docker/debian-base.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ RUN apt update && \
rm -rf /var/lib/apt/lists/* && \
apt --yes autoremove
RUN chown -R node:node /var/lib/mysql

ENV UPTIME_KUMA_ENABLE_EMBEDDED_MARIADB=1
2 changes: 1 addition & 1 deletion extra/remove-2fa.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const rl = readline.createInterface({
});

const main = async () => {
Database.init(args);
Database.initDataDir(args);
await Database.connect();

try {
Expand Down
2 changes: 1 addition & 1 deletion extra/reset-password.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const rl = readline.createInterface({

const main = async () => {
console.log("Connecting the database");
Database.init(args);
Database.initDataDir(args);
await Database.connect(false, false, true);

try {
Expand Down
64 changes: 37 additions & 27 deletions server/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Database {
*/
static uploadDir;

static path;
static sqlitePath;

/**
* @type {boolean}
Expand Down Expand Up @@ -83,10 +83,10 @@ class Database {
static noReject = true;

/**
* Initialize the database
* Initialize the data directory
* @param {Object} args Arguments to initialize DB with
*/
static init(args) {
static initDataDir(args) {
// Data Directory (must be end with "/")
Database.dataDir = process.env.DATA_DIR || args["data-dir"] || "./data/";

Expand All @@ -96,7 +96,7 @@ class Database {
PluginsManager.disable = true;
}

Database.path = Database.dataDir + "kuma.db";
Database.sqlitePath = Database.dataDir + "kuma.db";
if (! fs.existsSync(Database.dataDir)) {
fs.mkdirSync(Database.dataDir, { recursive: true });
}
Expand All @@ -110,6 +110,26 @@ class Database {
log.info("db", `Data Dir: ${Database.dataDir}`);
}

static readDBConfig() {
let dbConfig;

let dbConfigString = fs.readFileSync(path.join(Database.dataDir, "db-config.json")).toString("utf-8");
dbConfig = JSON.parse(dbConfigString);

if (typeof dbConfig !== "object") {
throw new Error("Invalid db-config.json, it must be an object");
}

if (typeof dbConfig.type !== "string") {
throw new Error("Invalid db-config.json, type must be a string");
}
return dbConfig;
}

static writeDBConfig(dbConfig) {
fs.writeFileSync(path.join(Database.dataDir, "db-config.json"), JSON.stringify(dbConfig, null, 4));
}

/**
* Connect to the database
* @param {boolean} [testMode=false] Should the connection be
Expand All @@ -121,21 +141,11 @@ class Database {
*/
static async connect(testMode = false, autoloadModels = true, noLog = false) {
const acquireConnectionTimeout = 120 * 1000;

let dbConfig;

try {
let dbConfigString = fs.readFileSync(path.join(Database.dataDir, "db-config.json")).toString("utf-8");
dbConfig = JSON.parse(dbConfigString);

if (typeof dbConfig !== "object") {
throw new Error("Invalid db-config.json, it must be an object");
}

if (typeof dbConfig.type !== "string") {
throw new Error("Invalid db-config.json, type must be a string");
}
} catch (_) {
dbConfig = this.readDBConfig();
} catch (err) {
log.warn("db", err.message);
dbConfig = {
type: "sqlite",
//type: "embedded-mariadb",
Expand All @@ -151,7 +161,7 @@ class Database {
config = {
client: Dialect,
connection: {
filename: Database.path,
filename: Database.sqlitePath,
acquireConnectionTimeout: acquireConnectionTimeout,
},
useNullAsDefault: true,
Expand Down Expand Up @@ -497,15 +507,15 @@ class Database {
if (! this.backupPath) {
log.info("db", "Backing up the database");
this.backupPath = this.dataDir + "kuma.db.bak" + version;
fs.copyFileSync(Database.path, this.backupPath);
fs.copyFileSync(Database.sqlitePath, this.backupPath);

const shmPath = Database.path + "-shm";
const shmPath = Database.sqlitePath + "-shm";
if (fs.existsSync(shmPath)) {
this.backupShmPath = shmPath + ".bak" + version;
fs.copyFileSync(shmPath, this.backupShmPath);
}

const walPath = Database.path + "-wal";
const walPath = Database.sqlitePath + "-wal";
if (fs.existsSync(walPath)) {
this.backupWalPath = walPath + ".bak" + version;
fs.copyFileSync(walPath, this.backupWalPath);
Expand Down Expand Up @@ -535,13 +545,13 @@ class Database {
if (this.backupPath) {
log.error("db", "Patching the database failed!!! Restoring the backup");

const shmPath = Database.path + "-shm";
const walPath = Database.path + "-wal";
const shmPath = Database.sqlitePath + "-shm";
const walPath = Database.sqlitePath + "-wal";

// Delete patch failed db
try {
if (fs.existsSync(Database.path)) {
fs.unlinkSync(Database.path);
if (fs.existsSync(Database.sqlitePath)) {
fs.unlinkSync(Database.sqlitePath);
}

if (fs.existsSync(shmPath)) {
Expand All @@ -557,7 +567,7 @@ class Database {
}

// Restore backup
fs.copyFileSync(this.backupPath, Database.path);
fs.copyFileSync(this.backupPath, Database.sqlitePath);

if (this.backupShmPath) {
fs.copyFileSync(this.backupShmPath, shmPath);
Expand All @@ -575,7 +585,7 @@ class Database {
/** Get the size of the database */
static getSize() {
log.debug("db", "Database.getSize()");
let stats = fs.statSync(Database.path);
let stats = fs.statSync(Database.sqlitePath);
log.debug("db", stats);
return stats.size;
}
Expand Down
2 changes: 1 addition & 1 deletion server/jobs/util-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const connectDb = async function () {
process.env.DATA_DIR || workerData["data-dir"] || "./data/"
);

Database.init({
Database.initDataDir({
"data-dir": dbPath,
});

Expand Down
40 changes: 27 additions & 13 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ const { Settings } = require("./settings");
const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent");
const { pluginsHandler } = require("./socket-handlers/plugins-handler");
const { EmbeddedMariaDB } = require("./embedded-mariadb");
const { SetupDatabase } = require("./setup-database");

app.use(express.json());

Expand All @@ -168,8 +169,20 @@ let jwtSecret = null;
let needSetup = false;

(async () => {
Database.init(args);
// Create a data directory
Database.initDataDir(args);

// Check if is chosen a database type
let setupDatabase = new SetupDatabase(args, server);
if (setupDatabase.isNeedSetup()) {
// Hold here and start a special setup page until user choose a database type
await setupDatabase.start(hostname, port);
}

// Connect to database
await initDatabase(testMode);

// Database should be ready now
await server.initAfterDatabaseReady();
server.loadPlugins();
server.entryPage = await Settings.get("entryPage");
Expand Down Expand Up @@ -334,7 +347,7 @@ let needSetup = false;
}

// Login Rate Limit
if (! await loginRateLimiter.pass(callback)) {
if (!await loginRateLimiter.pass(callback)) {
log.info("auth", `Too many failed requests for user ${data.username}. IP=${clientIP}`);
return;
}
Expand Down Expand Up @@ -407,7 +420,7 @@ let needSetup = false;

socket.on("logout", async (callback) => {
// Rate Limit
if (! await loginRateLimiter.pass(callback)) {
if (!await loginRateLimiter.pass(callback)) {
return;
}

Expand All @@ -421,7 +434,7 @@ let needSetup = false;

socket.on("prepare2FA", async (currentPassword, callback) => {
try {
if (! await twoFaRateLimiter.pass(callback)) {
if (!await twoFaRateLimiter.pass(callback)) {
return;
}

Expand Down Expand Up @@ -470,7 +483,7 @@ let needSetup = false;
const clientIP = await server.getClientIP(socket);

try {
if (! await twoFaRateLimiter.pass(callback)) {
if (!await twoFaRateLimiter.pass(callback)) {
return;
}

Expand Down Expand Up @@ -502,7 +515,7 @@ let needSetup = false;
const clientIP = await server.getClientIP(socket);

try {
if (! await twoFaRateLimiter.pass(callback)) {
if (!await twoFaRateLimiter.pass(callback)) {
return;
}

Expand Down Expand Up @@ -809,9 +822,10 @@ let needSetup = false;
}

let list = await R.getAll(`
SELECT * FROM heartbeat
WHERE monitor_id = ? AND
time > DATETIME('now', '-' || ? || ' hours')
SELECT *
FROM heartbeat
WHERE monitor_id = ?
AND time > DATETIME('now', '-' || ? || ' hours')
ORDER BY time ASC
`, [
monitorID,
Expand Down Expand Up @@ -1068,7 +1082,7 @@ let needSetup = false;
try {
checkLogin(socket);

if (! password.newPassword) {
if (!password.newPassword) {
throw new Error("Invalid new password");
}

Expand Down Expand Up @@ -1375,7 +1389,7 @@ let needSetup = false;
]);

let tagId;
if (! tag) {
if (!tag) {
// -> If it doesn't exist, create new tag from backup file
let beanTag = R.dispense("tag");
beanTag.name = oldTag.name;
Expand Down Expand Up @@ -1644,9 +1658,9 @@ async function afterLogin(socket, user) {
* @returns {Promise<void>}
*/
async function initDatabase(testMode = false) {
if (! fs.existsSync(Database.path)) {
if (! fs.existsSync(Database.sqlitePath)) {
log.info("server", "Copying Database");
fs.copyFileSync(Database.templatePath, Database.path);
fs.copyFileSync(Database.templatePath, Database.sqlitePath);
}

log.info("server", "Connecting to the Database");
Expand Down
Loading

0 comments on commit e4183ee

Please sign in to comment.