/* global __appdir */ var path = require('path') const fs = require('fs') var shell = require(path.join(__appdir, 'lib', 'shell')) var express = require('express') const { decorateApp } = require('@awaitjs/express') var router = decorateApp(express.Router()) var noAuthRouter = express.Router() const log = require(path.join(__appdir, 'lib', 'log')) const buildLinkPath = path.join(__appdir, 'ipxe', 'current_builds') const buildsPath = path.join(__appdir, 'ipxe', 'builds') const sanitize = require('sanitize-filename') // Permission check middleware router.all('/:x', async (req, res, next) => { switch (req.method) { case 'GET': switch (req.params.x) { case 'script': case 'certificate': case 'general': case 'console': case 'log': case 'config': case 'status': case 'builds': if (!await req.user.hasPermission('ipxe.view')) return res.status(403).send({ error: 'Missing permission', permission: 'ipxe.view' }) break case 'build': case 'cancel': case 'clean': if (!await req.user.hasPermission('ipxe.edit')) return res.status(403).send({ error: 'Missing permission', permission: 'ipxe.edit' }) break default: return res.status(400).send() } break case 'PUT': case 'POST': if (!await req.user.hasPermission('ipxe.edit')) return res.status(403).send({ error: 'Missing permission', permission: 'ipxe.edit' }) break default: return res.status(400).send() } next() }) // GET requests. router.get('/script', (req, res) => { res.setHeader('content-type', 'text/plain') res.sendFile(path.join(__appdir, 'ipxe', 'embedded.ipxe')) }) router.get('/certificate', (req, res) => { res.setHeader('content-type', 'text/plain') res.sendFile(path.join(__appdir, 'bin', 'fullchain.pem')) }) router.get('/general', (req, res) => { res.setHeader('content-type', 'text/plain') res.sendFile(path.join(__appdir, 'ipxe', 'general.h')) }) router.get('/console', (req, res) => { res.setHeader('content-type', 'text/plain') res.sendFile(path.join(__appdir, 'ipxe', 'console.h')) }) router.get('/log', (req, res) => { const max = req.query.max ? req.query.max : -1 res.setHeader('content-type', 'text/plain') const filepath = path.join(__appdir, 'ipxe', 'ipxelog.txt') fs.readFile(filepath, 'utf-8', function (err, content) { if (err) res.end() if (max !== -1 && content) { let c = content.split('\n') c = c.splice(-max) res.send(c.join('\n')) } else res.send(content) }) }) router.get('/config', (req, res) => { delete require.cache[require.resolve(path.join(__appdir, 'config', 'ipxe'))] var config = require(path.join(__appdir, 'config', 'ipxe')) res.send(config) }) router.post('/config', (req, res) => { let buildTargets = req.body.buildTargets const targetList = req.body.targetList const branch = req.body.branch const repository = req.body.repository var config = require(path.join(__appdir, 'config', 'ipxe')) if (repository) config.repository = repository if (branch) config.branch = branch if (targetList && config.targets.custom) config.targets.list = targetList if (buildTargets) { if (!config.targets.custom) buildTargets = buildTargets.filter(target => config.targets.list.indexOf(target) >= 0) config.targets.build = buildTargets } fs.writeFile(path.join(__appdir, 'config', 'ipxe.json'), JSON.stringify(config, null, 4), {}, (err) => { if (err) res.status(500).end() else res.send() }) }) router.put('/:filename', (req, res) => { var filepath = null let filename = '' // Set the file path if (req.params.filename === 'script') { filepath = path.join(__appdir, 'ipxe', 'embedded.ipxe') filename = 'embedded.ipxe' } else if (req.params.filename === 'console' || req.params.filename === 'general') { filepath = path.join(__appdir, 'ipxe', req.params.filename + '.h') filename = req.params.filename + '.h' } else if (req.params.filename === 'certificate') { filepath = path.join(__appdir, 'bin', 'fullchain.pem') filename = 'fullchain.pem' } else { log({ category: 'ERROR_IPXEBUILDER_SAVE', description: filename + ' cannot be saved. Filename unknown', userId: req.user.id }) res.status(500).send({ status: 'UNKNOWN_FILENAME', error: 'The provided filename is unknown' }) return } // Write File fs.writeFile(filepath, req.body.data, err => { if (err) { log({ category: 'ERROR_IPXEBUILDER_SAVE', description: 'Error while saving ' + filename + '. ' + err, userId: req.user.id }) res.status(500).send({ status: 'WRITE_FILE_ERROR', error: err }) } else { log({ category: 'IPXEBUILDER_SAVE', description: 'User has saved ' + filename, userId: req.user.id }) res.status(200).send({ status: 'SUCCESS' }) } }) }) /* * @return: Rebuild the ipxe. */ router.getAsync('/build', async (req, res) => { log({ category: 'IPXEBUILDER_BUILD', description: 'User initiated the ipxe building process.', userId: req.user.id }) delete require.cache[require.resolve(path.join(__appdir, 'config', 'ipxe'))] const config = require(path.join(__appdir, 'config', 'ipxe')) const build = await shell.buildIpxe(config.targets.build, config.repository, config.branch) res.send(build) }) router.getAsync('/cancel', async (req, res) => { log({ category: 'IPXEBUILDER_CANCEL', description: 'User canceled the ipxe building process.', userId: req.user.id }) const result = await shell.cancelBuilding(req, res) res.send(result) }) router.getAsync('/clean', async (req, res) => { log({ category: 'IPXEBUILDER_CLEAN', description: 'User initiated ipxe cleaning process.', userId: req.user.id }) const result = await shell.clean() res.send(result) }) router.get('/status', (req, res) => { res.send({ status: 'SUCCESS', data: shell.status(req.params.version) }) }) /* * Return a json of all available builds in /ipxe/builds * { 'YYYY-M-DD_h-m-s': { type: '', children: { ... }, selected: }} */ router.get('/builds', (req, res) => { // Reads directory of the builded ipxe targets /ipxe/builds let recursiveDirectory = shell.readdirRecursive(buildsPath) let linkname try { linkname = fs.readlinkSync(buildLinkPath).split('/').slice(-1)[0] } catch (error) { linkname = '' } for (let buildDir of recursiveDirectory) { buildDir.selected = (buildDir.name === linkname) } res.send(recursiveDirectory) }) /* * Recreates the symlinks to the selected target. */ router.post('/builds', (req, res) => { let buildname = sanitize(req.body.build) const buildDir = fs.readdirSync(buildsPath) if (buildDir.includes(buildname)) { // Recreate the symlink return res.send(shell.forceSymlink(path.join(buildsPath, buildname), buildLinkPath)) } else return res.send({ status: 'ERROR', error: 'BUILD_NOT_FOUND' }) }) /* * Renames the directory of the builds */ router.post('/builds/:buildname', (req, res) => { const buildname = sanitize(req.params.buildname) const newname = sanitize(req.body.name) const buildDir = fs.readdirSync(buildsPath) // Check if the build exists and the destination don't if (!buildDir.includes(buildname)) return res.send({ status: 'ERROR', error: 'BUILD_NOT_FOUND' }) if (buildDir.includes(newname)) return res.send({ status: 'ERROR', error: 'BUILD_ALREADY_EXISTS' }) // Rename the build fs.renameSync(path.join(buildsPath, buildname), path.join(buildsPath, newname)) // Check if the link has to be changed (if the current default build was renamed) let linkname = fs.readlinkSync(buildLinkPath).split('/').slice(-1)[0] if (linkname === buildname) shell.forceSymlink(path.join(buildsPath, newname), buildLinkPath) return res.send({ status: 'SUCCESS' }) }) router.delete('/builds/:buildname', (req, res) => { let buildname = sanitize(req.params.buildname) const buildDir = fs.readdirSync(buildsPath) if (buildDir.includes(buildname)) { shell.forceDeleteBuild(path.join(__appdir, 'ipxe', 'builds', buildname)) return res.send() } else return res.send({ status: 'ERROR', error: 'BUILDS_NOT_FOUND' }) }) module.exports.router = router noAuthRouter.get('/load/script', (req, res) => { res.setHeader('content-type', 'text/plain') fs.readFile(path.join(__appdir, 'ipxe', 'default.ipxe'), 'utf-8', function (err, content) { if (err) res.end() res.send(content) }) }) noAuthRouter.get('/load/registration', (req, res) => { res.setHeader('content-type', 'text/plain') res.sendFile(path.join(__appdir, 'ipxe', 'registration.ipxe')) }) module.exports.noAuthRouter = noAuthRouter