2023-01-19 20:22:26 +00:00
const express = require ( "express" ) ;
const bodyParser = require ( "body-parser" ) ;
const cookieParser = require ( "cookie-parser" )
const cors = require ( "cors" ) ;
const helmet = require ( "helmet" ) ;
const morgan = require ( "morgan" ) ;
2023-02-13 02:11:55 +00:00
var api = require ( "./package.json" ) ;
2023-01-25 00:28:44 +00:00
2023-02-28 23:24:49 +00:00
const { pveAPIToken , listenPort , domain } = require ( "./vars.js" ) ;
2023-04-19 02:42:35 +00:00
const { checkAuth , requestPVE , handleResponse , getUsedResources , getDiskInfo } = require ( "./pveutils.js" ) ;
2023-04-19 02:46:05 +00:00
const { init , getResourceMeta , getUser , approveResources , setUsedResources , getResourceUnits } = require ( "./db.js" ) ;
2023-01-17 23:46:42 +00:00
const app = express ( ) ;
app . use ( helmet ( ) ) ;
2023-01-25 02:01:47 +00:00
app . use ( bodyParser . urlencoded ( { extended : true } ) ) ;
2023-01-19 20:22:26 +00:00
app . use ( cookieParser ( ) )
2023-02-28 22:04:30 +00:00
app . use ( cors ( { origin : domain } ) ) ;
2023-01-19 20:22:26 +00:00
app . use ( morgan ( "combined" ) ) ;
2023-01-17 23:46:42 +00:00
2023-01-19 20:22:26 +00:00
app . get ( "/api/version" , ( req , res ) => {
2023-04-19 01:03:55 +00:00
res . status ( 200 ) . send ( { version : api . version } ) ;
2023-01-17 23:46:42 +00:00
} ) ;
2023-01-19 20:22:26 +00:00
app . get ( "/api/echo" , ( req , res ) => {
2023-04-19 01:03:55 +00:00
res . status ( 200 ) . send ( { body : req . body , cookies : req . cookies } ) ;
2023-01-19 20:22:26 +00:00
} ) ;
2023-02-13 02:11:55 +00:00
app . get ( "/api/auth" , async ( req , res ) => {
2023-04-19 01:03:55 +00:00
await checkAuth ( req . cookies ) ;
res . status ( 200 ) . send ( { auth : true } ) ;
2023-01-20 06:40:21 +00:00
} ) ;
2023-02-24 22:21:21 +00:00
app . get ( "/api/proxmox/*" , async ( req , res ) => { // proxy endpoint for GET proxmox api with no token
2023-02-24 22:19:30 +00:00
path = req . url . replace ( "/api/proxmox" , "" ) ;
let result = await requestPVE ( path , "GET" , req . cookies ) ;
2023-04-19 01:03:55 +00:00
res . status ( result . status ) . send ( result . data ) ;
2023-02-24 22:19:30 +00:00
} ) ;
2023-02-24 22:21:21 +00:00
app . post ( "/api/proxmox/*" , async ( req , res ) => { // proxy endpoint for POST proxmox api with no token
path = req . url . replace ( "/api/proxmox" , "" ) ;
2023-02-24 22:34:19 +00:00
let result = await requestPVE ( path , "POST" , req . cookies , JSON . stringify ( req . body ) ) ; // need to stringify body because of other issues
2023-04-19 01:03:55 +00:00
res . status ( result . status ) . send ( result . data ) ;
2023-02-24 22:21:21 +00:00
} ) ;
2023-04-03 21:45:06 +00:00
app . get ( "/api/user/resources" , async ( req , res ) => {
2023-04-19 02:42:35 +00:00
// check auth
2023-04-19 01:03:55 +00:00
await checkAuth ( req . cookies , res ) ;
2023-04-19 02:42:35 +00:00
let used = await getUsedResources ( req , getResourceMeta ( ) ) ;
setUsedResources ( req . cookies . username , used ) ;
let user = await getUser ( req . cookies . username ) ;
2023-04-19 02:46:05 +00:00
user . units = getResourceUnits ( ) ;
2023-04-19 02:42:35 +00:00
res . status ( 200 ) . send ( user ) ;
res . end ( ) ;
2023-04-03 21:53:11 +00:00
return ;
2023-04-03 21:45:06 +00:00
} ) ;
2023-02-13 02:11:55 +00:00
app . post ( "/api/disk/detach" , async ( req , res ) => {
2023-04-19 02:42:35 +00:00
// check auth
2023-04-19 01:03:55 +00:00
await checkAuth ( req . cookies , res ) ;
2023-03-01 00:38:09 +00:00
if ( req . body . disk . includes ( "unused" ) ) {
res . status ( 500 ) . send ( { auth : auth , data : { error : ` Requested disk ${ req . body . disk } cannot be unused. Use /disk/delete to permanently delete unused disks. ` } } ) ;
return ;
}
2023-02-27 02:29:39 +00:00
let action = JSON . stringify ( { delete : req . body . disk } ) ;
2023-02-24 00:17:28 +00:00
let method = req . body . type === "qemu" ? "POST" : "PUT" ;
2023-04-19 02:42:35 +00:00
let result = await requestPVE ( ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } /config ` , method , req . cookies , action , pveAPIToken ) ;
2023-04-19 01:03:55 +00:00
await handleResponse ( req . body . node , result , res ) ;
2023-02-10 21:41:36 +00:00
} ) ;
2023-02-13 02:11:55 +00:00
app . post ( "/api/disk/attach" , async ( req , res ) => {
2023-04-19 02:42:35 +00:00
// check auth
2023-04-19 01:03:55 +00:00
await checkAuth ( req . cookies , res ) ;
2023-02-27 02:29:39 +00:00
let action = { } ;
action [ req . body . disk ] = req . body . data ;
action = JSON . stringify ( action ) ;
2023-02-24 00:17:28 +00:00
let method = req . body . type === "qemu" ? "POST" : "PUT" ;
2023-04-19 02:42:35 +00:00
let result = await requestPVE ( ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } /config ` , method , req . cookies , action , pveAPIToken ) ;
2023-04-19 01:03:55 +00:00
await handleResponse ( req . body . node , result , res ) ;
2023-01-25 00:48:15 +00:00
} ) ;
2023-02-13 02:11:55 +00:00
2023-04-19 02:42:35 +00:00
app . post ( "/api/disk/resize" , async ( req , res ) => {
2023-02-28 23:36:22 +00:00
// check auth
2023-04-19 02:42:35 +00:00
await checkAuth ( req . cookies , res ) ;
// update used resources
let used = await getUsedResources ( req , getResourceMeta ( ) ) ;
setUsedResources ( req . cookies . username , used ) ;
// check disk existence
let diskConfig = await getDiskInfo ( req . body . node , req . body . type , req . body . vmid , req . body . disk ) ; // get target disk
2023-03-01 00:38:09 +00:00
if ( ! diskConfig ) { // exit if disk does not exist
2023-02-28 23:24:49 +00:00
res . status ( 500 ) . send ( { auth : auth , data : { error : ` requested disk ${ req . body . disk } does not exist ` } } ) ;
2023-03-01 00:38:09 +00:00
return ;
2023-02-28 23:24:49 +00:00
}
2023-04-19 02:42:35 +00:00
let storage = diskConfig . storage ; // get the storage
2023-02-28 23:24:49 +00:00
let request = { } ;
2023-04-19 02:42:35 +00:00
request [ storage ] = Number ( req . body . size * 1024 * * 3 ) ; // setup request object
// check request approval
if ( ! await approveResources ( req . cookies . username , request ) ) {
res . status ( 500 ) . send ( { request : request , error : ` Storage ${ storage } could not fulfill request of size ${ req . body . size } G. ` } ) ;
2023-02-24 00:17:28 +00:00
return ;
2023-02-13 02:11:55 +00:00
}
2023-04-19 02:42:35 +00:00
// action approved, commit to action
2023-02-27 02:29:39 +00:00
let action = JSON . stringify ( { disk : req . body . disk , size : ` + ${ req . body . size } G ` } ) ;
2023-04-19 02:42:35 +00:00
let result = await requestPVE ( ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } /resize ` , "PUT" , req . cookies , action , pveAPIToken ) ;
await handleResponse ( req . body . node , result , res ) ;
2023-02-02 19:34:51 +00:00
} ) ;
2023-02-13 02:11:55 +00:00
app . post ( "/api/disk/move" , async ( req , res ) => {
2023-02-28 23:36:22 +00:00
// check auth
2023-04-19 02:42:35 +00:00
await checkAuth ( req . cookies , res ) ;
// update used resources
let used = await getUsedResources ( req , getResourceMeta ( ) ) ;
setUsedResources ( req . cookies . username , used ) ;
// check disk existence
let diskConfig = await getDiskInfo ( req . body . node , req . body . type , req . body . vmid , req . body . disk ) ; // get target disk
if ( ! diskConfig ) { // exit if disk does not exist
res . status ( 500 ) . send ( { auth : auth , data : { error : ` requested disk ${ req . body . disk } does not exist ` } } ) ;
2023-02-24 00:17:28 +00:00
return ;
2023-02-13 02:11:55 +00:00
}
2023-04-19 02:42:35 +00:00
let size = parseInt ( diskConfig . size ) ; // get source disk size
let srcStorage = diskConfig . storage ; // get source storage
2023-03-01 00:38:09 +00:00
let dstStorage = req . body . storage ; // get destination storage
let request = { } ;
let release = { } ;
if ( req . body . delete ) { // if delete is true, increase resource used by the source storage
2023-03-24 19:35:16 +00:00
release [ srcStorage ] = Number ( size ) ;
2023-03-01 00:38:09 +00:00
}
2023-03-24 19:35:16 +00:00
request [ dstStorage ] = Number ( size ) ; // always decrease destination storage by size
2023-04-19 02:42:35 +00:00
// check request approval
if ( ! await approveResources ( req . cookies . username , request ) ) {
res . status ( 500 ) . send ( { request : request , error : ` Storage ${ storage } could not fulfill request of size ${ req . body . size } G. ` } ) ;
2023-03-01 00:38:09 +00:00
return ;
}
2023-02-27 02:29:39 +00:00
let action = { storage : req . body . storage , delete : req . body . delete } ;
if ( req . body . type === "qemu" ) {
action . disk = req . body . disk
}
else {
action . volume = req . body . disk
}
action = JSON . stringify ( action ) ;
2023-04-19 02:42:35 +00:00
let route = req . body . type === "qemu" ? "move_disk" : "move_volume" ;
let result = await requestPVE ( ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } / ${ route } ` , "POST" , req . cookies , action , pveAPIToken ) ;
await handleResponse ( req . body . node , result , res ) ;
2023-02-10 21:41:36 +00:00
} ) ;
2023-02-13 02:11:55 +00:00
app . post ( "/api/disk/delete" , async ( req , res ) => {
2023-02-28 23:36:22 +00:00
// check auth
2023-04-19 02:42:35 +00:00
await checkAuth ( req . cookies , res ) ;
// update used resources
let used = await getUsedResources ( req , getResourceMeta ( ) ) ;
setUsedResources ( req . cookies . username , used ) ;
// only ide or unused are allowed to be deleted
2023-03-01 00:38:09 +00:00
if ( ! req . body . disk . includes ( "unused" ) && ! req . body . disk . includes ( "ide" ) ) { // must be ide or unused
res . status ( 500 ) . send ( { auth : auth , data : { error : ` Requested disk ${ req . body . disk } must be unused or ide. Use /disk/detach to detach disks in use. ` } } ) ;
return ;
2023-04-19 02:42:35 +00:00
}
2023-02-27 02:29:39 +00:00
let action = JSON . stringify ( { delete : req . body . disk } ) ;
2023-02-24 00:17:28 +00:00
let method = req . body . type === "qemu" ? "POST" : "PUT" ;
2023-04-19 02:42:35 +00:00
let result = await requestPVE ( ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } /config ` , method , req . cookies , action , pveAPIToken ) ;
await handleResponse ( req . body . node , result , res ) ;
2023-02-17 22:17:00 +00:00
} ) ;
2023-02-23 20:39:33 +00:00
app . post ( "/api/disk/create" , async ( req , res ) => {
2023-02-28 23:36:22 +00:00
// check auth
2023-04-19 02:42:35 +00:00
await checkAuth ( req . cookies , res ) ;
// update used resources
let used = await getUsedResources ( req , getResourceMeta ( ) ) ;
setUsedResources ( req . cookies . username , used ) ;
// check resource approval
2023-03-01 00:38:09 +00:00
let request = { } ;
if ( ! req . body . disk . includes ( "ide" ) ) {
2023-04-19 02:42:35 +00:00
request [ req . body . storage ] = Number ( req . body . size * 1024 * * 3 ) ; // setup request object
// check request approval
if ( ! await approveResources ( req . cookies . username , request ) ) {
res . status ( 500 ) . send ( { request : request , error : ` Storage ${ storage } could not fulfill request of size ${ req . body . size } G. ` } ) ;
2023-03-01 00:38:09 +00:00
return ;
}
}
2023-02-27 02:29:39 +00:00
let action = { } ;
if ( req . body . disk . includes ( "ide" ) && req . body . iso ) {
action [ req . body . disk ] = ` ${ req . body . iso } ,media=cdrom ` ;
}
else if ( req . body . type === "qemu" ) { // type is qemu, use sata
action [ req . body . disk ] = ` ${ req . body . storage } : ${ req . body . size } ` ;
}
else { // type is lxc, use mp and add mp and backup values
action [ req . body . disk ] = ` ${ req . body . storage } : ${ req . body . size } ,mp=/mp ${ req . body . device } /,backup=1 ` ;
}
action = JSON . stringify ( action ) ;
2023-02-24 00:17:28 +00:00
let method = req . body . type === "qemu" ? "POST" : "PUT" ;
2023-04-19 02:42:35 +00:00
let result = await requestPVE ( ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } /config ` , method , req . cookies , action , pveAPIToken ) ;
await handleResponse ( req . body . node , result , res ) ;
2023-02-23 20:39:33 +00:00
} ) ;
2023-02-17 22:17:00 +00:00
app . post ( "/api/resources" , async ( req , res ) => {
2023-02-28 23:36:22 +00:00
// check auth
2023-04-19 02:42:35 +00:00
await checkAuth ( req . cookies , res ) ;
// update used resources
let used = await getUsedResources ( req , getResourceMeta ( ) ) ;
setUsedResources ( req . cookies . username , used ) ;
// get current config
let currentConfig = await requestPVE ( ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } /config ` , "GET" , null , null , pveAPIToken ) ;
2023-03-24 19:35:16 +00:00
let request = {
2023-03-24 22:10:17 +00:00
cores : Number ( req . body . cores ) - Number ( currentConfig . data . data . cores ) ,
memory : Number ( req . body . memory ) - Number ( currentConfig . data . data . memory )
2023-03-24 19:35:16 +00:00
} ;
2023-04-19 02:42:35 +00:00
// check resource approval
if ( ! await approveResources ( req . cookies . username , request ) ) {
res . status ( 500 ) . send ( { request : request , error : ` Could not fulfil request ` } ) ;
2023-03-01 00:38:09 +00:00
return ;
2023-03-01 00:50:24 +00:00
}
2023-04-19 02:42:35 +00:00
// commit action
2023-02-28 23:36:22 +00:00
let action = JSON . stringify ( { cores : req . body . cores , memory : req . body . memory } ) ;
2023-02-24 00:17:28 +00:00
let method = req . body . type === "qemu" ? "POST" : "PUT" ;
2023-04-19 02:42:35 +00:00
let result = await requestPVE ( ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } /config ` , method , req . cookies , action , pveAPIToken ) ;
await handleResponse ( req . body . node , result , res ) ;
2023-02-21 22:35:09 +00:00
} ) ;
2023-02-24 00:17:28 +00:00
app . post ( "/api/instance" , async ( req , res ) => {
2023-02-28 23:36:22 +00:00
// check auth
2023-04-19 02:42:35 +00:00
await checkAuth ( req . cookies , res ) ;
// update used resources
let used = await getUsedResources ( req , getResourceMeta ( ) ) ;
setUsedResources ( req . cookies . username , used ) ;
2023-03-01 00:50:24 +00:00
// setup request
2023-03-24 19:35:16 +00:00
let request = {
cores : Number ( req . body . cores ) ,
memory : Number ( req . body . memory )
} ;
2023-02-28 23:36:22 +00:00
// setup action
2023-02-28 02:38:30 +00:00
let user = await requestPVE ( ` /access/users/ ${ req . cookies . username } ` , "GET" , null , null , pveAPIToken ) ;
let group = user . data . data . groups [ 0 ] ;
if ( ! group ) {
2023-02-28 23:27:32 +00:00
res . status ( 500 ) . send ( { auth : auth , data : { error : ` user ${ req . cookies . username } has no group membership ` } } ) ;
2023-02-28 23:36:22 +00:00
}
2023-02-27 23:04:38 +00:00
let action = {
vmid : req . body . vmid ,
cores : req . body . cores ,
memory : req . body . memory ,
2023-02-28 02:38:30 +00:00
pool : group
2023-02-27 23:04:38 +00:00
} ;
if ( req . body . type === "lxc" ) {
action . swap = req . body . swap ;
action . hostname = req . body . name ;
action . unprivileged = 1 ;
action . features = "nesting=1" ;
action . password = req . body . password ;
action . ostemplate = req . body . ostemplate ;
action . rootfs = ` ${ req . body . rootfslocation } : ${ req . body . rootfssize } ` ;
2023-03-01 00:50:24 +00:00
request [ req . body . rootfslocation ] = req . body . rootfssize ;
2023-02-27 23:04:38 +00:00
}
else {
action . name = req . body . name ;
}
2023-03-01 00:50:24 +00:00
// check resource approval
2023-04-19 02:42:35 +00:00
if ( ! approveResources ( req . cookies . username , request ) ) { // check resource approval
2023-03-01 00:50:24 +00:00
res . status ( 500 ) . send ( { auth : auth , data : { request : request , error : ` Not enough resources to satisfy request. ` } } ) ;
return ;
}
2023-04-19 02:42:35 +00:00
// commit action
2023-02-27 23:04:38 +00:00
action = JSON . stringify ( action ) ;
let result = await requestPVE ( ` /nodes/ ${ req . body . node } / ${ req . body . type } ` , "POST" , req . cookies , action , pveAPIToken ) ;
2023-04-19 02:42:35 +00:00
await handleResponse ( req . body . node , result , res ) ;
2023-02-24 00:17:28 +00:00
} ) ;
2023-02-21 22:35:09 +00:00
app . delete ( "/api/instance" , async ( req , res ) => {
2023-04-19 02:42:35 +00:00
// check auth
await checkAuth ( req . cookies , res ) ;
// commit action
let result = await requestPVE ( ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } ` , "DELETE" , req . cookies , null , pveAPIToken ) ;
await handleResponse ( req . body . node , result , res ) ;
} ) ;
2023-02-13 02:11:55 +00:00
2023-01-25 00:28:44 +00:00
app . listen ( listenPort , ( ) => {
2023-02-27 01:09:49 +00:00
init ( ) ;
2023-02-13 02:11:55 +00:00
console . log ( ` proxmoxaas-api v ${ api . version } listening on port ${ listenPort } ` ) ;
2023-01-17 23:46:42 +00:00
} ) ;