From b1404761553b04a5b8805f150b1f160503328592 Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 20 Aug 2019 19:30:35 +0200 Subject: [PATCH] 0.1.0 move to gitea dist for downloads --- .drone.yml | 2 +- package-lock.json | 28 +++++- package.json | 1 + src/backend/FrontblockAdmin.ts | 153 +++--------------------------- src/backend/GitUpdater.ts | 56 ++++++----- src/backend/UpdateManger.ts | 166 +++++++++++++++++++++++++++++++++ 6 files changed, 240 insertions(+), 166 deletions(-) create mode 100644 src/backend/UpdateManger.ts diff --git a/.drone.yml b/.drone.yml index 4dc31e6..03d7d6e 100644 --- a/.drone.yml +++ b/.drone.yml @@ -46,7 +46,7 @@ steps: - cp -r ./dist/* ./${DRONE_REPO_NAME} - cd ./${DRONE_REPO_NAME} - git add -A - - git commit -m "drone tagged as version ${DRONE_TAG}" || true + - git commit --allow-empty -m "drone tagged as version ${DRONE_TAG}" - git tag ${DRONE_TAG} - git push https://$GIT_USER:$GIT_PASSWORD@gitea.frontblock.me/fb-dist/${DRONE_REPO_NAME}.git master ${DRONE_TAG} environment: diff --git a/package-lock.json b/package-lock.json index 8013735..ab90ba4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5546,6 +5546,11 @@ "isobject": "^3.0.1" } }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, "is-regex": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", @@ -6492,6 +6497,18 @@ "signal-exit": "^3.0.0" } }, + "lowdb": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-1.0.0.tgz", + "integrity": "sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ==", + "requires": { + "graceful-fs": "^4.1.3", + "is-promise": "^2.1.0", + "lodash": "4", + "pify": "^3.0.0", + "steno": "^0.4.1" + } + }, "lower-case": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", @@ -7876,8 +7893,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, "pinkie": { "version": "2.0.4", @@ -10215,6 +10231,14 @@ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", "dev": true }, + "steno": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/steno/-/steno-0.4.4.tgz", + "integrity": "sha1-BxEFvfwobmYVwEA8J+nXtdy4Vcs=", + "requires": { + "graceful-fs": "^4.1.3" + } + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", diff --git a/package.json b/package.json index 8e8960b..441b56a 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "http": "0.0.0", "key-file-storage": "^2.2.4", "log4js": "^4.4.0", + "lowdb": "^1.0.0", "memfs": "^2.15.5", "minimist": "^1.2.0", "node-fetch": "^2.6.0", diff --git a/src/backend/FrontblockAdmin.ts b/src/backend/FrontblockAdmin.ts index 36b4f2a..8a7963c 100644 --- a/src/backend/FrontblockAdmin.ts +++ b/src/backend/FrontblockAdmin.ts @@ -2,7 +2,7 @@ import * as Logger from 'log4js' import { socketioRPC, ConfigLoader, Plugin } from 'frontblock-generic/Plugin'; -import { ErrorResponse, SuccessResponse } from 'frontblock-generic/Types'; +import { ErrorResponse, SuccessResponse, SubscriptionResponse } from 'frontblock-generic/Types'; import express = require('express'); import http = require('http'); import bsock = require('bsock'); @@ -10,21 +10,22 @@ import { FrontblockCherryPicker } from "git-cherrypicker" import { promises as fs } from "fs" import { GitUpdater, RepoFolderStatus, } from "./GitUpdater"; import * as path from "path" +import { UpdateManager } from './UpdateManger'; const kfs = require("key-file-storage").default('kfs') Logger.configure({ appenders: { - "frontblock-admin": { type: 'stdout' }, + "admin": { type: 'stdout' }, //app: { type: 'file', filename: 'application.log' } }, categories: { - default: { appenders: [ 'frontblock-admin' ], level: 'debug' } + default: { appenders: [ 'admin' ], level: 'debug' } } }) -const logger = Logger.getLogger("frontblock-admin") +const logger = Logger.getLogger("admin") type hookRPC = { @@ -70,12 +71,7 @@ export class FrontblockAdmin extends ConfigLoader{ private httpServer private io = bsock.createServer() private wsServer = http.createServer() - - private dashboardUpdater:GitUpdater - private adminUpdater:GitUpdater - private pluginUpdaters:{[name in string]:GitUpdater} = {} - private loadedPlugins:{[name in string]:Plugin} = {} - + private updateManager:UpdateManager = new UpdateManager() constructor(){ super("FrontblockAdmin") @@ -87,83 +83,10 @@ export class FrontblockAdmin extends ConfigLoader{ return {httpPort: 8080} } - private async updateFrontblockLib(){ - logger.warn("Picking FrontblockLib...") - await (new FrontblockCherryPicker("fb-dist/admin", "master", "FrontblockLib.js")).write("./static/FrontblockLib.js") - } - - private async updateDashboard():Promise{ - logger.warn("Cloning dashboard...") - this.dashboardUpdater = new GitUpdater("./static") - const status = await this.dashboardUpdater.cloneRepo("https://gitea.frontblock.me/fb-dist/dashboard.git", true) - return(status) - } - - private async installPlugin(name: string):Promise{ - logger.warn("Cloning "+name+"...") - this.pluginUpdaters[name] = new GitUpdater("./plugins/"+name) - const status = await this.pluginUpdaters[name].cloneRepo("https://gitea.frontblock.me/fb-dist/"+name.toLowerCase()+".git", true) - return status - } - - private 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){ - 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 import(str) - const pluginObj = new pluginClass.default() - await pluginObj.start() - this.loadedPlugins[name] = pluginObj - return true - } - - private 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 - } - - private 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) - } - private async initialize(){ - await this.updateDashboard() - await this.updateFrontblockLib() - await this.installPlugin("PluginManager") - //await this.updatePluginmanager() + await this.updateManager.updateAdmin() + await this.updateManager.updateDashboard() + await this.updateManager.updateFrontblockLib() this.startWebsocket() this.startWebserver() @@ -188,61 +111,15 @@ export class FrontblockAdmin extends ConfigLoader{ type:'call', fn: () => { return rpcInfos } } - },{ - owner: 'Admin', - name: 'installPlugin', - args: 'name', - info: { - type: 'call', - fn: async (name:string) => { - return await this.installPlugin(name) - } - } - },{ - owner: 'Admin', - name: 'startPlugin', - args: 'name', - info: { - type: 'call', - fn: async (name:string) => { - return await this.startPlugin(name) - } - } - },{ - owner: 'PluginManager', - name: 'getLoadedPluginNames', - args: '', - info: { - type: 'call', - fn: () => Object.keys(this.loadedPlugins) - } - },{ - owner: 'Admin', - name: 'updatePlugin', - args: 'name', - info: { - type: 'call', - fn: async (name) => {return await this.updatePlugin(name)} - } - },{ - owner: 'Admin', - name: 'setPluginVersion', - args: 'name, tag', - info: { - type: 'call', - fn: async (name, tag) => {return await this.setPluginVersion(name, tag)} - } } ] //translate RPCs to socket-bound function metadata - const loadedPlugins = this.loadedPlugins - for(const name in loadedPlugins){ - loadedPlugins[name].exportRPCs().forEach(rpc => { - const info = this.rpcToRpcInfo(name, rpc) - rpcInfos.push(info) - }) - } + const loadedPlugins:Plugin[] = [this.updateManager, ...this.updateManager.getLoadedPlugins()] + loadedPlugins.forEach(plugin => plugin.exportRPCs().forEach(rpc => { + const info = this.rpcToRpcInfo(plugin.name, rpc) + rpcInfos.push(info) + })) //Hook up all the functions for(const api of rpcInfos){ @@ -405,7 +282,7 @@ export class FrontblockAdmin extends ConfigLoader{ * * Note callbacks *need* to accept a singular argument and they have to be the last parameter! */ - hookGenerator = (rpc:socketioRPC) => { + hookGenerator = (rpc:socketioRPC): (socket)=>(...args:any[])=>Promise => { const argsArr = this.extractArgs(rpc.rpc).split(',') argsArr.pop() const args = argsArr.join(',') diff --git a/src/backend/GitUpdater.ts b/src/backend/GitUpdater.ts index e7cc579..231c83b 100644 --- a/src/backend/GitUpdater.ts +++ b/src/backend/GitUpdater.ts @@ -1,10 +1,23 @@ "use strict"; -import { assert } from "bsert" import * as git from "simple-git/promise" import * as gyt from "git-describe" import { promises as fs, mkdirSync } from "fs" import * as rimraf from "rimraf" import * as path from "path" +import * as Logger from 'log4js' +Logger.configure({ + appenders: + { + "admin/gitupdater": { type: 'stdout' }, + //app: { type: 'file', filename: 'application.log' } + }, + categories: + { + default: { appenders: [ 'admin/gitupdater' ], level: 'debug' } + } +}) +const logger = Logger.getLogger("admin/gitupdater") + const rmrf = (path:string, options = {}) => { @@ -17,6 +30,7 @@ export type RepoFolderStatus = { currentTag?: string tags?: string[] latestTag?: string + remote?: string } export class GitUpdater{ @@ -41,6 +55,11 @@ export class GitUpdater{ await this.repo.fetch("origin", "master", { "--tags": null }); + const remoteInfo = await this.repo.getRemotes(true) + let remoteURL + if(remoteInfo.length != 0){ + remoteURL = remoteInfo.find(i => i.name === "origin").refs.fetch + } const remoteTags = await this.repo.tags(); const desc = await gyt.gitDescribe(this.folderPath, { match: "*" }); return { @@ -48,19 +67,22 @@ export class GitUpdater{ empty: empty, currentTag: !desc.tag?null:desc.tag, tags: !remoteTags.all?[]:remoteTags.all, - latestTag: remoteTags.latest + latestTag: remoteTags.latest, + remote: remoteURL }; } async cloneRepo(from:string, force:boolean = false):Promise { - if(this.folderPath === "." && force) - throw new Error("refusing to `rm -rf .`: \ncloneRepo(from: "+from+", force: "+force+")") if(force){ await rmrf(this.folderPath) await fs.mkdir(this.folderPath, { recursive: true }); } const status = await this.getStatus() - assert(status.exists && status.empty) + if(!status.exists || !status.empty){ + logger.error("The folder "+this.folderPath+" doesn't exist or is not empty. Not cloning! Pass the force parameter to override this check") + logger.error(status) + return status + } await git(this.folderPath).clone(from, path.resolve(this.folderPath)); return await this.getStatus(); @@ -68,27 +90,11 @@ export class GitUpdater{ async checkoutTag(tag:string):Promise { const status = await this.getStatus() - assert(status.exists && status.tags && status.tags.length !== 0) + if(!status.exists || !status.tags || status.tags.length === 0){ + logger.error("Unable to check out tag", tag, status) + return status + } await this.repo.checkout(tag); return await this.getStatus(); } } - - -/* -(async () => { - const staticPath = path.join(__dirname, "..", "static") - - const gl = new GitUpdater(staticPath) - let status = await gl.getStatus() - console.log(status) - - if(status.empty){ - status = await gl.cloneRepo("https://gitea.frontblock.me/fb-dist/dashboard.git") - console.log(status) - } - - status = await gl.checkoutTag(status.latestTag!) - console.log(status) -})() -*/ diff --git a/src/backend/UpdateManger.ts b/src/backend/UpdateManger.ts new file mode 100644 index 0000000..d0810de --- /dev/null +++ b/src/backend/UpdateManger.ts @@ -0,0 +1,166 @@ +import { Plugin, socketioRPC } from "frontblock-generic/Plugin"; +import { GitUpdater, RepoFolderStatus } from "./GitUpdater"; +import { FrontblockCherryPicker } from "git-cherrypicker"; +import * as Logger from 'log4js' +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 implements Plugin{ + name: string = "UpdateManager" + private loadedPlugins:{[name in string]:Plugin} = {} + private dashboardUpdater:GitUpdater + private adminUpdater:GitUpdater + private pluginUpdaters:{[name in string]:GitUpdater} = {} + + getDefaultConfig(): void { + return + } + + exportRPCs(): socketioRPC[] { + return [{ + name: 'installPlugin', + rpc: async (name:string) => {return await this.installPlugin(name)}, + type: 'call', + visibility: 'private' + },{ + name: 'startPlugin', + rpc: async (name:string) => {return await this.startPlugin(name)}, + type: 'call', + visibility: 'private' + },{ + name: 'updatePlugin', + rpc: async (name) => {return await this.updatePlugin(name)}, + type: 'call', + visibility: 'private' + },{ + name: 'setPluginVersion', + rpc: async (name, tag) => {return await this.setPluginVersion(name, tag)}, + type: 'call', + visibility: 'private' + },{ + name: 'updateDashboard', + rpc: async () => {return await this.updateDashboard()}, + type: 'call', + visibility: 'private' + },{ + name: 'getLoadedPluginNames', + rpc: async () => {return await this.getLoadedPluginNames()}, + type: 'call', + visibility: 'private' + }] + } + + 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 installPlugin(name: string, force:boolean = false):Promise{ + logger.warn("Cloning fb-dist/"+name+".git into ./plugins/"+name+" ..."+(force?" USING FORCE!":"")) + this.pluginUpdaters[name] = new GitUpdater("./plugins/"+name) + const status = await this.pluginUpdaters[name].cloneRepo("https://gitea.frontblock.me/fb-dist/"+name.toLowerCase()+".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){ + 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 import(str) + const pluginObj = new pluginClass.default() + await pluginObj.start() + 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) + } + + start(): void | Promise { + throw new Error("Method not implemented (on purpose)"); + } + + stop(): void | Promise { + throw new Error("Method not implemented (on purpose)"); + } + + +} \ No newline at end of file