Skip to content

Commit 6b540fa

Browse files
committed
Virtual server folder with keys management
1 parent 2a1fbcc commit 6b540fa

File tree

3 files changed

+190
-9
lines changed

3 files changed

+190
-9
lines changed

Diff for: config.js.example

+8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ module.exports = {
1313
'@mod1',
1414
'@mod2',
1515
],
16+
virtualServer: [
17+
enabled: false, // If virtual servers should be used
18+
fileExtensions: [ // Extra files in root of server folder that should be copied to virtual servers
19+
'.json'
20+
],
21+
folders: [ // Extra folders in root of server folder that should be linked to virtual servers
22+
]
23+
],
1624
admins: [], // add steam IDs here to enable #login without password
1725
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.
1826
username: '', // Username for HTTP Basic Auth

Diff for: lib/server.js

+53-9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var slugify = require('slugify')
66

77
var ArmaServer = require('arma-server')
88

9+
var virtualServer = require('./virtualServer')
910
var config = require('../config.js')
1011

1112
var queryInterval = 5000
@@ -134,6 +135,27 @@ Server.prototype.start = function () {
134135
return this
135136
}
136137

138+
var self = this
139+
140+
if (config.virtualServer && config.virtualServer.enabled) {
141+
virtualServer.create(config, self.mods)
142+
.then((serverFolder) => {
143+
self.virtualServerFolder = serverFolder
144+
self.realStart(serverFolder)
145+
})
146+
.catch((err) => {
147+
console.error('Error creating virtual server folder:', err)
148+
})
149+
} else {
150+
self.realStart(config.path)
151+
}
152+
}
153+
154+
Server.prototype.realStart = function (path) {
155+
if (this.instance) {
156+
return this
157+
}
158+
137159
var parameters = this.getParameters()
138160
var server = new ArmaServer.Server({
139161
additionalConfigurationOptions: this.getAdditionalConfigurationOptions(),
@@ -154,7 +176,7 @@ Server.prototype.start = function () {
154176
parameters: parameters,
155177
password: this.password,
156178
passwordAdmin: this.admin_password,
157-
path: this.config.path,
179+
path: path,
158180
persistent: this.persistent ? 1 : 0,
159181
platform: this.config.type,
160182
players: this.max_players,
@@ -196,8 +218,13 @@ Server.prototype.start = function () {
196218
self.instance = null
197219

198220
self.stopHeadlessClients()
199-
200-
self.emit('state')
221+
.then(() => {
222+
if (self.virtualServerFolder) {
223+
virtualServer.remove(self.virtualServerFolder)
224+
self.virtualServerFolder = null
225+
}
226+
self.emit('state')
227+
})
201228
})
202229

203230
instance.on('error', function (err) {
@@ -210,14 +237,14 @@ Server.prototype.start = function () {
210237
self.queryStatus()
211238
}, queryInterval)
212239

213-
this.startHeadlessClients()
240+
this.startHeadlessClients(path)
214241

215242
this.emit('state')
216243

217244
return this
218245
}
219246

220-
Server.prototype.startHeadlessClients = function () {
247+
Server.prototype.startHeadlessClients = function (path) {
221248
var parameters = this.getParameters()
222249
var self = this
223250
var headlessClientInstances = _.times(this.number_of_headless_clients, function (i) {
@@ -228,7 +255,7 @@ Server.prototype.startHeadlessClients = function () {
228255
mods: self.mods,
229256
parameters: parameters,
230257
password: self.password,
231-
path: self.config.path,
258+
path: path,
232259
platform: self.config.type,
233260
port: self.port
234261
})
@@ -293,9 +320,26 @@ Server.prototype.stop = function (cb) {
293320
}
294321

295322
Server.prototype.stopHeadlessClients = function () {
296-
this.headlessClientInstances.map(function (headlessClientInstance) {
297-
headlessClientInstance.kill()
298-
})
323+
return Promise.all(this.headlessClientInstances.map(function (headlessClientInstance) {
324+
var handled = false
325+
return new Promise(function (resolve, reject) {
326+
headlessClientInstance.on('close', function () {
327+
if (!handled) {
328+
handled = true
329+
resolve()
330+
}
331+
})
332+
333+
setTimeout(function () {
334+
if (!handled) {
335+
handled = true
336+
resolve()
337+
}
338+
}, 5000)
339+
340+
headlessClientInstance.kill()
341+
})
342+
}))
299343
}
300344

301345
Server.prototype.toJSON = function () {

Diff for: lib/virtualServer.js

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
var fs = require('fs')
2+
var fsExtra = require('fs.extra')
3+
var _ = require('lodash')
4+
var glob = require('glob')
5+
var os = require('os')
6+
var path = require('path')
7+
8+
const requiredFileExtensions = [
9+
'.dll',
10+
'.exe',
11+
'.so',
12+
'.txt' // Steam app id
13+
]
14+
15+
const serverFolders = [
16+
'addons',
17+
'aow',
18+
'argo',
19+
'battleye',
20+
'contact',
21+
'csla',
22+
'curator',
23+
'dll',
24+
'dta',
25+
'enoch',
26+
'expansion',
27+
'gm',
28+
'heli',
29+
'jets',
30+
'kart',
31+
'linux64',
32+
'mark',
33+
'mpmissions',
34+
'orange',
35+
'tacops',
36+
'tank'
37+
]
38+
39+
function copyKeys (config, serverFolder, mods) {
40+
// Copy needed keys, file symlinks on Windows are sketchy
41+
const keysFolder = path.join(serverFolder, 'keys')
42+
return fs.promises.mkdir(keysFolder, { recursive: true })
43+
.then(() => {
44+
const defaultKeysPath = path.join(config.path, 'keys')
45+
const defaultKeysPromise = fs.promises.readdir(defaultKeysPath)
46+
.then((files) => files.filter((file) => path.extname(file) === '.bikey'))
47+
.then((files) => files.map((file) => path.join(defaultKeysPath, file)))
48+
49+
const modKeysPromise = Promise.all(mods.map(mod => {
50+
return new Promise((resolve, reject) => {
51+
const modPath = path.join(config.path, mod)
52+
glob(`${modPath}/**/*.bikey`, function (err, files) {
53+
if (err) {
54+
return reject(err)
55+
}
56+
57+
return resolve(files)
58+
})
59+
})
60+
})).then((modsFiles) => modsFiles.flat())
61+
62+
return Promise.all([defaultKeysPromise, modKeysPromise].map((promise) => {
63+
return promise.then((keyFiles) => {
64+
return Promise.all(keyFiles.map((keyFile) => {
65+
return fs.promises.copyFile(keyFile, path.join(keysFolder, path.basename(keyFile)))
66+
}))
67+
})
68+
})).catch((err) => {
69+
console.error('Error copying keys:', err)
70+
})
71+
})
72+
}
73+
74+
function copyFiles (config, serverFolder) {
75+
const configFileExtensions = (config.virtualServer && config.virtualServer.fileExtensions) || []
76+
const allowedFileExtensions = _.uniq(requiredFileExtensions.concat(configFileExtensions))
77+
78+
return fs.promises.readdir(config.path)
79+
.then((files) => {
80+
// Copy needed files, file symlinks on Windows are sketchy
81+
const serverFiles = files.filter((file) => allowedFileExtensions.indexOf(path.extname(file)) >= 0 || path.basename(file) === 'arma3server' || path.basename(file) === 'arma3server_x64')
82+
return Promise.all(serverFiles.map((file) => {
83+
return fs.promises.copyFile(path.join(config.path, file), path.join(serverFolder, file))
84+
}))
85+
})
86+
}
87+
88+
function createModFolders (config, serverFolder, mods) {
89+
// Create virtual folders from default Arma and mods
90+
const configFolders = (config.virtualServer && config.virtualServer.folders) || []
91+
const serverMods = config.serverMods || []
92+
const symlinkFolders = _.uniq(serverFolders.concat(mods).concat(configFolders).concat(serverMods))
93+
94+
return Promise.all(symlinkFolders.map((symlink) => {
95+
return fs.promises.access(path.join(config.path, symlink))
96+
.then(() => {
97+
return fs.promises.symlink(path.join(config.path, symlink), path.join(serverFolder, symlink), 'junction')
98+
.catch((err) => {
99+
console.error('Could create symlink for', symlink, 'due to', err)
100+
})
101+
})
102+
.catch(() => {})
103+
}))
104+
}
105+
106+
module.exports.create = function (config, mods) {
107+
return fs.promises.mkdtemp(path.join(os.tmpdir(), 'arma-server-'))
108+
.then((serverFolder) => {
109+
console.log('Created virtual server folder:', serverFolder)
110+
111+
return Promise.all([
112+
copyKeys(config, serverFolder, mods),
113+
copyFiles(config, serverFolder),
114+
createModFolders(config, serverFolder, mods)
115+
]).then(() => {
116+
return serverFolder
117+
})
118+
})
119+
}
120+
121+
module.exports.remove = function (folder) {
122+
if (folder) {
123+
fsExtra.rmrf(folder, function (err) {
124+
if (err) {
125+
console.log('Error removing virtual server folder', err)
126+
}
127+
})
128+
}
129+
}

0 commit comments

Comments
 (0)