summaryrefslogblamecommitdiffstats
path: root/server/lib/confighelper.js
blob: 3cbbe58b8f491a2e47ff15c40eae4d3101a9bebc (plain) (tree)
1
2
3
4
5
6
7
8




                                                                      
                                                                     
 
                                         



                                         
                                                                    











                                                                                               
                                                                                                                               







                                                                                              

                                                       



                                      
                                                             
 


                                    


                                        
                                                                                  

                                                                                                                                          





                          
                                             



                                      
                                                                   

              
                                         
          
                               


                                        
                                                                                  

                                                                                                                                      





                          






                                                                            
 
      

                    
 









                                                                                                                                      

     





                                                   
 

                                                                                              
                                       
       
 
                                                                 
 







                                                              
 
                                                                                                  
 


                                                  
 
                                                                                       
                                                                                            
 
                                                       
 
                                                                                         
                                                                                              

                                                                              
                                                                                   
 











                                                                                                                         


                                                        

 
                                         
                                                     







                                                                                                                                                                                
                          










                                                                                

                                                                                                  



























                                                                                                




                                                                       









                                                                         
 
                         
                          



                                 

                                                                 
 


                                               
                                               







                                                                                      

                                                                                                  



                                                                 
                                           






                                                        
                                                                                                                         

                                                    
                              






                                               
                                                                                      
                                    
                               

                                                                                                  
                        
                                                                  
























                                                                                                                     
 
                                                                               
/* 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 => {
      let eventTimes = JSON.parse(event.times)
      if (!eventHelper.isActive(eventTimes)) 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 ||\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 ||\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 ||\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 }