changeset 195:3bdee6371c3f

Change various filenames. The shell script previously used to launch the daemon is now called "initscript". The script files have had the ".js" extension removed from their names.
author John "Elwin" Edwards
date Thu, 14 Jan 2016 20:52:29 -0500
parents 5483d413a45b
children 298a531776d6
files Makefile initscript rlgwebd rlgwebd-stop rlgwebd-stop.js rlgwebd.js rlgwebd.service webtty webtty.js
diffstat 9 files changed, 1554 insertions(+), 1554 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Thu Jan 14 19:10:46 2016 -0500
+++ b/Makefile	Thu Jan 14 20:52:29 2016 -0500
@@ -19,6 +19,6 @@
 	mkdir -p ${CHROOT}/bin
 	cp sqlickrypt dglwatcher ${CHROOT}/bin
 	for LIB in `ldd ./sqlickrypt | awk '$$1 ~ "^/" {print $$1}; $$3 ~ "^/" {print $$3}'`; do mkdir -p ${CHROOT}`dirname $$LIB`; cp $$LIB ${CHROOT}$$LIB; done
-	cp rlgwebd.js rlgwebd-stop.js ${BINDIR}
+	cp rlgwebd rlgwebd-stop ${BINDIR}
 	cp ${WEBASSETS} ${CHROOT}/var/www
 	cp rlgwebd.service /usr/lib/systemd/system
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/initscript	Thu Jan 14 20:52:29 2016 -0500
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+NODE_PATH=/usr/lib/node_modules
+LOGFILE=/var/log/rlgwebd.log
+CTLSOCKET=/var/run/rlgwebd.sock
+RLGWEBDJS=./rlgwebd
+
+export NODE_PATH
+
+if [ $UID != 0 ]
+then
+  echo "$0 needs to run as root." >&2
+  exit 1
+fi
+
+if [ $# -gt 0 ] && [ $1 = stop ]
+then
+  socat "EXEC:echo quit" "$CTLSOCKET"
+else
+  # Start
+  setsid node "$RLGWEBDJS" </dev/null &>>$LOGFILE &
+fi
+
+exit
+
--- a/rlgwebd	Thu Jan 14 19:10:46 2016 -0500
+++ b/rlgwebd	Thu Jan 14 20:52:29 2016 -0500
@@ -1,25 +1,1242 @@
-#!/bin/sh
+#!/usr/bin/env node
+
+var http = require('http');
+var net = require('net');
+var url = require('url');
+var path = require('path');
+var fs = require('fs');
+var events = require('events');
+var child_process = require('child_process');
+// Dependencies
+var posix = require("posix");
+var pty = require("pty.js");
+var WebSocketServer = require("websocket").server;
+
+/* Configuration variables */
+// The first file is NOT in the chroot.
+var ctlsocket = "/var/run/rlgwebd.sock";
+var httpPort = 8080;
+var chrootDir = "/var/dgl/";
+var dropToUser = "rodney";
+var serveStaticRoot = "/var/www/"; // inside the chroot
+
+var clearbufs = [
+  new Buffer([27, 91, 72, 27, 91, 50, 74]), // xterm: CSI H CSI 2J
+  new Buffer([27, 91, 72, 27, 91, 74]) // screen: CSI H CSI J
+];
+
+/* Data on the games available. */
+var games = {
+  "rogue3": {
+    "name": "Rogue V3",
+    "uname": "rogue3",
+    "suffix": ".r3sav",
+    "path": "/usr/bin/rogue3"
+  },
+  "rogue4": {
+    "name": "Rogue V4",
+    "uname": "rogue4",
+    "suffix": ".r4sav",
+    "path": "/usr/bin/rogue4"
+  },
+  "rogue5": {
+    "name": "Rogue V5",
+    "uname": "rogue5",
+    "suffix": ".r5sav",
+    "path": "/usr/bin/rogue5"
+  },
+  "srogue": {
+    "name": "Super-Rogue",
+    "uname": "srogue",
+    "suffix": ".srsav",
+    "path": "/usr/bin/srogue"
+  },
+  "arogue5": {
+    "name": "Advanced Rogue 5",
+    "uname": "arogue5",
+    "suffix": ".ar5sav",
+    "path": "/usr/bin/arogue5"
+  },
+  "arogue7": {
+    "name": "Advanced Rogue 7",
+    "uname": "arogue7",
+    "suffix": ".ar7sav",
+    "path": "/usr/bin/arogue7"
+  },
+  "xrogue": {
+    "name": "XRogue",
+    "uname": "xrogue",
+    "suffix": ".xrsav",
+    "path": "/usr/bin/xrogue"
+  }
+};
+
+/* Global state */
+var logins = {};
+var sessions = {};
+var dglgames = {};
+var allowlogin = true;
+var gamemux = new events.EventEmitter();
+
+/* A base class.  TermSession and DglSession inherit from it. */
+function BaseGame() {
+  /* Games subclass EventEmitter, though there are few listeners. */
+  events.EventEmitter.call(this);
+  /* Array of watching WebSockets. */
+  this.watchers = [];
+  /* replaybuf holds the output since the last screen clear, so watchers can
+   * begin with a complete screen. replaylen is the number of bytes stored. */
+  this.replaybuf = new Buffer(1024);
+  this.replaylen = 0;
+  /* Time of last activity. */
+  this.lasttime = new Date();
+}
+BaseGame.prototype = new events.EventEmitter();
+
+BaseGame.prototype.tag = function () {
+  if (this.pname === undefined || this.gname === undefined)
+    return "";
+  return this.gname + "/" + this.pname;
+};
+
+BaseGame.prototype.framepush = function(chunk) {
+  /* If this chunk resets the screen, discard what preceded it. */
+  if (isclear(chunk)) {
+    this.replaybuf = new Buffer(1024);
+    this.replaylen = 0;
+  }
+  /* Make sure there's space. */
+  while (this.replaybuf.length < chunk.length + this.replaylen) {
+    var nbuf = new Buffer(this.replaybuf.length * 2);
+    this.replaybuf.copy(nbuf, 0, 0, this.replaylen);
+    this.replaybuf = nbuf;
+    if (this.replaybuf.length > 65536) {
+      tslog("Warning: %s frame buffer at %d bytes", this.tag(), 
+              this.replaybuf.length);
+    }
+  }
+  chunk.copy(this.replaybuf, this.replaylen);
+  this.replaylen += chunk.length;
+};
+
+/* Adds a watcher. */
+BaseGame.prototype.attach = function (wsReq) {
+  var conn = wsReq.accept(null, wsReq.origin);
+  conn.sendUTF(JSON.stringify({
+          "t": "w", "w": this.w, "h": this.h, "p": this.pname, "g": this.gname
+  }));
+  conn.sendUTF(JSON.stringify({"t": "d",
+      "d": this.replaybuf.toString("hex", 0, this.replaylen)}));
+  conn.on('close', this.detach.bind(this, conn));
+  this.watchers.push(conn);
+};
+
+BaseGame.prototype.detach = function (socket) {
+  var n = this.watchers.indexOf(socket);
+  if (n >= 0) {
+    this.watchers.splice(n, 1);
+    tslog("A WebSocket watcher has left game %s", this.tag());
+  }
+};
+
+/* Constructor.  A TermSession handles a pty and the game running on it.
+ *   gname: (String) Name of the game to launch.
+ *   pname: (String) The player's name.
+ *   wsReq: (WebSocketRequest) The request from the client.
+ *
+ *  Events:
+ *   "data": Data generated by child. Parameters: buf (Buffer)
+ *   "exit": Child terminated. Parameters: none
+ */
+function TermSession(gname, pname, wsReq) {
+  BaseGame.call(this);
+  /* Don't launch anything that's not a real game. */
+  if (gname in games) {
+    this.game = games[gname];
+    this.gname = gname;
+  }
+  else {
+    this.failed = true;
+    wsReq.reject(404, errorcodes[2], "No such game");
+    tslog("Game %s is not available", game);
+    return;
+  }
+  this.pname = pname;
+  /* Set up the sizes. */
+  this.w = Math.floor(Number(wsReq.resourceURL.query.w));
+  if (!(this.w > 0 && this.w < 256))
+    this.w = 80;
+  this.h = Math.floor(Number(wsReq.resourceURL.query.h));
+  if (!(this.h > 0 && this.h < 256))
+    this.h = 24;
+  /* Environment. */
+  var childenv = {};
+  for (var key in process.env) {
+    childenv[key] = process.env[key];
+  }
+  var args = ["-n", this.pname];
+  var spawnopts = {"env": childenv, "cwd": "/", "rows": this.h, "cols": this.w,
+                   "name": "xterm-256color"};
+  this.term = pty.spawn(this.game.path, args, spawnopts);
+  tslog("%s playing %s (pid %d)", this.pname, this.gname, this.term.pid);
+  this.failed = false;
+  sessions[this.gname + "/" + this.pname] = this;
+  gamemux.emit('begin', this.gname, this.pname, 'rlg');
+  /* Set up the lockfile and ttyrec */
+  var ts = timestamp(this.lasttime);
+  var progressdir = path.join("/dgldir/inprogress", this.gname);
+  this.lock = path.join(progressdir, this.pname + ":node:" + ts + ".ttyrec");
+  var lmsg = this.term.pid.toString() + '\n' + this.h + '\n' + this.w + '\n'; 
+  fs.writeFile(this.lock, lmsg, "utf8"); 
+  var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.gname, 
+               ts + ".ttyrec");
+  this.record = fs.createWriteStream(ttyrec, { mode: 0664 });
+  /* The player's WebSocket and its handlers. */
+  this.playerconn = wsReq.accept(null, wsReq.origin);
+  this.playerconn.on('message', this.input_msg.bind(this));
+  this.playerconn.on('close', this.close.bind(this));
+  /* Send initial data. */
+  this.playerconn.sendUTF(JSON.stringify({"t": "s", "w": this.w, "h": this.h, 
+               "p": this.pname, "g": this.gname}));
+  /* Begin the ttyrec with some metadata, like dgamelaunch does. */
+  var descstr = "\x1b[2J\x1b[1;1H\r\n";
+  descstr += "Player: " + this.pname + "\r\nGame: " + this.game.name + "\r\n";
+  descstr += "Server: Roguelike Gallery - rlgallery.org\r\n";
+  descstr += "Filename: " + ts + ".ttyrec\r\n";
+  descstr += "Time: (" + Math.floor(this.lasttime.getTime() / 1000) + ") ";
+  descstr += this.lasttime.toUTCString().slice(0, -4) + "\r\n";
+  descstr += "Size: " + this.w + "x" + this.h + "\r\n\x1b[2J";
+  this.write_ttyrec(descstr);