From 062bcec80ed4ba9b80c52cae2dab7ba3b64cc9d2 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Sun, 7 Jan 2024 18:58:25 +0700 Subject: [PATCH] Logman --- .github/workflows/domcloud.yml | 12 ++--- package-lock.json | 74 ++++++++++++++++++++------ package.json | 5 +- src/controllers/logman.js | 26 ++++++++++ src/executor/logman.js | 94 ++++++++++++++++++++++++++++++++++ src/index.js | 5 +- src/util.js | 16 +++--- 7 files changed, 200 insertions(+), 32 deletions(-) create mode 100644 src/controllers/logman.js create mode 100644 src/executor/logman.js diff --git a/.github/workflows/domcloud.yml b/.github/workflows/domcloud.yml index 3d29709..2f146b4 100644 --- a/.github/workflows/domcloud.yml +++ b/.github/workflows/domcloud.yml @@ -15,7 +15,7 @@ jobs: webhook_secret: ${{ secrets.WEBHOOK_SECRET_SGA }} webhook_auth: ${{ secrets.WEBHOOK_AUTH_SGA }} data: >- - {"commands":["git pull","sudo systemctl restart bridge"]} + {"commands":["git pull","npm i","sudo systemctl restart bridge"]} - name: Invoke NYC deployment hook uses: distributhor/workflow-webhook@v2 env: @@ -23,7 +23,7 @@ jobs: webhook_secret: ${{ secrets.WEBHOOK_SECRET_NYC }} webhook_auth: ${{ secrets.WEBHOOK_AUTH_NYC }} data: >- - {"commands":["git pull","sudo systemctl restart bridge"]} + {"commands":["git pull","npm i","sudo systemctl restart bridge"]} - name: Invoke FRA deployment hook uses: distributhor/workflow-webhook@v2 env: @@ -31,7 +31,7 @@ jobs: webhook_secret: ${{ secrets.WEBHOOK_SECRET_FRA }} webhook_auth: ${{ secrets.WEBHOOK_AUTH_FRA }} data: >- - {"commands":["git pull","sudo systemctl restart bridge"]} + {"commands":["git pull","npm i","sudo systemctl restart bridge"]} - name: Invoke OSA deployment hook uses: distributhor/workflow-webhook@v2 env: @@ -39,7 +39,7 @@ jobs: webhook_secret: ${{ secrets.WEBHOOK_SECRET_OSA }} webhook_auth: ${{ secrets.WEBHOOK_AUTH_OSA }} data: >- - {"commands":["git pull","sudo systemctl restart bridge"]} + {"commands":["git pull","npm i","sudo systemctl restart bridge"]} - name: Invoke BLR deployment hook uses: distributhor/workflow-webhook@v2 env: @@ -47,7 +47,7 @@ jobs: webhook_secret: ${{ secrets.WEBHOOK_SECRET_BLR }} webhook_auth: ${{ secrets.WEBHOOK_AUTH_BLR }} data: >- - {"commands":["git pull","sudo systemctl restart bridge"]} + {"commands":["git pull","npm i","sudo systemctl restart bridge"]} - name: Invoke SAO deployment hook uses: distributhor/workflow-webhook@v2 env: @@ -55,4 +55,4 @@ jobs: webhook_secret: ${{ secrets.WEBHOOK_SECRET_SAO }} webhook_auth: ${{ secrets.WEBHOOK_AUTH_SAO }} data: >- - {"commands":["git pull","sudo systemctl restart bridge"]} + {"commands":["git pull","npm i","sudo systemctl restart bridge"]} diff --git a/package-lock.json b/package-lock.json index 36cd6d8..d615db4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "domcloud-bridge", - "version": "0.38.1", + "version": "0.39.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "domcloud-bridge", - "version": "0.38.1", + "version": "0.39.2", "license": "MIT", "dependencies": { - "axios": "^1.6.3", + "axios": "^1.6.5", "cli": "^1.0.1", "dotenv": "^16.3.1", "express": "^4.18.2", + "fast-xml-parser": "^4.3.2", "nginx-conf": "^2.1.0", "node-abort-controller": "^3.1.1", "proper-lockfile": "^4.1.2", @@ -172,11 +173,11 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.3.tgz", - "integrity": "sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.4", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -417,6 +418,27 @@ "node": ">= 0.10.0" } }, + "node_modules/fast-xml-parser": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz", + "integrity": "sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -435,9 +457,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "funding": [ { "type": "individual", @@ -967,6 +989,11 @@ "node": ">= 0.8" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -1171,11 +1198,11 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "axios": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.3.tgz", - "integrity": "sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", "requires": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.4", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -1363,6 +1390,14 @@ "vary": "~1.1.2" } }, + "fast-xml-parser": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz", + "integrity": "sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg==", + "requires": { + "strnum": "^1.0.5" + } + }, "finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -1378,9 +1413,9 @@ } }, "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==" }, "form-data": { "version": "4.0.0", @@ -1761,6 +1796,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", diff --git a/package.json b/package.json index 5db7202..bd0d612 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "domcloud-bridge", - "version": "0.38.1", + "version": "0.39.2", "description": "Deployment runner for DOM Cloud", "main": "app.js", "engines": { @@ -17,10 +17,11 @@ "license": "MIT", "type": "module", "dependencies": { - "axios": "^1.6.3", + "axios": "^1.6.5", "cli": "^1.0.1", "dotenv": "^16.3.1", "express": "^4.18.2", + "fast-xml-parser": "^4.3.2", "nginx-conf": "^2.1.0", "node-abort-controller": "^3.1.1", "proper-lockfile": "^4.1.2", diff --git a/src/controllers/logman.js b/src/controllers/logman.js new file mode 100644 index 0000000..6d19d28 --- /dev/null +++ b/src/controllers/logman.js @@ -0,0 +1,26 @@ +import express from 'express'; +import { + iptablesExec as executor +} from '../executor/iptables.js'; +import { + checkGet, + checkPost +} from '../util.js'; +import { virtualminExec } from '../executor/virtualmin.js'; +import { logmanExec } from '../executor/logman.js'; + +export default function () { + var router = express.Router(); + router.get('/get', checkGet(['domain', 'type']), async function (req, res, next) { + try { + let domain = await virtualminExec.getDomainInfo(req.query.domain.toString()); + let type = req.query.type.toString() + let n = parseInt(req.query.n.toString()) || 100; + let output = await logmanExec.getLog(domain, type, n); + return res.json(output); + } catch (error) { + next(error); + } + }); + return router; +} \ No newline at end of file diff --git a/src/executor/logman.js b/src/executor/logman.js new file mode 100644 index 0000000..3867a40 --- /dev/null +++ b/src/executor/logman.js @@ -0,0 +1,94 @@ +import { + cat, + executeLock, + spawnSudoUtil, + writeTo +} from '../util.js'; +import { XMLParser } from "fast-xml-parser"; + +class LogmanExecutor { + constructor() { + if (process.env.PASSENGERLOG) { + this.PASSENGERLOG = '/var/log/nginx/passenger.log'; + } + } + /** + * @param {any} domain + * @param {string} type + * @param {number} n + */ + async getLog(domain, type, n) { + switch (type) { + case 'access': + if (!domain['Access log']) { + return { + code: 255, + stdout: 'No access log found', + } + } + return await spawnSudoUtil("SHELL_SUDO", ["root", + "tail", "-n", n, domain['Access log']]); + case 'error': + if (!domain['Error log']) { + return { + code: 255, + stdout: 'No error log found', + } + } + return await spawnSudoUtil("SHELL_SUDO", ["root", + "tail", "-n", n, domain['Error log']]); + case 'passenger': + const user = domain['Username']; + const pe = await spawnSudoUtil("SHELL_SUDO", [user, + "passenger-status", "--show=xml"]); + const peo = pe.stdout.trim(); + if (!peo) { + return { + code: 255, + stdout: 'Passenger instance is not set here', + } + } + const parser = new XMLParser(); + let peom = parser.parse(peo); + let peoma = peom?.info?.supergroups?.supergroup; + if (!peoma) { + return { + code: 255, + stderr: 'incomplete response from passenger-status', + stdout: '' + } + } + let peomaa = Array.isArray(peoma) ? peoma : [peoma]; + let peomaps = peomaa.map(x => x.group.processes).filter(x => x); + if (!peomaps.length) { + return { + code: 255, + stderr: 'No processes from passenger-status is running', + stdout: '' + } + } + let procs = peomaps.reduce((a, b) => { + let x = (Array.isArray(b.process) ? b.process : [b.process]); + a[b.group.name] = x.map(y => y.pid).filter(y => typeof y === "number"); + }, {}); + let head = `List of passenger processes running:\n`; + head += JSON.stringify(procs, null, 2); + head += `\n------------------------\n`; + let pids = Object.values(procs).flatMap(x => x).join('\\|'); + const pes = await spawnSudoUtil("SHELL_SUDO", ["root", + "bash", "-c", `grep -w "\\^App \\(${pids}\\)" "${this.PASSENGERLOG}" | tail -n ${n}` + ]); + if (pes.code == 0) { + pes.stdout = head + pes.stdout; + } + return pes; + default: + return { + code: 255, + stdout: 'Unknown log type ' + type + } + } + } +} + +export const logmanExec = new LogmanExecutor(); diff --git a/src/index.js b/src/index.js index e5a7f20..88f4d80 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ import express from 'express' import dotenv from 'dotenv' +import logman from './controllers/logman.js'; import named from './controllers/named.js'; import nginx from './controllers/nginx.js'; import status from './controllers/status.js'; @@ -23,6 +24,7 @@ app.use(express.static('public')); app.use(express.json()); app.use('/status', status()); app.use(checkAuth); +app.use('/logman', logman()); app.use('/named', named()); app.use('/nginx', nginx()); app.use('/iptables', iptables()); @@ -35,8 +37,9 @@ app.use(function (err, req, res, next) { res.json(err); }); const port = process.env.PORT ? parseInt(process.env.PORT) : 2223; + app.listen(port, function () { - console.log(`Listening on ${port}`); console.log(`Start time takes ` + (Date.now() - startTime) / 1000 + ` s`) + console.log(`Listening on ${port}`); }) diff --git a/src/util.js b/src/util.js index 3be66fa..07dac9d 100644 --- a/src/util.js +++ b/src/util.js @@ -59,12 +59,16 @@ export const initUtils = async () => { } return a; }, {}); - const phpPath = process.env.PHPFPM_REMILIST || '/etc/opt/remi/'; - const phpFiles = fs.readdirSync(phpPath, { withFileTypes: true }); - phpVersionsList = phpFiles - .filter(dirent => dirent.isDirectory()) - .map(dirent => dirent.name.replace(/php(\d)(\d+)/, '$1.$2')) - phpVersionsList = sortSemver(phpVersionsList).reverse(); + try { + const phpPath = process.env.PHPFPM_REMILIST || '/etc/opt/remi/'; + const phpFiles = fs.readdirSync(phpPath, { withFileTypes: true }); + phpVersionsList = phpFiles + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name.replace(/php(\d)(\d+)/, '$1.$2')) + phpVersionsList = sortSemver(phpVersionsList).reverse(); + } catch (error) { + phpVersionsList = []; + } // TODO: detect OS/arch? await axios.get('https://rvm_io.global.ssl.fastly.net/binaries/centos/9/x86_64/').then(res => { // @ts-ignore