/* global __appdir */ var path = require('path') var db = require(path.join(__appdir, 'lib', 'sequelize')) var express = require('express') const { decorateApp } = require('@awaitjs/express') var noAuthRouter = decorateApp(express.Router()) const config = require(path.join(__appdir, 'config', 'config')) const url = config.https.host + ':' + config.https.port const configHelper = require(path.join(__appdir, 'lib', 'confighelper')) const log = require(path.join(__appdir, 'lib', 'log')) noAuthRouter.getAsync(['/test/group/:id', '/test/group/'], async (req, res) => { const list = req.query.list !== undefined && req.query.list !== 'false' const group = await db.group.findOne({ where: { id: req.params.id } }) const config = await configHelper.getGroupConfig(group, list) if (!config) return res.status(404).end() if (!list) { res.set('Content-Type', 'text/plain') res.send(config.script) } else { res.send(config) } }) noAuthRouter.getAsync(['/test/:uuid', '/test/'], async (req, res) => { const list = req.query.list !== undefined && req.query.list !== 'false' const client = await db.client.findOne({ where: { uuid: req.params.uuid } }) const config = await configHelper.getConfig(client, list) if (!list) { const logEntry = { category: 'CLIENT_BOOT', description: 'Client booted iPXE config [' + config.id + '] ' + config.name + '.\n' + 'Client UUID: ' + req.params.uuid + '\n' + 'Config ID: ' + config.id + '\n' + 'Config Name: ' + config.name, } if (client) logEntry.clientId = client.id log(logEntry) res.set('Content-Type', 'text/plain') res.send(config.script) } else { res.send(config) } }) // if client in db -> load script (default if none is found), else load registration script noAuthRouter.getAsync('/:uuid', async (req, res) => { const uuid = req.params.uuid res.setHeader('content-type', 'text/plain') var client = await db.client.findOne({ where: { uuid: uuid }, include: ['groups', 'events'] }) // client not known, start registration if (client === null) { await sendFilePromise(res, path.join(__appdir, 'ipxe', 'registration.ipxe')) return } // Client is in db, check for registration hooks. if (client.registrationState !== null) { // client is in registration state, load scripts var hook = await db.registrationhook.findOne({ where: { id: client.registrationState } }) if (hook.type === 'IPXE') res.send(hook.script) else if (hook.type === 'BASH') await sendFilePromise(res, path.join(__appdir, 'ipxe', 'minilinux.ipxe')) return } var events = [] var blacklist = [] var importantEvents = [] var groupIds = [] var result for (let i = 0; i < client.events.length; i++) { var times = JSON.parse(client.events[i].times) var intime = checkEventNow(times) if (intime) { if (client.events[i].client_x_event.blacklist) blacklist.push(client.events[i].id) else if (client.events[i].important) importantEvents.push(client.events[i]) else events.push(client.events[i]) } } importantEvents = importantEvents.filter(e => !blacklist.includes(e.id)) importantEvents = importantEvents.map(e => e.configId) importantEvents = importantEvents.filter(c => c !== null) importantEvents = importantEvents.filter(function (elem, pos, arr) { return arr.indexOf(elem) === pos }) if (importantEvents.length === 1) { result = await createIpxeScript(importantEvents[0]) res.send(result) return } if (importantEvents.length > 1) { result = await createDynamicMenu(importantEvents) res.send(result) return } events = events.filter(e => !blacklist.includes(e.id)) events = events.map(e => e.configId) events = events.filter(c => c !== null) events = events.filter(function (elem, pos, arr) { return arr.indexOf(elem) === pos }) groupIds = client.groups.map(x => x.id) var response = await fetchParentConfigs(groupIds, blacklist) if (response.ids.length > 0) { // Check if there is an important event if (response.type === 'important') { if (response.ids.length === 1) return res.send(await createIpxeScript(response.ids[0])) if (response.ids.length > 1) return res.send(await createDynamicMenu(response.ids)) } // No important event, use client event if existent if (events.length === 1) return res.send(await createIpxeScript(events[0])) if (events.length > 1) return res.send(await createDynamicMenu(events)) // No client event, use events of lowest parents if (response.type === 'event') { if (response.ids.length === 1) return res.send(await createIpxeScript(response.ids[0])) if (response.ids.length > 1) return res.send(await createDynamicMenu(response.ids)) } // No events, use client config if (client.configId !== null) return res.send(await createIpxeScript(client.configId)) // No client config, use configs of lowest parents if (response.type === 'config') { if (response.ids.length === 1) return res.send(await createIpxeScript(response.ids[0])) if (response.ids.length > 1) return res.send(await createDynamicMenu(response.ids)) } } else { if (events.length === 1) return res.send(await createIpxeScript(events[0])) if (events.length > 1) return res.send(await createDynamicMenu(events)) if (client.configId !== null) return res.send(await createIpxeScript(client.configId)) } // No config found, use default config // await sendFilePromise(res, path.join(__appdir, 'ipxe', 'default.ipxe')) res.send(configHelper.getDefaultConfig(client)) }) // load config by given id noAuthRouter.get('/getconfig/:configId', (req, res) => { const configId = req.params.configId res.setHeader('content-type', 'text/plain') createIpxeScript(configId).then(response => { res.send(response) }) }) function checkEventNow (times) { var now = new Date() var start = new Date(times.start) var end = new Date(times.end) if (!times.repetitive) { if (start < now && now < end) return true } if (times.repetitive) { var startSplit = times.start.split(' ') var startHourMinutes = startSplit[1].split(':').map(x => parseInt(x, 10)) var endSplit = times.end.split(' ') var endHourMinutes = endSplit[1].split(':').map(x => parseInt(x, 10)) var startAtDay = new Date() var endAtDay = new Date() var overnight = false startAtDay.setHours(startHourMinutes[0], startHourMinutes[1], 0, 0) endAtDay.setHours(endHourMinutes[0], endHourMinutes[1], 0, 0) if (endAtDay > now && startAtDay > now && endAtDay < startAtDay) { var yesterday = new Date(now) yesterday.setDate(yesterday.getDate() - 1) if (checkEventInterval(start, yesterday, times.intervalType, times.interval)) { startAtDay.setDate(startAtDay.getDate() - 1) overnight = true } else return false } else if (endAtDay < now && startAtDay > now) endAtDay.setDate(endAtDay.getDate() + 1) else if (endAtDay < now && startAtDay < now && endAtDay < startAtDay) endAtDay.setDate(endAtDay.getDate() + 1) if (!(times.monthMap[now.getMonth()] && (overnight ? times.dayMap[((yesterday.getDay() + 6) % 7)] : times.dayMap[((now.getDay() + 6) % 7)]) && start < now && now < end && startAtDay <= now && now < endAtDay)) return false return (overnight ? true : checkEventInterval(start, now, times.intervalType, times.interval)) } return false } function dateDiffInDays (a, b) { const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate()) const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()) return Math.floor((utc2 - utc1) / (1000 * 3600 * 24)) } function checkEventInterval (startDate, now, type, interval) { if (type === 'day') { let dateDiff = dateDiffInDays(startDate, now) if (dateDiff % interval === 0) return true } else if (type === 'week') { var startDay = (startDate.getDay() + 6) % 7 var nowDay = (now.getDay() + 6) % 7 let diff = startDay - nowDay var nowSameDayAsStart = new Date() nowSameDayAsStart.setDate(nowSameDayAsStart.getDate() + diff) let dateDiff = dateDiffInDays(startDate, nowSameDayAsStart) if (dateDiff % (7 * interval) === 0) return true } else if (type === 'month') { if (startDate.getFullYear() !== now.getFullYear()) { var monthDiff = (12 - startDate.getMonth()) + (12 * (now.getFullYear() - startDate.getFullYear() - 1)) + now.getMonth() if (!(monthDiff % interval === 0)) return false } else if (!((now.getMonth() - startDate.getMonth()) % interval === 0)) return false startDate.setFullYear(now.getFullYear()) startDate.setMonth(now.getMonth()) if (startDate.getMonth() !== now.getMonth()) startDate.setDate(0) if (startDate.getDate() === now.getDate()) return true } } async function fetchParentConfigs (groupIds, blacklist) { if (groupIds.length === 0) return { 'ids': [], 'type': '' } var importantEvents = [] var events = [] var configs = [] var parentIds = [] var newBlacklist = blacklist.slice(0) var groups = await db.group.findAll({ where: { id: groupIds }, include: ['parents', 'events'] }) for (let i = 0; i < groups.length; i++) { configs.push(groups[i].configId) // groups[i].map(g => g.parents.map(p => p.id)) for (let j = 0; j < groups[i].parents.length; j++) { parentIds.push(groups[i].parents[j].id) } for (let j = 0; j < groups[i].events.length; j++) { var times = JSON.parse(groups[i].events[j].times) var intime = checkEventNow(times) if (intime) { if (groups[i].events[j].group_x_event.blacklist) newBlacklist.push(groups[i].events[j].id) else if (groups[i].events[j].important) importantEvents.push(groups[i].events[j]) else events.push(groups[i].events[j]) } } } importantEvents = importantEvents.filter(e => !newBlacklist.includes(e.id)) importantEvents = importantEvents.map(e => e.configId) importantEvents = importantEvents.filter(c => c !== null) importantEvents = importantEvents.filter(function (elem, pos, arr) { return arr.indexOf(elem) === pos }) if (importantEvents.length > 0) return { 'ids': importantEvents, 'type': 'important' } var response = await fetchParentConfigs(parentIds, newBlacklist) if (response.type === 'important') return response events = events.filter(e => !newBlacklist.includes(e.id)) events = events.map(e => e.configId) events = events.filter(c => c !== null) events = events.filter(function (elem, pos, arr) { return arr.indexOf(elem) === pos }) if (events.length > 0) return { 'ids': events, 'type': 'event' } if (response.type === 'event') return response configs = configs.filter(function (elem, pos, arr) { return arr.indexOf(elem) === pos }) configs = configs.filter(c => c !== null) if (configs.length > 0) return { 'ids': configs, 'type': 'config' } if (response.type === 'config') return response return { 'ids': [], 'type': '' } } // create the config script from database function createIpxeScript (id) { return db.config.findOne({ where: { id: id }, include: ['entries'], order: [[['entries'], 'sortValue', 'ASC']] }).then(config => { if (config.script !== null && config.script !== '') { return config.script } var script = '' var menuscript = '' script += '#!ipxe\r\n\r\n' script += ':start\r\n' script += 'menu ' + config.name + '\r\n' config.entries.forEach(entry => { script += 'item' if (entry.config_x_entry.keyBind !== null) { script += ' --key ' + entry.config_x_entry.keyBind } script += ' menuentry' + entry.id + ' ' if (entry.config_x_entry.customName !== null && entry.config_x_entry.customName !== '') { script += entry.config_x_entry.customName } else { script += entry.name } script += '\r\n' menuscript += ':' + 'menuentry' + entry.id + '\r\n' menuscript += entry.script + '\r\n\r\n' }) script += 'choose ' if (config.defaultEntry !== null && config.timeout !== null) { script += '--default menuentry' + config.defaultEntry + ' --timeout ' + config.timeout + ' ' } script += `target && goto \${target}\r\n\r\n` script += menuscript return script }) } // create dynamic menu to load the different given configs for a client function createDynamicMenu (ids) { return db.config.findAll({ where: { id: ids } }).then(configs => { var script = '' var menuscript = '' var defaultentry = '' script += '#!ipxe\r\n\r\n' script += ':start\r\n' script += 'menu ' + 'Choose one configuration to boot' + '\r\n' configs.forEach(config => { script += 'item ' script += ' menuentry' + config.id + ' ' script += config.name script += '\r\n' // Last script processed is default script defaultentry = 'menuentry' + config.id menuscript += ':' + 'menuentry' + config.id + '\r\n' menuscript += 'chain ' + 'https://' + url + '/api/configloader/getconfig/' + config.id + '\r\n\r\n' }) script += 'choose --default ' + defaultentry + ' ' script += '--timeout 15 ' script += `target && goto \${target}\r\n\r\n` script += menuscript return script }) } function sendFilePromise (res, file) { return new Promise((resolve, reject) => { res.sendFile(file, {}, err => { if (err) console.log(err) resolve() }) }) } module.exports.noAuthRouter = noAuthRouter