2
2
mirror of https://git.coolaj86.com/coolaj86/telebit.js.git synced 2025-04-21 19:40:37 +00:00

Compare commits

...

182 Commits

Author SHA1 Message Date
f0049c7f06 better param parsing 2019-05-28 17:07:45 -06:00
b5d57817cf fix some typos, move some phrases to the i18n toml file 2019-05-18 13:52:51 -06:00
1e3f7f671d use Root links (new branding) 2019-05-18 13:37:49 -06:00
00f3b3ab45 more descriptive wait message 2019-05-18 13:24:19 -06:00
fountainheadllc
19a42a596c Update 'lib/html/index.html' 2019-05-17 16:09:23 +00:00
fountainheadllc
de2290dd3e Update 'lib/html/index.html' 2019-05-17 03:14:11 +00:00
fountainheadllc
7db8a7a4ae Update 'lib/html/index.html'
Rachel's edits to index.html to make information easier to understand.
2019-05-15 00:28:15 +00:00
ceddf444b0 update node version to v10.13 2019-05-11 18:22:20 -06:00
76ec7eb066 typo fix conifg -> config 2019-01-17 06:31:32 +00:00
RubenVinke
05dab9a52c 'README.md' updaten 2019-01-16 23:03:09 +00:00
20321b2fbe continue when systemd --user fails 2018-11-20 17:01:57 -07:00
8bf4bfc7c0 typo fix apt-install -> apt-get install 2018-11-20 16:59:32 -07:00
4f0db8bc9c continue when systemd --user fails 2018-11-20 16:57:44 -07:00
52d344c6e9 update toml docfile 2018-10-17 20:48:30 -06:00
8ffc30797a doc 'status' to take no arguments 2018-10-15 18:46:40 -06:00
909b479265 v0.20.8: fix npmPrefixPath 2018-10-09 16:30:10 -06:00
8589a66fca bugfix: always launchctl unload, not just on DEBUG 2018-10-07 23:05:32 -06:00
461166d3e3 single npm install process at a time 2018-10-07 22:50:11 -06:00
41d8674519 v0.20.6: bugfix: falsey ssh value means 'off', not 'default' 2018-09-25 01:39:05 -06:00
0a8fabef7a v0.20.5: minor bug fixes and WIP docs 2018-09-25 01:30:22 -06:00
3678c871cf comment out tcp instructions for now 2018-09-25 01:29:21 -06:00
e875b28f76 update docs 2018-09-24 22:47:03 -06:00
f3821b7dac export TELEBIT_VERSION 2018-09-24 22:44:39 -06:00
4d50c13c06 change greenlock._communityPackage 2018-09-24 19:01:17 -06:00
b5d2a759ce more doc updates 2018-09-23 02:03:31 -06:00
3e66e11f21 more doc updates 2018-09-22 23:35:13 -06:00
7e1243e71d add a chunk of docs 2018-09-22 20:56:08 -06:00
d0545a9a6b why, how and what, take 2 2018-09-22 15:09:05 -06:00
ddabf34c1b why, how and what, take 1 2018-09-22 15:09:00 -06:00
5512a4dd20 doc updates 2018-09-22 14:48:39 -06:00
4aaa87fd6c v0.20.4 [beta]: include single file logging 2018-09-18 23:04:52 -06:00
8c018bca69 v0.20.3 [beta]: include single file logging 2018-09-17 09:29:22 -06:00
db785fd267 single log file for both stdout and stderr 2018-09-16 02:45:05 -06:00
31e036e341 v0.20.2: cleanup welcome page 2018-09-16 02:43:57 -06:00
42d558b85e v0.20.1: cleanup restart 2018-09-16 00:16:43 -06:00
a6527d30a6 should poll for daemon start, but for now just wait 2018-09-14 03:51:58 -06:00
John Shaver
fd42234553 Some basic styling. Needs alot more work still 2018-09-14 01:28:18 -07:00
a714d7a7c5 chimney (log cleanup) 2018-09-13 23:42:04 -06:00
5d099b36a8 fix restart on close/error (TODO: double cehck enable/disable) 2018-09-12 09:55:07 -06:00
961090635c Merge branch 'v1' into next 2018-09-12 03:35:30 -06:00
7e21c85e82 merge with master 2018-09-12 03:35:22 -06:00
52805be470 Merge branch 'master' into v1 2018-09-12 03:33:57 -06:00
3726798062 update docs 2018-09-12 03:33:38 -06:00
ed34adb1a7 use sclient v1.4 2018-09-12 03:30:10 -06:00
3bbe616a12 Merge branch 'master' of josh/telebit.js into master 2018-09-12 08:18:35 +00:00
792f7c1914 Merge branch 'master' of imbdb/telebit.js into master 2018-09-12 08:17:58 +00:00
313b8f194b WIP simplify output 2018-09-11 02:03:50 -06:00
bcc8b957d4 add toml and sclient 2018-09-11 02:02:36 -06:00
f2f85cfa18 add sclient with inverse ssh proxying 2018-09-11 02:02:15 -06:00
imbdb
b2fc11d4bc Corrected a small language error 2018-09-07 11:29:04 +00:00
5ce4b90bcd WIP promisify bugfix enable 2018-09-06 03:39:21 -06:00
53ee77d8d3 WIP promisify (bug: enable acts as toggle) 2018-09-06 03:11:26 -06:00
45386c2649 bugfix: 0-index, duh 2018-09-05 13:33:34 -06:00
32f969cb18 bugfix: 0-index, duh 2018-09-05 13:33:01 -06:00
02a53f681f don't destroy empty socket 2018-09-05 01:21:47 -06:00
9b0d758a8b WIP reconnect on network change 2018-09-05 01:18:12 -06:00
d8aedb39c2 WIP refactor TelebitRemote with EventEmitters and Duplexes 2018-09-04 00:31:01 -06:00
d39ebf88a2 chimney 2018-09-03 23:18:59 -06:00
918eeb49d7 merge with telebit-remote 2018-09-03 23:13:45 -06:00
a361a76258 merge with master 2018-09-03 23:04:41 -06:00
4210243c35 one bin to rule them all 2018-09-03 23:02:11 -06:00
290d192bc9 bin/telebit.js -> bin/telebit-remote.js 2018-09-03 23:01:43 -06:00
6cd2d0ac16 begin refactor TelebitRemote 2018-09-03 22:56:52 -06:00
4870cd1ee0 one bin to rule them all 2018-09-03 19:16:08 -06:00
170518d55a WIP one bin to rule them all 2018-09-03 19:15:48 -06:00
Josh Mudge
f12e74b340 Switch URLs 2018-08-31 20:11:12 +00:00
3fa6d15848 latest serve-tpl-attachment with security update 2018-08-12 04:10:01 -06:00
73c4444b51 update serve-tpl-attachment dep 2018-08-12 04:02:58 -06:00
687b2a3567 v0.20.0-wip: enable direct download of files via serve-index/serve-static 2018-08-12 03:45:13 -06:00
fb8aa998b3 don't expect data on 'connection' event 2018-08-08 03:14:40 -06:00
4a1f020100 Merge branch 'master' into v1 2018-08-08 02:31:46 -06:00
3724f66429 bugfix using custom relay 2018-08-08 02:31:36 -06:00
e72a5f1f56 WIP mproxy v2.x, still missing connection event 2018-08-08 02:30:38 -06:00
3c068debc0 display advanced config for relay when present 2018-08-08 01:39:27 -06:00
b5e8832fea add sclient link 2018-08-08 07:31:46 +00:00
bd8d32d8ec WIP v0.20.x: add onconnection handler 2018-08-08 01:11:29 -06:00
78407f2a3e WIP v0.20.x: switch to proxy-packer v2.x and comment readable 2018-08-08 00:51:16 -06:00
017b14351a add screenshot for README 2018-08-03 13:44:03 -06:00
4aed537130 v0.19.28: a number of installer/service fixes for Linux 2018-07-28 14:09:05 -06:00
462c46dc5d treat as systemd userspace service 2018-07-28 14:08:05 -06:00
ae32916177 not required to create a symlink if it already exists 2018-07-28 13:51:30 -06:00
e1cfecfefc move killAll to own function 2018-07-28 02:14:23 -06:00
3be8c28421 enable launcher uninstall 2018-07-28 02:10:29 -06:00
c562cee3a9 minor refactor 2018-07-28 02:07:11 -06:00
da042f2dba handle 'none' better 2018-07-27 23:10:36 -06:00
568bcdcab2 updates 2018-07-27 22:30:58 -06:00
f2b16a5bcb use /dev/tty 2018-07-27 01:10:02 -06:00
5908871633 use 'who am i' rather than 'logname' 2018-07-26 16:46:57 -06:00
214096e216 fix null pipe 2018-07-26 16:40:06 -06:00
aaa11c4c87 ignore bad logname on some linuxes 2018-07-26 16:35:14 -06:00
515d74a1ea fix #29 by uri encoding and decoding json 2018-07-26 11:26:34 -06:00
28cef77806 v0.19.26: [linux] ignore bad stderr 2018-07-21 04:42:44 -06:00
c481d759d6 v0.19.25: bump for the sake of dependencies with windows bugfixes 2018-07-12 01:16:01 -06:00
4b22e7675e v0.19.24: bugfix windows https://github.com/nodejs/node/issues/21771 2018-07-11 18:11:37 -06:00
7f3df579ac v0.19.23 2018-07-07 20:19:41 -06:00
5bc9a58451 conditional logging 2018-07-07 20:19:03 -06:00
7dc24314c8 use patched urequest 2018-07-07 19:58:06 -06:00
4acf294caa show version as needed 2018-07-07 19:08:14 -06:00
55ed40fce7 show set variables 2018-07-07 19:06:17 -06:00
ac15f4d30c use node v10.6 2018-07-07 18:53:01 -06:00
da31c0154f lots of debugging 2018-07-07 18:46:45 -06:00
96424aad4b v0.19.22: only interpret strings with / or \ as paths 2018-07-05 22:33:05 -06:00
9c7a6c6eec v0.19.21: Fix https://git.coolaj86.com/coolaj86/telebit.js/issues/27 2018-07-05 22:13:56 -06:00
959ed59b2f use console.info to denote intentional output 2018-07-03 05:07:42 -06:00
ebe03df035 document my unverifiable woes 2018-07-03 04:53:24 -06:00
182045315b v0.19.20: winfixes 2018-07-03 04:40:21 -06:00
8f7ab08a99 don't define host and port, even as null 2018-07-03 04:37:41 -06:00
219166670b v0.19.19: winfixes 2018-07-03 04:13:31 -06:00
eec7bab97a minor cleanup and longer wait for windows pipe 2018-07-03 04:12:53 -06:00
3a71298183 v0.19.18: correct usage of windows pipe and retry connection on initial network failure 2018-07-03 03:43:47 -06:00
82763460ef retry after bad network conn 2018-07-03 03:42:17 -06:00
b9fae99ddc winfix: better pipe support 2018-07-02 20:30:18 -06:00
d52b4e7594 v0.19.17: include usr/share for npm install 2018-07-02 12:03:10 -06:00
50cae93370 v0.19.16 2018-07-01 01:36:04 -06:00
5723b368db fix error condition 2018-07-01 01:35:45 -06:00
cb322b4b1a v0.19.15 2018-07-01 01:18:30 -06:00
f0fa9c8615 send files over tcp 2018-07-01 01:18:10 -06:00
9a1e4e3b06 v0.19.14 2018-07-01 00:53:39 -06:00
d3d488c1d3 handle files, sites, and directories 2018-07-01 00:53:26 -06:00
e5076a3917 v0.19.13 2018-06-30 20:46:06 -06:00
ba0afc1bff pass acceptd domains, not fail them, duh 2018-06-30 20:45:42 -06:00
7214890e5b v0.19.12 2018-06-30 20:24:56 -06:00
e578aadab0 v0.19.11 2018-06-30 20:23:50 -06:00
ecc97558d8 v0.19.10 2018-06-30 20:16:11 -06:00
5b4dbbd77a show https:// prefix 2018-06-30 20:15:52 -06:00
649414403e better output before install exit 2018-06-30 20:14:38 -06:00
c1e09ce441 v0.19.9 2018-06-30 19:52:52 -06:00
9bcd78bd35 update docs 2018-06-30 19:52:34 -06:00
e4ca414ab7 v0.19.8 2018-06-30 19:34:31 -06:00
fc0054bbf1 make the output bearable 2018-06-30 19:34:15 -06:00
a015db2d16 silence! 2018-06-30 18:27:47 -06:00
a3cc4a7f78 silence and bugfix too little time to start 2018-06-30 18:25:54 -06:00
f18caced93 v0.19.7 2018-06-30 18:18:25 -06:00
9e61caaa6c bugfix missing relay on init 2018-06-30 18:18:03 -06:00
27a85a3e73 v0.19.6 2018-06-30 17:56:43 -06:00
b5136a9f08 https://git.coolaj86.com/coolaj86/telebit.js/issues/11 wildcard support 2018-06-30 17:56:10 -06:00
99589fa2ad v0.19.5 2018-06-30 16:18:40 -06:00
32e148961d tested normal and error conditions for a few different things 2018-06-30 16:18:22 -06:00
abf73259a6 v0.19.4 2018-06-30 13:20:17 -06:00
7a36b1af8a bugfix: show success, not just error 2018-06-30 13:20:00 -06:00
90fda2ac98 v0.19.3 2018-06-29 17:31:51 -06:00
b2b9094d60 error message when localhost app isn't running 2018-06-29 17:31:34 -06:00
1bb6a82f77 v0.19.2 2018-06-29 16:16:25 -06:00
9c5aeda776 don't kill process on cli commands 2018-06-29 16:16:03 -06:00
85f07fd021 v0.19.1 2018-06-29 15:54:18 -06:00
a928decbd8 renable parseCli 2018-06-29 15:53:55 -06:00
59baac2d2e prepare to use ports instead of pipes, if needed 2018-06-29 15:18:32 -06:00
8c3a89c25b v0.19.0 2018-06-29 14:52:33 -06:00
5584638907 working: manage token with client 2018-06-29 14:51:56 -06:00
fce8e366c6 save config on upgrade token 2018-06-29 04:33:22 -06:00
9441bf7f8e WIP get preauth clientside and realauth daemonside 2018-06-29 04:15:23 -06:00
02a89b52a4 server will wait for token 2018-06-28 20:35:58 -06:00
2456228ae5 login/create account 2018-06-28 17:19:49 -06:00
7929512fe5 silence enable success output that happens on stderr 2018-06-28 17:14:05 -06:00
AJ ONeal
cf063ec6e2 revert to not as good progress 2018-06-28 16:17:41 -06:00
AJ ONeal
998c198f0c try better progress 2018-06-28 16:12:51 -06:00
AJ ONeal
9974a8ab47 more awesome progress 2018-06-28 15:48:12 -06:00
AJ ONeal
3f319c5e9e remove some extra lines 2018-06-28 15:43:34 -06:00
AJ ONeal
fcc6c6c5b5 Quiet down and stop speaking nonsense! 2018-06-28 15:37:31 -06:00
AJ ONeal
999e8dfb5c SILENCE! 2018-06-28 15:26:07 -06:00
AJ ONeal
92c1180d0f fix npm progress pid 2018-06-28 15:23:02 -06:00
AJ ONeal
cc6f6fc537 fix npm progress pid 2018-06-28 15:21:33 -06:00
AJ ONeal
cbf8910e3e progress bar for npm install 2018-06-28 15:18:19 -06:00
AJ ONeal
330f28ad07 make more conditional 2018-06-28 15:09:24 -06:00
AJ ONeal
2ae4b5a25a SILENCE you fools! 2018-06-28 14:47:32 -06:00
AJ ONeal
0c924753c3 don't delete the runner, duh 2018-06-28 13:48:24 -06:00
AJ ONeal
a510cbe74e v0.18.9 2018-06-28 13:36:16 -06:00
AJ ONeal
69e28819d5 create log and sock dirs 2018-06-28 13:31:39 -06:00
AJ ONeal
39e6c98bed update tmpdir 2018-06-28 13:15:06 -06:00
AJ ONeal
4d6f51f204 update links and executable perms 2018-06-28 13:01:19 -06:00
AJ ONeal
38a4a1f0ef hard remove existing links 2018-06-28 12:48:20 -06:00
AJ ONeal
775077da28 v0.18.8 2018-06-28 12:38:26 -06:00
AJ ONeal
724460bb9e bugfix: always create sock dir 2018-06-28 12:37:57 -06:00
18b2584728 make 'press any key' last 2018-06-28 03:46:54 -06:00
28a66a9bd7 v0.18.7 2018-06-28 03:38:42 -06:00
2892316e25 read, not write, duh 2018-06-28 03:31:31 -06:00
ac1e46da1f pass flags directly, not as object property 2018-06-28 03:30:29 -06:00
7301ec1125 correction: first argument must be pathname 2018-06-28 03:29:05 -06:00
c1917f010b bugfix ignored path must still be a string 2018-06-28 03:27:34 -06:00
28eb15d2e4 typo fix mdir => mkdir 2018-06-28 03:25:36 -06:00
a8d5d91110 v0.18.6 2018-06-28 03:12:45 -06:00
18316b805e bugfixes windows start, node daemon error 2018-06-28 03:12:13 -06:00
30 changed files with 3350 additions and 1620 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
node_modules.* node_modules.*
*.*.sw* *.*.sw*
etc/acme/
bin/node bin/node
bin/npm bin/npm
bin/npx bin/npx

124
README.md
View File

