Related
So i made a small api to start up minecraft servers in screen, which works fine.
But now, everytime i start or stop the server, the api crashes somehow because the http server wrote after an end, i cant find anything that ended the server.
I cant seem to find where it ends, its very weird.
I am new to http servers, api's and this stuff so it may be just my dumb fault.
//#author WizzerStudios on Github
//CC 2021
//Allowed:
//:: Editing
//Disallowed
//:: Redistributing
//:: Claiming as yours
//Importing stuff
var http = require('http');
const { exec } = require("child_process");
var url = require('url');
var https = require('https');
const fs = require('fs');
//Console log
console.log("Wizzer API booted.")
//Create API / HTTP Server
http.createServer(function (req, res) {
var q = url.parse(req.url, true).query;
//Check if password is correctly given.
if(q.password == "Password"){
//Switch to the action.
switch(q.action){
//If action is undefined:
case undefined:
res.end("Action not given.");
break;
//Get Logs is coming soon.
case "getlogs":
if(q.sname == undefined) res.end("No server name given.");
try {
const data = fs.readFileSync('serverfiles/' + q.sname + '/logs/latest.log', 'utf8');
res.end(data);
} catch (err) {
console.error(err)
res.end("Error occured! Server does not exist or isn't available.");
}
break;
//Start the server using the bash screen command.
case "stop":
//If no server name is given, end the connection.
if(q.sname == undefined) res.end("Server name not specified.");
//If server exists, run the screen command.
if (fs.existsSync('./servers/' + q.sname + '.json')) {
//Check if a screen session with the same name is already running.
exec('screen -S ' + q.sname + ' -Q select . ; echo $?',
(error, stdout, stderror) => {
if (error) {
console.error("Error: ", error);
return;
}
var out = stdout;
if(out.includes('0')){
exec('screen -p 0 -S minecraft-server -X eval `stuff "say TEST MESSAGE..."\\015`', (error, stdout, stderror) => {
res.end("Server stopped!"); //It says it stopped and crashes here !!!
});
} else {
res.end('Server already stopped!');
return;
}});
} else {
res.end("Server does not exist!");
return;
}
break;
case "start":
//If no server name is given, end the connection.
if(q.sname == undefined) res.end("Server name not specified.");
//If server exists, run the screen command.
if (fs.existsSync('./servers/' + q.sname + '.json')) {
//Check if a screen session with the same name is already running.
exec('screen -S ' + q.sname + ' -Q select . ; echo $?',
(error, stdout, stderror) => {
if (error) {
console.error("Error: ", error);
return;
}
var out = stdout;
if(out.includes('1')){
exec('screen -S ' + q.sname + ' -dm bash /home/mcserver/api/serverfiles/' + q.sname + '/start.sh', (error, stdout, stderror) => {
res.end("Server started!"); // Here it says it already stopped!
});
} else {
res.end('Server already started!');
return;
}});
} else {
res.end("Server does not exist!");
return;
}
break;
//Create server action.
case "createServer":
//If no game is given, end connection.
if(q.game == undefined) res.end("Game not given");
//Switch to the game chosen.
switch(q.game){
//Minecraft
case "minecraft":
if(q.sname == undefined) res.end("Server name not given."); // No server name given, end connection.
if(q.port == undefined) res.end("Port not given."); // No port given, end connection.
if(q.ram == undefined) res.end("Please give ram in gigabytes."); // No ram given, end connection.
if(q.software == undefined) res.end("Server software not given."); // Server software not given, end connection.
if(q.version == undefined) res.end("Server version not given."); // Version not given, end connection.
//Check if server already exists:
var path = './servers/' + q.sname + '.json';
if (fs.existsSync(path)) {
res.end("Server Exists.");
return;
}
ram = Number(q.ram);
ram *= 1024;
let server = {
name: q.sname,
port: q.port,
game: 'Minecraft',
ram: Number(ram),
software: q.software,
version: q.version
};
let data = JSON.stringify(server, null, 2);
fs.mkdir('./serverfiles/' + q.sname, (err) => {
if (err) {
throw err;
res.end(err)
}
});
fs.writeFile('./serverfiles/' + q.sname + '/eula.txt', 'eula=true', function (err) {
if (err) res.end(err);
if(err) console.log(err);
});
fs.writeFile('./serverfiles/' + q.sname + '/start.sh', '#!/bin/bash\njava -Xmx' + Number(ram) + 'M ' + '-Xms' + Number(ram) + 'M -jar /home/mcserver/api/serverfiles/' + q.sname + '/server.jar', function (err) {
if (err) res.end(err);
if(err) console.log(err);
});
exec('chmod +x ./serverfiles/' + q.sname + '/start.sh');
fs.writeFile('./serverfiles/' + q.sname + '/server.properties', 'port=' + q.port, function (err) {
if (err) res.end(err);
if(err) console.log(err);
});
try{
https.get("https://serverjars.com/api/fetchJar/" + q.software + "/" + q.version, function(response) { response.pipe(fs.createWriteStream("serverfiles/" + q.sname + '/server.jar'))});
} catch(err){
res.end("Failed!")
console.log(err)
}
fs.writeFile("servers/" + q.sname + ".json", data, (err) => {});
res.write("Server Software: " + q.software);
res.write("\nRam: " + Number(ram));
res.write("\nServer name: " + q.sname);
res.write("\nGame: Minecraft");
res.write("\nPort: " + q.port);
res.end("\nVersion " + q.version);
}
}
//if(req.url == "/?password=Password&?action=create"){
// res.write("game not given")
//} else if(req.url == "/?password=Password&?action=create&game"){
// res.write("Authenticated!")
//}
//} else {
//res.write("WRONG PASSWORD!")
}
res.end(); //end the response
}).listen(8080); //the server object listens on port 808
events.js:174
throw er; // Unhandled 'error' event
^
Error [ERR_STREAM_WRITE_AFTER_END]: write after end
at write_ (_http_outgoing.js:572:17)
at ServerResponse.write (_http_outgoing.js:567:10)
at exec (/home/mcserver/api/index.js:86:21)
at ChildProcess.exithandler (child_process.js:285:7)
at ChildProcess.emit (events.js:198:13)
at maybeClose (internal/child_process.js:982:16)
at Process.ChildProcess._handle.onexit (internal/child_process.js:259:5)
Emitted 'error' event at:
at writeAfterEndNT (_http_outgoing.js:634:7)
at process._tickCallback (internal/process/next_tick.js:63:19)
``` is the error.
I do know that i'm writing after the end, but where did it end?
Also, changing res.write to res.end doesn't crash my code, but it doesn't give anything in my browser.
Take out the following line:
res.end(); //end the response <-----------------
}).listen(8080); //the server object listens on port 8080
You are calling "res.end()" after already calling it inside the function
res.end("\nVersion " + q.version); // In createServer
calls it then it drops out the switch statements and executes the above res.end() again
U have to check that in case u already send some response to not send something again.
So each it's better to use:
return res.end();
To stop function.
I always get an error message after adding the reload command.
Without the Reload Command, the bot works with all commands without any problems
Without the Reload Command, the bot works with all commands without any problems
I just can't find the mistake
The error message:
undefined commands found.
/home/runner/Discord-BreakpointBot/index.js:19
delete require.cache[require.resolve(`./commands/${f}`)];
^
TypeError: require.resolve is not a function
Code:
function loadCmds () {
fs.readdir('./commands/', (err, files) => {
if(err) console.error(err);
var jsfiles = files.filter(f => f.split('.').pop() === 'js');
if (jsfiles.length <=0) {return console.log('No commands found...')}
else {console.log(jsfiles.lenght + ' commands found.')}
jsfiles.forEach((f,i) => {
delete require.cache[require.resolve(`./commands/${f}`)];
var cmds = require(`./commands/${f}`);
console.log(`Command ${f} loading...`);
bot.commands.set(cmds.config.command, cmds);
})
})
}
bot.on('message', message => {
var sender = message.author;
var msg = message.content.toUpperCase();
var prefix ='>'
var cont = message.content.slice(prefix.length).split(" ");
var args = cont.slice(1);
if (!message.content.startsWith(prefix)) return;
var cmd = bot.commands.get(cont[0])
if (cmd) cmd.run(bot, message, args);
if (msg === prefix + 'RELOAD') {
message.channel.send({embed:{description:"All Commands Reloaded"}})
message.channel.send('All Commands Reloaded')
loadCmds()
}
});
loadCmds();
// Bot Launched
bot.on('ready', () => {
console.log('Bot Launched...')
bot.user.setStatus('Online')
bot.user.setActivity('https://www.twitch.tv');
});
I hope someone can help, thanks
I'm not 100% sure what causes your error but I can offer you my method of retrieving comamnds, which works pretty well:
const fileArray = [];
function readCommands(dir) {
const __dirname = rootDir;
// Read out all command files
const files = fs.readdirSync(path.join(__dirname, dir));
// Loop through all the files in ./commands
for (const file of files) {
// Get the status of 'file' (is it a file or directory?)
const stat = fs.lstatSync(path.join(__dirname, dir, file));
// If the 'file' is a directory, call the 'readCommands' function
// again with the path of the subdirectory
if (stat.isDirectory()) {
readCommands(path.join(dir, file));
}
else {
const fileDir = dir.replace('\\', '/');
fileArray.push(fileDir + '/' + file);
// fs.readdirSync(dir).filter(cmdFile => cmdFile.endsWith('.js'));
}
}
}
readCommands('commands');
for(const file of fileArray) {
const command = require(`../${file}`);
if(command.name) {
client.commands.set(command.name, command);
}
else {
continue;
}
}
This will recursively search the folder you specify when calling the function and then store it in fileArray.
This solution is more an alternative than an exact solution to the error you are experiencing
I am writing a Cmus Remote that is browser based and uses Nodejs on the backend. Part of the app involves getting the currently playing song and displaying it to the user.
Currently it successfully runs the command gets the output stored into a variable properly, but the client side request runs before the callback of the server side function thus it retruns an empty string for the song.
Here is the code to better illustrate what I mean:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Cmus Remote</title>
<script src="client.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body id="body">
</body>
</html>
client.js
"use strict";
window.onload = function () {
$.get("/songInfo", function(string){
alert(string);
});
};
server.js
var express = require('express');
var app = express();
var path = require('path');
var exec = require('child_process').exec;
var fs = require('fs');
var child;
var getSongCommand = "cmus-remote -Q | sed -n -e 's/^.*tag title //p'";
var getAlbumCommand = "cmus-remote -Q | sed -n -e 's/^.*tag album //p'";
var getArtistCommand = "cmus-remote -Q | sed -n -e 's/^.*tag artist //p'";
var song ="";
var album= "";
var artist = "";
app.use(express.static(__dirname + '/public'));
app.get('/', function (req, res){
res.sendFile(path.join(__dirname + '/index.html'));
});
app.get('/songInfo', function(req, res){
updateSongInfo(getSongCommand);
updateSongInfo(getAlbumCommand);
updateSongInfo(getArtistCommand);
var strings = [song, artist, album];
res.send(strings);
});
var server = app.listen(8080, function () {
console.log("Server online");
});
function updateSongInfo(command){
var exec = require('child_process').exec;
exec(command, function(error, stdout, stderr){
callback(command, stdout);
});
}
function callback(commandRan, output){
console.log("Commandran = " + commandRan);
console.log("Command output = " + output);
if(commandRan.includes("title")){
console.log("Updating song to " + output);
song = output;
}
if(commandRan.includes("album")){
album = output;
}
if(commandRan.includes("artist")){
artist = output;
}
// console.log("In callback");
// console.log(output);
return output;
}
To summarize, the ajax response is working properly, the command runs properly and the values are saved to the 3 global variables I have, but I am not sure how to set up the timing that the ajax request returns once the variables have values.
The problem is because exec is asynchronous, so exec will be called, the program will continue and return the still empty data to the caller and then later it will finish with the execution data now received.
To fix this you can use Promise and async/await.
app.get('/songInfo', async function(req, res){
await updateSongInfo(getSongCommand);
await updateSongInfo(getAlbumCommand);
await updateSongInfo(getArtistCommand);
var strings = [song, artist, album];
res.send(strings);
});
...
function updateSongInfo(command){
return new Promise((resolve, reject) => {
var exec = require('child_process').exec;
exec(command, function(error, stdout, stderr){
callback(command, stdout);
return resolve();
});
});
}
Calling resolve() inside the Promise will complete it, while calling reject() will throw an error. Also you can give those functions parameters as well.
Your updateSongInfo function runs asynchronously, so you're server is sending back a response before the update has completed. What you'll need to do is either implement Promises or a callback to run after those functions have completed. I would probably suggest that you not use global variables here, but instead return the result each time. Here's an example:
app.get('/songInfo', function(req, res) {
var song, artist, album;
updateSongInfo(getSongCommand, function(err, result) {
if (err) return res.send(err);
song = result;
updateSongInfo(getAlbumCommand, function(err, result) {
if (err) return res.send(err);
album = result;
updateSongInfo(getArtistCommand, function(err, result) {
if (err) return res.send(err);
artist = result
// Now your globals will be fulfilled
return res.send([song, artist, album]);
});
});
});
});
function updateSongInfo(command, cb){
var exec = require('child_process').exec;
exec(command, function(error, stdout, stderr){
callback(command, stdout, cb);
});
}
function callback(commandRan, output, cbFunction){
console.log("Commandran = " + commandRan);
console.log("Command output = " + output);
if(commandRan.includes("title")){
console.log("Updating song to " + output);
song = output;
}
if(commandRan.includes("album")){
album = output;
}
if(commandRan.includes("artist")){
artist = output;
}
// console.log("In callback");
// console.log(output);
return cbFunction(null, output);
}
I'm just wondering whether it is at all possible to transfer a directory from a unix server to my local machine using the ssh2 module in node.js. I have connected to the remote host and can read the directory as well as transfer single files, but there are 28 folders in the directory which each contain files and sub directories. What I'd like to do is take an exact copy of the main directory from the server to my local machine.
I was using fastGet with single files, but transferring a directory gives: Error: EISDIR, open __dirname/../localdirectory/ which I think implies I can't use fastGet to get an entire directory. I also tried using the exec command to try and scp it over, but I couldn't work out the syntax for the local directory:
// c is an active connection
c.exec('scp filethatexists.extension /../filepath/newname.extension', function(err, stream) {
if (err) {
console.log("error: " + err);
stream.end;
};
stream.on('data', function(data, extended) {
console.log((extended === 'stderr' ? 'STDERR: ' : 'STDOUT: ') + data);
});
stream.on('end', function() {
console.log('Stream :: EOF');
});
stream.on('close', function() {
console.log('Stream :: close');
});
stream.on('exit', function(code, signal) {
console.log('Stream :: exit :: code: ' + code + ', signal: ' + signal);
c.end();
});
});
This just results in the EOF calling. This code was just me testing If I could get a single file transferring.
Can anyone provide me with any assistance? Thank you in advance.
A couple of solutions:
You could recursively traverse the directory (making directories and transferring files as needed) using the sftp methods
Tar the directory (compress it too if you want) to stdout (e.g. tar cf - mydir) and then process that incoming stdout data with the tar module (and the built-in zlib module first if you end up compressing the directory).
// Requires:
// * `npm install tar-fs`
// * `ssh2` v0.5.x or newer
var tar = require('tar-fs');
var zlib = require('zlib');
function transferDir(conn, remotePath, localPath, compression, cb) {
var cmd = 'tar cf - "' + remotePath + '" 2>/dev/null';
if (typeof compression === 'function')
cb = compression;
else if (compression === true)
compression = 6;
if (typeof compression === 'number'
&& compression >= 1
&& compression <= 9)
cmd += ' | gzip -' + compression + 'c 2>/dev/null';
else
compression = undefined;
conn.exec(cmd, function(err, stream) {
if (err)
return cb(err);
var exitErr;
var tarStream = tar.extract(localPath);
tarStream.on('finish', function() {
cb(exitErr);
});
stream.on('exit', function(code, signal) {
if (typeof code === 'number' && code !== 0) {
exitErr = new Error('Remote process exited with code '
+ code);
} else if (signal) {
exitErr = new Error('Remote process killed with signal '
+ signal);
}
}).stderr.resume();
if (compression)
stream = stream.pipe(zlib.createGunzip());
stream.pipe(tarStream);
});
}
// USAGE ===============================================================
var ssh = require('ssh2');
var conn = new ssh();
conn.on('ready', function() {
transferDir(conn,
'/home/foo',
__dirname + '/download',
true, // uses compression with default level of 6
function(err) {
if (err) throw err;
console.log('Done transferring');
conn.end();
});
}).connect({
host: '192.168.100.10',
port: 22,
username: 'foo',
password: 'bar'
});
i m also trying to downlaod folders using ssh. It took me more than 10 days and i'm still trying to do that. But in the mean time i found some other code which will do the same thing for me.This code below will download every folder and file inside a directory
enter image description here
I am trying to write a newer watch module that uses the fs.watch method instead of the watchFile approach.
So far, it works beautifully, but only when I run it outside of mocha. I can't figure out why my unit test is throwing a tantrum, maybe someone here can?
Here is my class code:
/**
* requirements
*/
var fs, path, events;
fs = require('fs');
path = require('path');
events = require('events');
/**
* private
*/
var Monitor = function(directory, options) {
this.directory = directory;
this.options = options || {};
(this.options.lazy && this.empty()) || this.walk(this.directory);
this.watch(this.directory);
};
Monitor.prototype = new events.EventEmitter();
Monitor.prototype.watch = function(directory, stats) {
var stats = stats || {};
if (!this.directories[directory]) {
var w = fs.watch(directory, this.options, this.justlookatit.bind(this));
}
this.directories[directory] = { 'stats': stats, 'w': w };
};
Monitor.prototype.directories = function() {
if (!Object.keys(this.directories).length) {
this.walk(this.directory);
}
return this.directories;
};
Monitor.prototype.files = function() {
if (!Object.keys(this.files).length) {
this.walk(this.directory);
}
return this.files;
};
Monitor.prototype.unwatch = function() {
if (!Object.keys(this.directories).length) {
for (var dir in this.directories) {
dir.w.close();
}
}
};
Monitor.prototype.empty = function() {
this.unwatch();
this.files = {};
this.directories = {};
};
Monitor.prototype.walk = function(directory) {
var monitor = this;
this.empty();
fs.readdir(directory, function(err, files) {
if (err) return;
for (var file in files) {
var fullname = path.resolve(files[file]);
if (!monitor.options.filter || monitor.options.filter(fullname)) {
fs.stat(fullname, function(err, stats) {
if (err) return;
if (stats.isDirectory()) {
monitor.walk(fullname);
monitor.watch(fullname, stats);
} else {
monitor.files[fullname] = stats;
}
});
}
}
});
};
Monitor.prototype.justlookatit = function(action, file) {
var monitor = this;
var fullname = path.resolve(file);
if (this.options.filter && !this.options.filer(fullname)) return;
fs.exists(fullname, function(exists) {
if (exists) {
fs.stat(fullname, function(err, stats) {
if (stats.isDirectory()) {
monitor.watch(fullname, stats);
} else {
if (monitor.files[fullname]) {
if (stats.mtime.getTime() > monitor.files[fullname].mtime.getTime()) {
monitor.emit('modified', fullname, stats);
}
} else {
monitor.emit('added', fullname, stats);
}
monitor.files[fullname] = stats;
}
});
} else {
if (monitor.files[fullname]) {
delete monitor.files[fullname];
monitor.emit('deleted', fullname);
} else if (monitor.directories[fullname]) {
monitor.directories[fullname].w.close();
delete monitor.directories[fullname];
}
}
});
};
/**
* exports
*/
exports.start = function(directory, options) {
return new Monitor(directory, options);
};
Here is my Working external test code:
var watch = require("./watch.js");
var fs = require('fs');
monitor = watch.start(__dirname);
monitor.on('added', function(file, stats) {
console.log("Caught Added: " + file);
});
monitor.on('modified', function(file, stats) {
console.log("Caught Modified: " + file);
});
monitor.on('deleted', function(file) {
console.log("Caught deleted: " + file);
});
// try creating a file immediately
fs.openSync('v.md', 'w');
The first test file runs perfectly fine, and I've tried both openSync and open. Finally, here is a version of the same test code, wrapped in a mocha unit test which is timing out:
/**
* requirements
*/
var watch, Q, fs, path, mocha, chai, assert;
watch = require('../lib/watch.js');
Q = require('q');
fs = require('fs');
path = require('path');
mocha = require('mocha');
chai = require('chai');
assert = chai.assert;
/**
* variables
*/
var watch_directory = path.join(__dirname, './watch');
/**
* tests
*/
describe('test watch', function() {
it('should create a monitor and run callbacks after fs changes', function(done) {
// I had planned to implement promises that chained the three callbacks
// but couldn't get one of them working in general
var added = function(file, stats) {
console.log("added");
done();
};
var modified = function(file, stats) {
console.log("modified");
};
var deleted = function(file, stats) {
console.log("deleted");
};
// create our service
var monitor = watch.start(watch_directory);
// assert it is defined
assert.isDefined(monitor);
// establish a listener
monitor.on('added', added);
monitor.on('modified', modified);
monitor.on('deleted', deleted);
// here is a file name using the current date to prevent duplication during tests
var file = path.join(watch_directory, (new Date()).getTime() + '.md');
// let's create the file, then delete it
fs.open(file, 'w+', function(err, fileDescriptor) {
// this prints before console output from the watch.js's `justlookatit` method
console.log(err);
console.log("writing to file");
// we probably don't want to try closing the fileDescriptor if the open failed
if (err) return;
// close the file descriptor
fs.close(fileDescriptor, function() {
// delete the file we just created
// fs.unlink(file, function() { /* not a big deal */ });
});
});
// modify a known-existing test file
fs.open('test.md', 'w+', function() {/* we don't care about this */});
})
});
I checked with console.log(fullname) inside the justlookatit method on the watch code, and it spits out the correct file name, matching the one generated by the unit test.
However, it then proceeds to return false when I run fs.exists. As I undestand it, that means the file system is notifying me that a file exists before it exists, which doesn't make sense really. So I tried adding an additional delay by wrapping my fs.exists method in a setTimeout, and that didn't change the results. I have also tried using both openSync and existsSync, and that made no difference.
I'm stumped, does anyone have any ideas why the mocha code isn't working?
So, the solution was to go for a walk. I came back, looked at the code again and figured out the cause of the problem with mocha, and also identified many other bugs.
The problem was the lack of context. The justlookatit method does not have a context, and in the test.js scenario it is watching the current directory, while the mocha test is watching a sub-directory.
The path.resolve was receiving only the file name, not the directory, and therefore merged it with the default (executables) directory, so the level of test.js, or watch_test.js for mocha. It proceeded to fail to locate any of the files in the mocha test case because they were all one level below the executable.
I won't go into detail about all the other bugs, but I may come back and post the repository link when I get to a point that I want to push it online.
You're missing the callback return(done); at the end of your test. Unless you call that callback, Mocha will time out every time.