import { Plugin } from "frontblock-generic/Plugin"; import { socketioRPC } from "frontblock-generic/RPC"; import { GitUpdater, RepoFolderStatus } from "./GitUpdater"; import { FrontblockCherryPicker } from "git-cherrypicker"; import * as Logger from 'log4js' import * as path from "path"; import * as rmrf from "rimraf" import { FrontblockAdmin } from "./FrontblockAdmin"; import { TableDefiniton } from "frontblock-generic/Admin"; import { promises as fs, mkdirSync as mkdir } from "fs" import { version } from "punycode"; import * as git from "simple-git/promise" import * as trash from "trash" import { type } from "os"; var exec = require('child-process-promise').exec; Logger.configure({ appenders: { "admin/updatemanager": { type: 'stdout' }, //app: { type: 'file', filename: 'application.log' } }, categories: { default: { appenders: [ 'admin/updatemanager' ], level: 'debug' } } }) const logger = Logger.getLogger("admin/updatemanager") export class UpdateManager extends Plugin{ private loadedPlugins:{[name in string]:Plugin} = {} private dashboardUpdater:GitUpdater private adminUpdater:GitUpdater private pluginUpdaters:{[name in string]:GitUpdater} = {} constructor(admin:FrontblockAdmin){ super(admin, "UpdateManager") } getDefaultConfig(): {} { return {} } exportRPCs(): socketioRPC[] { return [{ name: 'installPlugin', func: async (name:string, force = false) => {return await this.installPlugin(name, force)}, type: 'call', visibility: 'private' },{ name: 'startPlugin', func: async (name:string) => {return await this.startPlugin(name)}, type: 'call', visibility: 'private' },{ name: 'updatePlugin', func: async (name) => {return await this.updatePlugin(name)}, type: 'call', visibility: 'private' },{ name: 'setPluginVersion', func: async (name, tag) => {return await this.setPluginVersion(name, tag)}, type: 'call', visibility: 'private' },{ name: 'updateDashboard', func: async () => {return await this.updateDashboard()}, type: 'call', visibility: 'private' },{ name: 'getLoadedPluginNames', func: async () => {return await this.getLoadedPluginNames()}, type: 'call', visibility: 'private' }] } getTableDefinitions(): TableDefiniton[] { return [] } async updatePlatformDependencies(){ logger.info("installing npm stuff") /* const res = await exec('npm install --prefix ./plugins knex sqlite3') logger.warn(res.stderr) logger.info(res.stdout) */ await Promise.all([ new NPMExtension("knex@0.19.2", "./plugins").install(), new NPMExtension("sqlite3@4.1.0", "./plugins").install() ]) } async updateAdmin(force:boolean = false){ this.adminUpdater = new GitUpdater("./dist") let status = await this.adminUpdater.getStatus() if(force || !status.remote || !status.remote.includes("fb-dist/admin") || !status.exists || status.empty || !status.currentTag){ logger.warn("Cloning fb-dist/admin into ./dist ..."+(force?" USING FORCE!":"")) status = await this.adminUpdater.cloneRepo("https://gitea.frontblock.me/fb-dist/admin.git", force) } return status } async updateFrontblockLib(){ logger.warn("Picking FrontblockLib...") await (new FrontblockCherryPicker("fb-dist/admin", "master", "FrontblockLib.js")).write("./static/FrontblockLib.js") } async updateDashboard(force:boolean = false):Promise{ this.dashboardUpdater = new GitUpdater("./static") let status = await this.dashboardUpdater.getStatus() if(force || !status.exists || status.empty || !status.currentTag){ logger.warn("Cloning fb-dist/dashboard into ./static ..."+(force?" USING FORCE!":"")) status = await this.dashboardUpdater.cloneRepo("https://gitea.frontblock.me/fb-dist/dashboard.git", force) } return status } async startPlugin(name:string):Promise{ if(!this.pluginUpdaters[name]) return false const status = await this.pluginUpdaters[name].getStatus() if(!status.exists || status.empty || !status.tags || status.tags.length === 0){ if(status.currentTag && !status.latestTag){ //git glitches sometimes if you check immediately after clone logger.warn("re-fetching tag for "+name+"...") return await this.startPlugin(name) } logger.error("Bad repo status", name, status) return false } if(this.loadedPlugins[name]){ logger.error("Plugin", name, "is already started") return false } let str = "../plugins/"+name+"/Plugin" const pluginClass = await eval('require')(str) const pluginObj = new pluginClass.default() await pluginObj.start() this.admin.addPlugin(pluginObj) this.loadedPlugins[name] = pluginObj return true } async updatePlugin(name:string):Promise{ if(!this.pluginUpdaters[name]) return false const status = await this.pluginUpdaters[name].getStatus() if(!status.exists || status.empty || !status.tags || status.tags.length === 0){ logger.error("Bad repo status", name, status) return false } if(status.currentTag == status.latestTag){ logger.warn(name, "already at latest tag") return false } if(this.loadedPlugins[name]){ logger.error("Plugin", name, "is running. Stop it first") return false } this.pluginUpdaters[name].checkoutTag(status.latestTag!) return true } async setPluginVersion(pluginName:string, tag:string):Promise{ const status = await this.pluginUpdaters[pluginName].getStatus() if(!status.exists || !status.tags || status.tags.length === 0 || !status.tags.includes(tag)){ logger.error("Bad repo status", pluginName, status) return status } return await this.pluginUpdaters[pluginName].checkoutTag(tag) } getLoadedPlugins():Plugin[]{ return Object.values(this.loadedPlugins) } getLoadedPluginNames():string[]{ return Object.keys(this.loadedPlugins) } }