@ -1,12 +1,14 @@
# Telebit™ Remote # Telebit™ Remote | a [Root](https://rootprojects.org) project
Because friends don't let friends localhost™ Because friends don't let friends localhost™
| Sponsored by [ppl](https://ppl.family)
| **Telebit Remote** | **Telebit Remote**
| [Telebit Relay](https://git.coolaj86.com/coolaj86/telebit-relay.js) | [Telebit Relay](https://git.coolaj86.com/coolaj86/telebit-relay.js)
| [sclient](https://telebit.cloud/sclient)
| |
<img align="center" src="https://git.coolaj86.com/coolaj86/telebit.js/raw/branch/master/usr/share/docs/terminal-example-1.png">
Break out of localhost. Break out of localhost.
======= =======
@ -31,49 +33,32 @@ Features
Examples Examples
======== ========
As a user service You do this:
```bash curl -fsSL https://get.telebit.io | bash
telebitd --config ~/.config/telebit/telebitd.yml &
```
As a system service You get this:
```bash
sudo telebitd --config ~/.config/telebit/telebitd.yml
```
Example output: ~/telebit http 3000
> Forwarding lucky-duck-42.telebit.cloud => localhost:3000
``` ~/telebit http ~/sites/example.com/
Connect to your device by any of the following means: > Serving ~/sites/example.com/ as lucky-duck-42.telebit.cloud
SSH+HTTPS And this:
ssh+https://lucky-duck-37.telebit.cloud:443
ex: ssh -o ProxyCommand='openssl s_client -connect %h:%p -servername %h -quiet' lucky-duck-37.telebit.cloud -p 443
SSH ~/telebit tcp 5050
ssh://ssh.telebit.cloud:32852 > Forwarding telebit.cloud:1337 => localhost:5050
ex: ssh ssh.telebit.cloud -p 32852
TCP And even this:
tcp://tcp.telebit.cloud:32852
ex: netcat tcp.telebit.cloud 32852
HTTPS ~/telebit ssh auto
https://lucky-duck-37.telebit.cloud > Forwarding ssh telebit.cloud -p 1337 => localhost:22
ex: curl https://lucky-duck-37.telebit.cloud > Forwarding ssh+https (openssl proxy) => localhost:22
```
```bash No privileged ports. No sudo. End-to-end encryption.
# Forward all https traffic to port 3000
telebit http 3000
# Forward all tcp traffic to port 5050 Fastest way to test a site, share a file, and pair over ssh.
telebit tcp 5050
# List all rules
telebit list
```
Install Install
======= =======
@ -84,64 +69,59 @@ Mac & Linux
Open Terminal and run this install script: Open Terminal and run this install script:
``` ```
curl -fsSL https://get.telebit.cloud | bash curl -fsSL https://get.telebit.io | bash
``` ```
<!-- <!--
``` ```
bash <( curl -fsSL https://get.telebit.cloud ) bash <( curl -fsSL https://get.telebit.io )
``` ```
<small> <small>
Note: **fish**, **zsh**, and other **non-bash** users should do this Note: **fish**, **zsh**, and other **non-bash** users should do this
``` ```
curl -fsSL https://get.telebit.cloud/ > get.sh; bash get.sh curl -fsSL https://get.telebit.io/ > get.sh; bash get.sh
``` ```
</small> </small>
--> -->
What does the installer do? What does the installer do?
* install Telebit Remote to `/opt/telebit` * install Telebit Remote to `~/Applications/telebit/`
* symlink the executables to `/usr/local/bin` for convenience * symlink the executable to `~/telebit` for convenience
* `/usr/local/bin/telebitd => /opt/telebit/bin/telebitd`
* `/usr/local/bin/telebit => /opt/telebit/bin/telebit`
* create the appropriate system launcher file * create the appropriate system launcher file
* `/etc/systemd/system/telebit.service` * `/etc/systemd/system/telebit.service`
* `/Library/LaunchDaemons/cloud.telebit.remote.plist` * `~/Library/LaunchAgents/cloud.telebit.remote.plist`
* create local user config * create local user config
* `~/.config/telebit/telebit.yml` * `~/.config/telebit/telebit.yml`
* `~/.local/share/telebit` * `~/.local/share/telebit`
Of course, feel free to inspect it before you run it: `curl -fsSL https://get.telebit.cloud` Of course, feel free to inspect it before you run it: `curl -fsSL https://get.telebit.io`
**You can customize the installation**: **You can customize the installation**:
```bash ```bash
export NODEJS_VER=v10.2 export NODEJS_VER=v10.2 # v10.2 is tested working, but we can test other versions
export TELEBIT_VERSION=master # git tag or branch to install from
export TELEBIT_USERSPACE=no # install as a system service (launchd, systemd only)
export TELEBIT_PATH=/opt/telebit export TELEBIT_PATH=/opt/telebit
export TELEBIT_VERSION=v1 # git tag or branch to install from export TELEBIT_USER=telebit
curl -fsSL https://get.telebit.cloud/ export TELEBIT_GROUP=telebit
curl -fsSL https://get.telebit.io/ | bash
``` ```
That will change the bundled version of node.js is bundled with Telebit Relay That will change the bundled version of node.js is bundled with Telebit Relay
and the path to which Telebit Relay installs. and the path to which Telebit Relay installs.
You can get rid of the tos + email and server domain name prompts by providing them right away:
```bash
curl -fsSL https://get.telebit.cloud/ | bash -- jon@example.com example.com telebit.example.com xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
Windows & Node.js Windows & Node.js
----------------- -----------------
1. Install [node.js](https://nodejs.org) 1. Install [node.js](https://nodejs.org)
2. Open _Node.js_ 2. Open _Node.js_
2. Run the command `npm install -g telebit` 2. Run the command `npm install -g telebit`
2. Copy the example daemon conifg to your user folder `.config/telebit/telebitd.yml` (such as `/Users/John/.config/telebit/telebitd.yml`) 2. Copy the example daemon config to your user folder `.config/telebit/telebitd.yml` (such as `/Users/John/.config/telebit/telebitd.yml`)
2. Copy the example remote conifg to your user folder `.config/telebit/telebit.yml` (such as `/Users/John/.config/telebit/telebit.yml`) 2. Copy the example remote config to your user folder `.config/telebit/telebit.yml` (such as `/Users/John/.config/telebit/telebit.yml`)
2. Change the email address 2. Change the email address
2. Run `npx telebit init` and follow the instructions 2. Run `npx telebit init` and follow the instructions
2. Run `npx telebit list` 2. Run `npx telebit list`
@ -223,16 +203,16 @@ SSH over non-standard port
ssh lucky-duck-42.telebit.cloud -p 3031 ssh lucky-duck-42.telebit.cloud -p 3031
``` ```
Daemon Usage Daemon Usage (non-global)
============ ============
```bash ```bash
telebitd --config /opt/telebit/etc/telebitd.yml ~/Applications/bin/node ~/Applications/bin/telebitd.js --config ~/.config/telebit/telebitd.yml
``` ```
Options Options
`/opt/telebit/etc/telebitd.yml:` `~/.config/telebit/telebitd.yml:`
``` ```
email: 'jon@example.com' # must be valid (for certificate recovery and security alerts) email: 'jon@example.com' # must be valid (for certificate recovery and security alerts)
agree_tos: true # agree to the Telebit, Greenlock, and Let's Encrypt TOSes agree_tos: true # agree to the Telebit, Greenlock, and Let's Encrypt TOSes
@ -249,7 +229,7 @@ servernames: # servernames that will be forwarded here
Choosing A Relay Choosing A Relay
================ ================
You can create a free or paid account at https://telebit.cloud You can create a free or paid account at <https://telebit.cloud>
or you can run [Telebit Relay](https://git.coolaj86.com/coolaj86/telebitd.js) or you can run [Telebit Relay](https://git.coolaj86.com/coolaj86/telebitd.js)
open source on a VPS (Vultr, Digital Ocean) open source on a VPS (Vultr, Digital Ocean)
or your Raspberry Pi at home (with port-forwarding). or your Raspberry Pi at home (with port-forwarding).
@ -329,25 +309,25 @@ Or if you want to bow down to the kings of the centralized dictator-net:
How to use Telebit Remote with your own instance of Telebit Relay: How to use Telebit Remote with your own instance of Telebit Relay:
```bash ```bash
telebit \ telebitd \
--locals <<external domain name>> \ --locals <<external domain name>> \
--relay wss://<<tunnel domain>>:<<tunnel port>> \ --relay wss://<<tunnel domain>>:<<tunnel port>> \
--secret <<128-bit hex key>> --secret <<128-bit hex key>>
``` ```
```bash ```bash
telebit --locals john.example.com --relay wss://tunnel.example.com:443 --secret abc123 telebitd --locals john.example.com --relay wss://tunnel.example.com:443 --secret abc123
``` ```
```bash ```bash
telebit \ telebitd \
--locals <<protocol>>:<<external domain name>>:<<local port>> \ --locals <<protocol>>:<<external domain name>>:<<local port>> \
--relay wss://<<tunnel domain>>:<<tunnel port>> \ --relay wss://<<tunnel domain>>:<<tunnel port>> \
--secret <<128-bit hex key>> --secret <<128-bit hex key>>
``` ```
```bash ```bash
telebit \ telebitd \
--locals http:john.example.com:3000,https:john.example.com \ --locals http:john.example.com:3000,https:john.example.com \
--relay wss://tunnel.example.com:443 \ --relay wss://tunnel.example.com:443 \
--secret abc123 --secret abc123
@ -506,17 +486,17 @@ Check Logs
**Linux**: **Linux**:
``` ```
sudo journalctl -xefu telebit SYSTEMD_LOG_LEVEL=debug journalctl -xef --user-unit=telebit
``` ```
**macOS**: **macOS**:
``` ```
sudo tail -f /opt/telebit/var/log/info.log tail -f ~/local/share/telebit/var/log/info.log
``` ```
``` ```
sudo tail -f /opt/telebit/var/log/error.log tail -f ~/.local/share/telebit/var/log/error.log
``` ```
Uninstall Uninstall
@ -525,16 +505,18 @@ Uninstall
**Linux**: **Linux**:
``` ```
sudo systemctl disable telebit; sudo systemctl stop telebit systemctl --user disable telebit; systemctl --user stop telebit
sudo rm -rf /etc/systemd/system/telebit.service /opt/telebit /usr/local/bin/telebit rm -f ~/.config/systemd/user/telebit.service
rm -rf ~/telebit ~/Applications/telebit
rm -rf ~/.config/telebit ~/.local/share/telebit rm -rf ~/.config/telebit ~/.local/share/telebit
``` ```
**macOS**: **macOS**:
``` ```
sudo launchctl unload -w /Library/LaunchDaemons/cloud.telebit.remote.plist launchctl unload -w ~/Library/LaunchAgents/cloud.telebit.remote.plist
sudo rm -rf /Library/LaunchDaemons/cloud.telebit.remote.plist /opt/telebit /usr/local/bin/telebit rm -f ~/Library/LaunchAgents/cloud.telebit.remote.plist
rm -rf ~/telebit ~/Applications/telebit
rm -rf ~/.config/telebit ~/.local/share/telebit rm -rf ~/.config/telebit ~/.local/share/telebit
``` ```
@ -546,4 +528,4 @@ This is implemented with websockets, so you should be able to
LICENSE LICENSE
======= =======
Copyright 2016 AJ ONeal Copyright 2016-2018+ AJ ONeal

812
bin/telebit-remote.js Executable file
View File

@ -0,0 +1,812 @@
#!/usr/bin/env node
(function () {
'use strict';
var pkg = require('../package.json');
var os = require('os');
//var url = require('url');
var fs = require('fs');
var path = require('path');
var http = require('http');
//var https = require('https');
var YAML = require('js-yaml');
var TOML = require('toml');
var TPLS = TOML.parse(fs.readFileSync(path.join(__dirname, "../lib/en-us.toml"), 'utf8'));
/*
if ('function' !== typeof TOML.stringify) {
TOML.stringify = require('json2toml');
}
*/
var recase = require('recase').create({});
var camelCopy = recase.camelCopy.bind(recase);
//var snakeCopy = recase.snakeCopy.bind(recase);
var urequest = require('@coolaj86/urequest');
var common = require('../lib/cli-common.js');
var argv = process.argv.slice(2);
var argIndex = argv.indexOf('--config');
if (-1 === argIndex) {
argIndex = argv.indexOf('-c');
}
var confpath;
var useTty;
var state = {};
if (-1 === argIndex) {
argIndex = argv.indexOf('-c');
}
if (-1 !== argIndex) {
confpath = argv.splice(argIndex, 2)[1];
}
argIndex = argv.indexOf('--tty');
if (-1 !== argIndex) {
useTty = argv.splice(argIndex, 1);
}
function help() {
var keys = Object.keys(TPLS.help).filter(function (key) {
return 'remote' !== key;
});
var key = keys.filter(function (key) {
return -1 !== process.argv.indexOf(key);
})[0] || 'remote';
console.info(TPLS.help[key].replace(/{version}/g, pkg.version));
}
var verstr = [ pkg.name + ' remote v' + pkg.version ];
if (!confpath) {
confpath = path.join(os.homedir(), '.config/telebit/telebit.yml');
verstr.push('(--config \'' + confpath.replace(new RegExp('^' + os.homedir()), '~') + '\')');
}
if ([ '-h', '--help', 'help' ].some(function (arg) {
return -1 !== argv.indexOf(arg);
})) {
help();
process.exit(0);
}
if (!confpath || /^--/.test(confpath)) {
help();
process.exit(1);
}
function askForConfig(state, mainCb) {
var fs = require('fs');
var ttyname = '/dev/tty';
var stdin = useTty ? fs.createReadStream(ttyname, {
fd: fs.openSync(ttyname, fs.constants.O_RDONLY | fs.constants.O_NOCTTY)
}) : process.stdin;
var readline = require('readline');
var rl = readline.createInterface({
input: stdin
, output: process.stdout
// https://github.com/nodejs/node/issues/21771
// https://github.com/nodejs/node/issues/21319
, terminal: !/^win/i.test(os.platform()) && !useTty
});
state._useTty = useTty;
// NOTE: Use of setTimeout
// We're using setTimeout just to make the user experience a little
// nicer, as if we're doing something inbetween steps, so that it
// is a smooth rather than jerky experience.
// >= 300ms is long enough to become distracted and change focus (a full blink, time for an idea to form as a thought)
// <= 100ms is shorter than normal human reaction time (ability to place events chronologically, which happened first)
// ~ 150-250ms is the sweet spot for most humans (long enough to notice change and not be jarred, but stay on task)
var firstSet = [
function askEmail(cb) {
if (state.config.email) { cb(); return; }
console.info(TPLS.remote.setup.email);
// TODO attempt to read email from npmrc or the like?
rl.question('email: ', function (email) {
email = /@/.test(email) && email.trim();
if (!email) { askEmail(cb); return; }
state.config.email = email.trim();
state.config.agreeTos = true;
console.info("");
setTimeout(cb, 250);
});
}
, function askRelay(cb) {
function checkRelay(relay) {
// TODO parse and check https://{{relay}}/.well-known/telebit.cloud/directives.json
if (!relay) { relay = 'telebit.cloud'; }
relay = relay.trim();
var urlstr = common.parseUrl(relay) + common.apiDirectory;
urequest({ url: urlstr, json: true }, function (err, resp, body) {
if (err) {
console.error("[Network Error] Failed to retrieve '" + urlstr + "'");
console.error(err);
askRelay(cb);
return;
}
if (200 !== resp.statusCode || (Buffer.isBuffer(body) || 'object' !== typeof body) || !body.api_host) {
console.warn("===================");
console.warn(" WARNING ");
console.warn("===================");
console.warn("");
console.warn("[" + resp.statusCode + "] '" + urlstr + "'");
console.warn("This server does not describe a current telebit version (but it may still work).");
console.warn("");
console.warn(body);
} else if (body && body.pair_request) {
state._can_pair = true;
}
state.config.relay = relay;
cb();
});
}
if (state.config.relay) { checkRelay(state.config.relay); return; }
console.info("");
console.info("");
console.info("What relay will you be using? (press enter for default)");
console.info("");
rl.question('relay [default: telebit.cloud]: ', checkRelay);
}
, function checkRelay(cb) {
nextSet = [];
if ('telebit.cloud' !== state.config.relay) {
nextSet = nextSet.concat(standardSet);
}
if (!state._can_pair) {
nextSet = nextSet.concat(fossSet);
}
cb();
}
];
var standardSet = [
// There are questions that we need to aks in the CLI
// if we can't guarantee that they are being asked in the web interface
function askAgree(cb) {
if (state.config.agreeTos) { cb(); return; }
console.info("");
console.info("");
console.info("Do you accept the terms of service for each and all of the following?");
console.info("");
console.info("\tTelebit - End-to-End Encrypted Relay");
console.info("\tGreenlock - Automated HTTPS");
console.info("\tLet's Encrypt - TLS Certificates");
console.info("");
console.info("Type 'y' or 'yes' to accept these Terms of Service.");
console.info("");
rl.question('agree to all? [y/N]: ', function (resp) {
resp = resp.trim();
if (!/^y(es)?$/i.test(resp) && 'true' !== resp) {
throw new Error("You didn't accept the Terms of Service... not sure what to do...");
}
state.config.agreeTos = true;
console.info("");
setTimeout(cb, 250);
});
}
, function askUpdates(cb) {
// required means transactional, security alerts, mandatory updates
var options = [ 'newsletter', 'important', 'required' ];
if (-1 !== options.indexOf(state._updates)) { cb(); return; }
console.info("");
console.info("");
console.info("What updates would you like to receive? (" + options.join(',') + ")");
console.info("");
rl.question('messages (default: important): ', function (updates) {
state._updates = (updates || '').trim().toLowerCase();
if (!state._updates) { state._updates = 'important'; }
if (-1 === options.indexOf(state._updates)) { askUpdates(cb); return; }
if ('newsletter' === state._updates) {
state.config.newsletter = true;
state.config.communityMember = true;
} else if ('important' === state._updates) {
state.config.communityMember = true;
}
setTimeout(cb, 250);
});
}
, function askTelemetry(cb) {
if (state.config.telemetry) { cb(); return; }
console.info("");
console.info("");
console.info("Contribute project telemetry data? (press enter for default [yes])");
console.info("");
rl.question('telemetry [Y/n]: ', function (telemetry) {
if (!telemetry || /^y(es)?$/i.test(telemetry)) {
state.config.telemetry = true;
}
setTimeout(cb, 250);
});
}
];
var fossSet = [
function askTokenOrSecret(cb) {
if (state._can_pair || state.token || state.config.token
|| state.secret || state.config.secret) { cb(); return; }
console.info("");
console.info("");
console.info("What's your authorization for '" + state.config.relay + "'?");
console.info("");
// TODO check .well-known to learn supported token types
console.info("Currently supported:");
console.info("");
console.info("\tToken (JWT format)");
console.info("\tShared Secret (HMAC hex)");
//console.info("\tPrivate key (hex)");
console.info("");
rl.question('auth: ', function (resp) {
var jwt = require('jsonwebtoken');
resp = (resp || '').trim();
try {
jwt.decode(resp);
state.config.token = resp;
} catch(e) {
// is not jwt
}
if (!state.config.token) {
resp = resp.toLowerCase();
if (resp === Buffer.from(resp, 'hex').toString('hex')) {
state.config.secret = resp;
}
}
if (!state.config.token && !state.config.secret) {
askTokenOrSecret(cb);
return;
}
setTimeout(cb, 250);
});
}
, function askServernames(cb) {
if (!state.config.secret || state.config._servernames) { cb(); return; }
console.info("");
console.info("");
console.info("What servername(s) will you be relaying here?");
console.info("(use a comma-separated list such as example.com,example.net)");
console.info("");
rl.question('domain(s): ', function (resp) {
resp = (resp || '').trim().split(/,/g);
if (!resp.length) { askServernames(); return; }
// TODO validate the domains
state.config._servernames = resp;
setTimeout(cb, 250);
});
}
, function askPorts(cb) {
if (!state.config.secret || state.config._ports) { cb(); return; }
console.info("");
console.info("");
console.info("What tcp port(s) will you be relaying here?");
console.info("(use a comma-separated list such as 2222,5050)");
console.info("");
rl.question('port(s) [default:none]: ', function (resp) {
resp = (resp || '').trim().split(/,/g);
if (!resp.length) { askPorts(); return; }
// TODO validate the domains
state.config._ports = resp;
setTimeout(cb, 250);
});
}
];
var nextSet = firstSet;
function next() {
var q = nextSet.shift();
if (!q) {
// https://github.com/nodejs/node/issues/21319
if (useTty) { try { stdin.push(null); } catch(e) { /*ignore*/ } }
rl.close();
if (useTty) { try { stdin.close(); } catch(e) { /*ignore*/ } }
mainCb(null, state);
return;
}
q(next);
}
next();
}
var utils = {
request: function request(opts, fn) {
if (!opts) { opts = {}; }
var service = opts.service || 'config';
var req = http.request({
socketPath: state._ipc.path
, method: opts.method || 'GET'
, path: '/rpc/' + service
}, function (resp) {
var body = '';
function finish() {
if (200 !== resp.statusCode) {
console.warn(resp.statusCode);
console.warn(body || ('get' + service + ' failed'));
//cb(new Error("not okay"), body);
return;
}
if (!body) { fn(null, null); return; }
try {
body = JSON.parse(body);
} catch(e) {
// ignore
}
fn(null, body);
}
if (resp.headers['content-length']) {
resp.on('data', function (chunk) {
body += chunk.toString();
});
resp.on('end', function () {
finish();
});
} else {
finish();
}
});
req.on('error', function (err) {
// ENOENT - never started, cleanly exited last start, or creating socket at a different path
// ECONNREFUSED - leftover socket just needs to be restarted
if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) {
if (opts._taketwo) {
console.error("Either the telebit service was not already (and could not be started) or its socket could not be written to.");
console.error(err);
return;
}
require('../usr/share/install-launcher.js').install({ env: process.env }, function (err) {
if (err) { fn(err); return; }
opts._taketwo = true;
setTimeout(function () {
utils.request(opts, fn);
}, 2500);
});
return;
}
if ('ENOTSOCK' === err.code) {
console.error(err);
return;
}
console.error(err);
return;
});
req.end();
}
, putConfig: function putConfig(service, args, fn) {
var req = http.request({
socketPath: state._ipc.path
, method: 'POST'
, path: '/rpc/' + service + '?_body=' + encodeURIComponent(JSON.stringify(args))
}, function (resp) {
function finish() {
if ('function' === typeof fn) {
fn(null, resp);
return;
}
console.info("");
if (200 !== resp.statusCode) {
console.warn("'" + service + "' may have failed."
+ " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log");
console.warn(resp.statusCode, body);
//cb(new Error("not okay"), body);
return;
}
if (!body) {
console.info("👌");
return;
}
try {
body = JSON.parse(body);
} catch(e) {
// ignore
}
if ("AWAIT_AUTH" === body.code) {
console.info(body.message);
} else if ("CONFIG" === body.code) {
delete body.code;
//console.info(TOML.stringify(body));
console.info(YAML.safeDump(body));
} else {
if ('http' === body.module) {
// TODO we'll support slingshot-ing in the future
if (String(body.local) === String(parseInt(body.local, 10))) {
console.info('> Forwarding https://' + body.remote + ' => localhost:' + body.local);
} else {
console.info('> Serving ' + body.local + ' as https://' + body.remote);
}
} else if ('tcp' === body.module) {
console.info('> Forwarding ' + state.config.relay + ':' + body.remote + ' => localhost:' + body.local);
} else if ('ssh' === body.module) {
//console.info('> Forwarding ' + state.config.relay + ' -p ' + JSON.stringify(body) + ' => localhost:' + body.local);
console.info('> Forwarding ssh+https (openssl proxy) => localhost:' + body.local);
} else {
console.info(JSON.stringify(body, null, 2));
}
console.info();
}
}
var body = '';
if (resp.headers['content-length']) {
resp.on('data', function (chunk) {
body += chunk.toString();
});
resp.on('end', function () {
finish();
});
} else {
finish();
}
});
req.on('error', function (err) {
console.error('Put Config Error:');
console.error(err);
return;
});
req.end();
}
};
// Two styles:
// http 3000
// http modulename
function makeRpc(key) {
if (key !== argv[0]) {
return false;
}
utils.putConfig(argv[0], argv.slice(1));
return true;
}
function packConfig(config) {
return Object.keys(config).map(function (key) {
var val = config[key];
if ('undefined' === val) {
throw new Error("'undefined' used as a string value");
}
if ('undefined' === typeof val) {
//console.warn('[DEBUG]', key, 'is present but undefined');
return;
}
if (val && 'object' === typeof val && !Array.isArray(val)) {
val = JSON.stringify(val);
}
return key + ':' + val; // converts arrays to strings with ,
});
}
function getToken(err, state) {
if (err) {
console.error("Error while initializing config [init]:");
throw err;
}
state.relay = state.config.relay;
// { _otp, config: {} }
common.api.token(state, {
error: function (err/*, next*/) {
console.error("[Error] common.api.token:");
console.error(err);
return;
}
, directory: function (dir, next) {
//console.log('[directory] Telebit Relay Discovered:');
//console.log(dir);
state._apiDirectory = dir;
next();
}
, tunnelUrl: function (tunnelUrl, next) {
//console.log('[tunnelUrl] Telebit Relay Tunnel Socket:', tunnelUrl);
state.wss = tunnelUrl;
next();
}
, requested: function (authReq, next) {
//console.log("[requested] Pairing Requested");
state.config._otp = state.config._otp = authReq.otp;
if (!state.config.token && state._can_pair) {
console.info("");
console.info("==============================================");
console.info(" Hey, Listen! ");
console.info("==============================================");
console.info(" ");
console.info(" GO CHECK YOUR EMAIL! ");
console.info(" ");
console.info(" DEVICE PAIR CODE: 0000 ".replace(/0000/g, state.config._otp));
console.info(" ");
console.info("==============================================");
console.info("");
}
next();
}
, connect: function (pretoken, next) {
//console.log("[connect] Enabling Pairing Locally...");
state.config.pretoken = pretoken;
state._connecting = true;
// TODO use php-style object querification
utils.putConfig('config', packConfig(state.config), function (err/*, body*/) {
if (err) {
state._error = err;
console.error("Error while initializing config [connect]:");
console.error(err);
return;
}
console.info(TPLS.remote.waiting.replace(/{email}/, state.config.email));
next();
});
}
, offer: function (token, next) {
//console.log("[offer] Pairing Enabled by Relay");
state.config.token = token;
if (state._error) {
return;
}
state._connecting = true;
try {
require('jsonwebtoken').decode(token);
//console.log(require('jsonwebtoken').decode(token));
} catch(e) {
console.warn("[warning] could not decode token");
}
utils.putConfig('config', packConfig(state.config), function (err/*, body*/) {
if (err) {
state._error = err;
console.error("Error while initializing config [offer]:");
console.error(err);
return;
}
//console.log("Pairing Enabled Locally");
next();
});
}
, granted: function (_, next) {
//console.log("[grant] Pairing complete!");
next();
}
, end: function () {
utils.putConfig('enable', [], function (err) {
if (err) { console.error(err); return; }
console.info(TPLS.remote.success);
// workaround for https://github.com/nodejs/node/issues/21319
if (state._useTty) {
setTimeout(function () {
console.info(TPLS.remote.next_steps);
process.exit(0);
}, 0.5 * 1000);
return;
}
// end workaround
parseCli(state);
});
}
});
}
function parseCli(/*state*/) {
var special = [
'false', 'none', 'off', 'disable'
, 'true', 'auto', 'on', 'enable'
];
if (-1 !== argv.indexOf('init')) {
utils.putConfig('list', []/*, function (err) {
}*/);
return;
}
if ([ 'ssh', 'http', 'tcp' ].some(function (key) {
if (key !== argv[0]) {
return false;
}
if (argv[1]) {
if (String(argv[1]) === String(parseInt(argv[1], 10))) {
// looks like a port
argv[1] = parseInt(argv[1], 10);
} else if (/\/|\\/.test(argv[1])) {
// looks like a path
argv[1] = path.resolve(argv[1]);
// TODO make a default assignment here
} else if (-1 === special.indexOf(argv[1])) {
console.error("Not sure what you meant by '" + argv[1] + "'.");
console.error("Remember: paths should begin with ." + path.sep + ", like '." + path.sep + argv[1] + "'");
return true;
}
utils.putConfig(argv[0], argv.slice(1));
return true;
}
return true;
})) {
return;
}
if ([ 'status', 'enable', 'disable', 'restart', 'list', 'save' ].some(makeRpc)) {
return;
}
help();
process.exit(11);
}
function handleConfig(err, config) {
//console.log('CONFIG');
//console.log(config);
state.config = config;
var verstrd = [ pkg.name + ' daemon v' + state.config.version ];
if (state.config.version && state.config.version !== pkg.version) {
console.info(verstr.join(' '), verstrd.join(' '));
} else {
console.info(verstr.join(' '));
}
if (err) { console.error(err); process.exit(101); return; }
//
// check for init first, before anything else
// because it has arguments that may help in
// the next steps
//
if (-1 !== argv.indexOf('init')) {
parsers.init(argv, getToken);
return;
}
if (!state.config.relay || !state.config.token) {
if (!state.config.relay) {
state.config.relay = 'telebit.cloud';
}
//console.log("question the user?", Date.now());
askForConfig(state, function (err, state) {
// no errors actually get passed, so this is just future-proofing
if (err) { throw err; }
if (!state.config.token && state._can_pair) {
state.config._otp = common.otp();
}
//console.log("done questioning:", Date.now());
if (!state.token && !state.config.token) {
getToken(err, state);
} else {
parseCli(state);
}
});
return;
}
//console.log("no questioning:");
parseCli(state);
}
function parseConfig(err, text) {
try {
state._clientConfig = JSON.parse(text || '{}');
} catch(e1) {
try {
state._clientConfig = YAML.safeLoad(text || '{}');
} catch(e2) {
try {
state._clientConfig = TOML.parse(text || '');
} catch(e3) {
console.error(e1.message);
console.error(e2.message);
process.exit(1);
return;
}
}
}
state._clientConfig = camelCopy(state._clientConfig || {}) || {};
common._init(
// make a default working dir and log dir
state._clientConfig.root || path.join(os.homedir(), '.local/share/telebit')
, (state._clientConfig.root && path.join(state._clientConfig.root, 'etc'))
|| path.resolve(common.DEFAULT_CONFIG_PATH, '..')
);
state._ipc = common.pipename(state._clientConfig, true);
if (!Object.keys(state._clientConfig).length) {
console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')');
console.info("");
}
if ((err && 'ENOENT' === err.code) || !Object.keys(state._clientConfig).length) {
if (!err || 'ENOENT' === err.code) {
//console.warn("Empty config file. Run 'telebit init' to configure.\n");
} else {
console.warn("Couldn't load config:\n\n\t" + err.message + "\n");
}
}
utils.request({ service: 'config' }, handleConfig);
}
var parsers = {
init: function (argv, parseCb) {
var answers = {};
var boolish = [ '--advanced' ];
if ('init' !== argv[0]) {
throw new Error("init must be the first argument");
}
argv.shift();
// init --foo bar
argv.forEach(function (arg, i) {
if (!/^--/.test(arg)) { return; }
if (-1 !== boolish.indexOf(arg)) {
answers['_' + arg.replace(/^--/, '')] = true;
}
if (/^-/.test(argv[i + 1])) {
throw new Error(argv[i + 1] + ' requires an argument');
}
answers[arg] = argv[i + 1];
});
// init foo:bar
argv.forEach(function (arg) {
if (/^--/.test(arg)) { return; }
var parts = arg.split(/:/g);
if (2 !== parts.length) {
throw new Error("bad option to init: '" + arg + "'");
}
if (answers[parts[0]]) {
throw new Error("duplicate key to init '" + parts[0] + "'");
}
answers[parts[0]] = parts[1];
});
if (answers.relay) {
console.info("using --relay " + answers.relay);
}
// things that aren't straight-forward copy-over
if (!answers.advanced && !answers.relay) {
answers.relay = 'telebit.cloud';
}
if (Array.isArray(common._NOTIFICATIONS[answers.update])) {
common._NOTIFICATIONS[answers.update].forEach(function (name) {
state.config[name] = true;
});
}
if (answers.servernames) {
state.config._servernames = answers.servernames;
}
if (answers.ports) {
state.config._ports = answers.ports;
}
// things that are straight-forward copy-over
common.CONFIG_KEYS.forEach(function (key) {
if ('true' === answers[key]) { answers[key] = true; }
if ('false' === answers[key]) { answers[key] = false; }
if ('null' === answers[key]) { answers[key] = null; }
if ('undefined' === answers[key]) { delete answers[key]; }
if ('undefined' !== typeof answers[key]) {
state.config[key] = answers[key];
}
});
askForConfig(state, function (err, state) {
if (err) { parseCb(err); return; }
if (!state.config.token && state._can_pair) {
state.config._otp = common.otp();
}
argv.unshift('init');
parseCb(null, state);
});
}
};
fs.readFile(confpath, 'utf8', parseConfig);
}());

View File

@ -2,645 +2,35 @@
(function () { (function () {
'use strict'; 'use strict';
var pkg = require('../package.json'); //
var os = require('os'); // node telebit daemon arg1 arg2
//
//var url = require('url'); if ('daemon' === process.argv[2]) {
var path = require('path'); require('./telebitd.js');
var http = require('http');
//var https = require('https');
var YAML = require('js-yaml');
var recase = require('recase').create({});
var camelCopy = recase.camelCopy.bind(recase);
//var snakeCopy = recase.snakeCopy.bind(recase);
var urequest = require('@coolaj86/urequest');
var common = require('../lib/cli-common.js');
var argv = process.argv.slice(2);
var argIndex = argv.indexOf('--config');
var confpath;
var useTty;
var state = {};
if (-1 === argIndex) {
argIndex = argv.indexOf('-c');
}
if (-1 !== argIndex) {
confpath = argv.splice(argIndex, 2)[1];
}
argIndex = argv.indexOf('--tty');
if (-1 !== argIndex) {
useTty = argv.splice(argIndex, 1);
}
function help() {
console.info('');
console.info('Telebit Remote v' + pkg.version);
console.info('');
console.info('Usage:');
console.info('');
console.info('\ttelebit [--config <path>] <module> <module-options>');
console.info('');
console.info('Examples:');
console.info('');
//console.info('\ttelebit init # bootstrap the config files');
//console.info('');
console.info('\ttelebit status # whether enabled or disabled');
console.info('\ttelebit enable # disallow incoming connections');
console.info('\ttelebit disable # allow incoming connections');
console.info('');
console.info('\ttelebit list # list rules for servernames and ports');
console.info('');
console.info('\ttelebit http none # remove all https handlers');
console.info('\ttelebit http 3000 # forward all https traffic to port 3000');
console.info('\ttelebit http /module/path # load a node module to handle all https traffic');
console.info('');
console.info('\ttelebit http none example.com # remove https handler from example.com');
console.info('\ttelebit http 3001 example.com # forward https traffic for example.com to port 3001');
console.info('\ttelebit http /module/path example.com # forward https traffic for example.com to port 3001');
console.info('');
console.info('\ttelebit tcp none # remove all tcp handlers');
console.info('\ttelebit tcp 5050 # forward all tcp to port 5050');
console.info('\ttelebit tcp /module/path # handle all tcp with a node module');
console.info('');
console.info('\ttelebit tcp none 6565 # remove tcp handler from external port 6565');
console.info('\ttelebit tcp 5050 6565 # forward external port 6565 to local 5050');
console.info('\ttelebit tcp /module/path 6565 # handle external port 6565 with a node module');
console.info('');
console.info('Config:');
console.info('');
console.info('\tSee https://git.coolaj86.com/coolaj86/telebit.js');
console.info('');
console.info('');
}
var verstr = [ pkg.name + ' remote v' + pkg.version ];
if (!confpath) {
confpath = path.join(os.homedir(), '.config/telebit/telebit.yml');
verstr.push('(--config "' + confpath + '")');
}
if (-1 !== argv.indexOf('-h') || -1 !== argv.indexOf('--help')) {
help();
process.exit(0);
}
if (!confpath || /^--/.test(confpath)) {
help();
process.exit(1);
}
function askForConfig(answers, mainCb) {
answers = answers || {};
//console.log("Please create a config file at '" + confpath + "' or specify --config /path/to/config");
var fs = require('fs');
var stdin = useTty ? fs.createReadStream(null, {
fd: fs.openSync({ path: '/dev/tty', flags: fs.constants.O_WRONLY | fs.constants.O_NOCTTY })
}) : process.stdin;
var readline = require('readline');
var rl = readline.createInterface({
input: stdin
, output: process.stdout
// https://github.com/nodejs/node/issues/21319
, terminal: !useTty
});
answers._useTty = useTty;
// NOTE: Use of setTimeout
// We're using setTimeout just to make the user experience a little
// nicer, as if we're doing something inbetween steps, so that it
// is a smooth rather than jerky experience.
// >= 300ms is long enough to become distracted and change focus (a full blink, time for an idea to form as a thought)
// <= 100ms is shorter than normal human reaction time (ability to place events chronologically, which happened first)
// ~ 150-250ms is the sweet spot for most humans (long enough to notice change and not be jarred, but stay on task)
var firstSet = [
function askEmail(cb) {
if (answers.email) { cb(); return; }
console.info("");
console.info("");
console.info("Telebit uses Greenlock for free automated ssl through Let's Encrypt.");
console.info("");
console.info("To accept the Terms of Service for Telebit, Greenlock and Let's Encrypt,");
console.info("please enter your email.");
console.info("");
// TODO attempt to read email from npmrc or the like?
rl.question('email: ', function (email) {
email = /@/.test(email) && email.trim();
if (!email) { askEmail(cb); return; }
answers.email = email.trim();
answers.agree_tos = true;
console.info("");
setTimeout(cb, 250);
});
}
, function askRelay(cb) {
function checkRelay(relay) {
// TODO parse and check https://{{relay}}/.well-known/telebit.cloud/directives.json
if (!relay) { relay = 'telebit.cloud'; }
relay = relay.trim();
var urlstr = common.parseUrl(relay) + common.apiDirectory;
urequest({ url: urlstr, json: true }, function (err, resp, body) {
if (err) {
console.error("[Network Error] Failed to retrieve '" + urlstr + "'");
console.error(err);
askRelay(cb);
return;
}
if (200 !== resp.statusCode || (Buffer.isBuffer(body) || 'object' !== typeof body) || !body.api_host) {
console.warn("===================");
console.warn(" WARNING ");
console.warn("===================");
console.warn("");
console.warn("[" + resp.statusCode + "] '" + urlstr + "'");
console.warn("This server does not describe a current telebit version (but it may still work).");
console.warn("");
console.warn(body);
} else if (body && body.pair_request) {
answers._can_pair = true;
}
answers.relay = relay;
cb();
});
}
if (answers.relay) { checkRelay(); return; }
console.info("");
console.info("");
console.info("What relay will you be using? (press enter for default)");
console.info("");
rl.question('relay [default: telebit.cloud]: ', checkRelay);
}
, function checkRelay(cb) {
nextSet = [];
if ('telebit.cloud' !== answers.relay) {
nextSet = nextSet.concat(standardSet);
}
if (!answers._can_pair) {
nextSet = nextSet.concat(fossSet);
}
cb();
}
];
var standardSet = [
// There are questions that we need to aks in the CLI
// if we can't guarantee that they are being asked in the web interface
function askAgree(cb) {
if (answers.agree_tos) { cb(); return; }
console.info("");
console.info("");
console.info("Do you accept the terms of service for each and all of the following?");
console.info("");
console.info("\tTelebit - End-to-End Encrypted Relay");
console.info("\tGreenlock - Automated HTTPS");
console.info("\tLet's Encrypt - TLS Certificates");
console.info("");
console.info("Type 'y' or 'yes' to accept these Terms of Service.");
console.info("");
rl.question('agree to all? [y/N]: ', function (resp) {
resp = resp.trim();
if (!/^y(es)?$/i.test(resp) && 'true' !== resp) {
throw new Error("You didn't accept the Terms of Service... not sure what to do...");
}
answers.agree_tos = true;
console.info("");
setTimeout(cb, 250);
});
}
, function askUpdates(cb) {
// required means transactional, security alerts, mandatory updates
var options = [ 'newsletter', 'important', 'required' ];
if (-1 !== options.indexOf(answers.updates)) { cb(); return; }
console.info("");
console.info("");
console.info("What updates would you like to receive? (" + options.join(',') + ")");
console.info("");
rl.question('messages (default: important): ', function (updates) {
updates = (updates || '').trim().toLowerCase();
if (!updates) { updates = 'important'; }
if (-1 === options.indexOf(updates)) { askUpdates(cb); return; }
if ('newsletter' === updates) {
answers.newsletter = true;
answers.communityMember = true;
} else if ('important' === updates) {
answers.communityMember = true;
}
setTimeout(cb, 250);
});
}
, function askTelemetry(cb) {
if (answers.telemetry) { cb(); return; }
console.info("");
console.info("");
console.info("Contribute project telemetry data? (press enter for default [yes])");
console.info("");
rl.question('telemetry [Y/n]: ', function (telemetry) {
if (!telemetry || /^y(es)?$/i.test(telemetry)) {
answers.telemetry = true;
}
setTimeout(cb, 250);
});
}
];
var fossSet = [
function askTokenOrSecret(cb) {
if (answers._can_pair || answers.token || answers.secret) { cb(); return; }
console.info("");
console.info("");
console.info("What's your authorization for '" + answers.relay + "'?");
console.info("");
// TODO check .well-known to learn supported token types
console.info("Currently supported:");
console.info("");
console.info("\tToken (JWT format)");
console.info("\tShared Secret (HMAC hex)");
//console.info("\tPrivate key (hex)");
console.info("");
rl.question('auth: ', function (resp) {
var jwt = require('jsonwebtoken');
resp = (resp || '').trim();
try {
answers.token = jwt.decode(resp);
} catch(e) {
// is not jwt
try {
if (JSON.parse(resp).subject) {
answers.token = resp;
}
} catch(e) {
// is not authRequest either
}
}
if (!answers.token) {
resp = resp.toLowerCase();
if (resp === Buffer.from(resp, 'hex').toString('hex')) {
answers.secret = resp;
}
}
if (!answers.token && !answers.secret) {
askTokenOrSecret(cb);
return;
}
setTimeout(cb, 250);
});
}
, function askServernames(cb) {
if (!answers.secret || answers.servernames) { cb(); return; }
console.info("");
console.info("");
console.info("What servername(s) will you be relaying here?");
console.info("(use a comma-separated list such as example.com,example.net)");
console.info("");
rl.question('domain(s): ', function (resp) {
resp = (resp || '').trim().split(/,/g);
if (!resp.length) { askServernames(); return; }
// TODO validate the domains
answers.servernames = resp;
setTimeout(cb, 250);
});
}
, function askPorts(cb) {
if (!answers.secret || answers.ports) { cb(); return; }
console.info("");
console.info("");
console.info("What tcp port(s) will you be relaying here?");
console.info("(use a comma-separated list such as 2222,5050)");
console.info("");
rl.question('port(s) [default:none]: ', function (resp) {
resp = (resp || '').trim().split(/,/g);
if (!resp.length) { askPorts(); return; }
// TODO validate the domains
answers.ports = resp;
setTimeout(cb, 250);
});
}
];
var nextSet = firstSet;
function next() {
var q = nextSet.shift();
if (!q) {
// https://github.com/nodejs/node/issues/21319
if (useTty) { stdin.push(null); }
rl.close();
if (useTty) { try { stdin.close(); } catch(e) { /*ignore*/ } }
mainCb(null, answers);
return;
}
q(next);
}
next();
}
var utils = {
request: function request(opts, fn) {
if (!opts) { opts = {}; }
var service = opts.service || 'config';
var req = http.get({
socketPath: state._ipc.path
, method: opts.method || 'GET'
, path: '/rpc/' + service
}, function (resp) {
var body = '';
function finish() {
console.info("");
if (200 !== resp.statusCode) {
console.warn(resp.statusCode);
console.warn(body || ('get' + service + ' failed'));
//cb(new Error("not okay"), body);
return; return;
} }
if (!body) { fn(null, null); return; } //
// sclient proxies
try { //
body = JSON.parse(body); if ('sclient' === process.argv[2]) {
} catch(e) { process.argv.splice(1,1);
// ignore
}
fn(null, body);
}
if (resp.headers['content-length']) {
resp.on('data', function (chunk) {
body += chunk.toString();
});
resp.on('end', function () {
finish();
});
} else {
finish();
}
});
req.on('error', function (err) {
// ENOENT - never started, cleanly exited last start, or creating socket at a different path
// ECONNREFUSED - leftover socket just needs to be restarted
if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) {
if (opts._taketwo) {
console.error("Either the telebit service was not already (and could not be started) or its socket could not be written to.");
console.error(err);
return; return;
} }
require('../usr/share/install-launcher.js').install({ env: process.env }, function (err) { if ('rsync' === process.argv[2]) {
if (err) { fn(err); return; } require('sclient/bin/sclient.js');
opts._taketwo = true;
utils.request(opts, fn);
});
return; return;
} }
if ('ENOTSOCK' === err.code) { if ('ssh' === process.argv[2] && /[\w-]+\.[a-z]{2,}/i.test(process.argv[3])) {
console.error(err); process.argv.splice(1,1,'sclient');
return; process.argv.splice(2,1,'ssh');
} require('sclient/bin/sclient.js');
console.error(err);
return;
});
}
, putConfig: function putConfig(service, args, fn) {
// console.log('got it', service, args);
var req = http.get({
socketPath: state._ipc.path
, method: 'POST'
, path: '/rpc/' + service + '?_body=' + JSON.stringify(args)
}, function (resp) {
function finish() {
if ('function' === typeof fn) {
fn(null, resp);
return; return;
} }
console.info(""); //
if (200 !== resp.statusCode) { // telebit remote
console.warn("'" + service + "' may have failed." //
+ " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log"); require('./telebit-remote.js');
console.warn(resp.statusCode, body);
//cb(new Error("not okay"), body);
return;
}
if (!body) {
console.info("👌");
return;
}
try {
body = JSON.parse(body);
} catch(e) {
// ignore
}
if ("AWAIT_AUTH" === body.code) {
console.info(body.message);
} else if ("CONFIG" === body.code) {
delete body.code;
console.info(YAML.safeDump(body));
} else {
console.info(JSON.stringify(body, null, 2));
}
}
var body = '';
if (resp.headers['content-length']) {
resp.on('data', function (chunk) {
body += chunk.toString();
});
resp.on('end', function () {
finish();
});
} else {
finish();
}
});
req.on('error', function (err) {
console.error('Put Config Error:');
console.error(err);
return;
});
}
};
function parseConfig(err, text) {
console.info("");
console.info(verstr.join(' '));
try {
state.config = JSON.parse(text || '{}');
} catch(e1) {
try {
state.config = YAML.safeLoad(text || '{}');
} catch(e2) {
console.error(e1.message);
console.error(e2.message);
process.exit(1);
return;
}
}
state.config = camelCopy(state.config || {}) || {};
common._init(
state.config.root || path.join(os.homedir(), '.local/share/telebit')
, (state.config.root && path.join(state.config.root, 'etc')) || path.join(os.homedir(), '.config/telebit')
);
state._ipc = common.pipename(state.config, true);
if (!Object.keys(state.config).length) {
console.info('(' + state._ipc.comment + ": " + state._ipc.path + ')');
}
console.info("");
if ((err && 'ENOENT' === err.code) || !Object.keys(state.config).length) {
if (!err || 'ENOENT' === err.code) {
//console.warn("Empty config file. Run 'telebit init' to configure.\n");
} else {
console.warn("Couldn't load config:\n\n\t" + err.message + "\n");
}
}
// Two styles:
// http 3000
// http modulename
function makeRpc(key) {
if (key !== argv[0]) {
return false;
}
utils.putConfig(argv[0], argv.slice(1));
return true;
}
if ([ 'ssh', 'http', 'tcp' ].some(function (key) {
if (key !== argv[0]) {
return false;
}
if (argv[1]) {
utils.putConfig(argv[0], argv.slice(1));
return true;
}
help();
return true;
})) {
return true;
}
if (-1 !== argv.indexOf('init')) {
parsers.init(argv, function (err, answers) {
if (err) {
console.error("Error while initializing config:");
throw err;
}
// TODO make one request to set and then poll for readiness
if (!answers.token && answers._can_pair) {
console.log("");
console.log("==============================================");
console.log(" Hey, Listen! ");
console.log("==============================================");
console.log(" ");
console.log(" GO CHECK YOUR EMAIL! ");
console.log(" ");
console.log(" DEVICE PAIR CODE: 0000 ".replace(/0000/g, answers._otp));
console.log(" ");
console.log("==============================================");
console.log("");
}
// TODO use php-style object querification
utils.putConfig('config', Object.keys(answers).map(function (key) {
return key + ':' + answers[key];
}), function (err/*, body*/) {
if (err) {
console.error("Error while initializing config:");
throw err;
}
// need just a little time to let the grants occur
setTimeout(function () {
utils.putConfig('list', []);
// workaround for https://github.com/nodejs/node/issues/21319
if (answers._useTty) {
console.log();
console.log("Press any key to continue...");
console.log();
setTimeout(function () {
process.exit(0);
}, 2 * 1000);
}
// end workaround
}, 1 * 1000);
});
});
return;
}
if ([ 'status', 'enable', 'disable', 'restart', 'list', 'save' ].some(makeRpc)) {
return;
}
help();
}
var parsers = {
init: function (argv, cb) {
var answers = {};
var bool = [
'--advanced'
];
if ('init' !== argv[0]) {
throw new Error("init must be the first argument");
}
argv.shift();
utils.request({ service: 'config' }, function (err/*, body*/) {
if (err) { cb(err); return; }
// init --foo bar
argv.forEach(function (arg, i) {
if (!/^--/.test(arg)) { return; }
if (-1 !== bool.indexOf(arg)) {
answers['_' + arg.replace(/^--/, '')] = true;
}
if (/^-/.test(argv[i + 1])) {
throw new Error(argv[i + 1] + ' requires an argument');
}
answers[arg] = argv[i + 1];
});
// init foo:bar
argv.forEach(function (arg) {
if (/^--/.test(arg)) { return; }
var parts = arg.split(/:/g);
if (2 !== parts.length) {
throw new Error("bad option to init: '" + arg + "'");
}
if (answers[parts[0]]) {
throw new Error("duplicate key to init '" + parts[0] + "'");
}
answers[parts[0]] = parts[1];
});
if (!answers._advanced && !answers.relay) {
answers.relay = 'telebit.cloud';
}
askForConfig(answers, function (err, answers) {
if (err) { cb(err); return; }
if (!answers.token && answers._can_pair) {
answers._otp = common.otp();
}
cb(null, answers);
});
});
}
};
require('fs').readFile(confpath, 'utf8', parseConfig);
}()); }());

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +1,95 @@
'use strict'; 'use strict';
module.exports.debug = (-1 !== (process.env.NODE_DEBUG||'').split(/\s+/g).indexOf('telebit'));
var common = module.exports; var common = module.exports;
var path = require('path'); var path = require('path');
var url = require('url'); var url = require('url');
var fs = require('fs');
var mkdirp = require('mkdirp'); var mkdirp = require('mkdirp');
var os = require('os'); var os = require('os');
var homedir = os.homedir(); var homedir = os.homedir();
var urequest = require('@coolaj86/urequest'); var urequest = require('@coolaj86/urequest');
var localshare = '.local/share/telebit'; common._NOTIFICATIONS = {
var localconf = '.config/telebit'; 'newsletter': [ 'newsletter', 'communityMember' ]
, 'important': [ 'communityMember' ]
};
common.CONFIG_KEYS = [
'newsletter'
, 'communityMember'
, 'telemetry'
, 'sshAuto'
, 'email'
, 'agreeTos'
, 'relay'
, 'token'
, 'pretoken'
, 'secret'
];
//, '_servernames' // list instead of object
//, '_ports' // list instead of object
//, '_otp' // otp should not be saved
//, '_token' // temporary token
common.pipename = function (config, newApi) { common.getPort = function (config, cb) {
var portfile = path.resolve(config.sock || common.DEFAULT_SOCK_PATH, '..', 'telebit.port');
if (cb) {
return fs.readFile(portfile, 'utf8', function (err, text) {
cb(err, parseInt((text||'').trim(), 10) || null);
});
} else {
try {
return parseInt(fs.readFileSync(portfile, 'utf8').trim(), 10) || null;
} catch(e) {
return null;
}
}
};
common.setPort = function (config, num, cb) {
var portfile = path.resolve(config.sock || common.DEFAULT_SOCK_PATH, '..', 'telebit.port');
var numstr = (num || '').toString();
if (cb) {
return fs.writeFile(portfile, numstr, 'utf8', function (err) {
cb(err);
});
} else {
try {
return fs.writeFileSync(portfile, numstr, 'utf8');
} catch(e) {
return null;
}
}
};
common.removePort = function (config, cb) {
var portfile = path.resolve(config.sock || common.DEFAULT_SOCK_PATH, '..', 'telebit.port');
if (cb) {
return fs.unlink(portfile, function (err, text) {
cb(err, (text||'').trim());
});
} else {
try {
return fs.unlinkSync(portfile);
} catch(e) {
return null;
}
}
};
common.pipename = function (config) {
var _ipc = { var _ipc = {
path: (config.sock || common.DEFAULT_SOCK_NAME) path: (config.sock || common.DEFAULT_SOCK_PATH)
, comment: (/^win/i.test(os.platform()) ? 'windows pipe' : 'unix socket') , comment: (/^win/i.test(os.platform()) ? 'windows pipe' : 'unix socket')
, type: (/^win/i.test(os.platform()) ? 'pipe' : 'socket') , type: (/^win/i.test(os.platform()) ? 'pipe' : 'socket')
}; };
if ('pipe' === _ipc.type) { if ('pipe' === _ipc.type) {
_ipc.path = '\\\\?\\pipe' + _ipc.path.replace(/\//, '\\'); // https://docs.microsoft.com/en-us/windows/desktop/ipc/pipe-names
// Allows all characters accept backslash as part of the name
_ipc.path = '\\\\.\\pipe\\' + _ipc.path.replace(/\\/g, '/');
} }
if (newApi) {
return _ipc; return _ipc;
}
return _ipc.path;
}; };
common.DEFAULT_SOCK_NAME = path.join(homedir, localshare, 'var', 'run', 'telebit.sock'); common.DEFAULT_SOCK_PATH = path.join(homedir, '.local/share/telebit/var/run', 'telebit.sock');
common.DEFAULT_CONFIG_PATH = path.join(homedir, '.config/telebit', 'telebitd.yml');
common.parseUrl = function (hostname) { common.parseUrl = function (hostname) {
var location = url.parse(hostname); var location = url.parse(hostname);
@ -54,24 +117,7 @@ common.apiDirectory = '_apis/telebit.cloud/index.json';
common.otp = function getOtp() { common.otp = function getOtp() {
return Math.round(Math.random() * 9999).toString().padStart(4, '0'); return Math.round(Math.random() * 9999).toString().padStart(4, '0');
}; };
common.api = {}; common.signToken = function (state) {
common.api.directory = function (state, next) {
state.relayUrl = common.parseUrl(state.relay);
urequest({ url: state.relayUrl + common.apiDirectory, json: true }, function (err, resp, body) {
next(err, body);
});
};
common.api.token = function (state, handlers) {
common.api.directory(state, function (err, dir) {
// directory, requested, connect, tunnelUrl, offer, granted, end
function afterDir() {
//console.log('[debug] after dir');
state.relayHostname = common.parseHostname(state.relay);
state.wss = dir.tunnel.method + '://' + dir.api_host.replace(/:hostname/g, state.relayHostname) + dir.tunnel.pathname;
handlers.tunnelUrl(state.wss, function () {
//console.log('[debug] after tunnelUrl');
if (!state.config.token && state.config.secret) {
var jwt = require('jsonwebtoken'); var jwt = require('jsonwebtoken');
var tokenData = { var tokenData = {
domains: Object.keys(state.config.servernames || {}).filter(function (name) { domains: Object.keys(state.config.servernames || {}).filter(function (name) {
@ -81,15 +127,49 @@ common.api.token = function (state, handlers) {
port = parseInt(port, 10); port = parseInt(port, 10);
return port > 0 && port <= 65535; return port > 0 && port <= 65535;
}) })
, aud: state.relayUrl , aud: state._relayUrl
, iss: Math.round(Date.now() / 1000) , iss: Math.round(Date.now() / 1000)
}; };
state.token = jwt.sign(tokenData, state.config.secret); return jwt.sign(tokenData, state.config.secret);
};
common.api = {};
common.api.directory = function (state, next) {
state._relayUrl = common.parseUrl(state.relay);
urequest({ url: state._relayUrl + common.apiDirectory, json: true }, function (err, resp, dir) {
if (!dir) { dir = { api_host: ':hostname', tunnel: { method: "wss", pathname: "" } }; }
state._apiDirectory = dir;
next(err, dir);
});
};
common.api._parseWss = function (state, dir) {
if (!dir || !dir.api_host) {
dir = { api_host: ':hostname', tunnel: { method: "wss", pathname: "" } };
} }
state.token = state.token || state.config.token; state._relayHostname = common.parseHostname(state.relay);
return dir.tunnel.method + '://' + dir.api_host.replace(/:hostname/g, state._relayHostname) + dir.tunnel.pathname;
};
common.api.wss = function (state, cb) {
common.api.directory(state, function (err, dir) {
cb(err, common.api._parseWss(state, dir));
});
};
common.api.token = function (state, handlers) {
common.api.directory(state, function (err, dir) {
// directory, requested, connect, tunnelUrl, offer, granted, end
function afterDir() {
if (common.debug) { console.log('[debug] after dir'); }
state.wss = common.api._parseWss(state, dir);
handlers.tunnelUrl(state.wss, function () {
if (common.debug) { console.log('[debug] after tunnelUrl'); }
if (state.config.secret /* && !state.config.token */) {
state.config._token = common.signToken(state);
}
state.token = state.token || state.config.token || state.config._token;
if (state.token) { if (state.token) {
//console.log('[debug] token via token or secret'); if (common.debug) { console.log('[debug] token via token or secret'); }
// { token, pretoken }
handlers.connect(state.token, function () { handlers.connect(state.token, function () {
handlers.end(null, function () {}); handlers.end(null, function () {});
}); });
@ -98,20 +178,20 @@ common.api.token = function (state, handlers) {
// backwards compat (TODO remove) // backwards compat (TODO remove)
if (err || !dir || !dir.pair_request) { if (err || !dir || !dir.pair_request) {
//console.log('[debug] no dir, connect'); if (common.debug) { console.log('[debug] no dir, connect'); }
handlers.error(new Error("No token found or generated, and no pair_request api found.")); handlers.error(new Error("No token found or generated, and no pair_request api found."));
return; return;
} }
// TODO sign token with own private key, including public key and thumbprint // TODO sign token with own private key, including public key and thumbprint
// (much like ACME JOSE account) // (much like ACME JOSE account)
var otp = state.otp || state._otp || '0000'; // common.otp(); var otp = state.config._otp; // common.otp();
var authReq = state.authRequest || state._auth || { var authReq = {
subject: state.config.email subject: state.config.email
, subject_scheme: 'mailto' , subject_scheme: 'mailto'
// TODO create domains list earlier // TODO create domains list earlier
, scope: Object.keys(state.config.servernames || {}) , scope: (state.config._servernames || Object.keys(state.config.servernames || {}))
.concat(Object.keys(state.config.ports || {})).join(',') .concat(state.config._ports || Object.keys(state.config.ports || {})).join(',')
, otp: otp , otp: otp
, hostname: os.hostname() , hostname: os.hostname()
// Used for User-Agent // Used for User-Agent
@ -120,7 +200,7 @@ common.api.token = function (state, handlers) {
, os_release: os.release() , os_release: os.release()
, os_arch: os.arch() , os_arch: os.arch()
}; };
var pairRequestUrl = url.resolve('https://' + dir.api_host.replace(/:hostname/g, state.relayHostname), dir.pair_request.pathname); var pairRequestUrl = url.resolve('https://' + dir.api_host.replace(/:hostname/g, state._relayHostname), dir.pair_request.pathname);
var req = { var req = {
url: pairRequestUrl url: pairRequestUrl
, method: dir.pair_request.method , method: dir.pair_request.method
@ -130,10 +210,11 @@ common.api.token = function (state, handlers) {
var firstReady = true; var firstReady = true;
function gotoNext(req) { function gotoNext(req) {
//console.log('[debug] gotoNext called'); if (common.debug) { console.log('[debug] gotoNext called'); }
if (common.debug) { console.log(req); }
urequest(req, function (err, resp, body) { urequest(req, function (err, resp, body) {
if (err) { if (err) {
//console.log('[debug] gotoNext error'); if (common.debug) { console.log('[debug] gotoNext error'); }
err._request = req; err._request = req;
err._hint = '[telebitd.js] pair request'; err._hint = '[telebitd.js] pair request';
handlers.error(err, function () {}); handlers.error(err, function () {});
@ -141,18 +222,19 @@ common.api.token = function (state, handlers) {
} }
function checkLocation() { function checkLocation() {
//console.log('[debug] checkLocation'); if (common.debug) { console.log('[debug] checkLocation'); }
if (common.debug) { console.log(body); }
// pending, try again // pending, try again
if ('pending' === body.status && resp.headers.location) { if ('pending' === body.status && resp.headers.location) {
//console.log('[debug] pending'); if (common.debug) { console.log('[debug] pending'); }
setTimeout(gotoNext, 2 * 1000, { url: resp.headers.location, json: true }); setTimeout(gotoNext, 2 * 1000, { url: resp.headers.location, json: true });
return; return;
} }
if ('ready' === body.status) { if ('ready' === body.status) {
//console.log('[debug] ready'); if (common.debug) { console.log('[debug] ready'); }
if (firstReady) { if (firstReady) {
//console.log('[debug] first ready'); if (common.debug) { console.log('[debug] first ready'); }
firstReady = false; firstReady = false;
state.token = body.access_token; state.token = body.access_token;
state.config.token = state.token; state.config.token = state.token;
@ -165,21 +247,21 @@ common.api.token = function (state, handlers) {
} }
if ('complete' === body.status) { if ('complete' === body.status) {
//console.log('[debug] complete'); if (common.debug) { console.log('[debug] complete'); }
handlers.granted(null, function () { handlers.granted(null, function () {
handlers.end(null, function () {}); handlers.end(null, function () {});
}); });
return; return;
} }
//console.log('[debug] bad status'); if (common.debug) { console.log('[debug] bad status'); }
var err = new Error("Bad State:" + body.status); var err = new Error("Bad State:" + body.status);
err._request = req; err._request = req;
handlers.error(err, function () {}); handlers.error(err, function () {});
} }
if (firstReq) { if (firstReq) {
//console.log('[debug] first req'); if (common.debug) { console.log('[debug] first req'); }
handlers.requested(authReq, function () { handlers.requested(authReq, function () {
handlers.connect(body.access_token || body.jwt, function () { handlers.connect(body.access_token || body.jwt, function () {
var err; var err;
@ -195,7 +277,7 @@ common.api.token = function (state, handlers) {
firstReq = false; firstReq = false;
return; return;
} else { } else {
//console.log('[debug] other req'); if (common.debug) { console.log('[debug] other req'); }
checkLocation(); checkLocation();
} }
}); });

493
lib/en-us.toml Normal file
View File

@ -0,0 +1,493 @@
[help]
remote = "telebit remote v{version}
Telebit Remote is the T-Rex long-arm of the Internet. UNSTOPPABLE!
Using reliable HTTPS tunneling to establishing peer-to-peer connections,
Telebit is empowering the next generation of tinkerers. Access your devices.
Share your stuff. Be UNSTOPPABLE! (Join us at https://rootprojects.org)
Usage:
telebit [flags] <command> [arguments]
ex: telebit http ~/Public
The flags are:
--config <path> specify config file (default is ~/.config/telebit/telebit.yml)
--json output json instead of text, if available
-h,--help display this menu (or sub-command menus)
The commands are:
status show status and configuration info
http access files, folders, and local apps via https (secure)
ssh enable remote access to this device with ssh-over-https
ssh (client) access devices via ssh-over-https (telebit, stunnel, openssl, etc)
tcp forward tcp locally
enable turn on remote access and sharing
disable turn off remote access and sharing
activate start and register the telebit service
disable stop and unregister the telebit service
config (doc) config file format and settings
client (doc) vpn, ftp, rsync, scp, ssh-proxy, sclient
Use \"telebit help [command]\" for more information about a command, including flags.
Additional help topics:
daemon telebit daemon secure background service
relay telebit secure relay, hosted, and self-hosting options
Copyright 2015-2018 AJ ONeal https://telebit.cloud MPL-2.0 Licensed (RAWR!)"
client = "telebit client v{version}
ftp secure ftp file transfer between devices
rsync rsync over https and proxy commands
scp scp over https and proxy commands
sclient use the sclient emebbed within telebit
ssh-proxy ssh over https and proxy commands
vpn (client) home network access and private web browsing via socks5
Use \"telebit help [command]\" for more information about a command, including flags.
Copyright 2015-2018 AJ ONeal https://telebit.cloud MPL-2.0 Licensed (RAWR!)"
status = "usage: telebit status
'telebit status' shows details about the current connections (or lack thereof).
Example:
Status: RAWR! (uptime: 45 minutes)
Forwarding ssh+https://jon.telebit.io => localhost:22
Forwarding https://client.jon.telebit.io => localhost:3000
Serving https://public.jon.telebit.io from ~/Public
Syncing ~/shared => home.jon.telebit.io:shared
Relay: https://telebit.cloud
Launcher: user
Additional help topics: enable, disable"
enable = "Enable Telebit - Re-enable and accept incoming connections
usage: telebit enable
enable Re-enable incoming connections for https, ssh, etc"
disable = "Disable Telebit - Reject https, ssh, and tcp connections
usage: telebit disable
disable (Temporarily) reject incoming connections for https,
ssh, etc without deleting the current configuration.
Perists on restart, but can be re-enabled remotely
(with your authorization only)."
activate = "Activate Telebit - Start telebit (if not running) and register a launcher
Usage:
telebit activate [flags]
ex: telebit activate --launcher none
The flags may be exactly one of:
--no-launcher uregister any launchers (start manually)
--user-launcher (default) register an unprivileged launcher (start on login)
--system-launcher register with the system launcher (start on boot)
Note: telebit relies on the system launcher to recover from certain error conditions"
deactivate = "Deactivate Telebit - Unregister userspace (or system) launcher and stop
Usage:
telebit deactivate [flags]
ex: telebit deactivate --keep alive
The flags are:
--keep-launcher stop telebit without unregistering the launcher
--keep-alive unregister launcher without stopping"
http = "Telebit HTTP - The UNSTOPPABLE way to share files, folders, and local apps.
usage: telebit http <path/port/none> [subdomain]
http <DIR> [subdomain] serve a file, folder, or node express app
ex: telebit http ~/Public pub ex: securely host ~/Public as pub.johndoe.telebit.io
http <PORT> [subdomain] forward all https traffic to a local app
ex: telebit http 3000 app ex: publicize localhost:3000 as app.johndoe.telebit.io
http none [subdomain] remove secure http access for (any or all) subdomain(s)
ex: telebit http none ex: remove all https access
Use cases:
- Lazy man's AirDrop (works for lazy women too!)
- Testing dev sites on a phone
- Sharing indie music and movies with friends"
ssh = "Telebit SSH - The UNSTOPPABLE way to remote into your devices.
usage: telebit ssh <auto|port|none>
All https traffic will be inspected to see if it looks like ssh Once enabled all traffic that looks
ssh auto Make ssh Just Work (on port 22)
ssh <port> forward ssh traffic to non-standard port
ex: telebit ssh 22 ex: explicitly forward ssh-looking packets to localhost:22
ssh none Disables ssh tunneling
Telebit SSH Client
usage: telebit ssh <remote> [ssh flags and options]
This is just a shortcut for \"ssh\", with all ssh-over-https options turned on.
ssh <remote> Make ssh Just Work (over https)
ex: telebit ssh jon.telebit.io ex:
\"telebit help ssh-proxy\" for more info
Use cases:
- Access your home computer from work.
- Access your work computer from home.
- Good ol' fashioned screen/tmux style pair programming"
ssh-proxy = "Proxying SSH over HTTPS
Wrapping SSH in HTTPS makes it accessible anywhere and also makes it routable.
Whether inside a harsh network environment or even if hindered by a poorly
configured firewall, once wrapped in tls, ssh becomes UNSTOPPABLE.
Usage:
telebit ssh <remote> [ssh flags and options]
Example:
telebit ssh jon.telebit.io
It is NOT at all neccessary to use \"telebit ssh\", it's just a convenience.
Wanna know why, and the alternatives? Keep reading!
## History
When TLS sends an encrypted packet over the network it begins with a handshake
which shows the things like the tls version and the host SERVERNAME unencrypted
so that the remote server can respond with the correct certificate.
SSH was created well before TLS and has a completely different header. The good
news is that, unlike some other early internet protocols, it does have a header
with its name and version, but it doesn't have anything to identify the server.
## Telebit + SSH
Here's why:
When you're running ssh through an https tunnel (as telebit does) you
can't just use \"ssh me.example.com\" to get in. You have to tell ssh that you
want to use an https tunnel. Using \"telebit ssh\" as a client will specify
all of the correct ssh options.
However, when you want to connect to ssh over https, you either have to pass
the correct arguments or modify your ~/.ssh/config to use \"openssl s_client\".
We explain the different configurations below:
## SSH + openssl
The configuration that's most likely to work with what's already installed on
your machine is this:
Host jon.telebit.io
ProxyCommand openssl s_client -quiet -connect %h:443 -servername %h
Or you would call ssh directly, like this:
ssh jon.telebit.io -o ProxyCommand=\"openssl s_client -quiet -connect %h:443 -servername %h\"
It's rather simple, but it looks quite daunting.
## SSH + sclient
Because that looks a little hairy, we created \"sclient\", so that the example
could look a bit more digestible:
Host jon.telebit.io
ProxyCommand sclient %h
Or
ssh jon.telebit.io -o ProxyCommand=\"sclient %h\"
## Inverse SSH Tunnel (same as stunnel)
The commands above instruct ssh to open a pipe into openssl or sclient. If we
instead want to connect ssh to a local tunnel, it looks like this:
Host jon.telebit.io
Hostname localhost
Port 3000
HostKeyAlias jon.telebit.io
CheckHostIP no
RequestTTY force
Or
ssh localhost -p 3000 -t -o CheckHostIP=no -o HostKeyAlias=jon.telebit.io
## See also
telebit ftp
telebit vpn"
tcp = "Telebit TCP - Seemless connectivity to LEGACY apps.
Use 'telebit http' instead, where possible (including for ssh).
usage: telebit tcp <path/port/none>
tcp <local> [remote] forward tcp to <local> from <remote>
ex: telebit tcp 5050 6565 ex: forward tcp port 6565 locally to port 5050
tcp <path> [remote] show ftp-style directory listing
ex: telebit tcp ~/Public ex: show listing of ~/Public
tcp none [remote] disable tcp access for [remote] port
ex: telebit tcp none 6565 ex: remove access to port 6565
Use cases:
- Debugging plain TCP when troubleshooting a legacy app
- You can't install a secure client (like telebit, sclient, openssl, or stunnel)
See also sclient <https://telebit.cloud/sclient> for connecting to legacy apps
with telebit-upscaled secure https access."
scp = "Telebit (Client) scp
See \"telebit rsync\"."
rsync = "Telebit (Client) rsync - Sync files to or from another computer
Sync files and directories from one computer to another.
Usage:
telebit rsync [flags] <src> <dst> [arguments]
ex: telebit rsync -av home.jon.telebit.cloud:shared/ ~/shared/ --exclude=tmp
This is not a full implementation of rsync, but rather a convenience wrapper
around rsync which passes the correct options to ssh for https tunneling.
Due to the way telebit wraps rsync, all flags which take an argumnt must
go after the source and destination paths / addresses.
See also: telebit help ssh-proxy"
vpn = "Telebit (Client) vpn - Use with Firefox for UNSTOPPABLE web browsing
This provides a very easy-to-use, lightweight VPN known as Socks5 that can be
used directly by Firefox and Chrome without requiring administrator privileges.
Usage:
telebit vpn --socks5 <port> <remote>
ex: telebit vpn --socks5 6789 home.jon.telebit.io
The flags are:
--socks5 <port> You MUST specify the socks5 port
Firefox Configuration:
Firefox -> Preferences
Advanced -> Network
Connection -> Settings
Manual proxy configuration:
SOCKS Host: localhost
Port: 6789
SOCKS v5
Just like a full vpn client, it routes your IP traffic places through the VPN
server (which in this case is another one of your telebit devices), but only
for traffic in the configured browser. You can still access school and office
resources in the other browser (and other applications) the need to switch a
full VPN on and off.
As will all other telebit functionality, this use https tunneling and will not
be disrupted by unfavorable network conditions.
Use cases:
- Watch your US Netflix using your home IP while traveling abroad.
- Log into your router as if from inside your home network.
- Disregard poorly configured web proxies at school or work.
See also: telebit help ssh-proxy"
ftp = "Telebit (Client) Secure FTP
Alias of \"telebit rsync\"
The original FTP was superseded by sftp and then rsync a few decades ago,
however, sometimes we refer to its successors, generically, as \"FTP\"
(just like you might say \"hang up\" the phone).
## History
FTP is a legacy of the 1970s. It served its purpose well on local networks, but
was extremely dangerous on the Internet due to its lack of security and various
vulnerabilities. On some legacy systems it remains an easy target to steal
passwords and load viruses onto computers.
Although very few systems have ftp installed today (thank goodness), almost every
computer comes with rsync already installed and ready to go.
Use \"telebit rsync\" instead."
daemon = "telebit daemon v{version}
Usage:
telebit daemon --config <path>
ex: telebit daemon --config ~/.config/telebit/telebitd.yml
Additional help topics:
config config file format and settings
remote telebit cli remote control
Copyright 2015-2018 https://telebit.cloud MPL-2.0 Licensed"
config = "Telebit Config (docs)
There are TWO config files:
remote ~/.config/telebit/telebit.yml
daemon ~/.config/telebit/telebitd.yml
### Remote Config
This only specifies the ipc - socket path (dir), address, or pipe name.
All other options are handled by the daemon.
ipc: /Users/aj/.local/share/telebit/var/run/
### Daemon Config
relay: telebit.cloud the relay to use
secret: null HMAC secret for self-hosted relay
email: jon@example.com the email to authenticate
agree_tos: true agree to Telebit, Greenlock, & Let's Encrypt, ToS
community_member: true get rare but relevant community updates
telemetry: true contribute to project telemetry
servernames:
example.com: don't reject https traffic for example.com
wildcard: true allow assignment to subdomains
handler: ~/Public whether to use a static server by path or app by port
home.example.com:
wildcard: true
handler: 3000
ssh_auto: 22 forward ssh-ish traffic to port 22
See also: telebit help relay"
sclient = "sclient
Usage:
sclient [flags] <remote> [local]
ex: sclient whatever.com:443 localhost:3000
ex: sclient whatever.com -
ex: printf \"GET / HTTP/1.1\\n\\n\" | sclient whatever.com
sclient is a standalane tls unwrapper. For convenience it's bundled with telebit
as the passthru subcommand \"telebit sclient\" and functions exactly the name.
telebit sclient [flags] <remote> [local]
ex: printf \"GET / HTTP/1.1\\n\\n\" | telebit sclient whatever.com
See https://telebit.cloud/sclient/"
relay = "Telebit Relay
We envision a future with better routers capable of providing reliable Internet
connectivity, and trusted peers bridging the gaps between unfavorable network
conditions.
We plan to always run telebit.cloud as a relay-as-a-service for convenience,
but it is our hope that, if your network conditions permit, you will also run
your own telebit relay for your friends, family, and yourself.
See https://git.coolaj86.com/coolaj86/telebit-relay.js"
in-n-out = "Telebit Secret Menu
The secret flags are:
--profile <name> Use config files, sockets, and pipes with this name.
For debugging and development. (default: telbit, telebitd)
--set-profile <name> Switch from the default profile
--address <path|host:port> Use explicit socket path (or address) or pipe name
Overrides \"--profile\""
[remote]
version = "telebit remote v{version}"
code = "
==============================================
Hey, Listen!
==============================================
GO CHECK YOUR EMAIL!
DEVICE PAIR CODE: 0000
==============================================
"
waiting = "waiting for you to check your email..."
success = "Success"
next_steps = "Some fun things to try first:
~/telebit http ~/Public
~/telebit tcp 5050
~/telebit ssh auto
Press any key to continue...
"
[remote.setup]
email = "Welcome!
By using Telebit you agree to:
[x] Accept the Telebit terms of service
[x] Accept the Let's Encrypt terms of service
Enter your email to agree and login/create your account:
"
[daemon]
version = "telebit daemon v{version}"

View File

@ -0,0 +1,19 @@
'use strict';
module.exports = function (opts) {
console.log("Could not connect");
var socket = opts.socket;
var handler = opts.handler;
var http = require('http');
var server = http.createServer(function (req, res) {
console.log('responding to thing');
res.statusCode = 500;
res.setHeader('Content-Type', 'text/html');
res.end("<html>"
+ "<head><title>Couldn't Connect</title></head>"
+ "<body>Could not connect to localhost:" + handler + "</body>"
+ "</html>");
});
//server.emit('connection', socket);
socket.end("Could not connect to localhost:" + handler);
};

31
lib/html/css/main.css Normal file
View File

@ -0,0 +1,31 @@
body {
font-family: Source Sans Pro, sans-serif;
font-size: 18px;
color: #1a1a1a;
letter-spacing: -0.022222222em;
line-height: 1.33;
margin: 0;
text-align: center;
padding: 2em 0 2em 0;
}
code {}
code, pre {
font-family: Source Code Pro, monospace;
}
.code-block {
text-align: left;
display: inline-block;
}
span.logo {
font-size: 1.666em;
font-weight: bold;
}
p {margin-bottom: 0.5em;margin-top: 1.5em;}

Binary file not shown.

Binary file not shown.

View File

@ -3,39 +3,92 @@
<head> <head>
<title>Telebit</title> <title>Telebit</title>
<meta charset="utf-8"> <meta charset="utf-8">
<link href="./css/main.css" rel="stylesheet">
<style>
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-display: block;
font-weight: 400;
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
font-display: block;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Source Code Pro';
font-style: normal;
font-weight: 400;
src: local('Source Code Pro'), local('SourceCodePro-Regular'), url(./fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
</style>
<link rel="preload" href="./fonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2" as="font" crossorigin="anonymous">
<link rel="preload" href="./fonts/6xK3dSBYKcSV-LCoeQqfX1RYOo3qOK7l.woff2" as="font" crossorigin="anonymous">
<link rel="preload" href="./fonts/HI_SiYsKILxRpg3hIP6sJ7fM7PqlPevW.woff2" as="font" crossorigin="anonymous">
</head> </head>
<body> <body>
<script>document.body.hidden = true;</script> <script>document.body.hidden = true;</script>
<!-- let's define our SVG that we will use later -->
<svg width="0" height="0" viewBox="0 0 24 24">
<defs>
<g id="svg-lock">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/>
</g>
</defs>
</svg>
<span class="logo">Telebit</span>
<h1>Welcome Home <!-- as in 127.0.0.1, y'know ;) --></h1> <h1>Welcome Home <!-- as in 127.0.0.1, y'know ;) --></h1>
<p>Go ahead and bookmark this page. It's yours now.</p> <div>Go ahead and bookmark this page. It's yours now.</div>
<div> <div>
<h2>You've claimed <span class="js-servername">{{servername}}</span></h2> <h2>You've claimed <span class="js-servername">{{servername}}</span></h2>
<p>Here's some ways you can use it:</p> <p>Here are some ways you can use Telebit via Terminal or other Command Line Interface:</p>
<pre><code> <div class="code-block">
telebit http 3000 # forward all https traffic to localhost:3000 <br />
telebit http /path/to/module # handle incoming https traffic with a node module <pre><code>~/telebit ssh auto # allows you to connect to your computer with <br /> ssh-over-https from a different computer</span></code></pre>
telebit http none # remove all https handlers</code></pre> <pre><code>~/telebit http ~/Public # serve a public folder
~/telebit http 3000 # forward all https traffic to localhost:3000
~/telebit http none # remove all https handlers</code></pre>
</div> </div>
<p>You can <em>always</em> use this port for <strong>SSH over HTTPS</strong>, even while you're using it for something else:</p> </div>
<pre><code> <p>And remember you can <em>always</em> tunnel <strong>SSH over HTTPS</strong>,
ssh -o ProxyCommand='openssl s_client -connect %h:443 -servername %h -quiet' <span class="js-servername">{{servername}}</span></code></pre> even while you're using it for something else:</p>
<p>&nbsp;</p>
<details>
<div class="js-port" hidden> <p><summary><strong>Here are some examples for those of you that want to access files and folders remotely. </strong></summary></p>
<p><strong>This function allows you to connect one computer to another computer you also have SSH on.</strong></p>
<div class="code-block"><pre><code>~/telebit ssh <span class="js-servername">{{servername}}</span></code></pre>
<br>
- or -
<pre><code>ssh -o ProxyCommand='<a href="https://telebit.cloud/sclient">sclient</a> %h' <span class="js-servername">{{servername}}</span></code></pre>
- or -
<pre><code>proxy_cmd='openssl s_client -connect %h:443 -servername %h -quiet'
ssh -o ProxyCommand="$proxy_cmd" <span class="js-servername">{{servername}}</span></code></pre>
</div>
<pre><code>ssh -o ProxyCommand='openssl s_client -connect %h:443 -servername %h -quiet' <span class="js-servername">{{servername}}</span></code></pre>
</details>
<!--div class="js-port" hidden>
<h2>You've claimed port <span class="js-serviceport">{{serviceport}}</span></h2> <h2>You've claimed port <span class="js-serviceport">{{serviceport}}</span></h2>
<p>Here's some ways you can use it:</p> <p>Here's some ways you can use it:</p>
<pre><code> <div class="code-block"><pre><code>telebit tcp 3000 # forward all tcp traffic to localhost:3000
telebit tcp 3000 # forward all tcp traffic to localhost:3000
telebit tcp /path/to/module # handle incoming tcp traffic with a node module telebit tcp /path/to/module # handle incoming tcp traffic with a node module
telebit tcp none # remove all tcp handlers</code></pre> telebit tcp none # remove all tcp handlers</code></pre>
</div> </div>
<p>You can <em>always</em> use this port for <strong>SSH</strong>, even while you're using it for something else:</p> <p>You can <em>always</em> use this port for <strong>SSH</strong>, even while you're using it for something else:</p>
<pre><code>telebit ssh 22 <div class="code-block"><pre><code>telebit ssh 22
ssh <span class="js-servername">{{servername}}</span> -p <span class="js-serviceport">{{serviceport}}</span></code></pre>
ssh <span class="js-servername">{{servername}}</span> -p <span class="js-serviceport">{{serviceport}}</span></code></pre></div>
</div -->
<script src="js/app.js"></script> <script src="js/app.js"></script>
</body> </body>

View File

@ -3,7 +3,7 @@
document.body.hidden = false; document.body.hidden = false;
var hash = window.location.hash.substr(1); var hash = window.location.hash.replace(/^[\/#?]+/, '');
var query = window.location.search; var query = window.location.search;
function parseQuery(search) { function parseQuery(search) {

View File

@ -1,11 +1,17 @@
(function () { (function () {
'use strict'; 'use strict';
var PromiseA;
try {
PromiseA = require('bluebird');
} catch(e) {
PromiseA = global.Promise;
}
var WebSocket = require('ws'); var WebSocket = require('ws');
var PromiseA = require('bluebird');
var sni = require('sni'); var sni = require('sni');
var Packer = require('proxy-packer'); var Packer = require('proxy-packer');
var os = require('os'); var os = require('os');
var EventEmitter = require('events').EventEmitter;
function timeoutPromise(duration) { function timeoutPromise(duration) {
return new PromiseA(function (resolve) { return new PromiseA(function (resolve) {
@ -13,21 +19,33 @@ function timeoutPromise(duration) {
}); });
} }
function _connect(state) { function TelebitRemote(state) {
// jshint latedef:false // jshint latedef:false
var defaultHttpTimeout = (2 * 60);
var activityTimeout = state.activityTimeout || (defaultHttpTimeout - 5) * 1000; if (!(this instanceof TelebitRemote)) {
return new TelebitRemote(state);
}
EventEmitter.call(this);
var me = this;
var priv = {};
//var defaultHttpTimeout = (2 * 60);
//var activityTimeout = state.activityTimeout || (defaultHttpTimeout - 5) * 1000;
var activityTimeout = 6 * 1000;
var pongTimeout = state.pongTimeout || 10*1000; var pongTimeout = state.pongTimeout || 10*1000;
// Allow the tunnel client to be created with no token. This will prevent the connection from // Allow the tunnel client to be created with no token. This will prevent the connection from
// being established initialy and allows the caller to use `.append` for the first token so // being established initialy and allows the caller to use `.append` for the first token so
// they can get a promise that will provide feedback about invalid tokens. // they can get a promise that will provide feedback about invalid tokens.
var tokens = []; priv.tokens = [];
var auth; var auth;
if(!state.sortingHat) { if(!state.sortingHat) {
state.sortingHat = "./sorting-hat.js"; state.sortingHat = "./sorting-hat.js";
} }
if (state.token) { if (state.token) {
tokens.push(state.token); if ('undefined' === state.token) {
throw new Error("passed string 'undefined' as token");
}
priv.tokens.push(state.token);
} }
var wstunneler; var wstunneler;
@ -35,19 +53,24 @@ function _connect(state) {
var authsent = false; var authsent = false;
var initialConnect = true; var initialConnect = true;
var localclients = {}; priv.localclients = {};
var pausedClients = []; var pausedClients = [];
var clientHandlers = { var clientHandlers = {
add: function (conn, cid, tun) { add: function (conn, cid, tun) {
localclients[cid] = conn; priv.localclients[cid] = conn;
console.info("[connect] new client '" + cid + "' for '" + tun.name + ":" + tun.serviceport + "' " console.info("[connect] new client '" + tun.name + ":" + tun.serviceport + "' for '" + cid + "'"
+ "(" + clientHandlers.count() + " clients)"); + "(" + clientHandlers.count() + " clients)");
conn.tunnelCid = cid; conn.tunnelCid = cid;
if (tun.data) {
conn.tunnelRead = tun.data.byteLength; conn.tunnelRead = tun.data.byteLength;
} else {
conn.tunnelRead = 0;
}
conn.tunnelWritten = 0; conn.tunnelWritten = 0;
conn.on('data', function onLocalData(chunk) { conn.on('data', function onLocalData(chunk) {
//var chunk = conn.read();
if (conn.tunnelClosing) { if (conn.tunnelClosing) {
console.warn("[onLocalData] received data for '"+cid+"' over socket after connection was ended"); console.warn("[onLocalData] received data for '"+cid+"' over socket after connection was ended");
return; return;
@ -59,8 +82,10 @@ function _connect(state) {
// down the data we are getting to send over. We also want to pause all active connections // down the data we are getting to send over. We also want to pause all active connections
// if any connections are paused to make things more fair so one connection doesn't get // if any connections are paused to make things more fair so one connection doesn't get
// stuff waiting for all other connections to finish because it tried writing near the border. // stuff waiting for all other connections to finish because it tried writing near the border.
var bufSize = wsHandlers.sendMessage(Packer.pack(tun, chunk)); var bufSize = sendMessage(Packer.packHeader(tun, chunk));
if (pausedClients.length || bufSize > 1024*1024) { // Sending 2 messages instead of copying the buffer
var bufSize2 = sendMessage(chunk);
if (pausedClients.length || (bufSize + bufSize2) > 1024*1024) {
// console.log('[onLocalData] paused connection', cid, 'to allow websocket to catch up'); // console.log('[onLocalData] paused connection', cid, 'to allow websocket to catch up');
conn.pause(); conn.pause();
pausedClients.push(conn); pausedClients.push(conn);
@ -69,32 +94,33 @@ function _connect(state) {
var sentEnd = false; var sentEnd = false;
conn.on('end', function onLocalEnd() { conn.on('end', function onLocalEnd() {
console.info("[onLocalEnd] connection '" + cid + "' ended, will probably close soon"); //console.info("[onLocalEnd] connection '" + cid + "' ended, will probably close soon");
conn.tunnelClosing = true; conn.tunnelClosing = true;
if (!sentEnd) { if (!sentEnd) {
wsHandlers.sendMessage(Packer.pack(tun, null, 'end')); sendMessage(Packer.packHeader(tun, null, 'end'));
sentEnd = true; sentEnd = true;
} }
}); });
conn.on('error', function onLocalError(err) { conn.on('error', function onLocalError(err) {
console.info("[onLocalError] connection '" + cid + "' errored:", err); console.info("[onLocalError] connection '" + cid + "' errored:", err);
if (!sentEnd) { if (!sentEnd) {
wsHandlers.sendMessage(Packer.pack(tun, {message: err.message, code: err.code}, 'error')); var packBody = true;
sendMessage(Packer.packHeader(tun, {message: err.message, code: err.code}, 'error', packBody));
sentEnd = true; sentEnd = true;
} }
}); });
conn.on('close', function onLocalClose(hadErr) { conn.on('close', function onLocalClose(hadErr) {
delete localclients[cid]; delete priv.localclients[cid];
console.log('[onLocalClose] closed "' + cid + '" read:'+conn.tunnelRead+', wrote:'+conn.tunnelWritten+' (' + clientHandlers.count() + ' clients)'); console.log('[onLocalClose] closed "' + cid + '" read:'+conn.tunnelRead+', wrote:'+conn.tunnelWritten+' (' + clientHandlers.count() + ' clients)');
if (!sentEnd) { if (!sentEnd) {
wsHandlers.sendMessage(Packer.pack(tun, null, hadErr && 'error' || 'end')); sendMessage(Packer.packHeader(tun, null, hadErr && 'error' || 'end'));
sentEnd = true; sentEnd = true;
} }
}); });
} }
, write: function (cid, opts) { , write: function (cid, opts) {
var conn = localclients[cid]; var conn = priv.localclients[cid];
if (!conn) { if (!conn) {
return false; return false;
} }
@ -111,11 +137,13 @@ function _connect(state) {
conn.tunnelRead += opts.data.byteLength; conn.tunnelRead += opts.data.byteLength;
if (!conn.remotePaused && conn.bufferSize > 1024*1024) { if (!conn.remotePaused && conn.bufferSize > 1024*1024) {
wsHandlers.sendMessage(Packer.pack(opts, conn.tunnelRead, 'pause')); var packBody = true;
sendMessage(Packer.packHeader(opts, conn.tunnelRead, 'pause', packBody));
conn.remotePaused = true; conn.remotePaused = true;
conn.once('drain', function () { conn.once('drain', function () {
wsHandlers.sendMessage(Packer.pack(opts, conn.tunnelRead, 'resume')); var packBody = true;
sendMessage(Packer.packHeader(opts, conn.tunnelRead, 'resume', packBody));
conn.remotePaused = false; conn.remotePaused = false;
}); });
} }
@ -123,13 +151,13 @@ function _connect(state) {
} }
, closeSingle: function (cid) { , closeSingle: function (cid) {
if (!localclients[cid]) { if (!priv.localclients[cid]) {
return; return;
} }
console.log('[closeSingle]', cid); //console.log('[closeSingle]', cid);
PromiseA.resolve().then(function () { PromiseA.resolve().then(function () {
var conn = localclients[cid]; var conn = priv.localclients[cid];
conn.tunnelClosing = true; conn.tunnelClosing = true;
conn.end(); conn.end();
@ -147,40 +175,49 @@ function _connect(state) {
}); });
}); });
}).then(function () { }).then(function () {
if (localclients[cid]) { if (priv.localclients[cid]) {
console.warn('[closeSingle]', cid, 'connection still present after calling `end`'); console.warn('[closeSingle]', cid, 'connection still present after calling `end`');
localclients[cid].destroy(); priv.localclients[cid].destroy();
return timeoutPromise(500); return timeoutPromise(500);
} }
}).then(function () { }).then(function () {
if (localclients[cid]) { if (priv.localclients[cid]) {
console.error('[closeSingle]', cid, 'connection still present after calling `destroy`'); console.error('[closeSingle]', cid, 'connection still present after calling `destroy`');
delete localclients[cid]; delete priv.localclients[cid];
} }
}).catch(function (err) { }).catch(function (err) {
console.error('[closeSingle] failed to close connection', cid, err.toString()); console.error('[closeSingle] failed to close connection', cid, err.toString());
delete localclients[cid]; delete priv.localclients[cid];
}); });
} }
, closeAll: function () { , closeAll: function () {
console.log('[closeAll]'); console.log('[closeAll]');
Object.keys(localclients).forEach(function (cid) { Object.keys(priv.localclients).forEach(function (cid) {
clientHandlers.closeSingle(cid); clientHandlers.closeSingle(cid);
}); });
} }
, count: function () { , count: function () {
return Object.keys(localclients).length; return Object.keys(priv.localclients).length;
} }
}; };
var pendingCommands = {}; var pendingCommands = {};
function sendMessage(msg) {
// There is a chance that this occurred after the websocket was told to close
// and before it finished, in which case we don't need to log the error.
if (wstunneler.readyState !== wstunneler.CLOSING) {
wstunneler.send(msg, {binary: true});
return wstunneler.bufferedAmount;
}
}
function sendCommand(name) { function sendCommand(name) {
var id = Math.ceil(1e9 * Math.random()); var id = Math.ceil(1e9 * Math.random());
var cmd = [id, name].concat(Array.prototype.slice.call(arguments, 1)); var cmd = [id, name].concat(Array.prototype.slice.call(arguments, 1));
if (state.debug) { console.log('[DEBUG] command sending', cmd); } if (state.debug) { console.log('[DEBUG] command sending', cmd); }
wsHandlers.sendMessage(Packer.pack(null, cmd, 'control')); var packBody = true;
sendMessage(Packer.packHeader(null, cmd, 'control', packBody));
setTimeout(function () { setTimeout(function () {
if (pendingCommands[id]) { if (pendingCommands[id]) {
console.warn('command', name, id, 'timed out'); console.warn('command', name, id, 'timed out');
@ -203,24 +240,6 @@ function _connect(state) {
}); });
} }
function sendAllTokens() {
if (auth) {
authsent = true;
sendCommand('auth', auth).catch(function (err) { console.error('1', err); });
}
tokens.forEach(function (jwtoken) {
if (state.debug) { console.log('[DEBUG] send token'); }
authsent = true;
sendCommand('add_token', jwtoken)
.catch(function (err) {
console.error('failed re-adding token', jwtoken, 'after reconnect', err);
// Not sure if we should do something like remove the token here. It worked
// once or it shouldn't have stayed in the list, so it's less certain why
// it would have failed here.
});
});
}
function noHandler(cmd) { function noHandler(cmd) {
console.warn("[telebit] state.handlers['" + cmd[1] + "'] not set"); console.warn("[telebit] state.handlers['" + cmd[1] + "'] not set");
console.warn(cmd[2]); console.warn(cmd[2]);
@ -228,6 +247,23 @@ function _connect(state) {
var connCallback; var connCallback;
function hyperPeek(tun) {
var m;
var str;
if (tun.data) {
if ('http' === tun.service) {
str = tun.data.toString();
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im);
tun._name = tun._hostname = (m && m[1].toLowerCase() || '').split(':')[0];
}
else if ('https' === tun.service || 'tls' === tun.service) {
tun._name = tun._servername = sni(tun.data);
} else {
tun._name = '';
}
}
}
var packerHandlers = { var packerHandlers = {
oncontrol: function (opts) { oncontrol: function (opts) {
var cmd, err; var cmd, err;
@ -259,7 +295,21 @@ function _connect(state) {
if (cmd[1] === 'hello') { if (cmd[1] === 'hello') {
if (state.debug) { console.log('[DEBUG] hello received'); } if (state.debug) { console.log('[DEBUG] hello received'); }
sendAllTokens(); if (auth) {
authsent = true;
sendCommand('auth', auth).catch(function (err) { console.error('1', err); });
}
priv.tokens.forEach(function (jwtoken) {
if (state.debug) { console.log('[DEBUG] send token'); }
authsent = true;
sendCommand('add_token', jwtoken)
.catch(function (err) {
console.error('failed re-adding token', jwtoken, 'after reconnect', err);
// Not sure if we should do something like remove the token here. It worked
// once or it shouldn't have stayed in the list, so it's less certain why
// it would have failed here.
});
});
if (connCallback) { if (connCallback) {
connCallback(); connCallback();
} }
@ -286,28 +336,19 @@ function _connect(state) {
err = { message: 'unknown command "'+cmd[1]+'"', code: 'E_UNKNOWN_COMMAND' }; err = { message: 'unknown command "'+cmd[1]+'"', code: 'E_UNKNOWN_COMMAND' };
} }
wsHandlers.sendMessage(Packer.pack(null, [-cmd[0], err], 'control')); var packBody = true;
sendMessage(Packer.packHeader(null, [-cmd[0], err], 'control', packBody));
} }
, onmessage: function (tun) { , onconnection: function (tun) {
var cid = tun._id = Packer.addrToId(tun); var cid = tun._id = Packer.addrToId(tun);
var str;
var m;
if ('http' === tun.service) { // this data should have been gathered already as part of the proxy protocol
str = tun.data.toString(); // but if it's available again here we can double check
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im); hyperPeek(tun);
tun._name = tun._hostname = (m && m[1].toLowerCase() || '').split(':')[0];
}
else if ('https' === tun.service || 'tls' === tun.service) {
tun._name = tun._servername = sni(tun.data);
} else {
tun._name = '';
}
if (clientHandlers.write(cid, tun)) { return; } // TODO use readable streams instead
wstunneler._socket.pause();
wstunneler.pause();
require(state.sortingHat).assign(state, tun, function (err, conn) { require(state.sortingHat).assign(state, tun, function (err, conn) {
if (err) { if (err) {
err.message = err.message.replace(/:tun_id/, tun._id); err.message = err.message.replace(/:tun_id/, tun._id);
@ -316,32 +357,46 @@ function _connect(state) {
} }
clientHandlers.add(conn, cid, tun); clientHandlers.add(conn, cid, tun);
if (tun.data) { conn.write(tun.data); } if (tun.data) { conn.write(tun.data); }
wstunneler.resume(); wstunneler._socket.resume();
}); });
} }
, onmessage: function (tun) {
var cid = tun._id = Packer.addrToId(tun);
var handled;
hyperPeek(tun);
handled = clientHandlers.write(cid, tun);
// quasi backwards compat
if (!handled) { console.log("[debug] did not get 'connection' event"); packerHandlers.onconnection(tun); }
}
, onpause: function (opts) { , onpause: function (opts) {
var cid = Packer.addrToId(opts); var cid = Packer.addrToId(opts);
if (localclients[cid]) { if (priv.localclients[cid]) {
console.log("[TunnelPause] pausing '"+cid+"', remote received", opts.data.toString(), 'of', localclients[cid].tunnelWritten, 'sent'); console.log("[TunnelPause] pausing '"+cid+"', remote received", opts.data.toString(), 'of', priv.localclients[cid].tunnelWritten, 'sent');
localclients[cid].manualPause = true; priv.localclients[cid].manualPause = true;
localclients[cid].pause(); priv.localclients[cid].pause();
} else { } else {
console.log('[TunnelPause] remote tried pausing finished connection', cid); console.log('[TunnelPause] remote tried pausing finished connection', cid);
// Often we have enough latency that we've finished sending before we're told to pause, so // Often we have enough latency that we've finished sending before we're told to pause, so
// don't worry about sending back errors, since we won't be sending data over anyway. // don't worry about sending back errors, since we won't be sending data over anyway.
// wsHandlers.sendMessage(Packer.pack(opts, {message: 'no matching connection', code: 'E_NO_CONN'}, 'error')); // var packBody = true;
// sendMessage(Packer.packHeader(opts, {message: 'no matching connection', code: 'E_NO_CONN'}, 'error', packBody));
} }
} }
, onresume: function (opts) { , onresume: function (opts) {
var cid = Packer.addrToId(opts); var cid = Packer.addrToId(opts);
if (localclients[cid]) { if (priv.localclients[cid]) {
console.log("[TunnelResume] resuming '"+cid+"', remote received", opts.data.toString(), 'of', localclients[cid].tunnelWritten, 'sent'); console.log("[TunnelResume] resuming '"+cid+"', remote received", opts.data.toString(), 'of', priv.localclients[cid].tunnelWritten, 'sent');
localclients[cid].manualPause = false; priv.localclients[cid].manualPause = false;
localclients[cid].resume(); priv.localclients[cid].resume();
} else { } else {
console.log('[TunnelResume] remote tried resuming finished connection', cid); console.log('[TunnelResume] remote tried resuming finished connection', cid);
// wsHandlers.sendMessage(Packer.pack(opts, {message: 'no matching connection', code: 'E_NO_CONN'}, 'error')); // var packBody = true;
// sendMessage(Packer.packHeader(opts, {message: 'no matching connection', code: 'E_NO_CONN'}, 'error', packBody));
} }
} }
@ -358,56 +413,74 @@ function _connect(state) {
, _onConnectError: function (cid, opts, err) { , _onConnectError: function (cid, opts, err) {
console.info("[_onConnectError] opening '" + cid + "' failed because " + err.message); console.info("[_onConnectError] opening '" + cid + "' failed because " + err.message);
wsHandlers.sendMessage(Packer.pack(opts, null, 'error')); sendMessage(Packer.packHeader(opts, null, 'error'));
} }
}; };
var lastActivity; priv.timeoutId = null;
var timeoutId; priv.lastActivity = Date.now();
var wsHandlers = { priv.refreshTimeout = function refreshTimeout() {
refreshTimeout: function () { priv.lastActivity = Date.now();
lastActivity = Date.now(); };
} priv.checkTimeout = function checkTimeout() {
, checkTimeout: function () {
if (!wstunneler) { if (!wstunneler) {
console.warn('checkTimeout called when websocket already closed'); console.warn('checkTimeout called when websocket already closed');
return; return;
} }
// Determine how long the connection has been "silent", ie no activity. // Determine how long the connection has been "silent", ie no activity.
var silent = Date.now() - lastActivity; var silent = Date.now() - priv.lastActivity;
// If we have had activity within the last activityTimeout then all we need to do is // If we have had activity within the last activityTimeout then all we need to do is
// call this function again at the soonest time when the connection could be timed out. // call this function again at the soonest time when the connection could be timed out.
if (silent < activityTimeout) { if (silent < activityTimeout) {
timeoutId = setTimeout(wsHandlers.checkTimeout, activityTimeout-silent); priv.timeoutId = setTimeout(priv.checkTimeout, activityTimeout-silent);
} }
// Otherwise we check to see if the pong has also timed out, and if not we send a ping // Otherwise we check to see if the pong has also timed out, and if not we send a ping
// and call this function again when the pong will have timed out. // and call this function again when the pong will have timed out.
else if (silent < activityTimeout + pongTimeout) { else if (silent < activityTimeout + pongTimeout) {
console.log('pinging tunnel server'); //console.log('DEBUG: pinging tunnel server');
try { try {
wstunneler.ping(); wstunneler.ping();
} catch (err) { } catch (err) {
console.warn('failed to ping tunnel server', err); console.warn('failed to ping tunnel server', err);
} }
timeoutId = setTimeout(wsHandlers.checkTimeout, pongTimeout); priv.timeoutId = setTimeout(priv.checkTimeout, pongTimeout);
} }
// Last case means the ping we sent before didn't get a response soon enough, so we // Last case means the ping we sent before didn't get a response soon enough, so we
// need to close the websocket connection. // need to close the websocket connection.
else { else {
console.log('connection timed out'); console.info('[info] closing due to connection timeout');
wstunneler.close(1000, 'connection timeout'); wstunneler.close(1000, 'connection timeout');
} }
};
me.destroy = function destroy() {
console.info('[info] destroy()');
try {
//wstunneler.close(1000, 're-connect');
wstunneler._socket.destroy();
} catch(e) {
// ignore
} }
};
me.connect = function connect() {
if (!priv.tokens.length && state.config.email) {
auth = TelebitRemote._tokenFromState(state);
}
priv.timeoutId = null;
var machine = Packer.create(packerHandlers);
, onOpen: function () { console.info("[telebit:lib/remote.js] [connect] '" + (state.wss || state.relay) + "'");
console.info("[open] connected to '" + (state.wss || state.relay) + "'"); var tunnelUrl = (state.wss || state.relay).replace(/\/$/, '') + '/'; // + auth;
wsHandlers.refreshTimeout(); wstunneler = new WebSocket(tunnelUrl, { rejectUnauthorized: !state.insecure });
// XXXXXX
timeoutId = setTimeout(wsHandlers.checkTimeout, activityTimeout); wstunneler.on('open', function () {
console.info("[telebit:lib/remote.js] [open] connected to '" + (state.wss || state.relay) + "'");
me.emit('connect');
priv.refreshTimeout();
priv.timeoutId = setTimeout(priv.checkTimeout, activityTimeout);
wstunneler._socket.on('drain', function () { wstunneler._socket.on('drain', function () {
// the websocket library has it's own buffer apart from node's socket buffer, but that one // the websocket library has it's own buffer apart from node's socket buffer, but that one
// is much more difficult to watch, so we watch for the lower level buffer to drain and // is much more difficult to watch, so we watch for the lower level buffer to drain and
@ -424,22 +497,13 @@ function _connect(state) {
conn.resume(); conn.resume();
} }
}); });
pausedClients.length = 0; pausedClients.length = 0;
}); });
//Call either Open or Reconnect handlers.
if(state.handlers.onOpen && initialConnect) {
state.handlers.onOpen();
} else if (state.handlers.onReconnect && !initialConnect) {
state.handlers.onReconnect();
}
initialConnect = false; initialConnect = false;
} });
wstunneler.on('close', function () {
, onClose: function () { console.info("[info] [closing] received close signal from relay");
clearTimeout(timeoutId); clearTimeout(priv.timeoutId);
wstunneler = null;
clientHandlers.closeAll(); clientHandlers.closeAll();
var error = new Error('websocket connection closed before response'); var error = new Error('websocket connection closed before response');
@ -451,62 +515,44 @@ function _connect(state) {
connCallback(error); connCallback(error);
} }
if (!authenticated) { me.emit('close');
if(state.handlers.onError) { });
let err = new Error('Failed to connect on first attempt... check authentication'); wstunneler.on('error', function (err) {
state.handlers.onError(err); me.emit('error', err);
} });
if(state.handlers.onClose) {
state.handlers.onClose()
}
console.info('[close] failed on first attempt... check authentication.');
timeoutId = null;
}
else if (tokens.length) {
if(state.handlers.onDisconnect) {
state.handlers.onDisconnect();
}
console.info('[retry] disconnected and waiting...');
timeoutId = setTimeout(connect, 5000);
} else {
if(state.handlers.onClose) {
state.handlers.onClose()
}
}
}
, onError: function (err) { // Our library will automatically handle sending the pong respose to ping requests.
console.error("[tunnel error] " + err.message); wstunneler.on('ping', priv.refreshTimeout);
console.error(err); wstunneler.on('pong', function () {
if (connCallback) { //console.log('DEBUG received pong');
connCallback(err); priv.refreshTimeout();
} });
} wstunneler.on('message', function (data, flags) {
priv.refreshTimeout();
, sendMessage: function (msg) { if (data.error || '{' === data[0]) {
if (wstunneler) { console.log(data);
try {
wstunneler.send(msg, {binary: true});
return wstunneler.bufferedAmount;
} catch (err) {
// There is a chance that this occurred after the websocket was told to close
// and before it finished, in which case we don't need to log the error.
if (wstunneler.readyState !== wstunneler.CLOSING) {
console.warn('[sendMessage] error sending websocket message', err);
}
}
}
}
};
function connect() {
if (wstunneler) {
console.warn('attempted to connect with connection already active');
return; return;
} }
if (!tokens.length) { machine.fns.addChunk(data, flags);
if (state.config.email) { });
auth = { };
me.end = function() {
priv.tokens.length = 0;
if (priv.timeoutId) {
clearTimeout(priv.timeoutId);
priv.timeoutId = null;
}
console.info('[info] closing due to tr.end()');
wstunneler.close(1000, 're-connect');
wstunneler.on('close', function () {
me.emit('end');
});
};
}
TelebitRemote.prototype = EventEmitter.prototype;
TelebitRemote._tokenFromState = function (state) {
return {
subject: state.config.email subject: state.config.email
, subject_scheme: 'mailto' , subject_scheme: 'mailto'
// TODO create domains list earlier // TODO create domains list earlier
@ -519,125 +565,19 @@ function _connect(state) {
, os_release: os.release() , os_release: os.release()
, os_arch: os.arch() , os_arch: os.arch()
}; };
}
}
timeoutId = null;
var machine = Packer.create(packerHandlers);
console.info("[connect] '" + (state.wss || state.relay) + "'");
var tunnelUrl = (state.wss || state.relay).replace(/\/$/, '') + '/'; // + auth;
wstunneler = new WebSocket(tunnelUrl, { rejectUnauthorized: !state.insecure });
wstunneler.on('open', wsHandlers.onOpen);
wstunneler.on('close', wsHandlers.onClose);
wstunneler.on('error', wsHandlers.onError);
// Our library will automatically handle sending the pong respose to ping requests.
wstunneler.on('ping', wsHandlers.refreshTimeout);
wstunneler.on('pong', wsHandlers.refreshTimeout);
wstunneler.on('message', function (data, flags) {
wsHandlers.refreshTimeout();
if (data.error || '{' === data[0]) {
console.log(data);
return;
}
machine.fns.addChunk(data, flags);
});
}
connect();
var connPromise;
return {
end: function(cb) {
tokens.length = 0;
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
if (wstunneler) {
try {
wstunneler.close(cb);
} catch(e) {
console.error("[error] wstunneler.close()");
console.error(e);
}
}
}
, append: function (token) {
if (tokens.indexOf(token) >= 0) {
return PromiseA.resolve();
}
tokens.push(token);
var prom;
if (tokens.length === 1 && !wstunneler) {
// We just added the only token in the list, and the websocket connection isn't up
// so we need to restart the connection.
if (timeoutId) {
// Handle the case were the last token was removed and this token added between
// reconnect attempts to make sure we don't try openning multiple connections.
clearTimeout(timeoutId);
timeoutId = null;
}
// We want this case to behave as much like the other case as we can, but we don't have
// the same kind of reponses when we open brand new connections, so we have to rely on
// the 'hello' and the 'un-associated' error commands to determine if the token is good.
prom = connPromise = new PromiseA(function (resolve, reject) {
connCallback = function (err) {
connCallback = null;
connPromise = null;
if (err) {
reject(err);
} else {
resolve();
}
}; };
});
connect();
}
else if (connPromise) {
prom = connPromise.then(function () {
return sendCommand('add_token', token);
});
}
else {
prom = sendCommand('add_token', token);
}
prom.catch(function (err) { TelebitRemote.create = function (opts) {
console.error('adding token', token, 'failed:', err); return new TelebitRemote(opts);
// Most probably an invalid token of some kind, so we don't really want to keep it.
tokens.splice(tokens.indexOf(token), 1);
});
return prom;
}
, clear: function (token) {
if (typeof token === 'undefined') {
token = '*';
}
if (token === '*') {
tokens.length = 0;
} else {
var index = tokens.indexOf(token);
if (index < 0) {
return PromiseA.resolve();
}
tokens.splice(index);
}
var prom = sendCommand('delete_token', token);
prom.catch(function (err) {
console.error('clearing token', token, 'failed:', err);
});
return prom;
}
}; };
} TelebitRemote.createConnection = function (opts, cb) {
var tunnel = TelebitRemote.create(opts);
tunnel.connect(opts);
tunnel.once('connect', cb);
return tunnel;
};
TelebitRemote.connect = TelebitRemote.createConnection;
module.exports.connect = _connect; module.exports.TelebitRemote = TelebitRemote;
module.exports.createConnection = _connect;
}()); }());

View File

@ -1,6 +1,8 @@
'use strict'; 'use strict';
var os = require('os'); var os = require('os');
var path = require('path'); var path = require('path');
var fs = require('fs');
module.exports.print = function (config) { module.exports.print = function (config) {
var services = { https: {}, http: {}, tcp: {} }; var services = { https: {}, http: {}, tcp: {} };
@ -57,7 +59,7 @@ module.exports.print = function (config) {
}; };
module.exports.assign = function (state, tun, cb) { module.exports.assign = function (state, tun, cb) {
console.log('first message from', tun); //console.log('first message from', tun);
var net = state.net || require('net'); var net = state.net || require('net');
function trySsh(tun, cb) { function trySsh(tun, cb) {
@ -73,7 +75,7 @@ module.exports.assign = function (state, tun, cb) {
cb(null, false); cb(null, false);
return; return;
} }
cb(null, getNetConn(sshPort)); getNetConn(sshPort, cb);
} }
var handlers = {}; var handlers = {};
@ -86,6 +88,7 @@ module.exports.assign = function (state, tun, cb) {
state.httpRedirectServer = require('http').createServer(state.greenlock.middleware(state.redirectHttps)); state.httpRedirectServer = require('http').createServer(state.greenlock.middleware(state.redirectHttps));
} }
state.httpRedirectServer.emit('connection', socket); state.httpRedirectServer.emit('connection', socket);
process.nextTick(function () { socket.resume(); });
}; };
handlers.https = function (tlsSocket) { handlers.https = function (tlsSocket) {
console.log('Encrypted', tlsSocket.encrypted, tlsSocket.remoteAddress, tlsSocket.remotePort); console.log('Encrypted', tlsSocket.encrypted, tlsSocket.remoteAddress, tlsSocket.remotePort);
@ -94,13 +97,15 @@ module.exports.assign = function (state, tun, cb) {
state._serveStatic = require('serve-static'); state._serveStatic = require('serve-static');
state._defaultServe = state._serveStatic(path.join(__dirname, 'html')); state._defaultServe = state._serveStatic(path.join(__dirname, 'html'));
state.defaultHttpServer = require('http').createServer(function (req, res) { state.defaultHttpServer = require('http').createServer(function (req, res) {
// TODO serve api
state._defaultServe(req, res, state._finalHandler(req, res)); state._defaultServe(req, res, state._finalHandler(req, res));
}); });
} }
state.defaultHttpServer.emit('connection', tlsSocket); state.defaultHttpServer.emit('connection', tlsSocket);
process.nextTick(function () { tlsSocket.resume(); });
}; };
function getNetConn(port) { function getNetConn(port, cb) {
var netOpts = { var netOpts = {
port: port port: port
, host: '127.0.0.1' , host: '127.0.0.1'
@ -117,8 +122,12 @@ module.exports.assign = function (state, tun, cb) {
// this will happen before 'data' or 'readable' is triggered // this will happen before 'data' or 'readable' is triggered
// We use the data from the netOpts object so that the createConnection function has // We use the data from the netOpts object so that the createConnection function has
// the oppurtunity of removing/changing it if it wants/needs to handle it differently. // the oppurtunity of removing/changing it if it wants/needs to handle it differently.
cb(null, conn);
cb = function () {}; // for error events
});
conn.on('error', function (err) {
cb(err);
}); });
return conn;
} }
function redirectHttp(cb) { function redirectHttp(cb) {
@ -132,6 +141,40 @@ module.exports.assign = function (state, tun, cb) {
return conn; return conn;
} }
function errorTcp(conf, cb) {
var socketPair = require('socket-pair');
var conn = socketPair.create(function (err, other) {
if (err) { cb(err); return; }
cb(null, conn);
other.write("\n" +
[ "[Telebit Error Server]"
, "Could not load '" + conf.handler + "' as a module, file, or directory."
].join("\n") + "\n\n");
other.end();
});
//if (tun.data) { conn.write(tun.data); }
return conn;
}
function fileDirTcp(opts, cb) {
var socketPair = require('socket-pair');
var conn = socketPair.create(function (err, other) {
if (err) { cb(err); return; }
if (opts.stat.isFile()) {
fs.createReadStream(opts.config.handler).pipe(other);
} else {
fs.readdir(opts.config.handler, function (err, nodes) {
other.write('\n' + nodes.join('\n') + '\n\n');
other.end();
});
}
cb(null, conn);
});
//if (tun.data) { conn.write(tun.data); }
return conn;
}
function echoTcp(cb) { function echoTcp(cb) {
var socketPair = require('socket-pair'); var socketPair = require('socket-pair');
var conn = socketPair.create(function (err, other) { var conn = socketPair.create(function (err, other) {
@ -194,8 +237,7 @@ module.exports.assign = function (state, tun, cb) {
function invokeTcpHandler(conf, socket, tun, id, cb) { function invokeTcpHandler(conf, socket, tun, id, cb) {
var conn; var conn;
if (parseInt(conf.handler, 10)) { if (parseInt(conf.handler, 10)) {
conn = getNetConn(conf.handler); getNetConn(conf.handler, cb);
cb(null, conn);
return conn; return conn;
} }
@ -219,10 +261,13 @@ module.exports.assign = function (state, tun, cb) {
} catch(e2) { } catch(e2) {
console.error("Failed to require('" + handlerpath + "'):", e1.message); console.error("Failed to require('" + handlerpath + "'):", e1.message);
console.error("Failed to require('" + path.join(localshare, handlerpath) + "'):", e2.message); console.error("Failed to require('" + path.join(localshare, handlerpath) + "'):", e2.message);
console.warn("Using default handler for '" + handle + ":" + id + "'"); console.warn("Trying static and index handlers for '" + handle + ":" + id + "'");
echoTcp(cb); handler = null;
// fallthru
} }
} }
if (handler) {
var socketPair = require('socket-pair'); var socketPair = require('socket-pair');
conn = socketPair.create(function (err, other) { conn = socketPair.create(function (err, other) {
handler(other, tun, id); handler(other, tun, id);
@ -230,15 +275,31 @@ module.exports.assign = function (state, tun, cb) {
}); });
return conn; return conn;
} }
fs.access(conf.handler, fs.constants.R_OK, function (err1) {
fs.stat(conf.handler, function (err2, stat) {
if ((err1 || err2) || !(stat.isFile() || stat.isDirectory())) {
errorTcp(conf, cb);
return;
}
fileDirTcp({ config: conf, stat: stat }, cb);
});
});
}
var handlerservers = {}; var handlerservers = {};
function invokeHandler(conf, tlsSocket, tun, id) { function invokeHandler(conf, tlsSocket, tun, id) {
var conn;
if (parseInt(conf.handler, 10)) { if (parseInt(conf.handler, 10)) {
// TODO http-proxy with proper headers and ws support // TODO http-proxy with proper headers and ws support
conn = getNetConn(conf.handler); getNetConn(conf.handler, function (err, conn) {
process.nextTick(function () { tlsSocket.resume(); });
if (err) {
require('./handlers/local-app-error.js')({ handler: conf.handler, socket: tlsSocket });
return;
}
console.info("Port-Forwarding '" + (tun.name || tun.serviceport) + "' to '" + conf.handler + "'"); console.info("Port-Forwarding '" + (tun.name || tun.serviceport) + "' to '" + conf.handler + "'");
conn.pipe(tlsSocket); conn.pipe(tlsSocket);
tlsSocket.pipe(conn); tlsSocket.pipe(conn);
});
return; return;
} }
var handle = tun.name || tun.port; var handle = tun.name || tun.port;
@ -256,6 +317,7 @@ module.exports.assign = function (state, tun, cb) {
tlsSocket._id = id; tlsSocket._id = id;
if (handlerservers[conf.handler]) { if (handlerservers[conf.handler]) {
handlerservers[conf.handler].emit('connection', tlsSocket); handlerservers[conf.handler].emit('connection', tlsSocket);
process.nextTick(function () { tlsSocket.resume(); });
return; return;
} }
@ -266,15 +328,15 @@ module.exports.assign = function (state, tun, cb) {
try { try {
handler = require(handlerpath); handler = require(handlerpath);
console.info("Handling '" + handle + ":" + id + "' with '" + handlerpath + "'"); console.info("Trying to handle '" + handle + ":" + id + "' with '" + handlerpath + "'");
} catch(e1) { } catch(e1) {
try { try {
handler = require(path.join(localshare, handlerpath)); handler = require(path.join(localshare, handlerpath));
console.info("Handling '" + handle + ":" + id + "' with '" + handlerpath + "'"); console.info("Skip. (couldn't require('" + handlerpath + "'):", e1.message + ")");
console.info("Trying to handle '" + handle + ":" + id + "' with '" + handlerpath + "'");
} catch(e2) { } catch(e2) {
console.error("Failed to require('" + handlerpath + "'):", e1.message); console.info("Skip. (couldn't require('" + path.join(localshare, handlerpath) + "'):", e2.message + ")");
console.error("Failed to require('" + path.join(localshare, handlerpath) + "'):", e2.message); console.info("Last chance! (using static and index handlers for '" + handle + ":" + id + "')");
console.warn("Using default handler for '" + handle + ":" + id + "'");
handler = null; handler = null;
// fallthru // fallthru
} }
@ -283,10 +345,70 @@ module.exports.assign = function (state, tun, cb) {
if (handler) { if (handler) {
handlerservers[conf.handler] = http.createServer(handler); handlerservers[conf.handler] = http.createServer(handler);
handlerservers[conf.handler].emit('connection', tlsSocket); handlerservers[conf.handler].emit('connection', tlsSocket);
process.nextTick(function () { tlsSocket.resume(); });
return; return;
} }
fs.access(conf.handler, fs.constants.R_OK, function (err1) {
fs.stat(conf.handler, function (err2, stat) {
if (err1 || err2) {
// TODO handle errors
handlers.https(tlsSocket, tun, id); handlers.https(tlsSocket, tun, id);
return;
}
var isFile = stat.isFile();
state._finalHandler = require('finalhandler');
state._serveStatic = require('serve-static');
state._serveIndex = require('serve-index');
var serveIndex;
var serveStatic;
var dlStatic;
if (isFile) {
serveStatic = state._serveStatic(path.dirname(conf.handler), { dotfiles: 'allow', index: [ 'index.html' ] });
dlStatic = state._serveStatic(path.dirname(conf.handler), { acceptRanges: false, dotfiles: 'allow', index: [ 'index.html' ] });
serveIndex = function (req, res, next) { next(); };
isFile = path.basename(conf.handler);
} else {
serveStatic = state._serveStatic(conf.handler, { dotfiles: 'allow', index: [ 'index.html' ] });
dlStatic = state._serveStatic(conf.handler, { acceptRanges: false, dotfiles: 'allow', index: [ 'index.html' ] });
serveIndex = state._serveIndex(conf.handler, {
hidden: true, icons: true
, template: require('serve-tpl-attachment')({ privatefiles: 'ignore' })
});
}
handler = function (req, res) {
var qIndex = req.url.indexOf('?');
var fIndex;
var fname;
if (-1 === qIndex) {
qIndex = req.url.length;
}
req.querystring = req.url.substr(qIndex);
req.url = req.url.substr(0, qIndex);
req.query = require('querystring').parse(req.querystring.substr(1));
if (isFile) {
req.url = '/' + isFile;
}
//console.log('[req.query]', req.url, req.query);
if (req.query.download) {
fIndex = req.url.lastIndexOf('/');
fname = req.url.substr(fIndex + 1);
res.setHeader('Content-Disposition', 'attachment; filename="'+decodeURIComponent(fname)+'"');
res.setHeader('Content-Type', 'application/octet-stream');
dlStatic(req, res, function () {
serveIndex(req, res, state._finalHandler(req, res));
});
} else {
serveStatic(req, res, function () {
serveIndex(req, res, state._finalHandler(req, res));
});
}
};
handlerservers[conf.handler] = http.createServer(handler);
handlerservers[conf.handler].emit('connection', tlsSocket);
process.nextTick(function () { tlsSocket.resume(); });
});
});
} }
function terminateTls(tun, cb) { function terminateTls(tun, cb) {
@ -318,8 +440,6 @@ module.exports.assign = function (state, tun, cb) {
tlsSocket._handle.onread(firstChunk.length, firstChunk); tlsSocket._handle.onread(firstChunk.length, firstChunk);
trySsh({ data: firstChunk }, function (err, conn) { trySsh({ data: firstChunk }, function (err, conn) {
process.nextTick(function () { tlsSocket.resume(); });
if (conn) { if (conn) {
conn.pipe(tlsSocket); conn.pipe(tlsSocket);
tlsSocket.pipe(conn); tlsSocket.pipe(conn);
@ -332,7 +452,7 @@ module.exports.assign = function (state, tun, cb) {
return; return;
} }
console.log('https invokeHandler'); //console.log('https invokeHandler');
invokeHandler(conf, tlsSocket, tun, id); invokeHandler(conf, tlsSocket, tun, id);
}); });
}); });

View File

@ -8,6 +8,7 @@ module.exports = function (pkg) {
https.get(url, function (resp) { https.get(url, function (resp) {
var str = ''; var str = '';
resp.on('data', function (chunk) { resp.on('data', function (chunk) {
//var chunk = conn.read();
str += chunk.toString('utf8'); str += chunk.toString('utf8');
}); });
resp.on('end', function () { resp.on('end', function () {
@ -128,8 +129,13 @@ module.exports = function (pkg) {
}); });
}); });
} }
setInterval(checkUpgrade, 2 * 60 * 60 * 1000);
var _interval = setInterval(checkUpgrade, 2 * 60 * 60 * 1000);
process.nextTick(function () { process.nextTick(function () {
checkUpgrade(); checkUpgrade();
}); });
return function cancel() {
clearInterval(_interval);
};
}; };

View File

@ -1,8 +1,13 @@
{ {
"name": "telebit", "name": "telebit",
"version": "0.18.5", "version": "0.20.8",
"description": "Break out of localhost. Connect to any device from anywhere over any tcp port or securely in a browser. A secure tunnel. A poor man's reverse VPN.", "description": "Break out of localhost. Connect to any device from anywhere over any tcp port or securely in a browser. A secure tunnel. A poor man's reverse VPN.",
"main": "lib/remote.js", "main": "lib/remote.js",
"files": [
"bin",
"lib",
"usr"
],
"bin": { "bin": {
"telebit": "bin/telebit.js", "telebit": "bin/telebit.js",
"telebitd": "bin/telebitd.js" "telebitd": "bin/telebitd.js"
@ -48,23 +53,30 @@
}, },
"homepage": "https://git.coolaj86.com/coolaj86/telebit.js#readme", "homepage": "https://git.coolaj86.com/coolaj86/telebit.js#readme",
"dependencies": { "dependencies": {
"@coolaj86/urequest": "^1.1.1", "@coolaj86/urequest": "^1.3.5",
"bluebird": "^3.5.1",
"finalhandler": "^1.1.1", "finalhandler": "^1.1.1",
"greenlock": "^2.2.20", "greenlock": "^2.3.1",
"js-yaml": "^3.11.0", "js-yaml": "^3.11.0",
"jsonwebtoken": "^7.1.9", "jsonwebtoken": "^7.1.9",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"proxy-packer": "^1.4.3", "proxy-packer": "^2.0.2",
"ps-list": "^5.0.0",
"recase": "^1.0.4", "recase": "^1.0.4",
"redirect-https": "^1.1.5", "redirect-https": "^1.1.5",
"sclient": "^1.4.1",
"serve-index": "^1.9.1",
"serve-static": "^1.13.2", "serve-static": "^1.13.2",
"serve-tpl-attachment": "^1.0.4",
"sni": "^1.0.0", "sni": "^1.0.0",
"socket-pair": "^1.0.3", "socket-pair": "^1.0.3",
"ws": "^2.2.3" "toml": "^0.4.1",
"ws": "^6.0.0"
},
"trulyOptionalDependencies": {
"bluebird": "^3.5.1"
}, },
"enginesStrict": true, "enginesStrict": true,
"engines": { "engines": {
"node": "10.2.1 10.4" "node": "10.2.1 10.4 10.6"
} }
} }

21
tests/README.md Normal file
View File

@ -0,0 +1,21 @@
There are a number of conditions and whatnot that must be tested in more-or-less real-world conditions.
telebit init // fresh install
telebit init // after install complete
telebit http 3000 // have an app listening on localhost:3000
telebit http 4545 // do not have an app listening
telebit http ./path/to/site
telebit http ./path/to/dir
telebit http ./path/to/file
telebit http ./doesnt/exist
telebit ssh auto // do have ssh listening on localhost:22
telebit ssh 4545 // do have ssh listenening
telebit tcp 3000 // have an echo server listening on localhost:3000
telebit tcp 4545 // no server listening
telebit tcp ./path/to/file
telebit tcp ./path/to/dir

27
tests/echo.js Normal file
View File

@ -0,0 +1,27 @@
'use strict';
var net = require('net');
var server = net.createServer(function (conn) {
function echo(chunk) {
conn.write(chunk);
if (chunk.length <= 10 && /\b(q|quit|end|cancel)\b/i.test(chunk.toString('utf8'))) {
conn.end();
conn.removeListener('data', echo);
}
}
conn.on('data', echo);
// NOTE: early versions of telebit do not support a 'connection' event
// and therefore will say hello after the first message from the client
conn.write(
"[Echo Server] Hello! I'm an echo server.\n"
+ "[Echo Server] I try to be your friend but when I see things like q|quit|end|cancel, I give up.\n"
);
});
server.on('error', function (err) {
console.error("[echo server]");
console.error(err);
});
server.listen(process.argv[2] || 3000, function () {
console.info("Listening on", this.address());
console.info('ctrl+c to cancel');
});

20
tests/windows-pipe.js Normal file
View File

@ -0,0 +1,20 @@
'use strict';
var os = require('os');
var net = require('net');
var ipc = {
path: /^win/.test(os.platform()) ? '\\\\.\\pipe\\X:/name/of/pipe' : (__dirname + '/tmp.sock')
};
var oldUmask = process.umask(0x0000);
var server = net.createServer();
server.listen({
path: ipc.path || null
, host: 'localhost'
, port: ipc.port || null
, writeableAll: true
, readableAll: true
}, function () {
process.umask(oldUmask);
console.log("Listening on", this.address());
});

View File

@ -0,0 +1,22 @@
'use strict';
var path = require('path');
var spawn = require('child_process').spawn;
var args = [
path.join(__dirname, 'windows-pipe.js')
];
var subprocess = spawn(
'node'
, args
, { detached: true
, stdio: [ 'ignore', process.stdout, process.stderr ]
}
);
//console.log('[debug]', vars.telebitNode, args.join(' '));
subprocess.unref();
subprocess.on('error', function (_err) {
console.error(_err);
});
subprocess.on('exit', function (code, signal) {
console.error('' + code + ' ' + signal + ' failure to launch');
});

View File

@ -54,8 +54,8 @@
<string>{TELEBIT_PATH}</string> <string>{TELEBIT_PATH}</string>
<key>StandardErrorPath</key> <key>StandardErrorPath</key>
<string>{TELEBIT_LOG_DIR}/error.log</string> <string>{TELEBIT_LOG_DIR}/telebit.log</string>
<key>StandardOutPath</key> <key>StandardOutPath</key>
<string>{TELEBIT_LOG_DIR}/info.log</string> <string>{TELEBIT_LOG_DIR}/telebit.log</string>
</dict> </dict>
</plist> </plist>

View File

@ -63,8 +63,8 @@
<string>{TELEBIT_PATH}</string> <string>{TELEBIT_PATH}</string>
<key>StandardErrorPath</key> <key>StandardErrorPath</key>
<string>{TELEBIT_LOG_DIR}/error.log</string> <string>{TELEBIT_LOG_DIR}/telebit.log</string>
<key>StandardOutPath</key> <key>StandardOutPath</key>
<string>{TELEBIT_LOG_DIR}/info.log</string> <string>{TELEBIT_LOG_DIR}/telebit.log</string>
</dict> </dict>
</plist> </plist>

View File

@ -61,4 +61,7 @@ NoNewPrivileges=true
; NoNewPrivileges=true ; NoNewPrivileges=true
[Install] [Install]
WantedBy=multi-user.target # For system-level service
;WantedBy=multi-user.target
# For userspace service
WantedBy=default.target

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -7,6 +7,76 @@ var exec = require('child_process').exec;
var path = require('path'); var path = require('path');
var Launcher = module.exports; var Launcher = module.exports;
Launcher._killAll = function (fn) {
var psList = require('ps-list');
psList().then(function (procs) {
procs.forEach(function (proc) {
if ('node' === proc.name && /\btelebitd\b/i.test(proc.cmd)) {
console.log(proc);
process.kill(proc.pid);
return true;
}
});
// Two things:
// 1) wait to see if the process dies
// 2) wait to give time for the socket to connect
setTimeout(function () {
if (fn) { fn(null); return; }
}, 1.75 * 1000);
});
};
Launcher._getError = function getError(err, stderr) {
if (err) { return err; }
if (stderr) {
err = new Error(stderr);
err.code = 'ELAUNCHER';
return err;
}
};
Launcher._detect = function (things, fn) {
if (things.launcher) {
if ('string' === typeof things.launcher) {
fn(null, things.launcher);
return;
}
if ('function' === typeof things.launcher) {
things.launcher(things);
return;
}
}
// could have used "command-exists" but I'm trying to stay low-dependency
// os.platform(), os.type()
if (!/^win/i.test(os.platform())) {
if (/^darwin/i.test(os.platform())) {
exec('command -v launchctl', things._execOpts, function (err, stdout, stderr) {
err = Launcher._getError(err, stderr);
fn(err, 'launchctl');
});
} else {
exec('command -v systemctl', things._execOpts, function (err, stdout, stderr) {
err = Launcher._getError(err, stderr);
fn(err, 'systemctl');
});
}
} else {
// https://stackoverflow.com/questions/17908789/how-to-add-an-item-to-registry-to-run-at-startup-without-uac
// wininit? regedit? SCM?
// REG ADD "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /V "My App" /t REG_SZ /F /D "C:\MyAppPath\MyApp.exe"
// https://www.microsoft.com/developerblog/2015/11/09/reading-and-writing-to-the-windows-registry-in-process-from-node-js/
// https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/reg-add
// https://social.msdn.microsoft.com/Forums/en-US/5b318f44-281e-4098-8dee-3ba8435fa391/add-registry-key-for-autostart-of-app-in-ice?forum=quebectools
// utils.elevate
// https://github.com/CatalystCode/windows-registry-node
exec('where reg.exe', things._execOpts, function (err, stdout, stderr) {
//console.log((stdout||'').trim());
if (stderr) {
console.error(stderr);
}
fn(err, 'reg.exe');
});
}
};
Launcher.install = function (things, fn) { Launcher.install = function (things, fn) {
if (!fn) { fn = function (err) { if (err) { console.error(err); } }; } if (!fn) { fn = function (err) { if (err) { console.error(err); } }; }
things = things || {}; things = things || {};
@ -40,24 +110,17 @@ Launcher.install = function (things, fn) {
, telebitConfig: path.join(os.homedir(), '.config/telebit/telebit.yml') , telebitConfig: path.join(os.homedir(), '.config/telebit/telebit.yml')
, telebitdConfig: path.join(os.homedir(), '.config/telebit/telebitd.yml') , telebitdConfig: path.join(os.homedir(), '.config/telebit/telebitd.yml')
, TELEBIT_LOG_DIR: path.join(os.homedir(), '.local/share/telebit/var/log') , TELEBIT_LOG_DIR: path.join(os.homedir(), '.local/share/telebit/var/log')
, TELEBIT_SOCK_DIR: path.join(os.homedir(), '.local/share/telebit/var/run')
}; };
vars.telebitBinTpl = path.join(telebitRoot, 'usr/share/dist/bin/telebit.tpl'); vars.telebitBinTpl = path.join(telebitRoot, 'usr/share/dist/bin/telebit.tpl');
vars.telebitNpm = path.resolve(vars.telebitNode, '../npm'); vars.telebitNpm = path.resolve(vars.telebitNode, '../npm');
vars.nodePath = path.resolve(vars.telebitNode, '../lib/node_modules'); vars.nodePath = path.resolve(vars.telebitNode, '../../lib/node_modules');
vars.npmConfigPrefix = path.resolve(vars.telebitNode, '..'); vars.npmConfigPrefix = path.resolve(vars.telebitNode, '..', '..');
vars.userspace = (!things.telebitUser || (things.telebitUser === os.userInfo().username)) ? true : false; vars.userspace = (!things.telebitUser || (things.telebitUser === os.userInfo().username)) ? true : false;
if (-1 === vars.telebitRwDirs.indexOf(vars.npmConfigPrefix)) { if (-1 === vars.telebitRwDirs.indexOf(vars.npmConfigPrefix)) {
vars.telebitRwDirs.push(vars.npmConfigPrefix); vars.telebitRwDirs.push(vars.npmConfigPrefix);
} }
vars.telebitRwDirs = vars.telebitRwDirs.join(' '); vars.telebitRwDirs = vars.telebitRwDirs.join(' ');
function getError(err, stderr) {
if (err) { return err; }
if (stderr) {
err = new Error(stderr);
err.code = 'ELAUNCHER';
return err;
}
}
var launchers = { var launchers = {
'node': function () { 'node': function () {
var fs = require('fs'); var fs = require('fs');
@ -74,17 +137,20 @@ Launcher.install = function (things, fn) {
var killed = 0; var killed = 0;
var err; var err;
var subprocess = spawn( var args = [
vars.telebitNode path.join(telebitRoot, 'bin/telebitd.js')
, [ path.join(telebitRoot, 'bin/telebitd.js')
, 'daemon' , 'daemon'
, '--config' , '--config'
, vars.telebitdConfig , vars.telebitdConfig
] ];
var subprocess = spawn(
vars.telebitNode
, args
, { detached: true , { detached: true
, stdio: [ 'ignore', stdout, stderr ] , stdio: [ 'ignore', stdout, stderr ]
} }
); );
//console.log('[debug]', vars.telebitNode, args.join(' '));
subprocess.unref(); subprocess.unref();
subprocess.on('error', function (_err) { subprocess.on('error', function (_err) {
err = _err; err = _err;
@ -95,21 +161,23 @@ Launcher.install = function (things, fn) {
killed += 1; killed += 1;
}); });
// Two things:
// 1) wait to see if the process dies
// 2) wait to give time for the socket to connect
setTimeout(function () { setTimeout(function () {
if (fn) { fn(null); return; } if (fn) { fn(err); return; }
}, 1 * 1000); }, 1.75 * 1000);
return; return;
} }
, 'launchctl': function () { , 'launchctl': function () {
var launcher = path.join(os.homedir(), 'Library/LaunchAgents/cloud.telebit.remote.plist'); var launcher = path.join(os.homedir(), 'Library/LaunchAgents/cloud.telebit.remote.plist');
try { try {
mkdirp.sync(path.join(os.homedir(), 'Library/LaunchAgents')); mkdirp.sync(path.join(os.homedir(), 'Library/LaunchAgents'));
mkdirp.sync(path.join(vars.telebitPath, 'bin'));
mkdirp.sync(vars.TELEBIT_LOG_DIR);
installLauncher.sync({ installLauncher.sync({
file: { file: {
tpl: vars.telebitBinTpl tpl: vars.telebitBinTpl
, launcher: path.join(vars.telebitPath, 'bin/telebit') , launcher: path.join(vars.telebitPath, 'bin/telebit')
, executable: true
} }
, vars: vars , vars: vars
}); });
@ -124,17 +192,19 @@ Launcher.install = function (things, fn) {
var execstr = launcherstr + "unload -w " + launcher; var execstr = launcherstr + "unload -w " + launcher;
exec(execstr, things._execOpts, function (/*err, stdout, stderr*/) { exec(execstr, things._execOpts, function (/*err, stdout, stderr*/) {
// we probably only need to skip the stderr (saying that it can't stop something that isn't started) // we probably only need to skip the stderr (saying that it can't stop something that isn't started)
//err = getError(err, stderr); //err = Launcher._getError(err, stderr);
//if (err) { fn(err); return; } //if (err) { fn(err); return; }
//console.log((stdout||'').trim()); //console.log((stdout||'').trim());
//console.log('unload worked?'); //console.log('unload worked?');
execstr = launcherstr + "load -w " + launcher; execstr = launcherstr + "load -w " + launcher;
exec(execstr, things._execOpts, function (err, stdout, stderr) { exec(execstr, things._execOpts, function (err, stdout, stderr) {
err = getError(err, stderr); err = Launcher._getError(err, stderr);
if (err) { fn(err); return; } if (err) { fn(err); return; }
//console.log((stdout||'').trim()); //console.log((stdout||'').trim());
//console.log('load worked?'); //console.log('load worked?');
setTimeout(function () {
fn(null); fn(null);
}, 1.25 * 1000);
}); });
}); });
} catch(e) { } catch(e) {
@ -162,23 +232,23 @@ Launcher.install = function (things, fn) {
var launcherstr = (vars.userspace ? "" : "sudo ") + "systemctl " + (vars.userspace ? "--user " : ""); var launcherstr = (vars.userspace ? "" : "sudo ") + "systemctl " + (vars.userspace ? "--user " : "");
var execstr = launcherstr + "daemon-reload"; var execstr = launcherstr + "daemon-reload";
exec(execstr, things._execOpts, function (err, stdout, stderr) { exec(execstr, things._execOpts, function (err, stdout, stderr) {
err = getError(err, stderr); err = Launcher._getError(err, stderr);
if (err) { fn(err); return; } if (err) { fn(err); return; }
//console.log((stdout||'').trim()); //console.log((stdout||'').trim());
var execstr = launcherstr + "enable " + launchername; var execstr = launcherstr + "enable " + launchername;
exec(execstr, things._execOpts, function (err, stdout, stderr) { exec(execstr, things._execOpts, function (err, stdout, stderr) {
err = getError(err, stderr); err = Launcher._getError(err, stderr && !/Created symlink/i.test(stderr) && stderr || '');
if (err) { fn(err); return; } if (err) { fn(err); return; }
//console.log((stdout||'').trim()); //console.log((stdout||'').trim());
var execstr = launcherstr + "restart " + launchername; var execstr = launcherstr + "restart " + launchername;
exec(execstr, things._execOpts, function (err, stdout, stderr) { exec(execstr, things._execOpts, function (err, stdout, stderr) {
err = getError(err, stderr); err = Launcher._getError(err, stderr);
if (err) { fn(err); return; } if (err) { fn(err); return; }
//console.log((stdout||'').trim()); //console.log((stdout||'').trim());
setTimeout(function () { setTimeout(function () {
var execstr = launcherstr + "status " + launchername; var execstr = launcherstr + "status " + launchername;
exec(execstr, things._execOpts, function (err, stdout, stderr) { exec(execstr, things._execOpts, function (err, stdout, stderr) {
err = getError(err, stderr); err = Launcher._getError(err, stderr);
if (err) { fn(err); return; } if (err) { fn(err); return; }
if (!/active.*running/i.test(stdout)) { if (!/active.*running/i.test(stdout)) {
err = new Error("systemd failed to start '" + launchername + "'"); err = new Error("systemd failed to start '" + launchername + "'");
@ -214,10 +284,10 @@ Launcher.install = function (things, fn) {
+ '" /F' + '" /F'
; ;
exec(cmd, things._execOpts, function (err, stdout, stderr) { exec(cmd, things._execOpts, function (err, stdout, stderr) {
err = getError(err, stderr); err = Launcher._getError(err, stderr);
if (err) { fn(err); return; } if (err) { fn(err); return; }
//console.log((stdout||'').trim()); // need to start it for the first time ourselves
fn(null); run(null, 'node');
}); });
} }
}; };
@ -231,6 +301,9 @@ Launcher.install = function (things, fn) {
if (launchers[launcher]) { if (launchers[launcher]) {
// console.log('Launching with launcher ' + launcher); // console.log('Launching with launcher ' + launcher);
mkdirp.sync(path.join(vars.telebitPath, 'bin'));
mkdirp.sync(vars.TELEBIT_LOG_DIR);
mkdirp.sync(vars.TELEBIT_SOCK_DIR);
launchers[launcher](); launchers[launcher]();
return; return;
} else { } else {
@ -238,54 +311,134 @@ Launcher.install = function (things, fn) {
} }
} }
if (things.launcher) {
if ('string' === typeof things.launcher) {
run(null, things.launcher);
return;
}
if ('function' === typeof things.launcher) {
things._vars = vars; things._vars = vars;
things._userspace = vars.userspace; things._userspace = vars.userspace;
things.launcher(things); Launcher._detect(things, run);
return; };
} Launcher.uninstall = function (things, fn) {
} if (!fn) { fn = function (err) { if (err) { console.error(err); } }; }
things = things || {};
// could have used "command-exists" but I'm trying to stay low-dependency // Right now this is just for npm install -g and npx
// os.platform(), os.type() if (things.env) {
if (!/^win/i.test(os.platform())) { things.env.PATH = things.env.PATH || process.env.PATH;
if (/^darwin/i.test(os.platform())) {
exec('command -v launchctl', things._execOpts, function (err, stdout, stderr) {
err = getError(err, stderr);
run(err, 'launchctl');
});
} else { } else {
exec('command -v systemctl', things._execOpts, function (err, stdout, stderr) { things.env = process.env;
err = getError(err, stderr); }
run(err, 'systemctl'); things.argv = things.argv || process.argv;
things._execOpts = { windowsHide: true, env: things.env };
var vars = {
telebitUser: os.userInfo().username
};
vars.userspace = (!things.telebitUser || (things.telebitUser === os.userInfo().username)) ? true : false;
var launchers = {
'node': function () {
Launcher._killAll(fn);
}
, 'launchctl': function () {
var launcher = path.join(os.homedir(), 'Library/LaunchAgents/cloud.telebit.remote.plist');
try {
var launcherstr = (vars.userspace ? "" : "sudo ") + "launchctl ";
var execstr = launcherstr + "unload -w " + launcher;
exec(execstr, things._execOpts, function (err, stdout, stderr) {
// we probably only need to skip the stderr (saying that it can't stop something that isn't started)
//err = Launcher._getError(err, stderr);
//if (err) { fn(err); return; }
//console.log((stdout||'').trim());
//console.log('unload worked?');
err = Launcher._getError(err, stderr);
if (err) { fn(err); return; }
//console.log((stdout||'').trim());
//console.log('load worked?');
setTimeout(function () {
fn(null);
}, 1.25 * 1000);
}); });
} catch(e) {
console.error("'" + launcher + "' error (uninstall):");
console.error(e);
if (fn) { fn(e); return; }
} }
} else {
// https://stackoverflow.com/questions/17908789/how-to-add-an-item-to-registry-to-run-at-startup-without-uac
// wininit? regedit? SCM?
// REG ADD "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /V "My App" /t REG_SZ /F /D "C:\MyAppPath\MyApp.exe"
// https://www.microsoft.com/developerblog/2015/11/09/reading-and-writing-to-the-windows-registry-in-process-from-node-js/
// https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/reg-add
// https://social.msdn.microsoft.com/Forums/en-US/5b318f44-281e-4098-8dee-3ba8435fa391/add-registry-key-for-autostart-of-app-in-ice?forum=quebectools
// utils.elevate
// https://github.com/CatalystCode/windows-registry-node
exec('where reg.exe', things._execOpts, function (err, stdout, stderr) {
console.log((stdout||'').trim());
if (stderr) {
console.error(stderr);
} }
run(err, 'reg.exe'); , 'systemctl': function () {
var launcher = path.join(os.homedir(), '.config/systemd/user/telebit.service');
var launchername = 'telebit.service';
try {
mkdirp.sync(path.join(os.homedir(), '.config/systemd/user'));
// IMPORTANT
// It's a dangerous to go alone, take this:
// SYSTEMD_LOG_LEVEL=debug journalctl -xef --user-unit=telebit
// (makes debugging systemd issues not "easy" per se, but possible)
var launcherstr = (vars.userspace ? "" : "sudo ") + "systemctl " + (vars.userspace ? "--user " : "");
var execstr = launcherstr + "disable " + launchername;
exec(execstr, things._execOpts, function (err, stdout, stderr) {
err = Launcher._getError(err, stderr && !/Removed symlink/i.test(stderr) && stderr || '');
if (err) { fn(err); return; }
//console.log((stdout||'').trim());
var execstr = launcherstr + "stop " + launchername;
exec(execstr, things._execOpts, function (err, stdout, stderr) {
err = Launcher._getError(err, stderr);
if (err) { fn(err); return; }
//console.log((stdout||'').trim());
setTimeout(function () {
var execstr = launcherstr + "status " + launchername;
exec(execstr, things._execOpts, function (err, stdout, stderr) {
err = Launcher._getError(err, stderr);
if (err) { fn(err); return; }
if (!/inactive.*dead/i.test(stdout)) {
err = new Error("systemd failed to stop '" + launchername + "'");
}
if (err) { fn(err); return; }
//console.log((stdout||'').trim());
fn(null);
});
}, 1.25 * 1000);
});
});
} catch(e) {
console.error("'" + launcher + "' error:");
console.error(e);
if (fn) { fn(e); return; }
}
}
, 'reg.exe': function () {
if (!vars.userspace) {
console.warn("sysetm-level, privileged services are not yet supported on windows");
}
var cmd = 'reg.exe add "HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"'
+ ' /V "Telebit" /F'
;
exec(cmd, things._execOpts, function (err, stdout, stderr) {
err = Launcher._getError(err, stderr);
if (err) { fn(err); return; }
// need to start it for the first time ourselves
kill(null, 'node');
}); });
} }
}; };
function kill(err, launcher) {
if (err) {
console.error("No luck with '" + launcher + "', trying a process.kill() instead...");
console.error(err);
launcher = 'node';
}
if (launchers[launcher]) {
launchers[launcher]();
return;
} else {
console.error("No launcher handler (uninstall) for '" + launcher + "'");
}
}
things._vars = vars;
things._userspace = vars.userspace;
Launcher._detect(things, kill);
};
if (module === require.main) { if (module === require.main) {
module.exports.install({ Launcher.install({
argv: process.argv argv: process.argv
, env: process.env , env: process.env
}, function (err) { }, function (err) {

View File

@ -59,9 +59,9 @@ http_get()
http_bash() http_bash()
{ {
_http_bash_url=$1 local _http_bash_url=$1
_http_bash_args=${2:-} local _http_bash_args=${2:-}
_http_bash_tmp=$(mktemp) local _http_bash_tmp=$(mktemp)
$_my_http_get $_my_http_opts $_my_http_out "$_http_bash_tmp" "$_http_bash_url" $_my_http_get $_my_http_opts $_my_http_out "$_http_bash_tmp" "$_http_bash_url"
bash "$_http_bash_tmp" $_http_bash_args; rm "$_http_bash_tmp" bash "$_http_bash_tmp" $_http_bash_args; rm "$_http_bash_tmp"
} }
@ -74,7 +74,10 @@ export -f http_bash
## END HTTP_GET ## ## END HTTP_GET ##
############################### ###############################
TELEBIT_VERSION=${TELEBIT_VERSION:-master} if [ -n "${TELEBIT_VERSION:-}" ]; then
echo 'TELEBIT_VERSION='${TELEBIT_VERSION}
fi
export TELEBIT_VERSION=${TELEBIT_VERSION:-master}
if [ -e "usr/share/install_helper.sh" ]; then if [ -e "usr/share/install_helper.sh" ]; then
bash usr/share/install_helper.sh "$@" bash usr/share/install_helper.sh "$@"
else else

View File

@ -31,13 +31,34 @@ set -u
### http_bash exported by get.sh ### http_bash exported by get.sh
if [ "$(logname)" != "$(id -u -n)" ]; then TELEBIT_DEBUG=${TELEBIT_DEBUG:-}
# NOTE: On OS X logname works from a pipe, but on Linux it does not
my_logname=$(who am i </dev/tty | awk '{print $1}')
#my_logname=${my_logname:-$(logname)}
#my_logname=${my_logname:-$SUDO_USER}
if [ -n "$my_logname" ] && [ "$my_logname" != "$(id -u -n)" ]; then
echo "WARNING:" echo "WARNING:"
echo " You are logged in as '$(logname)' but acting as '$(id -u -n)'." echo " You are logged in as '$(logname)' but acting as '$(id -u -n)'."
echo " If the installation is not successful please log in as '$(id -u -n)' directly." echo " If the installation is not successful please log in as '$(id -u -n)' directly."
sleep 3 sleep 3
fi fi
if [ -n "${TELEBIT_DEBUG:-}" ]; then
echo 'TELEBIT_DEBUG='${TELEBIT_DEBUG}
fi
if [ -n "${TELEBIT_PATH:-}" ]; then
echo 'TELEBIT_PATH='${TELEBIT_PATH}
fi
if [ -n "${TELEBIT_USERSPACE:-}" ]; then
echo 'TELEBIT_USERSPACE='${TELEBIT_USERSPACE}
fi
if [ -n "${TELEBIT_USER:-}" ]; then
echo 'TELEBIT_USER='${TELEBIT_USER}
fi
if [ -n "${TELEBIT_GROUP:-}" ]; then
echo 'TELEBIT_GROUP='${TELEBIT_GROUP}
fi
TELEBIT_VERSION=${TELEBIT_VERSION:-master} TELEBIT_VERSION=${TELEBIT_VERSION:-master}
TELEBIT_USERSPACE=${TELEBIT_USERSPACE:-no} TELEBIT_USERSPACE=${TELEBIT_USERSPACE:-no}
my_email=${1:-} my_email=${1:-}
@ -96,31 +117,33 @@ TELEBIT_REAL_PATH=${TELEBIT_PATH:-}
if [ $(id -u) -ne 0 ] && [ "$TELEBIT_USER" == "$cur_user" ]; then if [ $(id -u) -ne 0 ] && [ "$TELEBIT_USER" == "$cur_user" ]; then
TELEBIT_USERSPACE="yes" TELEBIT_USERSPACE="yes"
if [ -z "${TELEBIT_REAL_PATH:-}" ]; then if [ -z "${TELEBIT_REAL_PATH:-}" ]; then
echo 'TELEBIT_PATH="'${TELEBIT_REAL_PATH:-}'"'
TELEBIT_REAL_PATH=$HOME/Applications/$my_app TELEBIT_REAL_PATH=$HOME/Applications/$my_app
fi fi
else else
TELEBIT_USERSPACE="no" TELEBIT_USERSPACE="no"
if [ -z "${TELEBIT_REAL_PATH:-}" ]; then if [ -z "${TELEBIT_REAL_PATH:-}" ]; then
echo 'TELEBIT_PATH="'${TELEBIT_REAL_PATH:-}'"'
TELEBIT_REAL_PATH=/opt/$my_app TELEBIT_REAL_PATH=/opt/$my_app
fi fi
fi fi
TELEBIT_PATH="$TELEBIT_REAL_PATH" TELEBIT_PATH="$TELEBIT_REAL_PATH"
TELEBIT_TMP="$TELEBIT_REAL_PATH" TELEBIT_TMP="$TELEBIT_REAL_PATH"
my_tmp="$(mktemp -d)" # this works slightly differently between bsd (macOS) and gnu mktemp
# bsd requires the Xes for templates while GNU uses them literally
my_tmp="$(mktemp -d -t telebit.XXXXXXXX)"
#TELEBIT_TMP="$my_tmp/telebit" #TELEBIT_TMP="$my_tmp/telebit"
echo "Installing $my_name to '$TELEBIT_REAL_PATH'" echo "Installing $my_name to '$TELEBIT_REAL_PATH'"
# v10.2+ has much needed networking fixes, but breaks ursa. v9.x has severe networking bugs. v8.x has working ursa, but requires tls workarounds" # v10.2+ has much needed networking fixes, but breaks ursa.
NODEJS_VER="${NODEJS_VER:-v10.2}" # v9.x has severe networking bugs.
# v8.x has working ursa, but requires tls workarounds"
# v10.13 seems to work for me locally (new greenlock)
NODEJS_VER="${NODEJS_VER:-v10.13}"
export NODEJS_VER export NODEJS_VER
export NODE_PATH="$TELEBIT_TMP/lib/node_modules" export NODE_PATH="$TELEBIT_TMP/lib/node_modules"
export NPM_CONFIG_PREFIX="$TELEBIT_TMP" export NPM_CONFIG_PREFIX="$TELEBIT_TMP"
# this comes last for security # this comes last for security
export PATH="$PATH:$TELEBIT_REAL_PATH/bin" export PATH="$PATH:$TELEBIT_REAL_PATH/bin"
sleep 0.25 sleep 0.25
echo "(your password may be required to begin the installation)"
real_sudo_cmd=$soft_sudo_cmd real_sudo_cmd=$soft_sudo_cmd
real_sudo_cmde=$soft_sudo_cmde real_sudo_cmde=$soft_sudo_cmde
@ -137,8 +160,16 @@ fi
set -e set -e
if [ -n "${TELEBIT_DEBUG}" ]; then
echo " - installing node.js runtime to '$TELEBIT_REAL_PATH'..." echo " - installing node.js runtime to '$TELEBIT_REAL_PATH'..."
http_bash https://git.coolaj86.com/coolaj86/node-installer.sh/raw/branch/master/install.sh --no-dev-deps
else
echo -n "."
#bash -c 'while true; do echo -n "."; sleep 2; done' 2>/dev/null &
#_my_pid=$!
http_bash https://git.coolaj86.com/coolaj86/node-installer.sh/raw/branch/master/install.sh --no-dev-deps >/dev/null 2>/dev/null http_bash https://git.coolaj86.com/coolaj86/node-installer.sh/raw/branch/master/install.sh --no-dev-deps >/dev/null 2>/dev/null
#kill $_my_pid >/dev/null 2>/dev/null
fi
# #
# TODO create "upgrade" script and run that instead # TODO create "upgrade" script and run that instead
@ -158,16 +189,28 @@ my_tar=$(type -p tar)
# TODO extract to temporary directory, configure, copy etc, replace # TODO extract to temporary directory, configure, copy etc, replace
if [ -n "$my_unzip" ]; then if [ -n "$my_unzip" ]; then
rm -f $my_tmp/$my_app-$TELEBIT_VERSION.zip rm -f $my_tmp/$my_app-$TELEBIT_VERSION.zip
echo " - installing telebit zip to '$TELEBIT_REAL_PATH'..." if [ -n "${TELEBIT_DEBUG}" ]; then
echo " - installing telebit zip to '$TELEBIT_REAL_PATH'"
fi
echo -n "."
#bash -c 'while true; do echo -n "."; sleep 2; done' 2>/dev/null &
#_my_pid=$!
http_get https://git.coolaj86.com/coolaj86/$my_repo/archive/$TELEBIT_VERSION.zip $my_tmp/$my_app-$TELEBIT_VERSION.zip http_get https://git.coolaj86.com/coolaj86/$my_repo/archive/$TELEBIT_VERSION.zip $my_tmp/$my_app-$TELEBIT_VERSION.zip
#kill $_my_pid >/dev/null 2>/dev/null
# -o means overwrite, and there is no option to strip # -o means overwrite, and there is no option to strip
$my_unzip -o $my_tmp/$my_app-$TELEBIT_VERSION.zip -d $my_tmp/ >/dev/null $my_unzip -o $my_tmp/$my_app-$TELEBIT_VERSION.zip -d $my_tmp/ >/dev/null
$rsync_cmd $my_tmp/$my_repo/* $TELEBIT_TMP/ > /dev/null $rsync_cmd $my_tmp/$my_repo/* $TELEBIT_TMP/ > /dev/null
rm -rf $my_tmp/$my_repo rm -rf $my_tmp/$my_repo
elif [ -n "$my_tar" ]; then elif [ -n "$my_tar" ]; then
rm -f $my_tmp/$my_app-$TELEBIT_VERSION.tar.gz rm -f $my_tmp/$my_app-$TELEBIT_VERSION.tar.gz
echo " - installing telebit tar.gz to '$TELEBIT_REAL_PATH'..." if [ -n "${TELEBIT_DEBUG}" ]; then
echo " - installing telebit tar.gz to '$TELEBIT_REAL_PATH'"
fi
echo -n "."
#bash -c 'while true; do echo -n "."; sleep 2; done' 2>/dev/null &
#_my_pid=$!
http_get https://git.coolaj86.com/coolaj86/$my_repo/archive/$TELEBIT_VERSION.tar.gz $my_tmp/$my_app-$TELEBIT_VERSION.tar.gz http_get https://git.coolaj86.com/coolaj86/$my_repo/archive/$TELEBIT_VERSION.tar.gz $my_tmp/$my_app-$TELEBIT_VERSION.tar.gz
#kill $_my_pid >/dev/null 2>/dev/null
$my_tar -xzf $my_tmp/$my_app-$TELEBIT_VERSION.tar.gz --strip 1 -C $TELEBIT_TMP/ >/dev/null $my_tar -xzf $my_tmp/$my_app-$TELEBIT_VERSION.tar.gz --strip 1 -C $TELEBIT_TMP/ >/dev/null
else else
echo "Neither tar nor unzip found. Abort." echo "Neither tar nor unzip found. Abort."
@ -179,13 +222,31 @@ set -e
# TODO create slim packages that contain all the deps on each os and cpu # TODO create slim packages that contain all the deps on each os and cpu
# #
pushd $TELEBIT_TMP >/dev/null pushd $TELEBIT_TMP >/dev/null
if [ -n "${TELEBIT_DEBUG}" ]; then
echo " - installing telebit npm dependencies to '$TELEBIT_REAL_PATH'..." echo " - installing telebit npm dependencies to '$TELEBIT_REAL_PATH'..."
echo " (are you noticing a pattern of where things are installed?)" else
echo -n "."
fi
set +e
$tmp_npm install >/dev/null 2>/dev/null &
tmp_npm_pid=$!
while [ -n "$tmp_npm_pid" ]; do
sleep 2
echo -n "."
kill -s 0 $tmp_npm_pid >/dev/null 2>/dev/null || tmp_npm_pid=""
done
set -e
echo -n "."
$tmp_npm install >/dev/null 2>/dev/null $tmp_npm install >/dev/null 2>/dev/null
# ursa is now an entirely optional dependency for key generation
# but very much needed on ARM devices
$tmp_npm install ursa >/dev/null 2>/dev/null || true
popd >/dev/null popd >/dev/null
if [ -n "${TELEBIT_DEBUG}" ]; then
echo " - configuring telebit..." echo " - configuring telebit..."
echo "" echo ""
fi
############################################### ###############################################
# #
@ -233,10 +294,6 @@ rm -rf $HOME/.config/$my_app $HOME/.local/share/$my_app
EOF EOF
chmod a+x $TELEBIT_TMP/bin/${my_app}_uninstall chmod a+x $TELEBIT_TMP/bin/${my_app}_uninstall
echo""
echo "(your password may be required to complete the installation)"
echo""
#set +e #set +e
#if type -p setcap >/dev/null 2>&1; then #if type -p setcap >/dev/null 2>&1; then
# #echo "Setting permissions to allow $my_app to run on port 80 and port 443 without sudo or root" # #echo "Setting permissions to allow $my_app to run on port 80 and port 443 without sudo or root"
@ -287,10 +344,14 @@ export TELEBIT_CONFIG=$HOME/.config/$my_app/$my_app.yml
if [ "yes" == "$TELEBIT_USERSPACE" ]; then if [ "yes" == "$TELEBIT_USERSPACE" ]; then
TELEBIT_TMP_CONFIGD=$HOME/.config/$my_app/$my_daemon.yml TELEBIT_TMP_CONFIGD=$HOME/.config/$my_app/$my_daemon.yml
TELEBITD_CONFIG=$HOME/.config/$my_app/$my_daemon.yml TELEBITD_CONFIG=$HOME/.config/$my_app/$my_daemon.yml
TELEBIT_LOG_DIR=${TELEBIT_LOG_DIR:-$HOME/.local/share/$my_app/var/log/}
TELEBIT_SOCK_DIR=${TELEBIT_SOCK_DIR:-$HOME/.local/share/$my_app/var/run/}
TELEBIT_SOCK=${TELEBIT_SOCK:-$HOME/.local/share/$my_app/var/run/$my_app.sock} TELEBIT_SOCK=${TELEBIT_SOCK:-$HOME/.local/share/$my_app/var/run/$my_app.sock}
else else
TELEBIT_TMP_CONFIGD=$TELEBIT_TMP/etc/$my_daemon.yml TELEBIT_TMP_CONFIGD=$TELEBIT_TMP/etc/$my_daemon.yml
TELEBITD_CONFIG=$TELEBIT_REAL_PATH/etc/$my_daemon.yml TELEBITD_CONFIG=$TELEBIT_REAL_PATH/etc/$my_daemon.yml
TELEBIT_LOG_DIR=${TELEBIT_LOG_DIR:-$TELEBIT_REAL_PATH/var/log/}
TELEBIT_SOCK_DIR=${TELEBIT_SOCK_DIR:-$TELEBIT_REAL_PATH/var/run/}
TELEBIT_SOCK=${TELEBIT_SOCK:-$TELEBIT_REAL_PATH/var/run/$my_app.sock} TELEBIT_SOCK=${TELEBIT_SOCK:-$TELEBIT_REAL_PATH/var/run/$my_app.sock}
fi fi
export TELEBITD_CONFIG export TELEBITD_CONFIG
@ -301,7 +362,8 @@ export TELEBIT_BIN=$TELEBIT_REAL_PATH/bin/telebit
export TELEBITD_BIN=$TELEBIT_REAL_PATH/bin/telebitd export TELEBITD_BIN=$TELEBIT_REAL_PATH/bin/telebitd
export TELEBIT_JS=$TELEBIT_REAL_PATH/bin/telebit.js export TELEBIT_JS=$TELEBIT_REAL_PATH/bin/telebit.js
export TELEBITD_JS=$TELEBIT_REAL_PATH/bin/telebitd.js export TELEBITD_JS=$TELEBIT_REAL_PATH/bin/telebitd.js
export TELEBIT_LOG_DIR=${TELEBIT_LOG_DIR:-$TELEBIT_REAL_PATH/var/log} export TELEBIT_LOG_DIR
export TELEBIT_SOCK_DIR
export NODE_PATH="$TELEBIT_REAL_PATH/lib/node_modules" export NODE_PATH="$TELEBIT_REAL_PATH/lib/node_modules"
export NPM_CONFIG_PREFIX="$TELEBIT_REAL_PATH" export NPM_CONFIG_PREFIX="$TELEBIT_REAL_PATH"
@ -334,11 +396,15 @@ fi
# This should only affect non-USERSPACE installs # This should only affect non-USERSPACE installs
#echo "${soft_sudo_cmde}chown -R $TELEBIT_USER '$TELEBIT_REAL_PATH' #echo "${soft_sudo_cmde}chown -R $TELEBIT_USER '$TELEBIT_REAL_PATH'
$soft_sudo_cmd mkdir -p $TELEBIT_LOG_DIR
$soft_sudo_cmd mkdir -p $TELEBIT_SOCK_DIR
$soft_sudo_cmd chown -R $TELEBIT_USER "$TELEBIT_REAL_PATH" $soft_sudo_cmd chown -R $TELEBIT_USER "$TELEBIT_REAL_PATH"
# $HOME/.config/systemd/user/ # $HOME/.config/systemd/user/
# %h/.config/telebit/telebit.yml # %h/.config/telebit/telebit.yml
if [ -n "${TELEBIT_DEBUG}" ]; then
echo " - adding $my_app as a system service" echo " - adding $my_app as a system service"
fi
# TODO detect with type -p # TODO detect with type -p
my_system_launcher="" my_system_launcher=""
my_app_launchd_service="" my_app_launchd_service=""
@ -351,14 +417,23 @@ if [ -d "/Library/LaunchDaemons" ]; then
if [ "yes" == "$TELEBIT_USERSPACE" ]; then if [ "yes" == "$TELEBIT_USERSPACE" ]; then
my_app_launchd_service_skel="etc/skel/Library/LaunchAgents/${my_app_pkg_name}.plist" my_app_launchd_service_skel="etc/skel/Library/LaunchAgents/${my_app_pkg_name}.plist"
my_app_launchd_service="$HOME/Library/LaunchAgents/${my_app_pkg_name}.plist" my_app_launchd_service="$HOME/Library/LaunchAgents/${my_app_pkg_name}.plist"
if [ -n "${TELEBIT_DEBUG}" ]; then
echo " > $rsync_cmd $TELEBIT_REAL_PATH/usr/share/dist/$my_app_launchd_service $my_app_launchd_service" echo " > $rsync_cmd $TELEBIT_REAL_PATH/usr/share/dist/$my_app_launchd_service $my_app_launchd_service"
mdir -p $HOME/Library/LaunchAgents fi
mkdir -p $HOME/Library/LaunchAgents
$rsync_cmd "$TELEBIT_REAL_PATH/usr/share/dist/$my_app_launchd_service_skel" "$my_app_launchd_service" $rsync_cmd "$TELEBIT_REAL_PATH/usr/share/dist/$my_app_launchd_service_skel" "$my_app_launchd_service"
if [ -n "${TELEBIT_DEBUG}" ]; then
echo " > chown $(id -u -n):$(id -g -n) $my_app_launchd_service" echo " > chown $(id -u -n):$(id -g -n) $my_app_launchd_service"
fi
chown $(id -u -n):$(id -g -n) "$my_app_launchd_service" chown $(id -u -n):$(id -g -n) "$my_app_launchd_service"
my_sudo_cmd="" my_sudo_cmd=""
my_sudo_cmde="" my_sudo_cmde=""
if [ -n "${TELEBIT_DEBUG}" ]; then
echo " > launchctl unload -w $my_app_launchd_service >/dev/null 2>/dev/null"
fi
launchctl unload -w "$my_app_launchd_service" >/dev/null 2>/dev/null
else else
my_app_launchd_service_skel="usr/share/dist/Library/LaunchDaemons/${my_app_pkg_name}.plist" my_app_launchd_service_skel="usr/share/dist/Library/LaunchDaemons/${my_app_pkg_name}.plist"
my_app_launchd_service="$my_root/Library/LaunchDaemons/${my_app_pkg_name}.plist" my_app_launchd_service="$my_root/Library/LaunchDaemons/${my_app_pkg_name}.plist"
@ -367,16 +442,18 @@ if [ -d "/Library/LaunchDaemons" ]; then
echo " > ${real_sudo_cmde}chown root:wheel $my_app_launchd_service" echo " > ${real_sudo_cmde}chown root:wheel $my_app_launchd_service"
$real_sudo_cmd chown root:wheel "$my_app_launchd_service" $real_sudo_cmd chown root:wheel "$my_app_launchd_service"
fi
echo " > ${my_sudo_cmde}launchctl unload -w $my_app_launchd_service >/dev/null 2>/dev/null" echo " > ${real_sudo_cmde}launchctl unload -w $my_app_launchd_service >/dev/null 2>/dev/null"
$my_sudo_cmd launchctl unload -w "$my_app_launchd_service" >/dev/null 2>/dev/null $real_sudo_cmd launchctl unload -w "$my_app_launchd_service" >/dev/null 2>/dev/null
fi
elif [ -d "$my_root/etc/systemd/system" ]; then elif [ -d "$my_root/etc/systemd/system" ]; then
my_system_launcher="systemd" my_system_launcher="systemd"
if [ "yes" == "$TELEBIT_USERSPACE" ]; then if [ "yes" == "$TELEBIT_USERSPACE" ]; then
if [ -n "${TELEBIT_DEBUG}" ]; then
echo " > $rsync_cmd $TELEBIT_REAL_PATH/usr/share/dist/etc/skel/.config/systemd/user/$my_app.service $HOME/.config/systemd/user/$my_app.service" echo " > $rsync_cmd $TELEBIT_REAL_PATH/usr/share/dist/etc/skel/.config/systemd/user/$my_app.service $HOME/.config/systemd/user/$my_app.service"
fi
mkdir -p $HOME/.config/systemd/user mkdir -p $HOME/.config/systemd/user
$rsync_cmd "$TELEBIT_REAL_PATH/usr/share/dist/etc/skel/.config/systemd/user/$my_app.service" "$HOME/.config/systemd/user/$my_app.service" $rsync_cmd "$TELEBIT_REAL_PATH/usr/share/dist/etc/skel/.config/systemd/user/$my_app.service" "$HOME/.config/systemd/user/$my_app.service"
else else
@ -390,16 +467,23 @@ sleep 1
############################### ###############################
# Actually Launch the Service # # Actually Launch the Service #
############################### ###############################
if [ -n "${TELEBIT_DEBUG}" ]; then
echo "" echo ""
fi
if [ "launchd" == "$my_system_launcher" ]; then if [ "launchd" == "$my_system_launcher" ]; then
if [ "yes" == "$TELEBIT_USERSPACE" ]; then if [ "yes" == "$TELEBIT_USERSPACE" ]; then
if [ -n "${TELEBIT_DEBUG}" ]; then
echo " > launchctl load -w $my_app_launchd_service" echo " > launchctl load -w $my_app_launchd_service"
else
echo -n "."
fi
launchctl load -w "$my_app_launchd_service" launchctl load -w "$my_app_launchd_service"
else else
echo " > ${real_sudo_cmde}launchctl load -w $my_app_launchd_service" echo " > ${real_sudo_cmde}launchctl load -w $my_app_launchd_service"
$real_sudo_cmd launchctl load -w "$my_app_launchd_service" $real_sudo_cmd launchctl load -w "$my_app_launchd_service"
fi fi
sleep 2; # give it time to start
elif [ "systemd" == "$my_system_launcher" ]; then elif [ "systemd" == "$my_system_launcher" ]; then
@ -407,20 +491,36 @@ elif [ "systemd" == "$my_system_launcher" ]; then
# https://wiki.archlinux.org/index.php/Systemd/User # https://wiki.archlinux.org/index.php/Systemd/User
# sudo loginctl enable-linger username # sudo loginctl enable-linger username
if [ -n "${TELEBIT_DEBUG}" ]; then
echo " > systemctl --user enable $my_app" echo " > systemctl --user enable $my_app"
systemctl --user daemon-reload else
systemctl --user enable $my_app >/dev/null echo -n "."
fi
set +e
if systemctl --user daemon-reload; then
# enable also puts success output to stderr... why?
systemctl --user enable $my_app >/dev/null 2>/dev/null
#echo " > systemctl --user enable systemd-tmpfiles-setup.service systemd-tmpfiles-clean.timer" #echo " > systemctl --user enable systemd-tmpfiles-setup.service systemd-tmpfiles-clean.timer"
#systemctl --user enable systemd-tmpfiles-setup.service systemd-tmpfiles-clean.timer #systemctl --user enable systemd-tmpfiles-setup.service systemd-tmpfiles-clean.timer
if [ -n "${TELEBIT_DEBUG}" ]; then
echo " > systemctl --user start $my_app" echo " > systemctl --user start $my_app"
systemctl --user daemon-reload fi
systemctl --user stop $my_app 2>/dev/null systemctl --user stop $my_app >/dev/null 2>/dev/null
systemctl --user start $my_app systemctl --user start $my_app >/dev/null
sleep 2
systemctl --user stop $my_app 2>/dev/null sleep 2; # give it time to start
systemctl --user start $my_app _is_running=$(systemctl --user status --no-pager $my_app 2>/dev/null | grep "active.*running")
sleep 1 if [ -z "$_is_running" ]; then
echo "Something went wrong:"
systemctl --user status --no-pager $my_app systemctl --user status --no-pager $my_app
fi
else
echo "libpam-systemd is missing, which is required on Linux to register Telebit with the user launcher."
echo "sudo apt-get install -y libpam-systemd"
sudo apt-get install -y libpam-systemd
fi
set -e
echo -n "."
else else
$real_sudo_cmd systemctl daemon-reload $real_sudo_cmd systemctl daemon-reload
@ -429,7 +529,7 @@ elif [ "systemd" == "$my_system_launcher" ]; then
echo " > ${real_sudo_cmde}systemctl start $my_app" echo " > ${real_sudo_cmde}systemctl start $my_app"
$real_sudo_cmd systemctl daemon-reload $real_sudo_cmd systemctl daemon-reload
$real_sudo_cmd systemctl restart $my_app $real_sudo_cmd systemctl restart $my_app
sleep 1 sleep 2; # give it time to start
$real_sudo_cmd systemctl status --no-pager $my_app $real_sudo_cmd systemctl status --no-pager $my_app
fi fi
@ -437,24 +537,38 @@ else
echo "Run the service manually (we couldn't detect your system service to do that automatically):" echo "Run the service manually (we couldn't detect your system service to do that automatically):"
echo "" echo ""
echo " $my_daemon --config $TELEBITD_CONFIG" echo " $TELEBITD_BIN --config $TELEBITD_CONFIG"
echo " $my_app --config $TELEBIT_CONFIG" echo " ~/$my_app --config $TELEBIT_CONFIG"
fi fi
# NOTE: ln -sf *should* replace an existing link... but sometimes it doesn't, hence rm -f
if [ "yes" == "$TELEBIT_USERSPACE" ]; then
if [ -n "${TELEBIT_DEBUG}" ]; then
echo " > ${real_sudo_cmde}ln -sf $TELEBIT_REAL_PATH/bin/$my_app /usr/local/bin/$my_app" echo " > ${real_sudo_cmde}ln -sf $TELEBIT_REAL_PATH/bin/$my_app /usr/local/bin/$my_app"
fi
rm -f /usr/local/bin/$my_app 2>/dev/null || true
ln -sf $TELEBIT_REAL_PATH/bin/$my_app /usr/local/bin/$my_app 2>/dev/null || true
else
echo " > ${real_sudo_cmde}ln -sf $TELEBIT_REAL_PATH/bin/$my_app /usr/local/bin/$my_app"
rm -f /usr/local/bin/$my_app 2>/dev/null || \
$real_sudo_cmd rm -f /usr/local/bin/$my_app
ln -sf $TELEBIT_REAL_PATH/bin/$my_app /usr/local/bin/$my_app 2>/dev/null || \ ln -sf $TELEBIT_REAL_PATH/bin/$my_app /usr/local/bin/$my_app 2>/dev/null || \
$real_sudo_cmd ln -sf $TELEBIT_REAL_PATH/bin/$my_app /usr/local/bin/$my_app $real_sudo_cmd ln -sf $TELEBIT_REAL_PATH/bin/$my_app /usr/local/bin/$my_app
# telebitd
echo " > ${real_sudo_cmde}ln -sf $TELEBIT_REAL_PATH/bin/$my_daemon /usr/local/bin/$my_daemon" echo " > ${real_sudo_cmde}ln -sf $TELEBIT_REAL_PATH/bin/$my_daemon /usr/local/bin/$my_daemon"
rm -f $TELEBIT_REAL_PATH/bin/$my_daemon || $real_sudo_cmd rm -f $TELEBIT_REAL_PATH/bin/$my_daemon
ln -sf $TELEBIT_REAL_PATH/bin/$my_daemon /usr/local/bin/$my_daemon || \ ln -sf $TELEBIT_REAL_PATH/bin/$my_daemon /usr/local/bin/$my_daemon || \
$real_sudo_cmd ln -sf $TELEBIT_REAL_PATH/bin/$my_daemon /usr/local/bin/$my_daemon $real_sudo_cmd ln -sf $TELEBIT_REAL_PATH/bin/$my_daemon /usr/local/bin/$my_daemon
fi
rm -f $HOME/$my_app; ln -s $TELEBIT_REAL_PATH/bin/$my_app $HOME/
if [ -n "${TELEBIT_DEBUG}" ]; then
echo " > telebit init --tty" echo " > telebit init --tty"
echo "" echo ""
fi
sleep 0.25 sleep 0.25
echo ""
$TELEBIT_REAL_PATH/bin/node $TELEBIT_REAL_PATH/bin/telebit.js init --tty $TELEBIT_REAL_PATH/bin/node $TELEBIT_REAL_PATH/bin/telebit.js init --tty
$TELEBIT_REAL_PATH/bin/node $TELEBIT_REAL_PATH/bin/telebit.js enable

View File

@ -33,8 +33,13 @@ module.exports.sync = function (opts) {
.replace(/{TELEBIT_CONFIG}/g, vars.telebitConfig || '{TELEBIT_CONFIG}') .replace(/{TELEBIT_CONFIG}/g, vars.telebitConfig || '{TELEBIT_CONFIG}')
.replace(/{TELEBITD_CONFIG}/g, vars.telebitdConfig || '{TELEBITD_CONFIG}') .replace(/{TELEBITD_CONFIG}/g, vars.telebitdConfig || '{TELEBITD_CONFIG}')
.replace(/{TELEBIT_LOG_DIR}/g, vars.TELEBIT_LOG_DIR || '{TELEBIT_LOG_DIR}') .replace(/{TELEBIT_LOG_DIR}/g, vars.TELEBIT_LOG_DIR || '{TELEBIT_LOG_DIR}')
.replace(/{TELEBIT_SOCK_DIR}/g, vars.TELEBIT_LOG_DIR || '{TELEBIT_SOCK_DIR}')
; ;
fs.writeFileSync(f.launcher, text, 'utf8'); fs.writeFileSync(f.launcher, text, 'utf8');
if (f.executable && !/^win/i.test(os.platform())) {
// TODO not sure if chmod works on windows
fs.chmodSync(f.launcher, parseInt('755', 8));
}
}; };
function run() { function run() {
@ -74,8 +79,8 @@ function run() {
, TELEBIT_LOG_DIR: process.env.TELEBIT_LOG_DIR || path.join(os.homedir(), '.local/share/telebit/var/log') , TELEBIT_LOG_DIR: process.env.TELEBIT_LOG_DIR || path.join(os.homedir(), '.local/share/telebit/var/log')
}; };
vars.telebitNpm = process.env.TELEBIT_NPM || path.resolve(vars.telebitNode, '../npm'); vars.telebitNpm = process.env.TELEBIT_NPM || path.resolve(vars.telebitNode, '../npm');
vars.nodePath = process.env.NODE_PATH || path.resolve(vars.telebitNode, '../lib/node_modules'); vars.nodePath = process.env.NODE_PATH || path.resolve(vars.telebitNode, '../../lib/node_modules');
vars.npmConfigPrefix = process.env.NPM_CONFIG_PREFIX || path.resolve(vars.telebitNode, '..'); vars.npmConfigPrefix = process.env.NPM_CONFIG_PREFIX || path.resolve(vars.telebitNode, '..', '..');
if (-1 === vars.telebitRwDirs.indexOf(vars.npmConfigPrefix)) { if (-1 === vars.telebitRwDirs.indexOf(vars.npmConfigPrefix)) {
vars.telebitRwDirs.push(vars.npmConfigPrefix); vars.telebitRwDirs.push(vars.npmConfigPrefix);
} }