Browse Source

Merge branch 'master' into master

pull/98/head
Cian Butler 6 years ago
committed by GitHub
parent
commit
cedc65cb97
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      .babelrc
  2. 2
      .dockerignore
  3. 4
      .eslintignore
  4. 36
      .eslintrc.js
  5. 3
      .gitignore
  6. 21
      .npmignore
  7. 20
      Dockerfile
  8. 37
      Gruntfile.js
  9. 4
      LICENSE
  10. 178
      README.md
  11. 134
      app.js
  12. 3
      bin/index.js
  13. 15
      bin/ssh
  14. 3
      bin/wetty.js
  15. 6
      bin/wetty.service
  16. 95
      cli.mjs
  17. 15
      docker-compose.yml
  18. 40
      gulpfile.js
  19. 2
      index.js
  20. 121
      package.json
  21. BIN
      public/favicon.ico
  22. 30
      public/index.html
  23. 53
      public/wetty/index.html
  24. 64
      public/wetty/wetty.js
  25. 1
      public/wetty/wetty.min.js
  26. 37
      src/.eslintrc.js
  27. 5174
      src/hterm_all.js
  28. 84
      src/wetty.js
  29. 88
      wetty.mjs
  30. 4166
      yarn.lock

11
.babelrc

@ -0,0 +1,11 @@
{
"presets": [
[
"es2015",
{
"modules": false
}
]
],
"compact": true,
}

2
.dockerignore

@ -0,0 +1,2 @@
node_modules
.esm-cache

4
.eslintignore

@ -0,0 +1,4 @@
node_modules/
public/
.esm-cache
*hterm*

36
.eslintrc.js

@ -0,0 +1,36 @@
module.exports = {
env: {
es6 : true,
node: true,
},
extends: ['airbnb'],
rules : {
'linebreak-style' : ['error', 'unix'],
'arrow-parens' : ['error', 'as-needed'],
'no-param-reassign' : ['error', { props: false }],
'func-style' : ['error', 'declaration', { allowArrowFunctions: true }],
'no-use-before-define': ['error', { functions: false }],
'no-shadow' : [
'error',
{
builtinGlobals: true,
hoist : 'functions',
allow : ['resolve', 'reject', 'err'],
},
],
'no-console': [
'error',
{
allow: ['warn', 'trace', 'log', 'error'],
},
],
'consistent-return': 0,
'key-spacing' : [
'error',
{
multiLine: { beforeColon: false, afterColon: true },
align : { beforeColon: false, afterColon: true, on: 'colon', mode: 'strict' },
},
],
},
};

3
.gitignore

