summaryrefslogblamecommitdiffstats
path: root/server/api/configloader.js
blob: ba395d02bfed7db35d44adf0a95e840a24870b3a (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                     
                          

                                                         

                                                   

                                                               

                                                                        











                                                                                
                                                                      








                                                                         
 
                                                                                           
                                                     


                                             
                                                                                                
 




                                                                                
 







                                                                                                            
 




                          
 



                                                  


                                                                                        
     
   
 
                                                                          
                                                        













                                                                                                          
                                      











                                                                                             
     







                                                                                             

     






                                                                                             
     
          



                                                                                          
   
                                        

                                                                            
  
 









                                                        
                                
                      

                                   
                          
                                             


                         




                                                                             


                               





                                                                       
                                                                                     

                                                    

                                                                                           

                                                                                                                  
                                                                                                                                                                                                                                 
 
                                                                                                  



              
                                




                                                                   
                                                              





                                                 
                                
                                      
                                                                 













                                                                                                                             

                                                             



                          
                    







                                                                                                  
     
                                                       






                                                                                                  



                                                                             
                                                        

                                                                                                          
                                                                                        





                                                                  
                                      

                                                                                        
                                                                  




                                                                                          
                                                                     


                                                 
                                  


                                         
                                
                                                                                                                                    
                                                         












                                                          
                                                                                               











                                                                                                  
                                                 





                        




                                                                       
                         

                              
                                                                   




                                              
 


                                                
                                                          
                                                                                                         
      


                                                      
                        
 


                 
 








                                           
                                          
/* 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'))

noAuthRouter.getAsync(['/test/group/:id', '/test/group/'], async (req, res) => {
  const list = req.query.list !== undefined && req.query.list !== 'false'
  const config = await configHelper.getGroupConfig(req.params.id, 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 config = await configHelper.getConfig(req.params.uuid, list)
  if (!list) {
    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(await 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