2023-04-26 01:18:05 +00:00
import express from "express" ;
import bodyParser from "body-parser" ;
import cookieParser from "cookie-parser" ;
import cors from "cors" ;
import morgan from "morgan" ;
2023-04-26 01:40:12 +00:00
import api from "./package.json" assert { type : "json" } ;
2023-04-26 01:18:05 +00:00
2023-05-13 07:34:58 +00:00
import { pveAPIToken , listenPort , hostname , domain } from "./vars.js" ;
2023-05-23 00:11:48 +00:00
import { requestPVE , handleResponse , getDiskInfo } from "./pve.js" ;
2023-05-24 22:21:00 +00:00
import { checkAuth , approveResources , getUserResources } from "./utils.js" ;
import { db } from "./db.js" ;
2023-01-17 23:46:42 +00:00
const app = express ( ) ;
2023-05-16 15:46:39 +00:00
app . use ( bodyParser . urlencoded ( { extended : true } ) ) ;
2023-01-19 20:22:26 +00:00
app . use ( cookieParser ( ) )
2023-05-16 15:46:39 +00:00
app . use ( cors ( { origin : hostname } ) ) ;
2023-01-19 20:22:26 +00:00
app . use ( morgan ( "combined" ) ) ;
2023-01-17 23:46:42 +00:00
2023-05-18 20:05:41 +00:00
/ * *
* GET - get API version
* responses :
* - 200 : { version : String }
* /
2023-01-19 20:22:26 +00:00
app . get ( "/api/version" , ( req , res ) => {
2023-05-16 15:46:39 +00:00
res . status ( 200 ) . send ( { version : api . version } ) ;
2023-01-17 23:46:42 +00:00
} ) ;
2023-05-18 20:05:41 +00:00
/ * *
* GET - echo request
* responses :
* - 200 : { body : request . body , cookies : request . cookies }
* /
2023-01-19 20:22:26 +00:00
app . get ( "/api/echo" , ( req , res ) => {
2023-05-16 15:46:39 +00:00
res . status ( 200 ) . send ( { body : req . body , cookies : req . cookies } ) ;
2023-01-19 20:22:26 +00:00
} ) ;
2023-05-18 20:05:41 +00:00
/ * *
* GET - check authentication
* responses :
* - 200 : { auth : true , path : String }
* - 401 : { auth : false , path : String }
* /
2023-02-13 02:11:55 +00:00
app . get ( "/api/auth" , async ( req , res ) => {
2023-05-18 08:09:18 +00:00
let auth = await checkAuth ( req . cookies , res ) ;
2023-05-16 15:46:39 +00:00
if ( ! auth ) { return ; }
res . status ( 200 ) . send ( { auth : true } ) ;
2023-01-20 06:40:21 +00:00
} ) ;
2023-05-18 20:05:41 +00:00
/ * *
* GET - proxy proxmox api without privilege elevation
* request and responses passed through to / from proxmox
* /
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-04-26 01:18:05 +00:00
let path = req . url . replace ( "/api/proxmox" , "" ) ;
2023-02-24 22:19:30 +00:00
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-05-18 20:05:41 +00:00
/ * *
* POST - proxy proxmox api without privilege elevation
* request and responses passed through to / from proxmox
* /
2023-02-24 22:21:21 +00:00
app . post ( "/api/proxmox/*" , async ( req , res ) => { // proxy endpoint for POST proxmox api with no token
2023-04-26 01:18:05 +00:00
let 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-05-18 20:05:41 +00:00
/ * *
* POST - safer ticket generation using proxmox authentication but adding HttpOnly
* request :
* - username : String
* - password : String
* responses :
* - 200 : { auth : true , path : String }
* - 401 : { auth : false , path : String }
* /
2023-05-13 07:34:58 +00:00
app . post ( "/api/ticket" , async ( req , res ) => {
let response = await requestPVE ( "/access/ticket" , "POST" , null , JSON . stringify ( req . body ) ) ;
2023-05-15 22:11:24 +00:00
if ( ! ( response . status === 200 ) ) {
2023-05-16 15:46:39 +00:00
res . status ( response . status ) . send ( { auth : false } ) ;
2023-05-15 22:00:46 +00:00
res . end ( ) ;
return ;
}
2023-05-13 07:34:58 +00:00
let ticket = response . data . data . ticket ;
let csrftoken = response . data . data . CSRFPreventionToken ;
let username = response . data . data . username ;
2023-05-16 15:46:39 +00:00
let expire = new Date ( Date . now ( ) + ( 2 * 60 * 60 * 1000 ) ) ;
res . cookie ( "PVEAuthCookie" , ticket , { domain : domain , path : "/" , httpOnly : true , secure : true , expires : expire } ) ;
res . cookie ( "CSRFPreventionToken" , csrftoken , { domain : domain , path : "/" , httpOnly : true , secure : true , expires : expire } ) ;
res . cookie ( "username" , username , { domain : domain , path : "/" , secure : true , expires : expire } ) ;
res . cookie ( "auth" , 1 , { domain : domain , path : "/" , secure : true , expires : expire } ) ;
res . status ( 200 ) . send ( { auth : true } ) ;
2023-05-13 07:34:58 +00:00
} ) ;
2023-05-18 20:05:41 +00:00
/ * *
* DELETE - request to destroy ticket
* responses :
* - 200 : { auth : false , path : String }
* /
2023-05-13 07:34:58 +00:00
app . delete ( "/api/ticket" , async ( req , res ) => {
let expire = new Date ( 0 ) ;
2023-05-16 15:46:39 +00:00
res . cookie ( "PVEAuthCookie" , "" , { domain : domain , path : "/" , httpOnly : true , secure : true , expires : expire } ) ;
res . cookie ( "CSRFPreventionToken" , "" , { domain : domain , path : "/" , httpOnly : true , secure : true , expires : expire } ) ;
res . cookie ( "username" , "" , { domain : domain , path : "/" , httpOnly : true , secure : true , expires : expire } ) ;
res . cookie ( "auth" , 0 , { domain : domain , path : "/" , expires : expire } ) ;
res . status ( 200 ) . send ( { auth : false } ) ;
2023-05-13 07:34:58 +00:00
} ) ;
2023-05-18 20:05:41 +00:00
/ * *
* GET - get db user resource information including allocated , free , and maximum resource values along with resource metadata
* responses :
* - 200 : { avail : Object , max : Object , units : Object , used : Object }
* - 401 : { auth : false , path : String }
* /
2023-05-12 20:51:07 +00:00
app . get ( "/api/user/resources" , async ( req , res ) => {
2023-04-19 02:42:35 +00:00
// check auth
2023-05-16 15:46:39 +00:00
let auth = await checkAuth ( req . cookies , res ) ;
if ( ! auth ) { return ; }
2023-05-24 22:21:00 +00:00
let resources = await getUserResources ( req , req . cookies . username ) ;
2023-05-12 20:51:07 +00:00
res . status ( 200 ) . send ( resources ) ;
2023-04-21 22:37:12 +00:00
} ) ;
2023-05-18 20:05:41 +00:00
/ * *
* GET - get db user instance configuration
* responses :
* - 200 : { pool : String , templates : { lxc : Object , qemu : Object } , vmid : { min : Number , max : Number } }
* - 401 : { auth : false , path : String }
* /
2023-05-12 20:51:07 +00:00
app . get ( "/api/user/instances" , async ( req , res ) => {
// check auth
2023-05-16 15:46:39 +00:00
let auth = await checkAuth ( req . cookies , res ) ;
if ( ! auth ) { return ; }
2023-05-24 22:21:00 +00:00
let config = db . getUserConfig ( req . cookies . username ) ;
2023-05-12 20:51:07 +00:00
res . status ( 200 ) . send ( config . instances )
} ) ;
2023-05-18 20:05:41 +00:00
/ * *
* GET - get db user node configuration
* responses :
* - 200 : { nodes : String [ ] }
* - 401 : { auth : false , path : String }
* /
2023-05-12 20:51:07 +00:00
app . get ( "/api/user/nodes" , async ( req , res ) => {
// check auth
2023-05-16 15:46:39 +00:00
let auth = await checkAuth ( req . cookies , res ) ;
if ( ! auth ) { return ; }
2023-05-24 22:21:00 +00:00
let config = db . getUserConfig ( req . cookies . username ) ;
2023-05-18 20:05:41 +00:00
res . status ( 200 ) . send ( { nodes : config . nodes } )
2023-05-12 20:51:07 +00:00
} )
2023-05-18 20:05:41 +00:00
/ * *
* POST - detach mounted disk from instance
* request :
* - node : String - vm host node id
* - type : String - vm type ( lxc , qemu )
* - vmid : Number - vm id number
* - disk : String - disk id ( sata0 , NOT unused )
* responses :
2023-06-08 23:33:32 +00:00
* - 200 : PVE Task Object
2023-05-18 20:05:41 +00:00
* - 401 : { auth : false , path : String }
* - 500 : { error : String }
2023-06-08 23:33:32 +00:00
* - 500 : PVE Task Object
2023-05-18 20:05:41 +00:00
* /
2023-04-24 21:28:58 +00:00
app . post ( "/api/instance/disk/detach" , async ( req , res ) => {
2023-05-17 21:30:36 +00:00
// check auth for specific instance
let vmpath = ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } ` ;
let auth = await checkAuth ( req . cookies , res , vmpath ) ;
2023-05-16 15:46:39 +00:00
if ( ! auth ) { return ; }
2023-06-08 23:33:32 +00:00
// get current config
let config = ( await requestPVE ( ` ${ vmpath } /config ` , "GET" , req . cookies , null , null ) ) . data . data ;
// disk must exist
if ( ! config [ req . body . disk ] ) {
res . status ( 500 ) . send ( { error : ` Disk ${ req . body . disk } does not exist. ` } ) ;
res . end ( ) ;
return ;
}
// disk cannot be unused
2023-03-01 00:38:09 +00:00
if ( req . body . disk . includes ( "unused" ) ) {
2023-05-16 15:46:39 +00:00
res . status ( 500 ) . send ( { error : ` Requested disk ${ req . body . disk } cannot be unused. Use /disk/delete to permanently delete unused disks. ` } ) ;
2023-04-21 22:37:12 +00:00
res . end ( ) ;
2023-03-01 00:38:09 +00:00
return ;
}
2023-05-16 15:46: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-05-17 21:30:36 +00:00
let result = await requestPVE ( ` ${ vmpath } /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-05-18 20:05:41 +00:00
/ * *
* POST - attach unused disk image to instance
* request :
* - node : String - vm host node id
* - type : String - vm type ( lxc , qemu )
* - vmid : Number - vm id number
* - disk : String - disk id ( sata0 )
* - source : Number - source unused disk number ( 0 => unused0 )
* responses :
2023-06-08 23:33:32 +00:00
* - 200 : PVE Task Object
2023-05-18 20:05:41 +00:00
* - 401 : { auth : false , path : String }
* - 500 : { error : String }
2023-06-08 23:33:32 +00:00
* - 500 : PVE Task Object
2023-05-18 20:05:41 +00:00
* /
2023-04-24 21:28:58 +00:00
app . post ( "/api/instance/disk/attach" , async ( req , res ) => {
2023-05-17 21:30:36 +00:00
// check auth for specific instance
let vmpath = ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } ` ;
let auth = await checkAuth ( req . cookies , res , vmpath ) ;
2023-05-16 15:46:39 +00:00
if ( ! auth ) { return ; }
2023-06-08 23:33:32 +00:00
// get current config
let config = ( await requestPVE ( ` ${ vmpath } /config ` , "GET" , req . cookies , null , null ) ) . data . data ;
// disk must exist
if ( ! config [ ` unused ${ req . body . source } ` ] ) {
res . status ( 403 ) . send ( { error : ` Requested disk unused ${ req . body . source } does not exist. ` } ) ;
2023-05-18 08:09:18 +00:00
res . end ( ) ;
return ;
}
2023-06-09 00:24:37 +00:00
// target disk must be allowed according to source disk's storage options
let diskConfig = await getDiskInfo ( req . body . node , req . body . type , req . body . vmid , ` unused ${ req . body . source } ` ) ; // get target disk
let resourceConfig = db . getResourceConfig ( ) ;
if ( ! resourceConfig [ diskConfig . storage ] . disks . some ( diskPrefix => req . body . disk . startsWith ( diskPrefix ) ) ) {
res . status ( 500 ) . send ( { error : ` Requested target ${ req . body . disk } is not in allowed list [ ${ resourceConfig [ diskConfig . storage ] . disks } ]. ` } ) ;
res . end ( ) ;
return ;
}
2023-05-18 08:09:18 +00:00
// setup action using source disk info from vm config
2023-02-27 02:29:39 +00:00
let action = { } ;
2023-06-09 00:24:37 +00:00
action [ req . body . disk ] = config [ ` unused ${ req . body . source } ` ] ;
2023-02-27 02:29:39 +00:00
action = JSON . stringify ( action ) ;
2023-02-24 00:17:28 +00:00
let method = req . body . type === "qemu" ? "POST" : "PUT" ;
2023-05-18 08:09:18 +00:00
// commit action
2023-05-17 21:30:36 +00:00
let result = await requestPVE ( ` ${ vmpath } /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-05-18 20:05:41 +00:00
/ * *
* POST - increase size of mounted disk
* request :
* - node : String - vm host node id
* - type : String - vm type ( lxc , qemu )
* - vmid : Number - vm id number
* - disk : String - disk id ( sata0 )
* - size : Number - increase size in GiB
* responses :
2023-06-08 23:33:32 +00:00
* - 200 : PVE Task Object
2023-05-18 20:05:41 +00:00
* - 401 : { auth : false , path : String }
* - 500 : { error : String }
* - 500 : { request : Object , error : String }
2023-06-08 23:33:32 +00:00
* - 500 : PVE Task Object
2023-05-18 20:05:41 +00:00
* /
2023-04-24 21:28:58 +00:00
app . post ( "/api/instance/disk/resize" , async ( req , res ) => {
2023-05-17 21:30:36 +00:00
// check auth for specific instance
let vmpath = ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } ` ;
let auth = await checkAuth ( req . cookies , res , vmpath ) ;
2023-05-16 15:46:39 +00:00
if ( ! auth ) { return ; }
2023-04-19 02:42:35 +00:00
// 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-06-01 15:35:20 +00:00
res . status ( 500 ) . send ( { error : ` requested disk ${ req . body . disk } does not exist. ` } ) ;
2023-04-21 22:37:12 +00:00
res . end ( ) ;
2023-03-01 00:38:09 +00:00
return ;
2023-04-19 20:36:48 +00:00
}
// setup request
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
2023-04-24 21:45:32 +00:00
if ( ! await approveResources ( req , req . cookies . username , request ) ) {
2023-05-16 15:46:39 +00:00
res . status ( 500 ) . send ( { request : request , error : ` Storage ${ storage } could not fulfill request of size ${ req . body . size } G. ` } ) ;
2023-04-21 22:37:12 +00:00
res . end ( ) ;
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-05-16 15:46:39 +00:00
let action = JSON . stringify ( { disk : req . body . disk , size : ` + ${ req . body . size } G ` } ) ;
2023-05-17 21:30:36 +00:00
let result = await requestPVE ( ` ${ vmpath } /resize ` , "PUT" , req . cookies , action , pveAPIToken ) ;
2023-04-19 02:42:35 +00:00
await handleResponse ( req . body . node , result , res ) ;
2023-02-02 19:34:51 +00:00
} ) ;
2023-05-18 20:05:41 +00:00
/ * *
* POST - move mounted disk from one storage to another
* request :
* - node : String - vm host node id
* - type : String - vm type ( lxc , qemu )
* - vmid : Number - vm id number
* - disk : String - disk id ( sata0 )
* - storage : String - target storage to move disk
* - delete : Number - delete original disk ( 0 , 1 )
* responses :
2023-06-08 23:33:32 +00:00
* - 200 : PVE Task Object
2023-05-18 20:05:41 +00:00
* - 401 : { auth : false , path : String }
* - 500 : { error : String }
* - 500 : { request : Object , error : String }
2023-06-08 23:33:32 +00:00
* - 500 : PVE Task Object
2023-05-18 20:05:41 +00:00
* /
2023-04-24 21:28:58 +00:00
app . post ( "/api/instance/disk/move" , async ( req , res ) => {
2023-05-17 21:30:36 +00:00
// check auth for specific instance
let vmpath = ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } ` ;
let auth = await checkAuth ( req . cookies , res , vmpath ) ;
2023-05-16 15:46:39 +00:00
if ( ! auth ) { return ; }
2023-04-19 02:42:35 +00:00
// 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
2023-06-01 15:35:20 +00:00
res . status ( 500 ) . send ( { error : ` requested disk ${ req . body . disk } does not exist. ` } ) ;
2023-04-21 22:37:12 +00:00
res . end ( ) ;
2023-02-24 00:17:28 +00:00
return ;
2023-02-13 02:11:55 +00:00
}
2023-04-19 20:36:48 +00:00
// setup request
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
2023-05-16 15:46:39 +00:00
if ( ! await approveResources ( req , req . cookies . username , request ) ) {
res . status ( 500 ) . send ( { request : request , error : ` Storage ${ req . body . storage } could not fulfill request of size ${ req . body . size } G. ` } ) ;
2023-04-21 22:37:12 +00:00
res . end ( ) ;
2023-03-01 00:38:09 +00:00
return ;
}
2023-05-18 08:09:18 +00:00
// create action
2023-05-16 15:46:39 +00:00
let action = { storage : req . body . storage , delete : req . body . delete } ;
2023-02-27 02:29:39 +00:00
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" ;
2023-05-18 08:09:18 +00:00
// commit action
2023-05-17 21:30:36 +00:00
let result = await requestPVE ( ` ${ vmpath } / ${ route } ` , "POST" , req . cookies , action , pveAPIToken ) ;
2023-04-19 02:42:35 +00:00
await handleResponse ( req . body . node , result , res ) ;
2023-02-10 21:41:36 +00:00
} ) ;
2023-05-18 20:05:41 +00:00
/ * *
2023-06-08 23:33:32 +00:00
* DELETE - delete unused disk permanently
2023-05-18 20:05:41 +00:00
* request :
* - node : String - vm host node id
* - type : String - vm type ( lxc , qemu )
* - vmid : Number - vm id number
2023-06-08 23:33:32 +00:00
* - disk : String - disk id ( unused0 or ide0 )
2023-05-18 20:05:41 +00:00
* responses :
2023-06-08 23:33:32 +00:00
* - 200 : PVE Task Object
2023-05-18 20:05:41 +00:00
* - 401 : { auth : false , path : String }
* - 500 : { error : String }
2023-06-08 23:33:32 +00:00
* - 500 : PVE Task Object
2023-05-18 20:05:41 +00:00
* /
2023-06-08 23:33:32 +00:00
app . delete ( "/api/instance/disk/delete" , async ( req , res ) => {
2023-05-17 21:30:36 +00:00
// check auth for specific instance
let vmpath = ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } ` ;
let auth = await checkAuth ( req . cookies , res , vmpath ) ;
2023-05-16 15:46:39 +00:00
if ( ! auth ) { return ; }
2023-06-08 23:33:32 +00:00
// get current config
let config = ( await requestPVE ( ` ${ vmpath } /config ` , "GET" , req . cookies , null , null ) ) . data . data ;
// disk must exist
if ( ! config [ req . body . disk ] ) {
res . status ( 403 ) . send ( { error : ` Requested disk unused ${ req . body . source } does not exist. ` } ) ;
res . end ( ) ;
return ;
}
2023-04-19 02:42:35 +00:00
// 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
2023-05-16 15:46:39 +00:00
res . status ( 500 ) . send ( { error : ` Requested disk ${ req . body . disk } must be unused or ide. Use /disk/detach to detach disks in use. ` } ) ;
2023-04-21 22:37:12 +00:00
res . end ( ) ;
2023-03-01 00:38:09 +00:00
return ;
2023-05-16 15:46:39 +00:00
}
2023-05-18 08:09:18 +00:00
// create action
2023-05-16 15:46: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 20:36:48 +00:00
// commit action
2023-05-17 21:30:36 +00:00
let result = await requestPVE ( ` ${ vmpath } /config ` , method , req . cookies , action , pveAPIToken ) ;
2023-04-19 02:42:35 +00:00
await handleResponse ( req . body . node , result , res ) ;
2023-02-17 22:17:00 +00:00
} ) ;
2023-02-23 20:39:33 +00:00
2023-05-18 20:05:41 +00:00
/ * *
* POST - create a new disk in storage of specified size
* request :
* - node : String - vm host node id
* - type : String - vm type ( lxc , qemu )
* - vmid : Number - vm id number
* - disk : String - disk id ( sata0 , ide0 )
* - storage : String - storage to hold disk
2023-06-09 00:24:37 +00:00
* - size : Number - size of disk in GiB
2023-05-18 20:05:41 +00:00
* responses :
2023-06-08 23:33:32 +00:00
* - 200 : PVE Task Object
2023-05-18 20:05:41 +00:00
* - 401 : { auth : false , path : String }
* - 500 : { request : Object , error : String }
2023-06-08 23:33:32 +00:00
* - 500 : PVE Task Object
2023-05-18 20:05:41 +00:00
* /
2023-04-24 21:28:58 +00:00
app . post ( "/api/instance/disk/create" , async ( req , res ) => {
2023-05-17 21:30:36 +00:00
// check auth for specific instance
let vmpath = ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } ` ;
let auth = await checkAuth ( req . cookies , res , vmpath ) ;
2023-05-16 15:46:39 +00:00
if ( ! auth ) { return ; }
2023-06-08 23:33:32 +00:00
// get current config
let config = ( await requestPVE ( ` ${ vmpath } /config ` , "GET" , req . cookies , null , null ) ) . data . data ;
// disk must not exist
if ( config [ req . body . disk ] ) {
res . status ( 403 ) . send ( { error : ` Requested disk ${ req . body . disk } already exists. ` } ) ;
res . end ( ) ;
return ;
}
2023-04-19 20:36:48 +00:00
// setup request
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
2023-04-24 21:45:32 +00:00
if ( ! await approveResources ( req , req . cookies . username , request ) ) {
2023-05-16 15:46:39 +00:00
res . status ( 500 ) . send ( { request : request , error : ` Storage ${ req . body . storage } could not fulfill request of size ${ req . body . size } G. ` } ) ;
2023-04-21 22:37:12 +00:00
res . end ( ) ;
2023-03-01 00:38:09 +00:00
return ;
}
}
2023-06-09 00:24:37 +00:00
// target disk must be allowed according to storage options
let resourceConfig = db . getResourceConfig ( ) ;
if ( ! resourceConfig [ req . body . storage ] . disks . some ( diskPrefix => req . body . disk . startsWith ( diskPrefix ) ) ) {
res . status ( 500 ) . send ( { error : ` Requested target ${ req . body . disk } is not in allowed list [ ${ resourceConfig [ req . body . storage ] . disks } ]. ` } ) ;
res . end ( ) ;
return ;
}
2023-05-18 08:09:18 +00:00
// setup action
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
2023-04-24 21:45:32 +00:00
action [ req . body . disk ] = ` ${ req . body . storage } : ${ req . body . size } ,mp=/ ${ req . body . disk } /,backup=1 ` ;
2023-02-27 02:29:39 +00:00
}
action = JSON . stringify ( action ) ;
2023-02-24 00:17:28 +00:00
let method = req . body . type === "qemu" ? "POST" : "PUT" ;
2023-04-19 20:36:48 +00:00
// commit action
2023-05-17 21:30:36 +00:00
let result = await requestPVE ( ` ${ vmpath } /config ` , method , req . cookies , action , pveAPIToken ) ;
2023-04-19 02:42:35 +00:00
await handleResponse ( req . body . node , result , res ) ;
2023-02-23 20:39:33 +00:00
} ) ;
2023-02-17 22:17:00 +00:00
2023-06-08 23:33:32 +00:00
/ * *
* POST - create new virtual network interface
* request :
* - node : String - vm host node id
* - type : String - vm type ( lxc , qemu )
* - vmid : Number - vm id number
* - netid : Number - network interface id number ( 0 => net0 )
* - rate : Number - new bandwidth rate for interface in MB / s
* - name : String , optional - required interface name for lxc only
* responses :
* - 200 : PVE Task Object
* - 401 : { auth : false , path : String }
* - 500 : { error : String }
* - 500 : { request : Object , error : String }
* - 500 : PVE Task Object
* /
app . post ( "/api/instance/network/create" , async ( req , res ) => {
// check auth for specific instance
let vmpath = ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } ` ;
let auth = await checkAuth ( req . cookies , res , vmpath ) ;
if ( ! auth ) { return ; }
// get current config
let currentConfig = await requestPVE ( ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } /config ` , "GET" , null , null , pveAPIToken ) ;
// net interface must not exist
if ( currentConfig . data . data [ ` net ${ req . body . netid } ` ] ) {
res . status ( 500 ) . send ( { error : ` Network interface net ${ req . body . netid } already exists. Use /api/instance.network/modify to modify existing network interface. ` } ) ;
res . end ( ) ;
return ;
}
if ( req . body . type === "lxc" && ! req . body . name ) {
res . status ( 500 ) . send ( { error : ` Network interface must have name parameter. ` } ) ;
res . end ( ) ;
return ;
}
let request = {
network : Number ( req . body . rate )
} ;
// check resource approval
if ( ! await approveResources ( req , req . cookies . username , request ) ) {
res . status ( 500 ) . send ( { request : request , error : ` Could not fulfil network request of ${ req . body . rate } MB/s. ` } ) ;
res . end ( ) ;
return ;
}
// setup action
let vlan = getUserConfig ( ) . instances . vlan ;
let action = { } ;
if ( type === "lxc" ) {
action [ ` net ${ req . body . netid } ` ] = ` name= ${ req . body . name } ,bridge=vmbr0,ip=dhcp,ip6=dhcp,tag= ${ vlan } ,type=veth,rate= ${ req . body . rate } ` ;
}
else {
action [ ` new ${ req . body . netid } ` ] = ` virtio,bridge=vmbr0,tag= ${ vlan } ,rate= ${ req . body . rate } ` ;
}
action = JSON . stringify ( action ) ;
let method = req . body . type === "qemu" ? "POST" : "PUT" ;
// commit action
let result = await requestPVE ( ` ${ vmpath } /config ` , method , req . cookies , action , pveAPIToken ) ;
await handleResponse ( req . body . node , result , res ) ;
} ) ;
2023-05-18 20:05:41 +00:00
/ * *
* POST - modify virtual network interface
* request :
* - node : String - vm host node id
* - type : String - vm type ( lxc , qemu )
* - vmid : Number - vm id number
* - netid : Number - network interface id number ( 0 => net0 )
* - rate : Number - new bandwidth rate for interface in MB / s
* responses :
2023-06-08 23:33:32 +00:00
* - 200 : PVE Task Object
2023-05-18 20:05:41 +00:00
* - 401 : { auth : false , path : String }
2023-06-08 23:33:32 +00:00
* - 500 : { error : String }
2023-05-18 20:05:41 +00:00
* - 500 : { request : Object , error : String }
2023-06-08 23:33:32 +00:00
* - 500 : PVE Task Object
2023-05-18 20:05:41 +00:00
* /
2023-06-08 23:33:32 +00:00
app . post ( "/api/instance/network/modify" , async ( req , res ) => {
2023-05-17 21:30:36 +00:00
// check auth for specific instance
let vmpath = ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } ` ;
let auth = await checkAuth ( req . cookies , res , vmpath ) ;
2023-05-16 16:34:39 +00:00
if ( ! auth ) { return ; }
// get current config
let currentConfig = await requestPVE ( ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } /config ` , "GET" , null , null , pveAPIToken ) ;
2023-06-08 23:33:32 +00:00
// net interface must already exist
if ( ! currentConfig . data . data [ ` net ${ req . body . netid } ` ] ) {
res . status ( 500 ) . send ( { error : ` Network interface net ${ req . body . netid } does not exist. Use /api/instance/network/create to create a new network interface. ` } ) ;
res . end ( ) ;
return ;
}
2023-05-16 16:34:39 +00:00
let currentNetworkConfig = currentConfig . data . data [ ` net ${ req . body . netid } ` ] ;
let currentNetworkRate = currentNetworkConfig . split ( "rate=" ) [ 1 ] . split ( "," ) [ 0 ] ;
let request = {
network : Number ( req . body . rate ) - Number ( currentNetworkRate )
} ;
// check resource approval
if ( ! await approveResources ( req , req . cookies . username , request ) ) {
2023-06-01 15:35:20 +00:00
res . status ( 500 ) . send ( { request : request , error : ` Could not fulfil network request of ${ req . body . rate } MB/s. ` } ) ;
2023-05-16 16:34:39 +00:00
res . end ( ) ;
return ;
}
2023-05-18 08:09:18 +00:00
// setup action
2023-05-16 16:34:39 +00:00
let action = { } ;
action [ ` net ${ req . body . netid } ` ] = currentNetworkConfig . replace ( ` rate= ${ currentNetworkRate } ` , ` rate= ${ req . body . rate } ` ) ;
action = JSON . stringify ( action ) ;
let method = req . body . type === "qemu" ? "POST" : "PUT" ;
2023-05-18 08:09:18 +00:00
// commit action
2023-05-17 21:30:36 +00:00
let result = await requestPVE ( ` ${ vmpath } /config ` , method , req . cookies , action , pveAPIToken ) ;
2023-05-16 16:34:39 +00:00
await handleResponse ( req . body . node , result , res ) ;
} ) ;
2023-06-08 23:33:32 +00:00
/ * *
* DELETE - delete virtual network interface
* request :
* - node : String - vm host node id
* - type : String - vm type ( lxc , qemu )
* - vmid : Number - vm id number
* - netid : Number - network interface id number ( 0 => net0 )
* responses :
* - 200 : PVE Task Object
* - 401 : { auth : false , path : String }
* - 500 : { error : String }
* - 500 : PVE Task Object
* /
app . delete ( "/api/instance/network/delete" , async ( req , res ) => {
// check auth for specific instance
let vmpath = ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } ` ;
let auth = await checkAuth ( req . cookies , res , vmpath ) ;
if ( ! auth ) { return ; }
// get current config
let currentConfig = await requestPVE ( ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } /config ` , "GET" , null , null , pveAPIToken ) ;
// net interface must already exist
if ( ! currentConfig . data . data [ ` net ${ req . body . netid } ` ] ) {
res . status ( 500 ) . send ( { error : ` Network interface net ${ req . body . netid } does not exist. ` } ) ;
res . end ( ) ;
return ;
}
// setup action
let action = { delete : ` net ${ req . body . netid } ` } ;
let method = req . body . type === "qemu" ? "POST" : "PUT" ;
// commit action
let result = await requestPVE ( ` ${ vmpath } /config ` , method , req . cookies , action , pveAPIToken ) ;
await handleResponse ( req . body . node , result , res ) ;
} ) ;
2023-06-02 23:14:13 +00:00
/ * *
* GET - get instance pcie device data
* request :
* - node : String - vm host node id
* - type : String - vm type ( lxc , qemu )
* - vmid : Number - vm id number to destroy
* - hostpci : String - hostpci number
* responses :
2023-06-08 23:33:32 +00:00
* - 200 : PVE PCI Device Object
2023-06-02 23:14:13 +00:00
* - 401 : { auth : false , path : String }
* - 500 : { error : String }
* /
app . get ( "/api/instance/pci" , async ( req , res ) => {
// check auth for specific instance
let vmpath = ` /nodes/ ${ req . query . node } / ${ req . query . type } / ${ req . query . vmid } ` ;
let auth = await checkAuth ( req . cookies , res , vmpath ) ;
if ( ! auth ) { return ; }
// check device is in instance config
let config = ( await requestPVE ( ` ${ vmpath } /config ` , "GET" , req . cookies ) ) . data . data ;
if ( ! config [ ` hostpci ${ req . query . hostpci } ` ] ) {
res . status ( 500 ) . send ( { error : ` Could not find hostpci ${ req . query . hostpci } in ${ req . query . vmid } . ` } ) ;
res . end ( ) ;
return ;
}
let device = config [ ` hostpci ${ req . query . hostpci } ` ] . split ( "," ) [ 0 ] ;
console . log ( device )
// get node's pci devices
let result = ( await requestPVE ( ` /nodes/ ${ req . query . node } /hardware/pci ` , "GET" , req . cookies , null , pveAPIToken ) ) . data . data ;
let deviceData = [ ] ;
result . forEach ( ( element ) => {
if ( element . id . startsWith ( device ) ) {
deviceData . push ( element ) ;
}
} ) ;
res . status ( 200 ) . send ( deviceData ) ;
res . end ( ) ;
return ;
} ) ;
2023-05-18 20:05:41 +00:00
/ * *
* POST - set basic resources for vm
* request :
* - node : String - vm host node id
* - type : String - vm type ( lxc , qemu )
* - vmid : Number - vm id number
* - cores : Number - new number of cores for instance
* - memory : Number - new amount of memory for instance
* - swap : Number , optional - new amount of swap for instance
* responses :
2023-06-08 23:33:32 +00:00
* - 200 : PVE Task Object
2023-05-18 20:05:41 +00:00
* - 401 : { auth : false , path : String }
* - 500 : { request : Object , error : String }
2023-06-08 23:33:32 +00:00
* - 500 : PVE Task Object
2023-05-18 20:05:41 +00:00
* /
2023-04-25 15:30:28 +00:00
app . post ( "/api/instance/resources" , async ( req , res ) => {
2023-05-17 21:30:36 +00:00
// check auth for specific instance
let vmpath = ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } ` ;
let auth = await checkAuth ( req . cookies , res , vmpath ) ;
2023-05-16 15:46:39 +00:00
if ( ! auth ) { return ; }
2023-04-19 02:42:35 +00:00
// 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-05-16 15:46:39 +00:00
cores : Number ( req . body . cores ) - Number ( currentConfig . data . data . cores ) ,
2023-03-24 22:10:17 +00:00
memory : Number ( req . body . memory ) - Number ( currentConfig . data . data . memory )
2023-03-24 19:35:16 +00:00
} ;
2023-05-18 20:19:01 +00:00
if ( req . body . type === "lxc" ) {
2023-05-18 20:05:41 +00:00
request . swap = Number ( req . body . swap ) - Number ( currentConfig . data . data . swap ) ;
}
2023-04-19 02:42:35 +00:00
// check resource approval
2023-04-24 21:45:32 +00:00
if ( ! await approveResources ( req , req . cookies . username , request ) ) {
2023-06-01 15:35:20 +00:00
res . status ( 500 ) . send ( { request : request , error : ` Could not fulfil request. ` } ) ;
2023-04-21 22:37:12 +00:00
res . end ( ) ;
2023-03-01 00:38:09 +00:00
return ;
2023-03-01 00:50:24 +00:00
}
2023-05-18 08:09:18 +00:00
// setup action
2023-05-18 20:19:01 +00:00
let action = { cores : req . body . cores , memory : req . body . memory } ;
if ( req . body . type === "lxc" ) {
action . swap = Number ( req . body . swap ) ;
}
action = JSON . stringify ( action ) ;
2023-02-24 00:17:28 +00:00
let method = req . body . type === "qemu" ? "POST" : "PUT" ;
2023-05-18 08:09:18 +00:00
// commit action
2023-05-17 21:30:36 +00:00
let result = await requestPVE ( ` ${ vmpath } /config ` , method , req . cookies , action , pveAPIToken ) ;
2023-04-19 02:42:35 +00:00
await handleResponse ( req . body . node , result , res ) ;
2023-02-21 22:35:09 +00:00
} ) ;
2023-05-18 20:05:41 +00:00
/ * *
* POST - create new instance
* request :
* - node : String - vm host node id
* - type : String - vm type ( lxc , qemu )
* - vmid : Number - vm id number for instance
* - hostname : String , optional - hostname for lxc instance
* - name : String , optional - hostname for qemu instance
* - cores : Number - number of cores for instance
* - memory : Number - amount of memory for instance
* - swap : Number , optional - amount of swap for lxc instance
* - password : String , optional - password for lxc instance
* - ostemplate : String , optional - os template name for lxc instance
* - rootfslocation : String , optional - storage name for lxc instance rootfs
* - rootfssize : Number , optional , - size of lxc instance rootfs
* responses :
2023-06-08 23:33:32 +00:00
* - 200 : PVE Task Object
2023-05-18 20:05:41 +00:00
* - 401 : { auth : false , path : String }
* - 500 : { error : String }
* - 500 : { request : Object , error : String }
2023-06-08 23:33:32 +00:00
* - 500 : PVE Task Object
2023-05-18 20:05:41 +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-05-16 15:46:39 +00:00
let auth = await checkAuth ( req . cookies , res ) ;
if ( ! auth ) { return ; }
2023-05-19 20:50:07 +00:00
// get user db config
2023-05-24 22:21:00 +00:00
let user = await db . getUserConfig ( req . cookies . username ) ;
2023-04-20 21:27:32 +00:00
let vmid = Number . parseInt ( req . body . vmid ) ;
let vmid _min = user . instances . vmid . min ;
let vmid _max = user . instances . vmid . max ;
2023-05-17 21:30:36 +00:00
// check vmid is within allowed range
2023-04-20 21:27:32 +00:00
if ( vmid < vmid _min || vmid > vmid _max ) {
2023-06-01 15:35:20 +00:00
res . status ( 500 ) . send ( { error : ` Requested vmid ${ vmid } is out of allowed range [ ${ vmid _min } , ${ vmid _max } ]. ` } ) ;
2023-04-21 22:37:12 +00:00
res . end ( ) ;
2023-04-20 21:27:32 +00:00
return ;
2023-02-28 23:36:22 +00:00
}
2023-05-17 21:30:36 +00:00
// check node is within allowed list
2023-04-27 15:39:01 +00:00
if ( ! user . nodes . includes ( req . body . node ) ) {
2023-06-01 15:35:20 +00:00
res . status ( 500 ) . send ( { error : ` Requested node ${ req . body . node } is not in allowed nodes [ ${ user . nodes } ]. ` } ) ;
2023-04-27 15:39:01 +00:00
res . end ( ) ;
return ;
}
2023-05-19 20:50:07 +00:00
// setup request
let request = {
cores : Number ( req . body . cores ) ,
memory : Number ( req . body . memory )
} ;
if ( req . body . type === "lxc" ) {
request . swap = req . body . swap ;
request [ req . body . rootfslocation ] = req . body . rootfssize ;
}
for ( let key of Object . keys ( user . instances . templates [ req . body . type ] ) ) {
let item = user . instances . templates [ req . body . type ] [ key ] ;
if ( item . resource ) {
if ( request [ item . resource . name ] ) {
request [ item . resource . name ] += item . resource . amount ;
}
else {
request [ item . resource . name ] = item . resource . amount ;
}
}
}
// check resource approval
if ( ! await approveResources ( req , req . cookies . username , request ) ) { // check resource approval
res . status ( 500 ) . send ( { request : request , error : ` Not enough resources to satisfy request. ` } ) ;
res . end ( ) ;
return ;
}
// setup action by adding non resource values
2023-02-27 23:04:38 +00:00
let action = {
vmid : req . body . vmid ,
2023-05-19 20:50:07 +00:00
cores : Number ( req . body . cores ) ,
memory : Number ( req . body . memory ) ,
2023-04-20 21:27:32 +00:00
pool : user . instances . pool
2023-02-27 23:04:38 +00:00
} ;
2023-04-26 01:18:05 +00:00
for ( let key of Object . keys ( user . instances . templates [ req . body . type ] ) ) {
2023-05-19 20:50:07 +00:00
action [ key ] = user . instances . templates [ req . body . type ] [ key ] . value ;
2023-04-20 21:27:32 +00:00
}
2023-02-27 23:04:38 +00:00
if ( req . body . type === "lxc" ) {
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 } ` ;
}
else {
action . name = req . body . name ;
}
action = JSON . stringify ( action ) ;
2023-05-18 08:09:18 +00:00
// commit action
2023-02-27 23:04:38 +00:00
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
2023-05-18 20:05:41 +00:00
/ * *
* DELETE - destroy existing instance
* request :
* - node : String - vm host node id
* - type : String - vm type ( lxc , qemu )
* - vmid : Number - vm id number to destroy
* responses :
2023-06-08 23:33:32 +00:00
* - 200 : PVE Task Object
2023-05-18 20:05:41 +00:00
* - 401 : { auth : false , path : String }
2023-06-08 23:33:32 +00:00
* - 500 : PVE Task Object
2023-05-18 20:05:41 +00:00
* /
2023-02-21 22:35:09 +00:00
app . delete ( "/api/instance" , async ( req , res ) => {
2023-05-17 21:30:36 +00:00
// check auth for specific instance
let vmpath = ` /nodes/ ${ req . body . node } / ${ req . body . type } / ${ req . body . vmid } ` ;
let auth = await checkAuth ( req . cookies , res , vmpath ) ;
2023-05-16 15:46:39 +00:00
if ( ! auth ) { return ; }
2023-04-19 02:42:35 +00:00
// commit action
2023-05-17 21:30:36 +00:00
let result = await requestPVE ( vmpath , "DELETE" , req . cookies , null , pveAPIToken ) ;
2023-04-19 02:42:35 +00:00
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-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
} ) ;