-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathindex.js
More file actions
178 lines (154 loc) · 8.48 KB
/
index.js
File metadata and controls
178 lines (154 loc) · 8.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
require('./src/types/typedef');
const path = require('path');
const fs = require('fs-extra');
const Logger = require('chegs-simple-logger');
const Backups = require('./src/backups');
const Discord = require('./src/discord');
const Installer = require('./src/installer');
const Launcher = require('./src/launcher');
const System = require('./src/system');
const ValFiles = require('./src/valheimFiles');
const defConfig = require('./src/types/defaultConfig');
module.exports = class ValheimManager {
/** @param {Configuration} config - The configuration for the manager */
constructor(config) {
// Validate the configuration file
const configValidation = validateConfiguration(config);
if (configValidation !== 'ok') throw new Error(configValidation);
// Check operating system and add to the config
const os = process.platform;
if (os !== 'win32' && os !== 'linux') {
let error = `This operating system, ${os}, is not supported!\n`;
error += `You can set manager.operatingSystem to either win32 or linux to ignore this warning.`;
if (!config.manager.operatingSystem) throw new Error(error);
} else {
config.manager.operatingSystem = os;
}
// Create instances of all tools and dependencies
this.config = config;
this.validateConfig = validateConfiguration;
this.logger = new Logger(config.logging);
this.backups = new Backups(this);
this.installer = new Installer(this);
this.launcher = new Launcher(this);
this.system = new System(this);
this.valFiles = new ValFiles(this);
this.discord = new Discord(this);
// Prepare the commands
/**@type {Map<String, Command>} */
this.commands = new Map();
const commandsPath = path.resolve(__dirname, './src/commands/');
for (const file of fs.readdirSync(commandsPath)) {
if (file == 'command.js' || !file.endsWith('.js') || file.startsWith('old_')) continue;
const commandPath = path.join(commandsPath, file);
const command = new (require(commandPath))(this);
this.commands.set(command.name, command);
}
// Monitor for SIGINT and stop server
const manager = this;
process.on('SIGINT', async function() {
manager.logger.general('Signal interrupt detected');
await manager.launcher.stopValheim();
process.exit();
});
}
/**
* Processes a user executed command
* @param {String} commandString - The command including arguments
* @param {Function<String>} update - A callback function for providing updates in case of long execution time
* @returns {String} a user ready result from the command execution
*/
async execute(commandString, update) {
// Validate the command string and break it down to its components
if (!commandString || typeof(commandString) != 'string') throw new Error('Execute expects a command string as the first argument.');
const args = commandString.split(' ');
const commandName = args.shift();
// If this is a request for the command list, display all available commands
if (commandName && commandName.toLowerCase() == 'command-list') {
let list = '\n== COMMAND NAME == == DESCRIPTION ==';
for (const command of this.commands.values()) list += `\n ${command.name} - ${command.description}`;
return list;
}
// Identify the command being called
const command = this.commands.get(commandName);
if (!command) return `${commandName} is not a valid command. Try command-list.`;
// Validate and execute the command
try {
if (update && command.acknowledgment) update(command.acknowledgment);
const validationError = await command.validate(args);
if (validationError) return validationError;
return await command.execute(args);
} catch (err) {
this.logger.error(`Error executing command - ${commandString}. \n${err.stack}`);
return 'There was an error executing this command';
}
}
}
/**
* Synchronous / thread blocking validation of the config which can be used in the modules constructor.
* @param {Configuration} config - The user provided configuration to be validated.
* @returns {String} - A fully detailed error message to help an average user troubleshoot, or "ok".
*/
function validateConfiguration(config) {
const errors = [];
// Validate the main components are present
if (!config.manager) errors.push('The manager portion of this config is missing. Maybe this is not a config file?');
if (!config.launcher) errors.push('The launcher portion of this config is missing.');
if (!config.logging) errors.push('The logging portion of this config is missing.');
if (!config.discord) errors.push('The discord portion of this config is missing.');
if (errors.length > 0) return errors.join('\n');
// Validate the existence and types of required properties
const ignoreProperties = ['operatingSystem'];
for (const component of ['manager', 'launcher', 'discord', 'logging']) {
for (const [key, value] of Object.entries(defConfig[component])) {
if (ignoreProperties.includes(key)) continue;
if (config[component][key] == undefined) {
errors.push(`The ${component}.${key} property is required but does not seem to exist.`);
} else {
const propType = typeof(config[component][key]);
const expectedType = typeof(value);
if (propType != expectedType) {
errors.push(`The ${component}.${key} property is expected to be a ${expectedType}. It is currently ${config[component][key]}(${propType}).`);
}
}
}
}
// Validate the parent directory is accessible. Create the child directory if needed.
const validatePaths = [config.manager.configLocation, config.manager.serverLocation];
if (config.logging.writeLog) validatePaths.push(config.logging.filePath);
for (const location of validatePaths) {
const fullPath = path.resolve(location);
const parentPath = path.dirname(fullPath);
if (!fs.existsSync(parentPath)) {
errors.push(`The path ${parentPath} is not accessible.`);
continue;
}
if (!fs.existsSync(fullPath)) {
const creationMethod = fullPath.includes('.') ? fs.createFileSync : fs.mkdirSync;
creationMethod(fullPath);
}
}
// Check for spaces in the server path
if (config.manager.serverLocation.includes(' ')) {
const noSpaces = config.manager.serverLocation.replace(' ', '-');
errors.push(`Steam does not work with spaces in file paths.\n Current Path: ${config.manager.serverLocation}\n Recommended change: ${noSpaces}`);
}
// Validate the specifics of some property values
if (config.manager.backupFrequency < 0) errors.push('The backup frequency should be greater than 0.');
if (config.manager.backupRetention < 0) errors.push('The backup retention should be greater than 0.');
if (config.launcher.port < 1024 || config.launcher.port > 65535) errors.push('The port should be between 1024 and 65535.');
if (config.logging.writeLog) {
if (!config.logging.fileSize.match(/[BKMG]$/)) errors.push('The logging file size should end with B, K, M, or G. This letters represent the byte measurement options.');
if (config.logging.fileAge < 0) errors.push('The logging file age should be more than 0 days.');
if (config.logging.fileCount < 0) errors.push('The logging file max count should be more than 0.');
}
if (config.discord.token != '') {
if (config.discord.token.length < 20) errors.push('The discord token should be longer.');
if (config.discord.serverId.length < 17) errors.push('The discord server id should be at least 17 characters long.');
if (config.discord.adminRoleId.length < 17) errors.push('The discord admin role id should be at least 17 characters long.');
if (config.discord.serverLogChannel.length < 17) errors.push('The discord server log channel id should be at least 17 characters long.');
if (config.discord.commandLogChannel.length < 17) errors.push('The discord command log channel id should be at least 17 characters long.');
}
if (errors.length > 0) return errors.join('\n');
return 'ok';
}