From 42e08dec93017f6aa7ace3b4697e4e52e0dd4e62 Mon Sep 17 00:00:00 2001 From: hgw Date: Sun, 3 Dec 2023 02:09:53 +0000 Subject: [PATCH] CLI sqlite migratior --- server/clientManager.ts | 38 ++++++++++++------- server/command-line/index.ts | 9 +++-- server/command-line/storage.ts | 68 ++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 server/command-line/storage.ts diff --git a/server/clientManager.ts b/server/clientManager.ts index 78e94d18..70b7e81b 100644 --- a/server/clientManager.ts +++ b/server/clientManager.ts @@ -5,12 +5,12 @@ import fs from "fs"; import path from "path"; import Auth from "./plugins/auth"; -import Client, {UserConfig} from "./client"; +import Client, { UserConfig } from "./client"; import Config from "./config"; -import {NetworkConfig} from "./models/network"; +import { NetworkConfig } from "./models/network"; import WebPush from "./plugins/webpush"; import log from "./log"; -import {Server} from "socket.io"; +import { Server } from "socket.io"; class ClientManager { clients: Client[]; @@ -107,17 +107,21 @@ class ClientManager { // Existing users removed since last time users were loaded _.difference(loaded, updatedUsers).forEach((name) => { - const client = _.find(this.clients, {name}); + const client = _.find(this.clients, { name }); if (client) { client.quit(true); this.clients = _.without(this.clients, client); - log.info(`User ${colors.bold(name)} disconnected and removed.`); + log.info( + `User ${colors.bold( + name + )} disconnected and removed.` + ); } }); }, 1000, - {maxWait: 10000} + { maxWait: 10000 } ) ); } @@ -197,7 +201,8 @@ class ClientManager { if ( userFolderStat && userFileStat && - (userFolderStat.uid !== userFileStat.uid || userFolderStat.gid !== userFileStat.gid) + (userFolderStat.uid !== userFileStat.uid || + userFolderStat.gid !== userFileStat.gid) ) { log.warn( `User ${colors.green( @@ -227,13 +232,16 @@ class ClientManager { networks: client.networks.map((n) => n.export()), }); const newUser = JSON.stringify(json, null, "\t"); - const newHash = crypto.createHash("sha256").update(newUser).digest("hex"); + const newHash = crypto + .createHash("sha256") + .update(newUser) + .digest("hex"); - return {newUser, newHash}; + return { newUser, newHash }; } saveUser(client: Client, callback?: (err?: any) => void) { - const {newUser, newHash} = this.getDataToSave(client); + const { newUser, newHash } = this.getDataToSave(client); // Do not write to disk if the exported data hasn't actually changed if (client.fileHash === newHash) { @@ -254,7 +262,9 @@ class ClientManager { return callback ? callback() : true; } catch (e: any) { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - log.error(`Failed to update user ${colors.green(client.name)} (${e})`); + log.error( + `Failed to update user ${colors.green(client.name)} (${e})` + ); if (callback) { callback(e); @@ -266,7 +276,9 @@ class ClientManager { const userPath = Config.getUserConfigPath(name); if (!fs.existsSync(userPath)) { - log.error(`Tried to remove non-existing user ${colors.green(name)}.`); + log.error( + `Tried to remove non-existing user ${colors.green(name)}.` + ); return false; } @@ -275,7 +287,7 @@ class ClientManager { return true; } - private readUserConfig(name: string) { + readUserConfig(name: string) { const userPath = Config.getUserConfigPath(name); if (!fs.existsSync(userPath)) { diff --git a/server/command-line/index.ts b/server/command-line/index.ts index 44d79a2a..55a6320a 100644 --- a/server/command-line/index.ts +++ b/server/command-line/index.ts @@ -3,7 +3,7 @@ import log from "../log"; import fs from "fs"; import path from "path"; import colors from "chalk"; -import {Command} from "commander"; +import { Command } from "commander"; import Helper from "../helper"; import Config from "../config"; import Utils from "./utils"; @@ -42,6 +42,7 @@ program.addCommand(require("./install").default); program.addCommand(require("./uninstall").default); program.addCommand(require("./upgrade").default); program.addCommand(require("./outdated").default); +program.addCommand(require("./storage").default); if (!Config.values.public) { require("./users").default.forEach((command: Command) => { @@ -64,7 +65,7 @@ function createPackagesFolder() { const packagesConfig = path.join(packagesPath, "package.json"); // Create node_modules folder, otherwise yarn will start walking upwards to find one - fs.mkdirSync(path.join(packagesPath, "node_modules"), {recursive: true}); + fs.mkdirSync(path.join(packagesPath, "node_modules"), { recursive: true }); // Create package.json with private set to true, if it doesn't exist already if (!fs.existsSync(packagesConfig)) { @@ -99,7 +100,9 @@ function verifyFileOwner() { ); } - const configStat = fs.statSync(path.join(Config.getHomePath(), "config.js")); + const configStat = fs.statSync( + path.join(Config.getHomePath(), "config.js") + ); if (configStat && configStat.uid !== uid) { log.warn( diff --git a/server/command-line/storage.ts b/server/command-line/storage.ts new file mode 100644 index 00000000..4848f326 --- /dev/null +++ b/server/command-line/storage.ts @@ -0,0 +1,68 @@ +import log from "../log"; +import { Command } from "commander"; +import ClientManager from "../clientManager"; +import Utils from "./utils"; +import SqliteMessageStorage from "../plugins/messageStorage/sqlite"; + +const program = new Command("storage").description( + "various utilities related to the message storage" +); + +program + .command("migrate") + .argument("[user]", "migrate a specific user only, all if not provided") + .description("Migrate message storage where needed") + .on("--help", Utils.extraHelp) + .action(function (user) { + runMigrations(user).catch((err) => { + log.error(err.toString()); + process.exit(1); + }); + }); + +async function runMigrations(user: string) { + const manager = new ClientManager(); + const users = manager.getUsers(); + + if (user) { + if (!users.includes(user)) { + throw new Error(`invalid user ${user}`); + } + + return migrateUser(manager, user); + } + + for (const name of users) { + await migrateUser(manager, name); + // if any migration fails we blow up, + // chances are the rest won't complete either + } +} + +// runs sqlite migrations for a user, which must exist +async function migrateUser(manager: ClientManager, user: string) { + log.info("handling user", user); + + if (!isUserLogEnabled(manager, user)) { + log.info("logging disabled for user", user, ". Skipping"); + return; + } + + const sqlite = new SqliteMessageStorage(user); + await sqlite.enable(); // enable runs migrations + await sqlite.close(); + log.info("user", user, "migrated successfully"); +} + +function isUserLogEnabled(manager: ClientManager, user: string): boolean { + const conf = manager.readUserConfig(user); + + if (!conf) { + log.error("Could not open user configuration of", user); + return false; + } + + return conf.log; +} + +export default program;