Skip to content

Commit a124853

Browse files
committed
Virtual server folder with keys management
1 parent 3148efb commit a124853

File tree

5 files changed

+479
-7
lines changed

5 files changed

+479
-7
lines changed

Diff for: .github/workflows/node.yml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ jobs:
1414
runs-on: ${{ matrix.os }}
1515

1616
strategy:
17+
fail-fast: false
1718
matrix:
1819
node-version:
1920
- 14.x

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

+56-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ var slugify = require('slugify')
55

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

8+
var virtualServer = require('./virtualServer')
9+
810
var queryInterval = 5000
911
var queryTypes = {
1012
arma1: 'arma',
@@ -132,6 +134,29 @@ Server.prototype.start = function () {
132134
return this
133135
}
134136

137+
var self = this
138+
139+
if (self.config.virtualServer && self.config.virtualServer.enabled) {
140+
virtualServer.create(self.config, self.mods)
141+
.then((serverFolder) => {
142+
self.virtualServerFolder = serverFolder
143+
self.path = serverFolder
144+
self.realStart()
145+
})
146+
.catch((err) => {
147+
console.error('Error creating virtual server folder:', err)
148+
})
149+
} else {
150+
self.path = self.config.path
151+
self.realStart()
152+
}
153+
}
154+
155+
Server.prototype.realStart = function () {
156+
if (this.instance) {
157+
return this
158+
}
159+
135160
var parameters = this.getParameters()
136161
var server = new ArmaServer.Server({
137162
additionalConfigurationOptions: this.getAdditionalConfigurationOptions(),
@@ -152,7 +177,7 @@ Server.prototype.start = function () {
152177
parameters: parameters,
153178
password: this.password,
154179
passwordAdmin: this.admin_password,
155-
path: this.config.path,
180+
path: this.path,
156181
persistent: this.persistent ? 1 : 0,
157182
platform: this.config.type,
158183
players: this.max_players,
@@ -171,8 +196,13 @@ Server.prototype.start = function () {
171196
self.instance = null
172197

173198
self.stopHeadlessClients()
174-
175-
self.emit('state')
199+
.then(() => {
200+
if (self.virtualServerFolder) {
201+
virtualServer.remove(self.virtualServerFolder)
202+
self.virtualServerFolder = null
203+
}
204+
self.emit('state')
205+
})
176206
})
177207

178208
this.pid = instance.pid
@@ -207,7 +237,7 @@ Server.prototype.startHeadlessClients = function () {
207237
mods: self.mods,
208238
parameters: parameters,
209239
password: self.password,
210-
path: self.config.path,
240+
path: self.path,
211241
platform: self.config.type,
212242
port: self.port
213243
})
@@ -248,10 +278,29 @@ Server.prototype.stop = function (cb) {
248278
}
249279

250280
Server.prototype.stopHeadlessClients = function () {
251-
this.headlessClientInstances.map(function (headlessClientInstance) {
252-
headlessClientInstance.kill()
281+
var self = this
282+
return Promise.all(this.headlessClientInstances.map(function (headlessClientInstance) {
283+
var handled = false
284+
return new Promise(function (resolve, reject) {
285+
headlessClientInstance.on('close', function () {
286+
if (!handled) {
287+
handled = true
288+
resolve()
289+
}
290+
})
291+
292+
setTimeout(function () {
293+
if (!handled) {
294+
handled = true
295+
resolve()
296+
}
297+
}, 5000)
298+
299+
headlessClientInstance.kill()
300+
})
301+
})).then(function () {
302+
self.headlessClientInstances = []
253303
})
254-
this.headlessClientInstances = []
255304
}
256305

257306
Server.prototype.toJSON = function () {

Diff for: lib/virtualServer.js

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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+
'heli',
28+
'jets',
29+
'kart',
30+
'linux64',
31+
'mark',
32+
'mpmissions',
33+
'orange',
34+
'tacops',
35+
'tank',
36+
'vn',
37+
'ws'
38+
]
39+
40+
function copyKeys (config, serverFolder, mods) {
41+
// Copy needed keys, file symlinks on Windows are sketchy
42+
const keysFolder = path.join(serverFolder, 'keys')
43+
return fs.promises.mkdir(keysFolder, { recursive: true })
44+
.then(() => {
45+
const defaultKeysPath = path.join(config.path, 'keys')
46+
const defaultKeysPromise = fs.promises.readdir(defaultKeysPath)
47+
.then((files) => files.filter((file) => path.extname(file) === '.bikey'))
48+
.then((files) => files.map((file) => path.join(defaultKeysPath, file)))
49+
50+
const modKeysPromise = Promise.all(mods.map(mod => {
51+
return new Promise((resolve, reject) => {
52+
const modPath = path.join(config.path, mod)
53+
glob(`${modPath}/**/*.bikey`, function (err, files) {
54+
if (err) {
55+
return reject(err)
56+
}
57+
58+
return resolve(files)
59+
})
60+
})
61+
})).then((modsFiles) => modsFiles.flat())
62+
63+
return Promise.all([defaultKeysPromise, modKeysPromise].map((promise) => {
64+
return promise.then((keyFiles) => {
65+
return Promise.all(keyFiles.map((keyFile) => {
66+
return fs.promises.copyFile(keyFile, path.join(keysFolder, path.basename(keyFile)))
67+
}))
68+
})
69+
})).catch((err) => {
70+
console.error('Error copying keys:', err)
71+
})
72+
})
73+
}
74+
75+
function copyFiles (config, serverFolder) {
76+
const configFileExtensions = (config.virtualServer && config.virtualServer.fileExtensions) || []
77+
const allowedFileExtensions = _.uniq(requiredFileExtensions.concat(configFileExtensions))
78+
79+
return fs.promises.readdir(config.path)
80+
.then((files) => {
81+
// Copy needed files, file symlinks on Windows are sketchy
82+
const serverFiles = files.filter((file) => allowedFileExtensions.indexOf(path.extname(file)) >= 0 || path.basename(file) === 'arma3server' || path.basename(file) === 'arma3server_x64')
83+
return Promise.all(serverFiles.map((file) => {
84+
return fs.promises.copyFile(path.join(config.path, file), path.join(serverFolder, file))
85+
}))
86+
})
87+
}
88+
89+
function createModFolders (config, serverFolder, mods) {
90+
// Create virtual folders from default Arma and mods
91+
const configFolders = (config.virtualServer && config.virtualServer.folders) || []
92+
const serverMods = config.serverMods || []
93+
const symlinkFolders = _.uniq(serverFolders
94+
.concat(mods)
95+
.concat(configFolders)
96+
.concat(serverMods)
97+
.map(function (folder) {
98+
return folder.split(path.sep)[0]
99+
})
100+
)
101+
102+
return Promise.all(symlinkFolders.map((symlink) => {
103+
return fs.promises.access(path.join(config.path, symlink))
104+
.then(() => {
105+
return fs.promises.symlink(path.join(config.path, symlink), path.join(serverFolder, symlink), 'junction')
106+
.catch((err) => {
107+
console.error('Could create symlink for', symlink, 'due to', err)
108+
})
109+
})
110+
.catch(() => {})
111+
}))
112+
}
113+
114+
module.exports.create = function (config, mods) {
115+
return fs.promises.mkdtemp(path.join(os.tmpdir(), 'arma-server-'))
116+
.then((serverFolder) => {
117+
console.log('Created virtual server folder:', serverFolder)
118+
119+
return Promise.all([
120+
copyKeys(config, serverFolder, mods),
121+
copyFiles(config, serverFolder),
122+
createModFolders(config, serverFolder, mods)
123+
]).then(() => {
124+
return serverFolder
125+
})
126+
})
127+
}
128+
129+
module.exports.remove = function (folder, cb) {
130+
if (folder) {
131+
fsExtra.rmrf(folder, function (err) {
132+
if (err) {
133+
console.log('Error removing virtual server folder', err)
134+
}
135+
136+
if (cb) {
137+
cb(err)
138+
}
139+
})
140+
}
141+
}

0 commit comments

Comments
 (0)