committed by
GitHub
30 changed files with 8629 additions and 1830 deletions
@ -0,0 +1,11 @@ |
|||||
|
{ |
||||
|
"presets": [ |
||||
|
[ |
||||
|
"es2015", |
||||
|
{ |
||||
|
"modules": false |
||||
|
} |
||||
|
] |
||||
|
], |
||||
|
"compact": true, |
||||
|
} |
@ -0,0 +1,2 @@ |
|||||
|
node_modules |
||||
|
.esm-cache |
@ -0,0 +1,4 @@ |
|||||
|
node_modules/ |
||||
|
public/ |
||||
|
.esm-cache |
||||
|
*hterm* |
@ -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' }, |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
}; |
@ -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 |
@ -1,15 +1,9 @@ |
|||||
FROM node:0.10.38 |
FROM node:8-alpine |
||||
MAINTAINER Nathan LeClaire <nathan@docker.com> |
MAINTAINER butlerx@notthe.cloud |
||||
|
|
||||
ADD . /app |
|
||||
WORKDIR /app |
WORKDIR /app |
||||
RUN npm install |
RUN adduser -D -h /home/term -s /bin/sh term && \ |
||||
RUN apt-get update |
echo "term:term" | chpasswd |
||||
RUN apt-get install -y vim |
|
||||
RUN useradd -d /home/term -m -s /bin/bash term |
|
||||
RUN echo 'term:term' | chpasswd |
|
||||
|
|
||||
EXPOSE 3000 |
EXPOSE 3000 |
||||
|
COPY . /app |
||||
ENTRYPOINT ["node"] |
RUN apk add --update build-base python openssh && yarn |
||||
CMD ["app.js", "-p", "3000"] |
CMD yarn start |
||||
|
@ -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']); |
|
||||
}; |
|
@ -1,111 +1,173 @@ |
|||||
Wetty = Web + tty |
## Wetty = Web + tty |
||||
----------------- |
|
||||
|
|
||||
Terminal over HTTP and HTTPS. Wetty is an alternative to |
Terminal over HTTP and HTTPS. Wetty is an alternative to ajaxterm/anyterm but |
||||
ajaxterm/anyterm but much better than them because wetty uses ChromeOS' |
much better than them because wetty uses ChromeOS' terminal emulator (hterm) |
||||
terminal emulator (hterm) which is a full fledged implementation of |
which is a full fledged implementation of terminal emulation written entirely in |
||||
terminal emulation written entirely in Javascript. Also it uses |
Javascript. Also it uses websockets instead of Ajax and hence better response |
||||
websockets instead of Ajax and hence better response time. |
time. |
||||
|
|
||||
hterm source - https://chromium.googlesource.com/apps/libapps/+/master/hterm/ |
[hterm source](https://chromium.googlesource.com/apps/libapps/+/master/hterm/) |
||||
|
|
||||
 |
 |
||||
|
|
||||
Install |
## Install |
||||
------- |
|
||||
|
|
||||
* `git clone https://github.com/krishnasrinivas/wetty` |
* `git clone https://github.com/krishnasrinivas/wetty` |
||||
|
* `cd wetty` |
||||
|
* `yarn` |
||||
|
|
||||
* `cd wetty` |
or |
||||
|
|
||||
* `npm install` |
`yarn add wetty` |
||||
|
|
||||
Run on HTTP: |
## Run on HTTP |
||||
----------- |
|
||||
|
|
||||
node app.js -p 3000 |
```bash |
||||
|
node bin/index.js -p 3000 |
||||
|
``` |
||||
|
|
||||
If you run it as root it will launch `/bin/login` (where you can specify |
If you run it as root it will launch `/bin/login` (where you can specify the |
||||
the user name), else it will launch `ssh` and connect by default to |
user name), else it will launch `ssh` and connect by default to `localhost`. |
||||
`localhost`. |
|
||||
|
|
||||
If instead you wish to connect to a remote host you can specify the |
If instead you wish to connect to a remote host you can specify the `--sshhost` |
||||
`--sshhost` option, the SSH port using the `--sshport` option and the |
option, the SSH port using the `--sshport` option and the SSH user using the |
||||
SSH user using the `--sshuser` option. |
`--sshuser` option. |
||||
|
|
||||
You can also specify the SSH user name in the address bar like this: |
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 |
||||
|
|
||||
Run on HTTPS: |
`http://yourserver:3000/ssh/<username>` |
||||
------------ |
|
||||
|
|
||||
Always use HTTPS! If you don't have SSL certificates from a CA you can |
## Run on HTTPS |
||||
create a self signed certificate using this command: |
|
||||
|
|
||||
`openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 30000 -nodes` |
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 |
||||
|
``` |
||||
|
|
||||
And then run: |
And then run: |
||||
|
|
||||
node app.js --sslkey key.pem --sslcert cert.pem -p 3000 |
``` |
||||
|
node bin/index.js --sslkey key.pem --sslcert cert.pem -p 3000 |
||||
|
``` |
||||
|
|
||||
Again, if you run it as root it will launch `/bin/login`, else it will |
Again, if you run it as root it will launch `/bin/login`, else it will launch |
||||
launch SSH to `localhost` or a specified host as explained above. |
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: |
Put the following configuration in nginx's conf: |
||||
|
|
||||
location /wetty { |
location /wetty { |
||||
proxy_pass http://127.0.0.1:3000/wetty; |
proxy_pass http://127.0.0.1:3000/wetty; |
||||
proxy_http_version 1.1; |
proxy_http_version 1.1; |
||||
proxy_set_header Upgrade $http_upgrade; |
proxy_set_header Upgrade $http_upgrade; |
||||
proxy_set_header Connection "upgrade"; |
proxy_set_header Connection "upgrade"; |
||||
proxy_read_timeout 43200000; |
proxy_read_timeout 43200000; |
||||
|
|
||||
proxy_set_header X-Real-IP $remote_addr; |
proxy_set_header X-Real-IP $remote_addr; |
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
||||
proxy_set_header Host $http_host; |
proxy_set_header Host $http_host; |
||||
proxy_set_header X-NginX-Proxy true; |
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`). |
**Note that if your Nginx is configured for HTTPS you should run wetty without |
||||
The username is `term` and the password is `term`. |
SSL.** |
||||
|
|
||||
|
## Dockerized Version |
||||
|
|
||||
Run wetty as a service daemon |
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 |
||||
|
``` |
||||
|
|
||||
Install wetty globally with -g option: |
Visit the appropriate URL in your browser |
||||
|
(`[localhost|$(boot2docker ip)]:PORT`). |
||||
|
|
||||
|
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 |
```bash |
||||
$ sudo npm install wetty -g |
$ yarn global add wetty |
||||
$ sudo cp /usr/local/lib/node_modules/wetty/bin/wetty.conf /etc/init |
$ cp ~/.config/yarn/global/node_modules/wetty.js/bin/wetty.service ~/.config/systemd/user/ |
||||
$ sudo start wetty |
$ 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: |
||||
|
|
||||
``` |
``` |
||||
|
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. |
||||
|
|
||||
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: |
### 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. |
||||
|
@ -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(); |
|
||||
}); |
|
||||
}) |
|
@ -0,0 +1,3 @@ |
|||||
|
#! /usr/bin/env node
|
||||
|
require = require('@std/esm')(module); // eslint-disable-line no-global-assign
|
||||
|
require('../cli.mjs'); |
@ -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 |
@ -1,3 +0,0 @@ |
|||||
#!/usr/bin/env node
|
|
||||
|
|
||||
require("../app"); |
|
@ -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); |
||||
|
}); |
||||
|
} |
@ -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 |
@ -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'])); |
@ -0,0 +1,2 @@ |
|||||
|
require = require('@std/esm')(module); // eslint-disable-line no-global-assign
|
||||
|
module.exports = require('./wetty.mjs').default; |
@ -1,34 +1,91 @@ |
|||||
{ |
{ |
||||
"name": "wetty", |
"name": "wetty.js", |
||||
"version": "0.2.0", |
"version": "0.5.1", |
||||
"dependencies": { |
"description": "Wetty = Web + tty. Terminal access in browser over http/https ", |
||||
"express": "3.5.1", |
"homepage": "https://github.com/krishnasrinivas/wetty", |
||||
"socket.io": "^1.3.7", |
"repository": { |
||||
"pty.js": "^0.3.0", |
"type": "git", |
||||
"optimist": "^0.6" |
"url": "git://github.com/krishnasrinivas/wetty.git" |
||||
}, |
}, |
||||
"devDependencies": { |
"author": "Krishna Srinivas <krishna.srinivas@gmail.com> (https://github.com/krishnasrinivas)", |
||||
"load-grunt-tasks": "^3.0", |
"license": "MIT", |
||||
"grunt": "^0.4", |
"bugs": { |
||||
"grunt-shell": "^1.1", |
"url": "https://github.com/butlerx/wetty/issues" |
||||
"grunt-mkdir": "^0.1", |
}, |
||||
"grunt-git": "^0.3", |
"main": "index.js", |
||||
"grunt-contrib-clean": "^0.6" |
"scripts": { |
||||
}, |
"lint": "eslint .", |
||||
"description": "Wetty = Web + tty. Terminal access in browser over http/https ", |
"lint:fix": "eslint . --fix", |
||||
"main": "app.js", |
"build": "gulp", |
||||
"repository": { |
"start": "node bin", |
||||
"type": "git", |
"commit": "git add public", |
||||
"url": "git://github.com/krishnasrinivas/wetty.git" |
"fix": "eslint . --fix" |
||||
}, |
}, |
||||
"author": "Krishna Srinivas <krishna.srinivas@gmail.com> (https://github.com/krishnasrinivas)", |
"bin": { |
||||
"license": "MIT", |
"wetty": "./bin/index.js" |
||||
"bugs": { |
}, |
||||
"url": "https://github.com/krishnasrinivas/wetty/issues" |
"pre-commit": [ |
||||
}, |
"fix", |
||||
"homepage": "https://github.com/krishnasrinivas/wetty", |
"commit" |
||||
"preferGlobal": "true", |
], |
||||
"bin": { |
"preferGlobal": "true", |
||||
"wetty": "./bin/wetty.js" |
"@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>" |
||||
|
] |
||||
} |
} |
||||
|
After Width: | Height: | Size: 165 KiB |
@ -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> |
|
@ -1,30 +1,41 @@ |
|||||
<!doctype html> |
<!doctype html> |
||||
<html lang="en"> |
<html lang="en"> |
||||
|
<head> |
||||
<head> |
|
||||
<meta charset="UTF-8"> |
<meta charset="UTF-8"> |
||||
<title>Wetty - The WebTTY Terminal Emulator</title> |
<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/socket.io/socket.io.js"></script> |
||||
<script src="/wetty/wetty.js"></script> |
|
||||
<style> |
<style> |
||||
html, |
html, body { |
||||
body { |
height: 100%; |
||||
height: 100%; |
width: 100%; |
||||
width: 100%; |
margin: 0px; |
||||
margin: 0px; |
} |
||||
} |
#overlay { |
||||
#terminal { |
position: absolute; |
||||
display: block; |
height: 100%; |
||||
position: relative; |
width: 100%; |
||||
width: 100%; |
background-color: rgba(0,0,0,0.75);; |
||||
height: 100%; |
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> |
</style> |
||||
</head> |
</head> |
||||
|
<body> |
||||
<body> |
<div id="overlay"><input type="button" onclick="location.reload();" value="reconnect" /></div> |
||||
<div id="terminal"></div> |
<div id="terminal"></div> |
||||
</body> |
<script src="/wetty/wetty.min.js"></script> |
||||
|
</body> |
||||
</html> |
</html> |
||||
|
@ -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"); |
|
||||
}); |
|
File diff suppressed because one or more lines are too long
@ -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' }, |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
}; |
File diff suppressed because it is too large
@ -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; |
||||
|
} |
@ -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; |
||||
|
} |
File diff suppressed because it is too large
Loading…
Reference in new issue