mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-10-31 03:19:20 +08:00 
			
		
		
		
	add db migration
This commit is contained in:
		
							
								
								
									
										37
									
								
								db/patch1.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								db/patch1.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | -- You should not modify if this have pushed to Github, unless it do serious wrong with the db. | ||||||
|  | -- Change Monitor.created_date from "TIMESTAMP" to "DATETIME" | ||||||
|  | -- SQL Generated by Intellij Idea | ||||||
|  | PRAGMA foreign_keys=off; | ||||||
|  |  | ||||||
|  | BEGIN TRANSACTION; | ||||||
|  |  | ||||||
|  | create table monitor_dg_tmp | ||||||
|  | ( | ||||||
|  |     id INTEGER not null | ||||||
|  |         primary key autoincrement, | ||||||
|  |     name VARCHAR(150), | ||||||
|  |     active BOOLEAN default 1 not null, | ||||||
|  |     user_id INTEGER | ||||||
|  |         references user | ||||||
|  |                    on update cascade on delete set null, | ||||||
|  |     interval INTEGER default 20 not null, | ||||||
|  |     url TEXT, | ||||||
|  |     type VARCHAR(20), | ||||||
|  |     weight INTEGER default 2000, | ||||||
|  |     hostname VARCHAR(255), | ||||||
|  |     port INTEGER, | ||||||
|  |     created_date DATETIME, | ||||||
|  |     keyword VARCHAR(255) | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | insert into monitor_dg_tmp(id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword) select id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword from monitor; | ||||||
|  |  | ||||||
|  | drop table monitor; | ||||||
|  |  | ||||||
|  | alter table monitor_dg_tmp rename to monitor; | ||||||
|  |  | ||||||
|  | create index user_id on monitor (user_id); | ||||||
|  |  | ||||||
|  | COMMIT; | ||||||
|  |  | ||||||
|  | PRAGMA foreign_keys=on; | ||||||
							
								
								
									
										119
									
								
								server/database.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								server/database.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | |||||||
|  | const fs = require("fs"); | ||||||
|  | const {sleep} = require("./util"); | ||||||
|  | const {R} = require("redbean-node"); | ||||||
|  | const {setSetting, setting} = require("./util-server"); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Database { | ||||||
|  |  | ||||||
|  |     static templatePath = "./db/kuma.db" | ||||||
|  |     static path =  './data/kuma.db'; | ||||||
|  |     static latestVersion = 1; | ||||||
|  |     static noReject = true; | ||||||
|  |  | ||||||
|  |     static async patch() { | ||||||
|  |         let version = parseInt(await setting("database_version")); | ||||||
|  |  | ||||||
|  |         if (! version) { | ||||||
|  |             version = 0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         console.info("Your database version: " + version); | ||||||
|  |         console.info("Latest database version: " + this.latestVersion); | ||||||
|  |  | ||||||
|  |         if (version === this.latestVersion) { | ||||||
|  |             console.info("Database no need to patch"); | ||||||
|  |         } else { | ||||||
|  |             console.info("Database patch is needed") | ||||||
|  |  | ||||||
|  |             console.info("Backup the db") | ||||||
|  |             const backupPath = "./data/kuma.db.bak" + version; | ||||||
|  |             fs.copyFileSync(Database.path, backupPath); | ||||||
|  |  | ||||||
|  |             // Try catch anything here, if gone wrong, restore the backup | ||||||
|  |             try { | ||||||
|  |                 for (let i = version + 1; i <= this.latestVersion; i++) { | ||||||
|  |                     const sqlFile = `./db/patch${i}.sql`; | ||||||
|  |                     console.info(`Patching ${sqlFile}`); | ||||||
|  |                     await Database.importSQLFile(sqlFile); | ||||||
|  |                     console.info(`Patched ${sqlFile}`); | ||||||
|  |                     await setSetting("database_version", i); | ||||||
|  |                 } | ||||||
|  |                 console.log("Database Patched Successfully"); | ||||||
|  |             } catch (ex) { | ||||||
|  |                 await Database.close(); | ||||||
|  |                 console.error("Patch db failed!!! Restoring the backup") | ||||||
|  |                 fs.copyFileSync(backupPath, Database.path); | ||||||
|  |                 console.error(ex) | ||||||
|  |  | ||||||
|  |                 console.error("Start Uptime-Kuma failed due to patch db failed") | ||||||
|  |                 console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues") | ||||||
|  |                 process.exit(1); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Sadly, multi sql statements is not supported by many sqlite libraries, I have to implement it myself | ||||||
|  |      * @param filename | ||||||
|  |      * @returns {Promise<void>} | ||||||
|  |      */ | ||||||
|  |     static async importSQLFile(filename) { | ||||||
|  |  | ||||||
|  |         await R.getCell("SELECT 1"); | ||||||
|  |  | ||||||
|  |         let text = fs.readFileSync(filename).toString(); | ||||||
|  |  | ||||||
|  |         // Remove all comments (--) | ||||||
|  |         let lines = text.split("\n"); | ||||||
|  |         lines = lines.filter((line) => { | ||||||
|  |             return ! line.startsWith("--") | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // Split statements by semicolon | ||||||
|  |         // Filter out empty line | ||||||
|  |         text = lines.join("\n") | ||||||
|  |  | ||||||
|  |         let statements = text.split(";") | ||||||
|  |             .map((statement) => { | ||||||
|  |                 return statement.trim(); | ||||||
|  |             }) | ||||||
|  |             .filter((statement) => { | ||||||
|  |                 return statement !== ""; | ||||||
|  |             }) | ||||||
|  |  | ||||||
|  |         for (let statement of statements) { | ||||||
|  |             await R.exec(statement); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Special handle, because tarn.js throw a promise reject that cannot be caught | ||||||
|  |      * @returns {Promise<void>} | ||||||
|  |      */ | ||||||
|  |     static async close() { | ||||||
|  |         const listener = (reason, p) => { | ||||||
|  |             Database.noReject = false; | ||||||
|  |         }; | ||||||
|  |         process.addListener('unhandledRejection', listener); | ||||||
|  |  | ||||||
|  |         console.log("Closing DB") | ||||||
|  |  | ||||||
|  |         while (true) { | ||||||
|  |             Database.noReject = true; | ||||||
|  |             await R.close() | ||||||
|  |             await sleep(2000) | ||||||
|  |  | ||||||
|  |             if (Database.noReject) { | ||||||
|  |                 break; | ||||||
|  |             } else { | ||||||
|  |                 console.log("Waiting to close the db") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         console.log("SQLite closed") | ||||||
|  |  | ||||||
|  |         process.removeListener('unhandledRejection', listener); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = Database; | ||||||
| @@ -12,6 +12,7 @@ const fs = require("fs"); | |||||||
| const {getSettings} = require("./util-server"); | const {getSettings} = require("./util-server"); | ||||||
| const {Notification} = require("./notification") | const {Notification} = require("./notification") | ||||||
| const gracefulShutdown = require('http-graceful-shutdown'); | const gracefulShutdown = require('http-graceful-shutdown'); | ||||||
|  | const Database = require("./database"); | ||||||
| const {sleep} = require("./util"); | const {sleep} = require("./util"); | ||||||
| const args = require('args-parser')(process.argv); | const args = require('args-parser')(process.argv); | ||||||
|  |  | ||||||
| @@ -27,9 +28,28 @@ const server = http.createServer(app); | |||||||
| const io = new Server(server); | const io = new Server(server); | ||||||
| app.use(express.json()) | app.use(express.json()) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Total WebSocket client connected to server currently, no actual use | ||||||
|  |  * @type {number} | ||||||
|  |  */ | ||||||
| let totalClient = 0; | let totalClient = 0; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Use for decode the auth object | ||||||
|  |  * @type {null} | ||||||
|  |  */ | ||||||
| let jwtSecret = null; | let jwtSecret = null; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Main monitor list | ||||||
|  |  * @type {{}} | ||||||
|  |  */ | ||||||
| let monitorList = {}; | let monitorList = {}; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Show Setup Page | ||||||
|  |  * @type {boolean} | ||||||
|  |  */ | ||||||
| let needSetup = false; | let needSetup = false; | ||||||
|  |  | ||||||
| (async () => { | (async () => { | ||||||
| @@ -555,19 +575,21 @@ function checkLogin(socket) { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function initDatabase() { | async function initDatabase() { | ||||||
|     const path = './data/kuma.db'; |     if (! fs.existsSync(Database.path)) { | ||||||
|  |  | ||||||
|     if (! fs.existsSync(path)) { |  | ||||||
|         console.log("Copying Database") |         console.log("Copying Database") | ||||||
|         fs.copyFileSync("./db/kuma.db", path); |         fs.copyFileSync(Database.templatePath, Database.path); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     console.log("Connecting to Database") |     console.log("Connecting to Database") | ||||||
|     R.setup('sqlite', { |     R.setup('sqlite', { | ||||||
|         filename: path |         filename: Database.path | ||||||
|     }); |     }); | ||||||
|     console.log("Connected") |     console.log("Connected") | ||||||
|  |  | ||||||
|  |     // Patch the database | ||||||
|  |     await Database.patch() | ||||||
|  |  | ||||||
|  |     // Auto map the model to a bean object | ||||||
|     R.freeze(true) |     R.freeze(true) | ||||||
|     await R.autoloadModels("./server/model"); |     await R.autoloadModels("./server/model"); | ||||||
|  |  | ||||||
| @@ -587,6 +609,7 @@ async function initDatabase() { | |||||||
|         console.log("Load JWT secret from database.") |         console.log("Load JWT secret from database.") | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // If there is no record in user table, it is a new Uptime Kuma instance, need to setup | ||||||
|     if ((await R.count("user")) === 0) { |     if ((await R.count("user")) === 0) { | ||||||
|         console.log("No user, need setup") |         console.log("No user, need setup") | ||||||
|         needSetup = true; |         needSetup = true; | ||||||
| @@ -705,11 +728,6 @@ const startGracefulShutdown = async () => { | |||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| let noReject = true; |  | ||||||
| process.on('unhandledRejection', (reason, p) => { |  | ||||||
|     noReject = false; |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| async function shutdownFunction(signal) { | async function shutdownFunction(signal) { | ||||||
|     console.log('Called signal: ' + signal); |     console.log('Called signal: ' + signal); | ||||||
|  |  | ||||||
| @@ -718,24 +736,8 @@ async function shutdownFunction(signal) { | |||||||
|         let monitor = monitorList[id] |         let monitor = monitorList[id] | ||||||
|         monitor.stop() |         monitor.stop() | ||||||
|     } |     } | ||||||
|     await sleep(2000) |     await sleep(2000); | ||||||
|  |     await Database.close(); | ||||||
|     console.log("Closing DB") |  | ||||||
|  |  | ||||||
|     // Special handle, because tarn.js throw a promise reject that cannot be caught |  | ||||||
|     while (true) { |  | ||||||
|         noReject = true; |  | ||||||
|         await R.close() |  | ||||||
|         await sleep(2000) |  | ||||||
|  |  | ||||||
|         if (noReject) { |  | ||||||
|             break; |  | ||||||
|         } else { |  | ||||||
|             console.log("Waiting...") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     console.log("OK") |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function finalFunction() { | function finalFunction() { | ||||||
|   | |||||||
| @@ -45,6 +45,18 @@ exports.setting = async function (key) { | |||||||
|     ]) |     ]) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | exports.setSetting = async function (key, value) { | ||||||
|  |     let bean = await R.findOne("setting", " `key` = ? ", [ | ||||||
|  |         key | ||||||
|  |     ]) | ||||||
|  |     if (! bean) { | ||||||
|  |         bean = R.dispense("setting") | ||||||
|  |         bean.key = key; | ||||||
|  |     } | ||||||
|  |     bean.value = value; | ||||||
|  |     await R.store(bean) | ||||||
|  | } | ||||||
|  |  | ||||||
| exports.getSettings = async function (type) { | exports.getSettings = async function (type) { | ||||||
|     let list = await R.getAll("SELECT * FROM setting WHERE `type` = ? ", [ |     let list = await R.getAll("SELECT * FROM setting WHERE `type` = ? ", [ | ||||||
|         type |         type | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user