Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Database Setup Page #2738

Merged
merged 3 commits into from
Feb 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 39 additions & 29 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 @@ -82,10 +82,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 @@ -95,7 +95,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 @@ -109,6 +109,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 @@ -120,24 +140,14 @@ 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",
type: "sqlite",
//type: "embedded-mariadb",
};
}

Expand All @@ -150,7 +160,7 @@ class Database {
config = {
client: Dialect,
connection: {
filename: Database.path,
filename: Database.sqlitePath,
acquireConnectionTimeout: acquireConnectionTimeout,
},
useNullAsDefault: true,
Expand Down Expand Up @@ -496,15 +506,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 @@ -534,13 +544,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 @@ -556,7 +566,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 @@ -574,7 +584,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