@ -13,4 +13,5 @@ logs
results
npm-debug.log
node_modules/*
node_modules/*
.esm-cache

21
.npmignore

@ -0,0 +1,21 @@
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
tmp
pids
logs
results
npm-debug.log
node_modules/*
.esm-cache
src
*.yml
Dockerfile
*.png

20
Dockerfile

@ -1,15 +1,9 @@
FROM node:0.10.38
MAINTAINER Nathan LeClaire <nathan@docker.com>
ADD . /app
FROM node:8-alpine
MAINTAINER butlerx@notthe.cloud
WORKDIR /app
RUN npm install
RUN apt-get update
RUN apt-get install -y vim
RUN useradd -d /home/term -m -s /bin/bash term
RUN echo 'term:term' | chpasswd
RUN adduser -D -h /home/term -s /bin/sh term && \
echo "term:term" | chpasswd
EXPOSE 3000
ENTRYPOINT ["node"]
CMD ["app.js", "-p", "3000"]
COPY . /app
RUN apk add --update build-base python openssh && yarn
CMD yarn start

37
Gruntfile.js

@ -1,37 +0,0 @@
module.exports = function (grunt) {
require('load-grunt-tasks')(grunt);
var config = {
mkdir: {
tmp: {
options: {
create: ['tmp']
}
}
},
gitclone: {
hterm: {
options: {
cwd: './tmp',
repository: 'https://chromium.googlesource.com/apps/libapps'
}
}
},
shell: {
build_hterm: {
command: 'LIBDOT_SEARCH_PATH=$(pwd) ./libdot/bin/concat.sh -i ./hterm/concat/hterm_all.concat -o ../../public/wetty/hterm_all.js',
options: {
execOptions: {
cwd: './tmp/libapps'
}
}
}
},
clean: ['./tmp']
};
grunt.initConfig(config);
grunt.registerTask('update-hterm', ['mkdir:tmp', 'gitclone:hterm', 'shell:build_hterm', 'clean']);
};

4
LICENSE

@ -1,4 +1,4 @@
The MIT License (MIT)
MIT License
Copyright (c) 2014 Krishna Srinivas
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

178
README.md

@ -1,107 +1,165 @@
Wetty = Web + tty
-----------------
## Wetty = Web + tty
Terminal over HTTP and HTTPS. Wetty is an alternative to
ajaxterm/anyterm but much better than them because wetty uses ChromeOS'
terminal emulator (hterm) which is a full fledged implementation of
terminal emulation written entirely in Javascript. Also it uses
websockets instead of Ajax and hence better response time.
Terminal over HTTP and HTTPS. Wetty is an alternative to ajaxterm/anyterm but
much better than them because wetty uses ChromeOS' terminal emulator (hterm)
which is a full fledged implementation of terminal emulation written entirely in
Javascript. Also it uses websockets instead of Ajax and hence better response
time.
hterm source - https://chromium.googlesource.com/apps/libapps/+/master/hterm/
[hterm source](https://chromium.googlesource.com/apps/libapps/+/master/hterm/)
![Wetty](/terminal.png?raw=true)
Install
-------
## Install
* `npm install -g wetty`
Run on HTTP:
-----------
## Run on HTTP
wetty -p 3000
```bash
wetty -p 3000
```
If you run it as root it will launch `/bin/login` (where you can specify
the user name), else it will launch `ssh` and connect by default to
`localhost`.
If you run it as root it will launch `/bin/login` (where you can specify the
user name), else it will launch `ssh` and connect by default to `localhost`.
If instead you wish to connect to a remote host you can specify the
`--sshhost` option, the SSH port using the `--sshport` option and the
SSH user using the `--sshuser` option.
If instead you wish to connect to a remote host you can specify the `--sshhost`
option, the SSH port using the `--sshport` option and the SSH user using the
`--sshuser` option.
You can also specify the SSH user name in the address bar like this:
`http://yourserver:3000/wetty/ssh/<username>`
`http://yourserver:3000/wetty/ssh/<username>`
or
`http://yourserver:3000/ssh/<username>`
Run on HTTPS:
------------
## Run on HTTPS
Always use HTTPS! If you don't have SSL certificates from a CA you can
create a self signed certificate using this command:
Always use HTTPS. If you don't have SSL certificates from a CA you can create a
self signed certificate using this command:
`openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 30000 -nodes`
```
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 30000 -nodes
```
And then run:
wetty --sslkey key.pem --sslcert cert.pem -p 3000
Again, if you run it as root it will launch `/bin/login`, else it will
launch SSH to `localhost` or a specified host as explained above.
Again, if you run it as root it will launch `/bin/login`, else it will launch
SSH to `localhost` or a specified host as explained above.
Run wetty behind nginx:
----------------------
## Run wetty behind nginx or apache
Put the following configuration in nginx's conf:
location /wetty {
proxy_pass http://127.0.0.1:3000/wetty;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 43200000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:3000/wetty;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 43200000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
}
If you are running `app.js` as `root` and have an Nginx proxy you have to use:
Put the following configuration in apache's conf:
http://yourserver.com/wetty
RewriteCond %{REQUEST_URI} ^/wetty/socket.io [NC]
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule /wetty/socket.io/(.*) ws://localhost:9123/wetty/socket.io/$1 [P,L]
Else if you are running `app.js` as a regular user you have to use:
<LocationMatch ^/wetty/(.*)>
DirectorySlash On
Require all granted
ProxyPassMatch http://127.0.0.1:9123
ProxyPassReverse /wetty/
</LocationMatch>
http://yourserver.com/wetty/ssh/<username>
If you are running `bin/index.js` as `root` and have an Nginx proxy you have to
use:
**Note that if your Nginx is configured for HTTPS you should run wetty without SSL.**
```
http://yourserver.com/wetty
```
Dockerized Version
------------------
Else if you are running `bin/index.js` as a regular user you can use:
This repo includes a Dockerfile you can use to run a Dockerized version of wetty. You can run
whatever you want!
```
http://yourserver.com/wetty/ssh/<username>
```
Just do:
or
```
docker run --name term -p 3000 -dt krishnasrinivas/wetty
http://yourserver.com/wetty
```
Visit the appropriate URL in your browser (`[localhost|$(boot2docker ip)]:PORT`).
The username is `term` and the password is `term`.
**Note that if your Nginx is configured for HTTPS you should run wetty without
SSL.**
## Dockerized Version
This repo includes a Dockerfile you can use to run a Dockerized version of
wetty. You can run whatever you want!
Just modify docker-compose and run:
```
docker-compose up -d
```
Run wetty as a service daemon
-----------------------------
Visit the appropriate URL in your browser
(`[localhost|$(boot2docker ip)]:PORT`).
Install wetty globally with -g option:
The default username is `term` and the password is `term`, if you did not modify
`SSHHOST`
If you dont want to build the image yourself just remove the line `build; .`
## Run wetty as a service daemon
Install wetty globally with global option:
### init.d
```bash
$ sudo yarn global add wetty
$ sudo cp /usr/local/lib/node_modules/wetty.js/bin/wetty.conf /etc/init
$ sudo start wetty
```
### systemd
```bash
$ sudo npm install wetty -g
$ sudo cp /usr/local/lib/node_modules/wetty/bin/wetty.conf /etc/init
$ sudo start wetty
$ yarn global add wetty
$ cp ~/.config/yarn/global/node_modules/wetty.js/bin/wetty.service ~/.config/systemd/user/
$ systemctl --user enable wetty
$ systemctl --user start wetty
```
This will start wetty on port 3000. If you want to change the port or redirect stdout/stderr you should change the last line in `wetty.conf` file, something like this:
This will start wetty on port 3000. If you want to change the port or redirect
stdout/stderr you should change the last line in `wetty.conf` file, something
like this:
```
exec sudo -u root wetty -p 80 >> /var/log/wetty.log 2>&1
```
## FAQ
### What browsers are supported?
Wetty supports all browsers that Google's hterm supports. Wetty has been
[reported](https://github.com/krishnasrinivas/wetty/issues/45#issuecomment-181448586)
to work on Google Chrome, Firefox and IE 11.
### Why isn't Wetty working with IE?
exec sudo -u root wetty -p 80 >> /var/log/wetty.log 2>&1
[This fix](https://stackoverflow.com/questions/13102116/access-denied-for-localstorage-in-ie10#20848924)
has been known to help some users.

134
app.js

@ -1,134 +0,0 @@
var express = require('express');
var http = require('http');
var https = require('https');
var path = require('path');
var server = require('socket.io');
var pty = require('pty.js');
var fs = require('fs');
var opts = require('optimist')
.options({
sslkey: {
demand: false,
description: 'path to SSL key'
},
sslcert: {
demand: false,
description: 'path to SSL certificate'
},
sshhost: {
demand: false,
description: 'ssh server host'
},
sshport: {
demand: false,
description: 'ssh server port'
},
sshuser: {
demand: false,
description: 'ssh user'
},
sshauth: {
demand: false,
description: 'defaults to "password", you can use "publickey,password" instead'
},
port: {
demand: true,
alias: 'p',
description: 'wetty listen port'
},
}).boolean('allow_discovery').argv;
var runhttps = false;
var sshport = 22;
var sshhost = 'localhost';
var sshauth = 'password,keyboard-interactive';
var globalsshuser = '';
if (opts.sshport) {
sshport = opts.sshport;
}
if (opts.sshhost) {
sshhost = opts.sshhost;
}
if (opts.sshauth) {
sshauth = opts.sshauth
}
if (opts.sshuser) {
globalsshuser = opts.sshuser;
}
if (opts.sslkey && opts.sslcert) {
runhttps = true;
opts['ssl'] = {};
opts.ssl['key'] = fs.readFileSync(path.resolve(opts.sslkey));
opts.ssl['cert'] = fs.readFileSync(path.resolve(opts.sslcert));
}
process.on('uncaughtException', function(e) {
console.error('Error: ' + e);
});
var httpserv;
var app = express();
app.get('/wetty/ssh/:user', function(req, res) {
res.sendfile(__dirname + '/public/wetty/index.html');
});
app.use('/', express.static(path.join(__dirname, 'public')));
if (runhttps) {
httpserv = https.createServer(opts.ssl, app).listen(opts.port, function() {
console.log('https on port ' + opts.port);
});
} else {
httpserv = http.createServer(app).listen(opts.port, function() {
console.log('http on port ' + opts.port);
});
}
var io = server(httpserv,{path: '/wetty/socket.io'});
io.on('connection', function(socket){
var sshuser = '';
var request = socket.request;
console.log((new Date()) + ' Connection accepted.');
if (match = request.headers.referer.match('/wetty/ssh/.+$')) {
sshuser = match[0].replace('/wetty/ssh/', '') + '@';
} else if (globalsshuser) {
sshuser = globalsshuser + '@';
}
var term;
if (process.getuid() == 0) {
term = pty.spawn('/usr/bin/env', ['login'], {
name: 'xterm-256color',
cols: 80,
rows: 30
});
} else {
term = pty.spawn('ssh', [sshuser + sshhost, '-p', sshport, '-o', 'PreferredAuthentications=' + sshauth], {
name: 'xterm-256color',
cols: 80,
rows: 30
});
}
console.log((new Date()) + " PID=" + term.pid + " STARTED on behalf of user=" + sshuser)
term.on('data', function(data) {
socket.emit('output', data);
});
term.on('exit', function(code) {
console.log((new Date()) + " PID=" + term.pid + " ENDED")
});
socket.on('resize', function(data) {
term.resize(data.col, data.row);
});
socket.on('input', function(data) {
term.write(data);
});
socket.on('disconnect', function() {
term.end();
});
})

3
bin/index.js

@ -0,0 +1,3 @@
#! /usr/bin/env node
require = require('@std/esm')(module); // eslint-disable-line no-global-assign
require('../cli.mjs');

15
bin/ssh

@ -0,0 +1,15 @@
#!/usr/bin/env sh
userAtAddress="$1"
USER=$(echo "$userAtAddress" | cut -d"@" -f1);
HOST=$(echo "$userAtAddress" | cut -d"@" -f2);
if [ "$USER" = "$HOST" ]
then
printf "Enter your username: "
read -r USER
USER=$(echo "${USER}" | tr -d '[:space:]')
ssh "$USER"@"$HOST"
else
ssh "$userAtAddress"
fi

3
bin/wetty.js

@ -1,3 +0,0 @@
#!/usr/bin/env node
require("../app");

6
bin/wetty.service

@ -9,11 +9,9 @@ Description=Wetty Web Terminal
After=network.target
[Service]
User=root
Group=root
WorkingDirectory=/home/akadmin/pack/wetty # CHANGE ME
ExecStart=/usr/bin/node app.js -p 9123 --host 127.0.0.1
WorkingDirectory=$HOME/.config/yarn/global/node_modules/wetty.js/
ExecStart=/usr/bin/node app.js -p 3000 --host 127.0.0.1
[Install]
WantedBy=multi-user.target

95
cli.mjs

@ -0,0 +1,95 @@
import fs from 'fs-extra';
import path from 'path';
import optimist from 'optimist';
import wetty from './wetty';
const opts = optimist
.options({
sslkey: {
demand : false,
description: 'path to SSL key',
},
sslcert: {
demand : false,
description: 'path to SSL certificate',
},
sshhost: {
demand : false,
description: 'ssh server host',
},
sshport: {
demand : false,
description: 'ssh server port',
},
sshuser: {
demand : false,
description: 'ssh user',
},
sshauth: {
demand : false,
description: 'defaults to "password", you can use "publickey,password" instead',
},
port: {
demand : false,
alias : 'p',
description: 'wetty listen port',
},
help: {
demand : false,
alias : 'h',
description: 'Print help message',
},
})
.boolean('allow_discovery').argv;
if (opts.help) {
optimist.showHelp();
process.exit(0);
}
const sshuser = opts.sshuser || process.env.SSHUSER || '';
const sshhost = opts.sshhost || process.env.SSHHOST || 'localhost';
const sshauth = opts.sshauth || process.env.SSHAUTH || 'password,keyboard-interactive';
const sshport = opts.sshport || process.env.SSHPOST || 22;
const port = opts.port || process.env.PORT || 3000;
loadSSL(opts)
.then(ssl => {
opts.ssl = ssl;
})
.catch(err => {
console.error(`Error: ${err}`);
process.exit(1);
});
process.on('uncaughtException', err => {
console.error(`Error: ${err}`);
});
const tty = wetty(port, sshuser, sshhost, sshport, sshauth, opts.ssl);
tty.on('exit', code => {
console.log(`exit with code: ${code}`);
});
tty.on('disconnect', () => {
console.log('disconnect');
});
function loadSSL({ sslkey, sslcert }) {
return new Promise((resolve, reject) => {
const ssl = {};
if (sslkey && sslcert) {
fs
.readFile(path.resolve(sslkey))
.then(key => {
ssl.key = key;
})
.then(fs.readFile(path.resolve(sslcert)))
.then(cert => {
ssl.cert = cert;
})
.then(resolve(ssl))
.catch(reject);
}
resolve(ssl);
});
}

15
docker-compose.yml

@ -0,0 +1,15 @@
version: "3"
services:
wetty:
build: .
image: butlerx/wetty
container_name: wetty
tty: true
working_dir: /app
ports:
- "3000:3000"
environment:
PORT: 3000
SSHHOST: 'localhost'
SSHPORT: 22

40
gulpfile.js

@ -0,0 +1,40 @@
const gulp = require('gulp');
const concat = require('gulp-concat');
const minify = require('gulp-minify');
const babel = require('gulp-babel');
const shell = require('gulp-shell');
const del = require('del');
gulp.task('compress', [], () =>
gulp
.src(['./src/hterm_all.js', './src/wetty.js'])
.pipe(concat('wetty.js'))
.pipe(babel())
.pipe(
minify({
ext: {
min: '.min.js',
},
exclude : ['tasks'],
noSource : true,
ignoreFiles: ['.combo.js', '*.min.js'],
}),
)
.pipe(gulp.dest('./public/wetty')),
);
gulp.task(
'hterm',
shell.task(
[
'git clone https://chromium.googlesource.com/apps/libapps',
'LIBDOT_SEARCH_PATH=$(pwd)/libapps ./libapps/libdot/bin/concat.sh -i ./libapps/hterm/concat/hterm_all.concat -o ./src/hterm_all.js',
],
{
verbose: true,
},
),
);
gulp.task('default', ['compress']);
gulp.task('upgrade', ['hterm'], () => del(['./libapps']));

2
index.js

@ -0,0 +1,2 @@
require = require('@std/esm')(module); // eslint-disable-line no-global-assign
module.exports = require('./wetty.mjs').default;

121
package.json

@ -1,34 +1,91 @@
{
"name": "wetty",
"version": "0.2.0",
"dependencies": {
"express": "3.5.1",
"socket.io": "^1.3.7",
"pty.js": "^0.3.0",
"optimist": "^0.6"
},
"devDependencies": {
"load-grunt-tasks": "^3.0",
"grunt": "^0.4",
"grunt-shell": "^1.1",
"grunt-mkdir": "^0.1",
"grunt-git": "^0.3",
"grunt-contrib-clean": "^0.6"
},
"description": "Wetty = Web + tty. Terminal access in browser over http/https ",
"main": "app.js",
"repository": {
"type": "git",
"url": "git://github.com/krishnasrinivas/wetty.git"
},
"author": "Krishna Srinivas <krishna.srinivas@gmail.com> (https://github.com/krishnasrinivas)",
"license": "MIT",
"bugs": {
"url": "https://github.com/krishnasrinivas/wetty/issues"
},
"homepage": "https://github.com/krishnasrinivas/wetty",
"preferGlobal": "true",
"bin": {
"wetty": "./bin/wetty.js"
}
"name": "wetty.js",
"version": "0.5.1",
"description": "Wetty = Web + tty. Terminal access in browser over http/https ",
"homepage": "https://github.com/krishnasrinivas/wetty",
"repository": {
"type": "git",
"url": "git://github.com/krishnasrinivas/wetty.git"
},
"author": "Krishna Srinivas <krishna.srinivas@gmail.com> (https://github.com/krishnasrinivas)",
"license": "MIT",
"bugs": {
"url": "https://github.com/butlerx/wetty/issues"
},
"main": "index.js",
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"build": "gulp",
"start": "node bin",
"commit": "git add public",
"fix": "eslint . --fix"
},
"bin": {
"wetty": "./bin/index.js"
},
"pre-commit": [
"fix",
"commit"
],
"preferGlobal": "true",
"@std/esm": {
"cjs": "true"
},
"dependencies": {
"@std/esm": "^0.12.1",
"express": "^4.15.3",
"fs-extra": "^4.0.1",
"optimist": "^0.6",
"pre-commit": "^1.2.2",
"node-pty": "^0.7.4",
"serve-favicon": "^2.4.3",
"socket.io": "^1.3.7"
},
"devDependencies": {
"babel-cli": "6.24.1",
"babel-core": "6.24.1",
"babel-eslint": "7.2.3",
"babel-plugin-add-module-exports": "0.2.1",
"babel-plugin-es6-promise": "1.1.1",
"babel-plugin-syntax-async-functions": "6.13.0",
"babel-plugin-transform-async-to-generator": "6.24.1",
"babel-plugin-transform-object-assign": "6.22.0",
"babel-preset-es2015": "6.24.1",
"del": "^3.0.0",
"es6-promise": "^4.1.1",
"eslint": "3.19.0",
"eslint-config-airbnb": "^15.1.0",
"eslint-config-standard": "10.2.1",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-jsx-a11y": "^5.1.1",
"eslint-plugin-node": "^5.1.1",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-react": "^7.1.0",
"eslint-plugin-standard": "^3.0.1",
"gulp": "^3.9.1",
"gulp-babel": "^6.1.2",
"gulp-concat": "^2.6.1",
"gulp-minify": "^1.0.0",
"gulp-shell": "^0.6.3"
},
"contributors": [
"Krishna Srinivas <krishna.srinivas@gmail.com>",
"butlerx <butlerx@notthe.cloud>",
"Boyan Rabchev <boyan@rabchev.com>",
"Boyan Rabchev <TELERIK\\rabchev@rabchevlnx.telerik.com>",
"Luca Milanesio <luca.milanesio@gmail.com>",
"Antonio Calatrava <antonio@antoniocalatrava.com>",
"Krishna Srinivas <krishna@minio.io>",
"Strubbl <github@linux4tw.de>",
"Jarrett Gilliam <jarrettgilliam@gmail.com>",
"Nathan LeClaire <nathan.leclaire@docker.com>",
"nosemeocurrenada <nosemeocurrenada93@gmail.com>",
"Andreas Kloeckner <inform@tiker.net>",
"Farhan Khan <khanzf@gmail.com>",
"Imuli <i@imu.li>",
"James Turnbull <james@lovedthanlost.net>",
"Kasper Holbek Jensen <kholbekj@gmail.com>",
"mirtouf <mirtouf@gmail.com>"
]
}

BIN
public/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

30
public/index.html

@ -1,30 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Wetty - The WebTTY Terminal Emulator</title>
<script src="/wetty/hterm_all.js"></script>
<script src="/wetty/socket.io/socket.io.js"></script>
<script src="/wetty/wetty.js"></script>
<style>
html,
body {
height: 100%;
width: 100%;
margin: 0px;
}
#terminal {
display: block;
position: relative;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="terminal"></div>
</body>
</html>

53
public/wetty/index.html

@ -1,30 +1,41 @@
<!doctype html>
<html lang="en">
<head>
<head>
<meta charset="UTF-8">
<title>Wetty - The WebTTY Terminal Emulator</title>
<script src="/wetty/hterm_all.js"></script>
<script src="/wetty/socket.io/socket.io.js"></script>
<script src="/wetty/wetty.js"></script>
<style>
html,
body {
height: 100%;
width: 100%;
margin: 0px;
}
#terminal {
display: block;
position: relative;
width: 100%;
height: 100%;
}
html, body {
height: 100%;
width: 100%;
margin: 0px;
}
#overlay {
position: absolute;
height: 100%;
width: 100%;
background-color: rgba(0,0,0,0.75);;
display: none;
z-index: 100;
}
#overlay input {
display: block;
margin: auto;
position: relative;
top: 50%;
transform: translateY(-50%);
}
#terminal {
display: block;
position: relative;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
</head>
<body>
<div id="overlay"><input type="button" onclick="location.reload();" value="reconnect" /></div>
<div id="terminal"></div>
</body>
<script src="/wetty/wetty.min.js"></script>
</body>
</html>

64
public/wetty/wetty.js

@ -1,64 +0,0 @@
var term;
var socket = io(location.origin, {path: '/wetty/socket.io'})
var buf = '';
function Wetty(argv) {
this.argv_ = argv;
this.io = null;
this.pid_ = -1;
}
Wetty.prototype.run = function() {
this.io = this.argv_.io.push();
this.io.onVTKeystroke = this.sendString_.bind(this);
this.io.sendString = this.sendString_.bind(this);
this.io.onTerminalResize = this.onTerminalResize.bind(this);
}
Wetty.prototype.sendString_ = function(str) {
socket.emit('input', str);
};
Wetty.prototype.onTerminalResize = function(col, row) {
socket.emit('resize', { col: col, row: row });
};
socket.on('connect', function() {
lib.init(function() {
hterm.defaultStorage = new lib.Storage.Local();
term = new hterm.Terminal();
window.term = term;
term.decorate(document.getElementById('terminal'));
term.setCursorPosition(0, 0);
term.setCursorVisible(true);
term.prefs_.set('ctrl-c-copy', true);
term.prefs_.set('ctrl-v-paste', true);
term.prefs_.set('use-default-window-copy', true);
term.runCommandClass(Wetty, document.location.hash.substr(1));
socket.emit('resize', {
col: term.screenSize.width,
row: term.screenSize.height
});
if (buf && buf != '')
{
term.io.writeUTF16(buf);
buf = '';
}
});
});
socket.on('output', function(data) {
if (!term) {
buf += data;
return;
}
term.io.writeUTF16(data);
});
socket.on('disconnect', function() {
console.log("Socket.io connection closed");
});

1
public/wetty/wetty.min.js

File diff suppressed because one or more lines are too long

37
src/.eslintrc.js

@ -0,0 +1,37 @@
module.exports = {
env: {
es6 : true,
browser: true,
},
globals: {
hterm: true,
lib : true,
io : true,
},
extends: ['airbnb'],
rules : {
'no-underscore-dangle': 0,
'class-methods-use-this': 0,
'linebreak-style' : ['error', 'unix'],
'arrow-parens' : ['error', 'as-needed'],
'no-param-reassign' : ['error', { props: false }],
'func-style' : ['error', 'declaration', { allowArrowFunctions: true }],
'no-use-before-define': ['error', { functions: false }],
'no-shadow' : [
'error',
{
builtinGlobals: true,
hoist : 'functions',
allow : ['resolve', 'reject', 'err'],
},
],
'consistent-return': 0,
'key-spacing' : [
'error',
{
multiLine: { beforeColon: false, afterColon: true },
align : { beforeColon: false, afterColon: true, on: 'colon', mode: 'strict' },
},
],
},
};

5174
public/wetty/hterm_all.js → src/hterm_all.js

File diff suppressed because it is too large

84
src/wetty.js

@ -0,0 +1,84 @@
const socket = io(location.origin, { path: '/wetty/socket.io' });
let term;
let buf = '';
class Wetty {
constructor(argv) {
this.argv_ = argv;
this.io = null;
this.pid_ = -1;
}
run() {
this.io = this.argv_.io.push();
this.io.onVTKeystroke = this.sendString_.bind(this);
this.io.sendString = this.sendString_.bind(this);
this.io.onTerminalResize = this.onTerminalResize.bind(this);
}
sendString_(str) {
socket.emit('input', str);
}
onTerminalResize(col, row) {
socket.emit('resize', { col, row });
}
}
socket.on('connect', () => {
document.getElementById('overlay').style.display = 'none';
window.addEventListener('beforeunload', handler, false);
lib.init(() => {
hterm.defaultStorage = new lib.Storage.Local();
term = new hterm.Terminal();
window.term = term;
term.decorate(document.getElementById('terminal'));
term.setCursorPosition(0, 0);
term.setCursorVisible(true);
term.prefs_.set('ctrl-c-copy', true);
term.prefs_.set('ctrl-v-paste', true);
term.prefs_.set('use-default-window-copy', true);
term.prefs_.set('send-encoding', 'raw');
term.prefs_.set('receive-encoding', 'raw');
term.prefs_.set('font-size', 14);
term.scrollPort_.screen_.setAttribute('spellcheck', 'false');
term.scrollPort_.screen_.setAttribute('autocorrect', 'false');
term.scrollPort_.screen_.setAttribute('autocomplete', 'false');
term.scrollPort_.screen_.setAttribute('contenteditable', 'false');
term.runCommandClass(Wetty, document.location.hash.substr(1));
socket.emit('resize', {
col: term.screenSize.width,
row: term.screenSize.height,
});
if (buf && buf !== '') {
term.io.writeUTF8(buf);
buf = '';
}
});
});
socket.on('output', data => {
if (!term) {
buf += data;
return;
}
term.io.writeUTF8(data);
});
socket.on('logout', () => {
document.getElementById('overlay').style.display = 'block';
window.removeEventListener('beforeunload', handler, false);
});
socket.on('disconnect', () => {
document.getElementById('overlay').style.display = 'block';
window.removeEventListener('beforeunload', handler, false);
});
function handler(e) {
e.returnValue = 'Are you sure?';
return e.returnValue;
}

88
wetty.mjs

@ -0,0 +1,88 @@
import express from 'express';
import http from 'http';
import https from 'https';
import path from 'path';
import server from 'socket.io';
import { spawn } from 'node-pty';
import EventEmitter from 'events';
import favicon from 'serve-favicon';
const app = express();
app.use(favicon(`${__dirname}/public/favicon.ico`));
// For using wetty at /wetty on a vhost
app.get('/wetty/ssh/:user', (req, res) => {
res.sendFile(`${__dirname}/public/wetty/index.html`);
});
app.get('/wetty/', (req, res) => {
res.sendFile(`${__dirname}/public/wetty/index.html`);
});
// For using wetty on a vhost by itself
app.get('/ssh/:user', (req, res) => {
res.sendFile(`${__dirname}/public/wetty/index.html`);
});
app.get('/', (req, res) => {
res.sendFile(`${__dirname}/public/wetty/index.html`);
});
// For serving css and javascript
app.use('/', express.static(path.join(__dirname, 'public')));
function createServer(port, sslopts) {
return sslopts && sslopts.key && sslopts.cert
? https.createServer(sslopts, app).listen(port, () => {
console.log(`https on port ${port}`);
})
: http.createServer(app).listen(port, () => {
console.log(`http on port ${port}`);
});
}
function getCommand(socket, sshuser, sshhost, sshport, sshauth) {
const { request } = socket;
const match = request.headers.referer.match('.+/ssh/.+$');
const sshAddress = sshuser ? `${sshuser}@${sshhost}` : sshhost;
const ssh = match ? `${match[0].split('/ssh/').pop()}@${sshhost}` : sshAddress;
return [
process.getuid() === 0 && sshhost === 'localhost'
? ['login', '-h', socket.client.conn.remoteAddress.split(':')[3]]
: [
path.join(__dirname, 'bin/ssh'),
ssh,
'-p',
sshport,
'-o',
`PreferredAuthentications=${sshauth}`,
],
ssh,
];
}
export default function start(port, sshuser, sshhost, sshport, sshauth, sslopts) {
const events = new EventEmitter();
const io = server(createServer(port, sslopts), { path: '/wetty/socket.io' });
io.on('connection', socket => {
console.log(`${new Date()} Connection accepted.`);
const [args, ssh] = getCommand(socket, sshuser, sshhost, sshport, sshauth);
const term = spawn('/usr/bin/env', args, {
name: 'xterm-256color',
cols: 80,
rows: 30,
});
console.log(`${new Date()} PID=${term.pid} STARTED on behalf of user=${ssh}`);
term.on('data', data => socket.emit('output', data));
term.on('exit', code => {
console.log(`${new Date()} PID=${term.pid} ENDED`);
socket.emit('logout');
events.emit('exit', code);
});
socket.on('resize', ({ col, row }) => term.resize(col, row));
socket.on('input', input => term.write(input));
socket.on('disconnect', () => {
term.end();
term.destroy();
events.emit('disconnect');
});
});
return events;
}

4166
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save