mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-05-21 14:32:34 +02:00
Merge 5d2d261278
into c67f6efe29
This commit is contained in:
commit
d45f1b1a37
4 changed files with 104 additions and 3 deletions
|
@ -11,6 +11,7 @@ const { UptimeCalculator } = require("./uptime-calculator");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const { SimpleMigrationServer } = require("./utils/simple-migration-server");
|
const { SimpleMigrationServer } = require("./utils/simple-migration-server");
|
||||||
const KumaColumnCompiler = require("./utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler");
|
const KumaColumnCompiler = require("./utils/knex/lib/dialects/mysql2/schema/mysql2-columncompiler");
|
||||||
|
const { tmpdir } = require("node:os");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database & App Data Folder
|
* Database & App Data Folder
|
||||||
|
@ -183,11 +184,29 @@ class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @throws The CA file must be a pem file and not contain any illegal characters inside the filename which would allow it to escape the data directory
|
||||||
* @typedef {string|undefined} envString
|
* @typedef {string|undefined} envString
|
||||||
* @param {{type: "sqlite"} | {type:envString, hostname:envString, port:envString, database:envString, username:envString, password:envString}} dbConfig the database configuration that should be written
|
* @param {{type: "sqlite"} | {type:envString, hostname:envString, port:envString, database:envString, username:envString, password:envString, caFilePath:envString}} dbConfig the database configuration that should be written
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
static writeDBConfig(dbConfig) {
|
static writeDBConfig(dbConfig) {
|
||||||
|
// Move CA file to the data directory
|
||||||
|
const temporaryDirectoryPath = tmpdir();
|
||||||
|
if (dbConfig.caFilePath && dbConfig.caFile && dbConfig.caFilePath.startsWith(temporaryDirectoryPath)) {
|
||||||
|
const dataCaFilePath = path.resolve(Database.dataDir, "mariadb-ca.pem");
|
||||||
|
const sourcePath = fs.realpathSync(path.resolve(temporaryDirectoryPath, dbConfig.caFilePath));
|
||||||
|
if (dataCaFilePath.startsWith(path.resolve(Database.dataDir)) && path.resolve(sourcePath).startsWith(temporaryDirectoryPath)) {
|
||||||
|
fs.renameSync(sourcePath, dataCaFilePath);
|
||||||
|
} else {
|
||||||
|
throw new Error("The file cannot contains any characters that would allow it to escape the data directory");
|
||||||
|
}
|
||||||
|
dbConfig.caFilePath = dataCaFilePath;
|
||||||
|
}
|
||||||
|
if (dbConfig.caFilePath && dbConfig.caFilePath.startsWith(temporaryDirectoryPath)) {
|
||||||
|
dbConfig.caFilePath = undefined;
|
||||||
|
}
|
||||||
|
dbConfig.ssl = undefined;
|
||||||
|
dbConfig.caFile = undefined;
|
||||||
fs.writeFileSync(path.join(Database.dataDir, "db-config.json"), JSON.stringify(dbConfig, null, 4));
|
fs.writeFileSync(path.join(Database.dataDir, "db-config.json"), JSON.stringify(dbConfig, null, 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,11 +278,22 @@ class Database {
|
||||||
throw Error("Invalid database name. A database name can only consist of letters, numbers and underscores");
|
throw Error("Invalid database name. A database name can only consist of letters, numbers and underscores");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sslConfig = null;
|
||||||
|
let serverCa = undefined;
|
||||||
|
if (dbConfig.caFilePath) {
|
||||||
|
serverCa = [ fs.readFileSync(dbConfig.caFilePath, "utf8") ];
|
||||||
|
sslConfig = {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
ca: serverCa
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const connection = await mysql.createConnection({
|
const connection = await mysql.createConnection({
|
||||||
host: dbConfig.hostname,
|
host: dbConfig.hostname,
|
||||||
port: dbConfig.port,
|
port: dbConfig.port,
|
||||||
user: dbConfig.username,
|
user: dbConfig.username,
|
||||||
password: dbConfig.password,
|
password: dbConfig.password,
|
||||||
|
ssl: sslConfig
|
||||||
});
|
});
|
||||||
|
|
||||||
await connection.execute("CREATE DATABASE IF NOT EXISTS " + dbConfig.dbName + " CHARACTER SET utf8mb4");
|
await connection.execute("CREATE DATABASE IF NOT EXISTS " + dbConfig.dbName + " CHARACTER SET utf8mb4");
|
||||||
|
@ -277,7 +307,9 @@ class Database {
|
||||||
user: dbConfig.username,
|
user: dbConfig.username,
|
||||||
password: dbConfig.password,
|
password: dbConfig.password,
|
||||||
database: dbConfig.dbName,
|
database: dbConfig.dbName,
|
||||||
|
ssl: sslConfig,
|
||||||
timezone: "Z",
|
timezone: "Z",
|
||||||
|
//ssl: sslConfig,
|
||||||
typeCast: function (field, next) {
|
typeCast: function (field, next) {
|
||||||
if (field.type === "DATETIME") {
|
if (field.type === "DATETIME") {
|
||||||
// Do not perform timezone conversion
|
// Do not perform timezone conversion
|
||||||
|
|
|
@ -6,6 +6,7 @@ const path = require("path");
|
||||||
const Database = require("./database");
|
const Database = require("./database");
|
||||||
const { allowDevAllOrigin } = require("./util-server");
|
const { allowDevAllOrigin } = require("./util-server");
|
||||||
const mysql = require("mysql2/promise");
|
const mysql = require("mysql2/promise");
|
||||||
|
const { tmpdir } = require("node:os");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A standalone express app that is used to setup a database
|
* A standalone express app that is used to setup a database
|
||||||
|
@ -77,6 +78,7 @@ class SetupDatabase {
|
||||||
dbConfig.dbName = process.env.UPTIME_KUMA_DB_NAME;
|
dbConfig.dbName = process.env.UPTIME_KUMA_DB_NAME;
|
||||||
dbConfig.username = process.env.UPTIME_KUMA_DB_USERNAME;
|
dbConfig.username = process.env.UPTIME_KUMA_DB_USERNAME;
|
||||||
dbConfig.password = process.env.UPTIME_KUMA_DB_PASSWORD;
|
dbConfig.password = process.env.UPTIME_KUMA_DB_PASSWORD;
|
||||||
|
dbConfig.caFilePath = process.env.UPTIME_KUMA_DB_CA_CERT;
|
||||||
Database.writeDBConfig(dbConfig);
|
Database.writeDBConfig(dbConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,13 +208,43 @@ class SetupDatabase {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent someone from injecting a CA file path not generated by the code below
|
||||||
|
if (dbConfig.caFilePath) {
|
||||||
|
dbConfig.caFilePath = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dbConfig.caFile) {
|
||||||
|
const base64Data = dbConfig.caFile.replace(/^data:application\/octet-stream;base64,/, "");
|
||||||
|
const binaryData = Buffer.from(base64Data, "base64").toString("binary");
|
||||||
|
const tempCaDirectory = fs.mkdtempSync(path.join(tmpdir(), "kuma-ca-"));
|
||||||
|
dbConfig.caFilePath = path.resolve(tempCaDirectory, "ca.pem");
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(dbConfig.caFilePath, binaryData, "binary");
|
||||||
|
} catch (err) {
|
||||||
|
|
||||||
|
response.status(400).json("Cannot write CA file: " + err.message);
|
||||||
|
this.runningSetup = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dbConfig.ssl = {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
ca: [ fs.readFileSync(dbConfig.caFilePath) ]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Test connection
|
// Test connection
|
||||||
try {
|
try {
|
||||||
|
let sslConfig = null;
|
||||||
|
if (dbConfig.ssl) {
|
||||||
|
sslConfig = dbConfig.ssl;
|
||||||
|
}
|
||||||
|
|
||||||
const connection = await mysql.createConnection({
|
const connection = await mysql.createConnection({
|
||||||
host: dbConfig.hostname,
|
host: dbConfig.hostname,
|
||||||
port: dbConfig.port,
|
port: dbConfig.port,
|
||||||
user: dbConfig.username,
|
user: dbConfig.username,
|
||||||
password: dbConfig.password,
|
password: dbConfig.password,
|
||||||
|
ssl: sslConfig
|
||||||
});
|
});
|
||||||
await connection.execute("SELECT 1");
|
await connection.execute("SELECT 1");
|
||||||
connection.end();
|
connection.end();
|
||||||
|
@ -224,7 +256,13 @@ class SetupDatabase {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write db-config.json
|
// Write db-config.json
|
||||||
|
try {
|
||||||
Database.writeDBConfig(dbConfig);
|
Database.writeDBConfig(dbConfig);
|
||||||
|
} catch (e) {
|
||||||
|
response.status(400).json("Cannot write db-config.json: " + e.message);
|
||||||
|
this.runningSetup = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
response.json({
|
response.json({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"setupDatabaseEmbeddedMariaDB": "You don't need to set anything. This docker image has embedded and configured MariaDB for you automatically. Uptime Kuma will connect to this database via unix socket.",
|
"setupDatabaseEmbeddedMariaDB": "You don't need to set anything. This docker image has embedded and configured MariaDB for you automatically. Uptime Kuma will connect to this database via unix socket.",
|
||||||
"setupDatabaseMariaDB": "Connect to an external MariaDB database. You need to set the database connection information.",
|
"setupDatabaseMariaDB": "Connect to an external MariaDB database. You need to set the database connection information.",
|
||||||
"setupDatabaseSQLite": "A simple database file, recommended for small-scale deployments. Prior to v2.0.0, Uptime Kuma used SQLite as the default database.",
|
"setupDatabaseSQLite": "A simple database file, recommended for small-scale deployments. Prior to v2.0.0, Uptime Kuma used SQLite as the default database.",
|
||||||
|
"configureMariaCaFile": "You will sometimes need to provide a CA certificate to connect to database with 'require-secure-transport' on. Such as when using Azure MySql flexible servers. You can upload the CA file that will be used to enable a secure connection.",
|
||||||
"settingUpDatabaseMSG": "Setting up the database. It may take a while, please be patient.",
|
"settingUpDatabaseMSG": "Setting up the database. It may take a while, please be patient.",
|
||||||
"dbName": "Database Name",
|
"dbName": "Database Name",
|
||||||
"Settings": "Settings",
|
"Settings": "Settings",
|
||||||
|
|
|
@ -90,8 +90,12 @@
|
||||||
<input id="floatingInput" v-model="dbConfig.dbName" type="text" class="form-control" required>
|
<input id="floatingInput" v-model="dbConfig.dbName" type="text" class="form-control" required>
|
||||||
<label for="floatingInput">{{ $t("dbName") }}</label>
|
<label for="floatingInput">{{ $t("dbName") }}</label>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
|
|
||||||
|
<div class="mb2 mt-3 short">
|
||||||
|
<p class="mb-2">{{ $t("configureMariaCaFile") }}</p>
|
||||||
|
<input id="ca-input" type="file" accept="application/x-pem-file, .pem" class="form-control" @change="onCaFileChange">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<button class="btn btn-primary mt-4 short" type="submit" :disabled="disabledButton">
|
<button class="btn btn-primary mt-4 short" type="submit" :disabled="disabledButton">
|
||||||
{{ $t("Next") }}
|
{{ $t("Next") }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -117,6 +121,7 @@ export default {
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
dbName: "kuma",
|
dbName: "kuma",
|
||||||
|
caFile: ""
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
needSetup: false,
|
needSetup: false,
|
||||||
|
@ -178,6 +183,15 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onCaFileChange(e) {
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
fileReader.onload = () => {
|
||||||
|
this.dbConfig.caFile = fileReader.result;
|
||||||
|
console.log(this.dbConfig.caFile);
|
||||||
|
};
|
||||||
|
fileReader.readAsDataURL(e.target.files[0]);
|
||||||
|
},
|
||||||
|
|
||||||
test() {
|
test() {
|
||||||
this.$root.toastError("not implemented");
|
this.$root.toastError("not implemented");
|
||||||
}
|
}
|
||||||
|
@ -186,6 +200,22 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@import "../assets/vars.scss";
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
#ca-input {
|
||||||
|
&::file-selector-button {
|
||||||
|
color: $primary;
|
||||||
|
background-color: $dark-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:disabled):not([readonly])::file-selector-button {
|
||||||
|
color: $dark-font-color2;
|
||||||
|
background-color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.form-container {
|
.form-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
Loading…
Add table
Reference in a new issue