Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Virtual server folder with keys management #156

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
node-version:
- 16.x
Expand Down
8 changes: 8 additions & 0 deletions config.js.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ module.exports = {
'@mod1',
'@mod2',
],
virtualServer: {
enabled: false, // If virtual servers should be used
fileExtensions: [ // Extra files in root of server folder that should be copied to virtual servers
'.json'
],
folders: [ // Extra folders in root of server folder that should be linked to virtual servers
]
},
admins: [], // add steam IDs here to enable #login without password
auth: { // If both username and password is set, HTTP Basic Auth will be used. You may use an array to specify more than one user.
username: '', // Username for HTTP Basic Auth
Expand Down
63 changes: 56 additions & 7 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ var slugify = require('slugify')

var ArmaServer = require('arma-server')

var virtualServer = require('./virtualServer')

var queryInterval = 5000
var queryTypes = {
arma1: 'arma',
Expand Down Expand Up @@ -132,6 +134,29 @@ Server.prototype.start = function () {
return this
}

var self = this

if (self.config.virtualServer && self.config.virtualServer.enabled) {
virtualServer.create(self.config, self.mods)
.then((serverFolder) => {
self.virtualServerFolder = serverFolder
self.path = serverFolder
self.realStart()
})
.catch((err) => {
console.error('Error creating virtual server folder:', err)
})
} else {
self.path = self.config.path
self.realStart()
}
}

Server.prototype.realStart = function () {
if (this.instance) {
return this
}

var parameters = this.getParameters()
var server = new ArmaServer.Server({
additionalConfigurationOptions: this.getAdditionalConfigurationOptions(),
Expand All @@ -152,7 +177,7 @@ Server.prototype.start = function () {
parameters: parameters,
password: this.password,
passwordAdmin: this.admin_password,
path: this.config.path,
path: this.path,
persistent: this.persistent ? 1 : 0,
platform: this.config.type,
players: this.max_players,
Expand All @@ -171,8 +196,13 @@ Server.prototype.start = function () {
self.instance = null

self.stopHeadlessClients()

self.emit('state')
.then(() => {
if (self.virtualServerFolder) {
virtualServer.remove(self.virtualServerFolder)
self.virtualServerFolder = null
}
self.emit('state')
})
})

this.pid = instance.pid
Expand Down Expand Up @@ -207,7 +237,7 @@ Server.prototype.startHeadlessClients = function () {
mods: self.mods,
parameters: parameters,
password: self.password,
path: self.config.path,
path: self.path,
platform: self.config.type,
port: self.port
})
Expand Down Expand Up @@ -248,10 +278,29 @@ Server.prototype.stop = function (cb) {
}

Server.prototype.stopHeadlessClients = function () {
this.headlessClientInstances.map(function (headlessClientInstance) {
headlessClientInstance.kill()
var self = this
return Promise.all(this.headlessClientInstances.map(function (headlessClientInstance) {
var handled = false
return new Promise(function (resolve, reject) {
headlessClientInstance.on('close', function () {
if (!handled) {
handled = true
resolve()
}
})

setTimeout(function () {
if (!handled) {
handled = true
resolve()
}
}, 5000)

headlessClientInstance.kill()
})
})).then(function () {
self.headlessClientInstances = []
})
this.headlessClientInstances = []
}

Server.prototype.toJSON = function () {
Expand Down
141 changes: 141 additions & 0 deletions lib/virtualServer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
var fs = require('fs')
var fsExtra = require('fs.extra')
var _ = require('lodash')
var glob = require('glob')
var os = require('os')
var path = require('path')

const requiredFileExtensions = [
'.dll',
'.exe',
'.so',
'.txt' // Steam app id
]

const serverFolders = [
'addons',
'aow',
'argo',
'battleye',
'contact',
'csla',
'curator',
'dll',
'dta',
'enoch',
'expansion',
'heli',
'jets',
'kart',
'linux64',
'mark',
'mpmissions',
'orange',
'tacops',
'tank',
'vn',
'ws'
]

function copyKeys (config, serverFolder, mods) {
// Copy needed keys, file symlinks on Windows are sketchy
const keysFolder = path.join(serverFolder, 'keys')
return fs.promises.mkdir(keysFolder, { recursive: true })
.then(() => {
const defaultKeysPath = path.join(config.path, 'keys')
const defaultKeysPromise = fs.promises.readdir(defaultKeysPath)
.then((files) => files.filter((file) => path.extname(file) === '.bikey'))
.then((files) => files.map((file) => path.join(defaultKeysPath, file)))

const modKeysPromise = Promise.all(mods.map(mod => {
return new Promise((resolve, reject) => {
const modPath = path.join(config.path, mod)
glob(`${modPath}/**/*.bikey`, function (err, files) {
if (err) {
return reject(err)
}

return resolve(files)
})
})
})).then((modsFiles) => modsFiles.flat())

return Promise.all([defaultKeysPromise, modKeysPromise].map((promise) => {
return promise.then((keyFiles) => {
return Promise.all(keyFiles.map((keyFile) => {
return fs.promises.copyFile(keyFile, path.join(keysFolder, path.basename(keyFile)))
}))
})
})).catch((err) => {
console.error('Error copying keys:', err)
})
})
}

function copyFiles (config, serverFolder) {
const configFileExtensions = (config.virtualServer && config.virtualServer.fileExtensions) || []
const allowedFileExtensions = _.uniq(requiredFileExtensions.concat(configFileExtensions))

return fs.promises.readdir(config.path)
.then((files) => {
// Copy needed files, file symlinks on Windows are sketchy
const serverFiles = files.filter((file) => allowedFileExtensions.indexOf(path.extname(file)) >= 0 || path.basename(file) === 'arma3server' || path.basename(file) === 'arma3server_x64')
return Promise.all(serverFiles.map((file) => {
return fs.promises.copyFile(path.join(config.path, file), path.join(serverFolder, file))
}))
})
}

function createModFolders (config, serverFolder, mods) {
// Create virtual folders from default Arma and mods
const configFolders = (config.virtualServer && config.virtualServer.folders) || []
const serverMods = config.serverMods || []
const symlinkFolders = _.uniq(serverFolders
.concat(mods)
.concat(configFolders)
.concat(serverMods)
.map(function (folder) {
return folder.split(path.sep)[0]
})
)

return Promise.all(symlinkFolders.map((symlink) => {
return fs.promises.access(path.join(config.path, symlink))
.then(() => {
return fs.promises.symlink(path.join(config.path, symlink), path.join(serverFolder, symlink), 'junction')
.catch((err) => {
console.error('Could create symlink for', symlink, 'due to', err)
})
})
.catch(() => {})
}))
}

module.exports.create = function (config, mods) {
return fs.promises.mkdtemp(path.join(os.tmpdir(), 'arma-server-'))
.then((serverFolder) => {
console.log('Created virtual server folder:', serverFolder)

return Promise.all([
copyKeys(config, serverFolder, mods),
copyFiles(config, serverFolder),
createModFolders(config, serverFolder, mods)
]).then(() => {
return serverFolder
})
})
}

module.exports.remove = function (folder, cb) {
if (folder) {
fsExtra.rmrf(folder, function (err) {
if (err) {
console.log('Error removing virtual server folder', err)
}

if (cb) {
cb(err)
}
})
}
}
Loading
Loading