/* global __appdir */ const path = require('path') const fs = require('fs') const db = require(path.join(__appdir, 'lib', 'sequelize')) const eventHelper = require(path.join(__appdir, 'lib', 'eventhelper')) const serverConfig = require(path.join(__appdir, 'config', 'config')) async function getConfig (client, list) { let configPath = [] // client not known, start registration if (client === null) { let config = { id: 'REGISTRATION', name: 'Client Registration' } if (!list) { config.script = fs.readFileSync(path.join(__appdir, 'ipxe', 'registration.ipxe'), 'utf8') return config } configPath.push(config) return configPath } // 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 } }) let config = { id: 'HOOK' + hook.id, name: hook.name, source: { type: hook.type + '_HOOK', id: hook.id, name: hook.name } } if (!list) { if (hook.type === 'IPXE') { config.script = hook.script } else if (hook.type === 'BASH') { config.script = fs.readFileSync(path.join(__appdir, 'ipxe', 'minilinux.ipxe'), 'utf8') } return config } if (hook.type === 'BASH') config.name = 'Minilinux' else config.name = hook.name configPath.push(config) } // Check for event or direct configs const configs = await _checkParentConfigs(client.id, !list) if (configs.length) { if (!list) return configs[0] else configPath.push(...configs) } // No config found, use default config let config = await prepareConfig({ id: 'DEFAULT', source: { type: 'DEFAULT' } }) if (config === null) config = { id: 'DEFAULT', name: 'Fallback Default', script: getDefaultConfig(client), source: { type: 'DEFAULT' } } if (!list) return config configPath.push(config) return configPath } async function getGroupConfig (group, list) { if (!group) return let configPath = [] // Check for event or direct configs const configs = await _checkParentConfigs(group.id, !list, false) if (!list) { if (configs.length) return configs[0] } else { configPath.push(...configs) } // No config found, use default config let config = await prepareConfig({ id: 'DEFAULT', source: { type: 'DEFAULT' } }) if (config === null) config = { id: 'DEFAULT', name: 'Fallback Default', script: getDefaultConfig({}), source: { type: 'DEFAULT' } } if (!list) return config configPath.push(config) return configPath } async function _checkParentConfigs (id, breakOnImportant, isClient = true) { const importantList = [] const eventList = [] const configList = [] const blackList = [] const seenGroupsList = isClient ? [] : [id] let parents = isClient ? [] : [id] do { let events = [] let configs = [] if (isClient) { events = await db.event.findAll({ where: { '$clients.id$': id }, include: ['clients', 'config'] }) let client = await db.client.findOne({ where: { id: id }, include: ['config'] }) if (client && client.configId) configs = [{ id: client.configId, source: { type: 'CLIENT', id: client.id, name: client.name } }] } else { events = await db.event.findAll({ where: { '$groups.id$': parents }, include: ['groups', 'config'] }) let groups = await db.group.findAll({ where: { id: parents }, include: ['config'] }) groups.forEach(group => { if (group.configId) configs.push({ id: group.configId, source: { type: 'GROUP', id: group.id, name: group.name } }) }) } let importants = [] let nonImportants = [] events.forEach(event => { if (!eventHelper.isActive(event)) return if ((event.groups && event.groups.some(group => group.group_x_event.blacklist)) || (event.clients && event.clients.length && event.clients[0].client_x_event.blacklist)) { return blackList.push(event.id) } if (blackList.includes(event.id) || !event.configId) return let config = { id: event.configId, source: { type: event.important ? 'IMPORTANT_EVENT' : 'EVENT', id: event.id, name: event.name } } if (event.groups) config.source.groups = event.groups.map(x => ({ id: x.id, name: x.name })) if (event.important) importants.push(config) else nonImportants.push(config) }) if (importants.length > 1) importantList.push(await _createDynamicMenu(importants)) else if (importants.length === 1) importantList.push(await prepareConfig(importants[0])) if (breakOnImportant && importantList.length) break if (nonImportants.length > 1) eventList.push(await _createDynamicMenu(nonImportants)) else if (nonImportants.length === 1) eventList.push(await prepareConfig(nonImportants[0])) if (configs.length > 1) configList.push(await _createDynamicMenu(configs)) else if (configs.length === 1) configList.push(await prepareConfig(configs[0])) if (isClient) { parents = (await db.group.findAll({ where: { '$clients.id$': id }, include: ['clients'] })).map(x => x.id) isClient = false } else { parents = (await db.group.findAll({ where: { '$subgroups.id$': parents }, include: ['subgroups'] })).map(x => x.id) } for (let i = parents.length - 1; i >= 0; i--) { if (seenGroupsList.includes(parents[i])) parents.splice(i, 1) } seenGroupsList.push(...parents) } while (parents.length) return [...importantList, ...eventList, ...configList] } // create the config script from database async function prepareConfig (configInfo, noScript) { let id = configInfo.id // If no id is given, find the default config const where = {} if (id === 'DEFAULT') where.isDefault = true else where.id = id const config = await db.config.findOne({ where, include: ['entries'], order: [[db.config.associations.entries, db.config.associations.entries.through, 'sortValue', 'ASC']] }) if (!config) return null const result = { id: config.id, name: config.name, source: configInfo.source } if (noScript) return result if (config.script !== null && config.script !== '') { result.script = config.script return result } var script = '' var menuscript = '' script += '#!ipxe\r\n\r\n' script += 'set img https://bas.intra.uni-freiburg.de/files/ipxe_wallpaper.png || goto start\r\n' script += `console --picture \${img} --x 800 --y 600 || console --x 1024 --y 768 ||\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 result.script = script return result } // create dynamic menu to load the different given configs for a client async function _createDynamicMenu (configInfos, noScript) { const ids = [] const idSourceMap = {} for (let i in configInfos) { let id = configInfos[i].id let source = configInfos[i].source if (!ids.includes(id)) ids.push(id) if (idSourceMap[id] === undefined) idSourceMap[id] = source else if (Array.isArray(idSourceMap[id])) idSourceMap[id].push(source) else idSourceMap[id] = [idSourceMap[id], source] } if (ids.length === 1) { return prepareConfig({ id: ids[0], source: idSourceMap[ids[0]] }) } const configs = await db.config.findAll({ where: { id: ids } }) const result = { merged: true, id: JSON.stringify(configs.map(x => x.id)), name: configs.map(x => x.name).join(' + '), configs: configs.map(x => ({ id: x.id, name: x.name, source: idSourceMap[x.id] })) } if (noScript) return result var script = '' var menuscript = '' var defaultentry = '' script += '#!ipxe\r\n\r\n' script += 'set img https://bas.intra.uni-freiburg.de/files/ipxe_wallpaper.png || goto start\r\n' script += `console --picture \${img} --x 800 --y 600 || console --x 1024 --y 768 ||\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://' + serverConfig.https.host + '/api/configloader/configs/' + config.id + '\r\n\r\n' }) script += 'choose --default ' + defaultentry + ' ' script += '--timeout 15000 ' script += `target && goto \${target}\r\n\r\n` script += menuscript result.script = script return result } // Creates a default overview config, when there is no default config in the frontend. function getDefaultConfig (client) { let script = '#!ipxe\r\n\r\n' script += 'set img https://bas.intra.uni-freiburg.de/files/ipxe_wallpaper.png || goto start\r\n' script += `console --picture \${img} --x 800 --y 600 || console --x 1024 --y 768 ||\r\n\r\n` script += ':start\r\n' script += 'menu ' + 'Client is successfully registered' + '\r\n' script += 'item --gap BAS-ID: ' + client.id + '\r\n' script += 'item --gap Name: ' + client.name + '\r\n' script += 'item --gap\r\n' script += 'item --gap IP: ' + client.ip + '\r\n' script += 'item --gap MAC: ' + client.mac + '\r\n' script += 'item --gap UUID: ' + client.uuid + '\r\n' script += 'item --gap\r\n' script += 'item --gap\r\n' script += 'item --gap ' + 'No config is set for the client or any of its parents!' + '\r\n' script += 'item --gap ' + 'Please assign a config to the client or a parent first! (e.g. in the frontend)' + '\r\n' script += 'item --gap\r\n' script += 'item ' + 'poweroff Power Off' + '\r\n' script += 'item ' + 'reboot Reboot' + '\r\n' script += `choose --default poweroff --timeout 25000 target && goto \${target}\r\n\r\n` script += '\r\n' script += ':poweroff\r\n' script += 'poweroff\r\n' script += '\r\n' script += ':reboot\r\n' script += 'reboot\r\n' script += '\r\n' return script } module.exports = { getConfig, getGroupConfig, getDefaultConfig, prepareConfig }