From ffc5050fcdd0efed4f01d7f1f20465cadd9c9f5b Mon Sep 17 00:00:00 2001 From: Jannik Schönartz Date: Tue, 15 Jan 2019 03:39:31 +0000 Subject: [ipxe builder] Add ipxe builder module EFI and BIOS version can be build and configured Fancy log to see the stdout and stderr --- server/api/ipxe.js | 63 +++++++++++++++++- server/ipxe/embedded_bios.ipxe | 15 +++++ server/ipxe/embedded_efi.ipxe | 15 +++++ server/ipxe/main.ipxe | 10 --- server/lib/shell.js | 144 +++++++++++++++++++++++++++++++---------- server/lib/socketio.js | 1 + 6 files changed, 201 insertions(+), 47 deletions(-) create mode 100644 server/ipxe/embedded_bios.ipxe create mode 100644 server/ipxe/embedded_efi.ipxe delete mode 100644 server/ipxe/main.ipxe (limited to 'server') diff --git a/server/api/ipxe.js b/server/api/ipxe.js index f50d9d3..549eb6f 100644 --- a/server/api/ipxe.js +++ b/server/api/ipxe.js @@ -7,12 +7,71 @@ var router = express.Router() var noAuthRouter = express.Router() // GET requests. +router.get('/build', (req, res) => { + shell.buildIpxe(req, res) +}) + +router.get('/:version/script', (req, res) => { + res.setHeader('content-type', 'text/plain') + res.sendFile(path.join(__appdir, 'ipxe', 'embedded_' + req.params.version + '.ipxe')) +}) + +router.get('/:version/certificate', (req, res) => { + res.setHeader('content-type', 'text/plain') + res.sendFile(path.join(__appdir, 'bin', 'fullchain.pem')) +}) + +router.get('/:version/general', (req, res) => { + res.setHeader('content-type', 'text/plain') + res.sendFile(path.join(__appdir, 'ipxe', 'general_' + req.params.version + '.h')) +}) + +router.get('/:version/console', (req, res) => { + res.setHeader('content-type', 'text/plain') + res.sendFile(path.join(__appdir, 'ipxe', 'console_' + req.params.version + '.h')) +}) + +router.get('/:version/log', async (req, res) => { + res.setHeader('content-type', 'text/plain') + const filepath = path.join(__appdir, 'ipxe', 'log_' + req.params.version + '.txt') + res.sendFile(filepath, err => { + if (err) console.log('Could not send file ' + filepath) + }) +}) + +router.put('/:version/:filename', (req, res) => { + var filepath = null + // Set the file path + if (req.params.filename === 'script') { + filepath = path.join(__appdir, 'ipxe', 'embedded_' + req.params.version + '.ipxe') + } else if (req.params.filename === 'console' || req.params.filename === 'general') { + filepath = path.join(__appdir, 'ipxe', req.params.filename + '_' + req.params.version + '.h') + } else if (req.params.filename === 'certificate') { + filepath = path.join(__appdir, 'bin', 'fullchain.pem') + } else { + 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) res.status(500).send({ status: 'WRITE_FILE_ERROR', error: err }) + else res.status(200).send({ status: 'SUCCESS' }) + }) +}) /* * @return: Rebuild the ipxe. */ -router.get('/build', (req, res) => { - shell.buildIpxe(req, res) +router.get('/:version/build', async (req, res) => { + if (req.params.version === 'efi' || req.params.version === 'bios') await shell.buildIpxe(req, res) + res.status(200).send({ status: 'success' }) +}) + +router.get('/:version/clean', (req, res) => { + if (req.params.version === 'efi') shell.clean('efi') + else if (req.params.version === 'bios') shell.clean('bios') + res.send() }) module.exports.router = router diff --git a/server/ipxe/embedded_bios.ipxe b/server/ipxe/embedded_bios.ipxe new file mode 100644 index 0000000..698c683 --- /dev/null +++ b/server/ipxe/embedded_bios.ipxe @@ -0,0 +1,15 @@ +#!ipxe + +################ +# BIOS Version # +################ + +ifopen + +# Wallpaper +set img tftp://10.8.102.124/ipxeWallpaper3_scale.png || shell +console --picture ${img} --x 800 --y 600 || shell + +:loop +chain https://bas.intra.uni-freiburg.de/api/configloader/${uuid} +goto loop \ No newline at end of file diff --git a/server/ipxe/embedded_efi.ipxe b/server/ipxe/embedded_efi.ipxe new file mode 100644 index 0000000..70bfc17 --- /dev/null +++ b/server/ipxe/embedded_efi.ipxe @@ -0,0 +1,15 @@ +#!ipxe + +################ +# EFI Version # +################ + +ifopen + +# Wallpaper +set img tftp://10.8.102.124/ipxeWallpaper3_scale.png || shell +console --picture ${img} --x 800 --y 600 || shell + +:loop +chain https://bas.intra.uni-freiburg.de/api/configloader/${uuid} +goto loop \ No newline at end of file diff --git a/server/ipxe/main.ipxe b/server/ipxe/main.ipxe deleted file mode 100644 index 3bb8e86..0000000 --- a/server/ipxe/main.ipxe +++ /dev/null @@ -1,10 +0,0 @@ -#!ipxe -ifopen - -# Wallpaper -set img tftp://10.8.102.124/ipxeWallpaper3_scale.png || shell -console --picture ${img} --x 800 --y 600 || shell - -:loop -chain https://bas.intra.uni-freiburg.de/api/configloader/${uuid} -goto loop \ No newline at end of file diff --git a/server/lib/shell.js b/server/lib/shell.js index 2b1ea0b..09a3ae2 100644 --- a/server/lib/shell.js +++ b/server/lib/shell.js @@ -1,47 +1,121 @@ +// Those packages needs to be installed to build ipxe: +// sudo apt install liblzma-dev +// sudo apt install mkisofs + /* global __appdir */ var path = require('path') var shell = require('shelljs') var ipxeGIT = 'git://git.ipxe.org/ipxe.git' +var io = require(path.join(__appdir, 'lib', 'socketio')) +const fs = require('fs') module.exports = { - buildIpxe: function (req, res) { - if (!shell.which('git')) { - return res.status(500).send({ status: 'GIT_MISSING', error_message: 'Please install git on the server.' }) - } + buildIpxe: async function (req, res) { + const ipxeVersion = req.params.version + + // Cloning git. + sendToLog(ipxeVersion, 'Cloning git ...\n', 'primary') + await cloneIpxe(ipxeVersion) - var gitclone = 'git clone ' + ipxeGIT - shell.cd(path.join(__appdir, 'ipxe')) - shell.exec(gitclone, function (code, stdout, stderr) { - shell.cd(path.join(__appdir, 'ipxe', 'ipxe', 'src')) - - // Remove the general config and paste in the own one - shell.rm(path.join(__appdir, 'ipxe', 'ipxe', 'src', 'config', 'general.h')) - // shell.cp(path.join(__appdir, 'ipxe', 'general.h'), path.join(__appdir, 'ipxe', 'ipxe', 'src', 'config')) - shell.cp(path.join(__appdir, 'ipxe', 'general_efi.h'), path.join(__appdir, 'ipxe', 'ipxe', 'src', 'config', 'general.h')) - shell.rm(path.join(__appdir, 'ipxe', 'ipxe', 'src', 'config', 'console.h')) - // shell.cp(path.join(__appdir, 'ipxe', 'console.h'), path.join(__appdir, 'ipxe', 'ipxe', 'src', 'config')) - shell.cp(path.join(__appdir, 'ipxe', 'console_efi.h'), path.join(__appdir, 'ipxe', 'ipxe', 'src', 'config', 'console.h')) - // PCBIOS Variant - // var make = 'make EMBED=' + path.join(__appdir, 'ipxe', 'main.ipxe') + ' TRUST=' + path.join(__appdir, 'bin', 'fullchain.pem')// + ' bin/undionly.kpxe' - - // EFI Variant - var make = 'make bin-x86_64-efi/snponly.efi EMBED=' + path.join(__appdir, 'ipxe', 'main.ipxe') + ' TRUST=' + path.join(__appdir, 'bin', 'fullchain.pem')// + ' bin/undionly.kpxe' - - // USB - // var make = 'make EMBED=' + path.join(__appdir, 'ipxe', 'reboot.ipxe') + ' TRUST=' + path.join(__appdir, 'bin', 'fullchain.pem') + ' bin/ipxe.usb' - shell.env.DEBUG = '' - shell.exec(make, function (code, stdout, stderr) { - // shell.rm(path.join(__appdir, 'tftp', 'ipxe.0')) - shell.rm(path.join(__appdir, 'ipxe', 'ipxe.0')) - // shell.cp('bin/undionly.kpxe', path.join(__appdir, 'tftp')) - shell.cp('bin/undionly.kpxe', path.join(__appdir, 'ipxe')) - shell.mv(path.join(__appdir, 'ipxe', 'undionly.kpxe'), path.join(__appdir, 'ipxe', 'ipxe.0')) - // shell.rm('-rf', 'ipxe'); - return res.status(200).send({ status: 'success' }) + // Copying configs. + sendToLog(ipxeVersion, 'Copying configs ...\n', 'primary') + copyConfigs(ipxeVersion) + + // Make ipxe. + sendToLog(ipxeVersion, 'Make iPXE ...\n', 'primary') + shell.cd(path.join(__appdir, 'ipxe', 'ipxe_' + ipxeVersion, 'src')) + + // Different make command for efi / bios are needed. + var makeCmd = '' + if (ipxeVersion === 'efi') makeCmd = 'make bin-x86_64-efi/snponly.efi EMBED=' + path.join(__appdir, 'ipxe', 'embedded_' + ipxeVersion + '.ipxe') + ' TRUST=' + path.join(__appdir, 'bin', 'fullchain.pem')// + ' bin/undionly.kpxe' + else if (ipxeVersion === 'bios') makeCmd = 'make EMBED=' + path.join(__appdir, 'ipxe', 'embedded_' + ipxeVersion + '.ipxe') + ' TRUST=' + path.join(__appdir, 'bin', 'fullchain.pem')// + ' bin/undionly.kpxe' + await new Promise((resolve, reject) => { + var make = shell.exec(makeCmd, { async: true }, () => { + resolve() + }) + + // Send the output to the frontend log. + make.stdout.on('data', data => { + sendToLog(ipxeVersion, data, 'normal') + }) + make.stderr.on('data', data => { + sendToLog(ipxeVersion, data, 'error') }) }) + + // Copy and rename the ipxe file. + shell.cp('bin/undionly.kpxe', path.join(__appdir, 'ipxe')) + shell.mv(path.join(__appdir, 'ipxe', 'undionly.kpxe'), path.join(__appdir, 'ipxe', 'ipxe.' + ipxeVersion)) + }, + + clean: async function (ipxeVersion) { + shell.cd(path.join(__appdir, 'ipxe')) + shell.rm('-rf', 'ipxe_' + ipxeVersion) + shell.rm(path.join(__appdir, 'ipxe', 'ipxe.' + ipxeVersion)) + shell.rm(path.join(__appdir, 'ipxe', 'log_' + ipxeVersion + '.txt')) + sendToLog(ipxeVersion, 'iPXE files successfully cleaned', 'success', false) } } -// sudo apt-get install liblzma-dev -// sudo apt-get install mkisofs +async function cloneIpxe(ipxeVersion) { + // Check if git is installed on the server. + if (!shell.which('git')) { + return { status: 'GIT_MISSING', error_message: 'Please install git on the server.' } + } + + shell.cd(path.join(__appdir, 'ipxe')) + return new Promise((resolve, reject) => { + var clone = shell.exec('git clone ' + ipxeGIT + ' ipxe_' + ipxeVersion, { async: true }, () => { + resolve() + }) + clone.stdout.on('data', data => { + sendToLog(ipxeVersion, data, 'normal') + }) + clone.stderr.on('data', data => { + sendToLog(ipxeVersion, data, 'error') + }) + }) +} + +function copyConfigs(ipxeVersion) { + // Remove the default configs and paste in the customized ones. + shell.rm(path.join(__appdir, 'ipxe', 'ipxe_' + ipxeVersion, 'src', 'config', 'general.h')) + shell.rm(path.join(__appdir, 'ipxe', 'ipxe_' + ipxeVersion, 'src', 'config', 'console.h')) + shell.cp(path.join(__appdir, 'ipxe', 'general_' + ipxeVersion + '.h'), path.join(__appdir, 'ipxe', 'ipxe_' + ipxeVersion, 'src', 'config', 'general.h')) + shell.cp(path.join(__appdir, 'ipxe', 'console_' + ipxeVersion + '.h'), path.join(__appdir, 'ipxe', 'ipxe_' + ipxeVersion, 'src', 'config', 'console.h')) + return +} + +function sendToLog(ipxeVersion, msg, status, writeLog = true) { + var date = new Date(); + var dateString = '[' + date.getFullYear() + '-' + + var month = parseInt(date.getMonth()) + 1 + if (month < 10) dateString += '0' + month + '-' + else dateString += month + '-' + + if (parseInt(date.getDate()) < 10) dateString += '0' + date.getDate() + ' ' + else dateString += date.getDate() + ' ' + + if (parseInt(date.getHours()) < 10) dateString += '0' + date.getHours() + ':' + else dateString += date.getHours() + ':' + + if (parseInt(date.getMinutes()) < 10) dateString += '0' + date.getMinutes() + ':' + else dateString += date.getMinutes() + ':' + + if (parseInt(date.getSeconds()) < 10) dateString += '0' + date.getSeconds() + else dateString += date.getSeconds() + dateString += ']' + + var logEntry = { date: dateString, status: status, msg: msg } + + var socket = ipxeVersion + ' log' + io.in('broadcast ipxeBuild').emit(socket, logEntry) + if (writeLog) writeLog(ipxeVersion, logEntry) +} + +function writeLog(ipxeVersion, logEntry) { + fs.writeFile(path.join(__appdir, 'ipxe', 'log_' + ipxeVersion + '.txt'), logEntry.date + '\t' + logEntry.status + '\t' + logEntry.msg, { flag: 'a+' }, (err) => { + if (err) throw err + }) +} diff --git a/server/lib/socketio.js b/server/lib/socketio.js index 07c2f22..5cbedb7 100644 --- a/server/lib/socketio.js +++ b/server/lib/socketio.js @@ -27,6 +27,7 @@ io.on('connection', socket => { // Join broadcast rooms (TODO: check if user has permission to join those rooms) socket.join('broadcast newClient') + socket.join('broadcast ipxeBuild') // Notification broadcasts socket.on('notifications clearAll', () => socket.to(userId).emit('notifications clearAll')) -- cgit v1.2.3-55-g7522