UNPKG

@wocker/mariadb-plugin

Version:
719 lines (718 loc) 30.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MariadbService = void 0; const core_1 = require("@wocker/core"); const utils_1 = require("@wocker/utils"); const Path = __importStar(require("path")); const cli_table3_1 = __importDefault(require("cli-table3")); const format_1 = require("date-fns/format"); const Config_1 = require("../makes/Config"); const Service_1 = require("../makes/Service"); let MariadbService = class MariadbService { constructor(appConfigService, pluginConfigService, dockerService, proxyService) { this.appConfigService = appConfigService; this.pluginConfigService = pluginConfigService; this.dockerService = dockerService; this.proxyService = proxyService; } get configPath() { return "config.json"; } get config() { if (!this._config) { this._config = Config_1.Config.make(this.fs, this.configPath); } return this._config; } get fs() { return this.pluginConfigService.fs; } get dbFs() { return new core_1.FileSystem(this.appConfigService.fs.path("db/mariadb")); } get dataFs() { return new core_1.FileSystem(Path.join(__dirname, "../../data")); } query(service, query) { return __awaiter(this, void 0, void 0, function* () { const container = yield this.dockerService.getContainer(service.containerName); if (!container) { return null; } const exec = yield container.exec({ Cmd: ["mariadb", ...service.auth, "-e", query], AttachStdout: true, AttachStderr: true }); const stream = yield exec.start({}); return new Promise((resolve, reject) => { let result = ""; stream.on("data", (data) => { result += data.toString(); }); stream.on("end", () => { resolve(result); }); stream.on("error", reject); }); }); } getDatabases(service) { return __awaiter(this, void 0, void 0, function* () { const res = yield this.query(service, "SHOW DATABASES;"); if (!res) { return []; } return res.split(/\r?\n/) .filter((database) => { return !!database && !/Database$/.test(database); }) .filter((database) => { return database !== "mysql"; }) .filter((database) => { return !/_schema/.test(database); }); }); } getDumpsDatabases(service) { return __awaiter(this, void 0, void 0, function* () { if (!this.fs.exists(`dump/${service.name}`)) { return []; } return this.fs.readdir(`dump/${service.name}`); }); } getFiles(service, database) { return __awaiter(this, void 0, void 0, function* () { if (!this.fs.exists(`dump/${service.name}/${database}`)) { return []; } return this.fs.readdir(`dump/${service.name}/${database}`); }); } init(adminHostname) { return __awaiter(this, void 0, void 0, function* () { const config = this.config; if (!adminHostname) { adminHostname = (yield (0, utils_1.promptInput)({ message: "Admin hostname", required: true, default: config.admin.hostname })); } config.admin.hostname = adminHostname; config.save(); }); } list() { return __awaiter(this, void 0, void 0, function* () { const config = this.config; const table = new cli_table3_1.default({ head: ["Name", "Host", "User", "External", "Storage", "IP"] }); for (const service of config.services) { let ip = ""; if (!service.host) { const container = yield this.dockerService.getContainer(service.containerName); if (container) { const { NetworkSettings: { // IPAddress, Networks: { workspace: { IPAddress } } } } = yield container.inspect(); ip = `${IPAddress}`; } } table.push([ service.name + (config.default === service.name ? " (default)" : ""), service.host ? service.host : service.containerName, service.username, !!service.host, !service.host ? service.storage : "", ip || "-" ]); } return table.toString(); }); } getServices() { return (this.config.services || []).map((service) => { return service.name; }); } start(name, restart) { return __awaiter(this, void 0, void 0, function* () { if (!name && !this.config.hasDefaultService()) { yield this.create(); } const service = this.config.getServiceOrDefault(name); if (service.host) { throw new Error("Service is external"); } yield this.dockerService.pullImage(service.imageTag); if (restart) { yield this.dockerService.removeContainer(service.containerName); } let container = yield this.dockerService.getContainer(service.containerName); if (!container) { console.info(`Starting ${service.name} service...`); const volumes = []; switch (service.storage) { case Service_1.STORAGE_VOLUME: { if (!this.pluginConfigService.isVersionGTE("1.0.19")) { throw new Error("Please update wocker for using volume storage"); } if (!(yield this.dockerService.hasVolume(service.volume))) { yield this.dockerService.createVolume(service.volume); } volumes.push(`${service.volume}:/var/lib/mysql`); break; } case Service_1.STORAGE_FILESYSTEM: default: { if (!this.dbFs.exists(service.name)) { this.dbFs.mkdir(service.name, { recursive: true }); } volumes.push(`${this.dbFs.path(service.name)}:/var/lib/mysql`); break; } } container = yield this.dockerService.createContainer({ name: service.containerName, image: service.imageTag, restart: "always", env: Object.assign(Object.assign(Object.assign(Object.assign({}, service.username ? { MARIADB_USER: service.username } : {}), service.password ? { MARIADB_PASSWORD: service.password } : {}), service.passwordHash ? { MARIADB_ROOT_PASSWORD_HASH: service.passwordHash } : {}), service.rootPassword ? { MARIADB_ROOT_PASSWORD: service.rootPassword } : {}), volumes, ports: service.containerPort ? [`${service.containerPort}:3306`] : undefined }); } const { State: { Running } } = yield container.inspect(); if (!Running) { yield container.start(); } }); } startAdmin() { return __awaiter(this, void 0, void 0, function* () { if (!this.config.admin.enabled) { return; } console.info("Phpmyadmin starting..."); const config = this.config; const servers = []; for (const service of config.services) { if (service.host) { continue; } const container = yield this.dockerService.getContainer(service.containerName); if (!container) { continue; } servers.push(service); } yield this.dockerService.removeContainer(config.admin.hostname); if (servers.length === 0) { return; } for (const service of config.services) { if (!service.host) { continue; } servers.push(service); } let conf = this.dataFs.readFile("conf/config.user.inc.php"); let file = conf.toString() + servers.map((service, index) => { const host = service.host || service.containerName; const user = service.host ? service.username : "root"; const password = service.host ? service.password : service.rootPassword; const res = [ index !== 0 ? `$i++;` : "", `$cfg['Servers'][$i]['host'] = '${host}';` ]; if (user && password) { res.push(`$cfg['Servers'][$i]['auth_type'] = 'config';`); res.push(`$cfg['Servers'][$i]['user'] = '${user}';`); res.push(`$cfg['Servers'][$i]['password'] = '${password}';`); } else if (user) { res.push(`$cfg['Servers'][$i]['auth_type'] = 'cookie';`); res.push(`$cfg['Servers'][$i]['user'] = '${user}';`); } return res.join("\n"); }).join("\n"); this.fs.writeFile("config.user.inc.php", file); this.fs.mkdir("dump", { recursive: true }); this.fs.mkdir("save", { recursive: true }); this.fs.mkdir("upload", { recursive: true }); let container = yield this.dockerService.getContainer(config.admin.hostname); if (!container) { yield this.dockerService.pullImage("phpmyadmin/phpmyadmin:latest"); container = yield this.dockerService.createContainer({ name: config.admin.hostname, image: "phpmyadmin/phpmyadmin:latest", restart: "always", env: { VIRTUAL_HOST: config.admin.hostname, VIRTUAL_PORT: "80" }, volumes: [ `${this.fs.path("config.user.inc.php")}:/etc/phpmyadmin/config.user.inc.php`, `${this.fs.path("save")}:/etc/phpmyadmin/save`, `${this.fs.path("upload")}:/etc/phpmyadmin/upload` ] }); } const { State: { Running } } = yield container.inspect(); if (!Running) { yield container.start(); yield this.dockerService.exec(config.admin.hostname, [ "bash", "-c", [ "apt-get update", "apt-get install acl", "setfacl -R -m u:www-data:rwx /etc/phpmyadmin/save" ].join(" && ") ]); yield this.proxyService.start(); } }); } stop(name) { return __awaiter(this, void 0, void 0, function* () { const config = this.config; const service = name ? config.getService(name) : config.getDefaultService(); if (!service) { throw new Error("Service not found"); } console.info("Mariadb stopping..."); yield this.dockerService.removeContainer(service.containerName); }); } create() { return __awaiter(this, arguments, void 0, function* (serviceProps = {}) { if (serviceProps.name && this.config.hasService(serviceProps.name)) { console.info(`Service "${serviceProps.name}" is already exists`); delete serviceProps.name; } if (!serviceProps.name) { serviceProps.name = yield (0, utils_1.promptInput)({ message: "Service name", required: "Service name is required", validate: (value) => { if (value && this.config.hasService(value)) { return `Service "${value}" is already exists`; } return true; } }); } if (!serviceProps.username) { serviceProps.username = yield (0, utils_1.promptInput)({ message: "User", required: true }); } if (!serviceProps.password) { serviceProps.password = yield (0, utils_1.promptInput)({ message: "Password", type: "password", required: true }); const confirmPassword = yield (0, utils_1.promptInput)({ message: "Confirm password", type: "password" }); if (serviceProps.password !== confirmPassword) { throw new Error("Password didn't match"); } } if (!serviceProps.host) { if (!serviceProps.rootPassword && serviceProps.username !== "root") { serviceProps.rootPassword = yield (0, utils_1.promptInput)({ message: "Root password", type: "password", required: true }); const confirmPassword = yield (0, utils_1.promptInput)({ message: "Confirm root password", type: "password", required: true }); if (serviceProps.rootPassword !== confirmPassword) { throw new Error("Password didn't match"); } } if (!serviceProps.storage || ![Service_1.STORAGE_VOLUME, Service_1.STORAGE_FILESYSTEM].includes(serviceProps.storage)) { serviceProps.storage = yield (0, utils_1.promptSelect)({ message: "Storage:", options: [Service_1.STORAGE_VOLUME, Service_1.STORAGE_FILESYSTEM] }); } if (!serviceProps.containerPort) { const needPort = yield (0, utils_1.promptConfirm)({ message: "Do you need to expose container port?", default: false }); if (needPort) { serviceProps.containerPort = yield (0, utils_1.promptInput)({ required: true, message: "Container port:", type: "number", min: 1, default: 3306 }); } } } this.config.setService(new Service_1.Service(serviceProps)); this.config.save(); }); } upgrade() { return __awaiter(this, arguments, void 0, function* (serviceProps = {}) { const service = this.config.getServiceOrDefault(serviceProps.name); if (serviceProps.storage) { if (![Service_1.STORAGE_FILESYSTEM, Service_1.STORAGE_VOLUME].includes(serviceProps.storage)) { throw new Error("Invalid storage type"); } service.storage = serviceProps.storage; } if (serviceProps.volume) { service.volume = serviceProps.volume; } if (serviceProps.imageName) { service.imageName = serviceProps.imageName; } if (serviceProps.imageVersion) { service.imageVersion = serviceProps.imageVersion; } if (serviceProps.containerPort) { service.containerPort = serviceProps.containerPort; } this.config.setService(service); this.config.save(); }); } destroy(name, yes, force) { return __awaiter(this, void 0, void 0, function* () { if (!name) { throw new Error("Service name required"); } const service = this.config.getService(name); if (this.config.default === service.name) { if (!force) { throw new Error("Can't destroy default service"); } } if (!yes) { const confirm = yield (0, utils_1.promptConfirm)({ message: `Are you sure you want to delete the "${name}" database? This action cannot be undone and all data will be lost.`, default: false }); if (!confirm) { throw new Error("Aborted"); } } if (!service.host) { yield this.dockerService.removeContainer(service.containerName); switch (service.storage) { case Service_1.STORAGE_VOLUME: { if (service.volume !== service.defaultVolume) { console.info(`Deletion of custom volume "${service.volume}" skipped.`); break; } if (!this.pluginConfigService.isVersionGTE("1.0.19")) { throw new Error("Please update wocker for using volume storage"); } if (yield this.dockerService.hasVolume(service.volume)) { yield this.dockerService.rmVolume(service.volume); } break; } case Service_1.STORAGE_FILESYSTEM: default: { this.dbFs.rm(service.name, { recursive: true, force: true }); break; } } } this.config.unsetService(name); this.config.save(); }); } setDefault(name) { return __awaiter(this, void 0, void 0, function* () { const service = this.config.getService(name); this.config.default = service.name; this.config.save(); }); } mariadb(name, database) { return __awaiter(this, void 0, void 0, function* () { const service = this.config.getServiceOrDefault(name); const container = yield this.dockerService.getContainer(service.containerName); if (!container) { throw new Error(`Service "${service.name}" is not started`); } if (!database) { if (!process.stdin.isTTY) { throw new Error("Database name missing"); } database = yield (0, utils_1.promptSelect)({ message: "Database:", options: yield this.getDatabases(service) }); } const exec = yield container.exec({ Cmd: ["mariadb", ...service.auth, database], AttachStdin: true, AttachStdout: true, AttachStderr: true, Tty: process.stdin.isTTY }); const stream = yield exec.start({ hijack: true, stdin: true, Tty: process.stdin.isTTY }); yield this.dockerService.attachStream(stream); }); } backup(name, database, filename) { return __awaiter(this, void 0, void 0, function* () { const service = this.config.getServiceOrDefault(name); const container = yield this.dockerService.getContainer(service.containerName); if (!container) { throw new Error("Service not running"); } if (!database) { const databases = yield this.getDatabases(service); database = (yield (0, utils_1.promptSelect)({ message: "Database:", options: databases })); } if (!filename) { const date = (0, format_1.format)(new Date(), "yyyy-MM-dd HH-mm"); filename = yield (0, utils_1.promptInput)({ message: "File", default: date, suffix: ".sql" }); filename += ".sql"; } this.pluginConfigService.fs.mkdir(`dump/${service.name}/${database}`, { recursive: true }); const file = this.fs.createWriteStream(`dump/${service.name}/${database}/${filename}`); const exec = yield container.exec({ Cmd: ["mariadb-dump", ...service.auth, database, "--add-drop-table", "--hex-blob"], Tty: true, AttachStdin: true, AttachStdout: true, AttachStderr: true }); const stream = yield exec.start({ Tty: true, stdin: true, hijack: true }); yield new Promise((resolve, reject) => { stream.on("data", (data) => { file.write(data.toString("utf-8")); }); stream.on("end", resolve); stream.on("error", reject); }); }); } deleteBackup(name, database, filename, confirm) { return __awaiter(this, void 0, void 0, function* () { const service = this.config.getServiceOrDefault(name); if (!database) { const databases = yield this.getDumpsDatabases(service); database = yield (0, utils_1.promptSelect)({ message: "Database:", options: databases }); } if (!filename) { const files = yield this.getFiles(service, database); filename = yield (0, utils_1.promptSelect)({ message: "File:", options: files }); } const path = `dump/${service.name}/${database}/${filename}`; if (!this.fs.exists(path)) { throw new Error(`File "${filename}" does not exists.`); } if (!confirm) { confirm = yield (0, utils_1.promptConfirm)({ message: "Are you sure you want to delete?", default: false }); } if (!confirm) { throw new Error("Canceled"); } this.fs.rm(path); console.info(`File "${filename}" deleted`); return; }); } restore(name, database, filename) { return __awaiter(this, void 0, void 0, function* () { const service = this.config.getServiceOrDefault(name); const container = yield this.dockerService.getContainer(service.containerName); if (!container) { throw new Error("Mariadb instance isn't started"); } if (!database) { database = yield (0, utils_1.promptSelect)({ options: yield this.getDumpsDatabases(service), message: "Database:" }); } if (!filename) { filename = yield (0, utils_1.promptSelect)({ options: yield this.getFiles(service, database), message: "Filename:" }); } const exec = yield container.exec({ Cmd: ["mariadb", ...service.auth, database], AttachStdin: true, AttachStderr: true, AttachStdout: true }); const stream = yield exec.start({ hijack: true, stdin: true }); yield new Promise((resolve, reject) => { const file = this.fs.createReadStream(`dump/${service.name}/${database}/${filename}`); file.on("data", (data) => { stream.write(data); }); file.on("error", reject); file.on("end", () => resolve(undefined)); stream.on("data", (data) => { process.stdout.write(data); }); stream.on("error", (err) => { file.close(); reject(err); }); }); stream.write("exit\n"); }); } dump(name, database) { return __awaiter(this, void 0, void 0, function* () { const service = this.config.getServiceOrDefault(name); const container = yield this.dockerService.getContainer(service.containerName); if (!container) { throw new Error("Service isn't started"); } if (!database) { if (!process.stdin.isTTY) { throw new Error("Database is missing"); } const databases = yield this.getDatabases(service); database = yield (0, utils_1.promptSelect)({ message: "Database:", options: databases }); } const exec = yield container.exec({ Cmd: ["mariadb-dump", ...service.auth, database, "--add-drop-table"], AttachStdout: true, AttachStderr: true }); const stream = yield exec.start({ Tty: process.stdin.isTTY, hijack: true }); stream.pipe(process.stdout); }); } }; exports.MariadbService = MariadbService; exports.MariadbService = MariadbService = __decorate([ (0, core_1.Injectable)(), __metadata("design:paramtypes", [core_1.AppConfigService, core_1.PluginConfigService, core_1.DockerService, core_1.ProxyService]) ], MariadbService);