Browse Source

move most user config into node

pull/8/head
AJ ONeal 6 years ago
parent
commit
a98da102b9
  1. 248
      bin/telebit.js
  2. 100
      bin/telebitd.js
  3. 0
      etc/.gitkeep
  4. 16
      lib/cli-common.js
  5. 4
      lib/remote.js
  6. 153
      usr/share/install_helper.sh
  7. 3
      usr/share/telebitd.tpl.yml
  8. 0
      var/log/.gitkeep
  9. 0
      var/run/.gitkeep

248
bin/telebit.js

@ -33,10 +33,12 @@ function help() {
console.info('');
console.info('Usage:');
console.info('');
console.info('\ttelebit [--config <path>] <module> <module-option>');
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');
@ -82,8 +84,202 @@ if (!confpath || /^--/.test(confpath)) {
process.exit(1);
}
function askForConfig() {
console.log("Please create a config file at '" + confpath + "' or specify --config /path/to/config");
function askForConfig(answers, mainCb) {
answers = answers || {};
//console.log("Please create a config file at '" + confpath + "' or specify --config /path/to/config");
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// 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) {
if (!email) { askEmail(cb); return; }
answers.email = email.trim();
answers.agree_tos = true;
console.info("");
setTimeout(cb, 250);
});
}
, 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 askRelay(cb) {
if (answers.relay) { cb(); 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]: ', function (relay) {
// TODO parse and check https://{{relay}}/.well-known/telebit.cloud/directives.json
if (!relay) {
relay = 'telebit.cloud';
}
answers.relay = relay.trim();
setTimeout(cb, 250);
});
}
, function checkRelay(cb) {
if (!/\btelebit\.cloud\b/i.test(answers.relay)) {
standardSet = standardSet.concat(advancedSet);
}
nextSet = standardSet;
cb();
}
];
var standardSet = [
function askNewsletter(cb) {
if (answers.newsletter) { cb(); return; }
console.info("");
console.info("");
console.info("Would you like to subscribe to our newsletter? (press enter for default [no])");
console.info("");
rl.question('newsletter [y/N] (default: no): ', function (newsletter) {
if (/^y(es)?$/.test(newsletter)) {
answers.newsletter = true;
}
setTimeout(cb, 250);
});
}
, function askCommunity(cb) {
if (answers.community_member) { cb(); return; }
console.info("");
console.info("");
console.info("Receive important and relevant updates? (press enter for default [yes])");
console.info("");
rl.question('community_member [Y/n]: ', function (community) {
if (!community || /^y(es)?$/i.test(community)) {
answers.community_member = 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 advancedSet = [
function askTokenOrSecret(cb) {
if (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) {
// delete answers.token;
}
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.join(',');
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.join(',');
setTimeout(cb, 250);
});
}
];
var nextSet = firstSet;
function next() {
var q = nextSet.shift();
if (!q) { rl.close(); mainCb(null, answers); return; }
q(next);
}
next();
}
function parseConfig(err, text) {
@ -94,7 +290,7 @@ function parseConfig(err, text) {
if ('ENOENT' === err.code) {
text = 'relay: \'\'';
}
askForConfig();
//askForConfig();
}
try {
@ -125,12 +321,15 @@ function parseConfig(err, text) {
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);
} else {
if (body) {
console.info('Response');
console.info(body);
//cb(null, body);
} else {
console.info("👌");
//cb(null, "");
}
}
}
@ -179,6 +378,47 @@ function parseConfig(err, text) {
return true;
}
if (-1 !== argv.indexOf('init')) {
var answers = {};
if ('init' !== argv[0]) {
throw new Error("init must be the first argument");
}
argv.shift();
argv.forEach(function (arg) {
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];
});
askForConfig(answers, function (err, answers) {
// TODO use php-style object querification
putConfig('config', Object.keys(answers).map(function (key) {
return key + ':' + answers[key];
}));
/* TODO
if [ "telebit.cloud" == $my_relay ]; then
echo ""
echo ""
echo "=============================================="
echo " Hey, Listen! "
echo "=============================================="
echo ""
echo "GO CHECK YOUR EMAIL"
echo ""
echo "You MUST verify your email address to activate this device."
echo "(if the activation link expires, just run 'telebit restart' and check your email again)"
echo ""
$read_cmd -p "hit [enter] once you've clicked the verification" my_ignore
fi
*/
});
return;
}
if ([ 'status', 'enable', 'disable', 'restart', 'list', 'save' ].some(makeRpc)) {
return;
}

100
bin/telebitd.js

@ -47,6 +47,10 @@ function help() {
var verstr = '' + pkg.name + ' v' + pkg.version;
if (-1 === confIndex) {
// We have two possible valid paths if no --config is given (i.e. run from an npm-only install)
// * {install}/etc/telebitd.yml
// * ~/.config/telebit/telebitd.yml
// We'll asume the later since the installers include --config in the system launcher script
confpath = path.join(state.homedir, '.config/telebit/telebitd.yml');
verstr += ' (--config "' + confpath + '")';
}
@ -87,11 +91,18 @@ function serveControls() {
}
function listSuccess() {
res.end(YAML.safeDump({
var dumpy = {
servernames: state.servernames
, ports: state.ports
, ssh: state.config.sshAuto || 'disabled'
}));
};
if (/\btelebit\.cloud\b/i.test(conf.relay) && state.config.email && !state.token) {
dumpy.code = "AWAIT_AUTH";
dumpy.message = "Check your email. You must verify your email address to activate this device.";
}
res.end(YAML.safeDump(dumpy));
}
function sshSuccess() {
@ -105,6 +116,91 @@ function serveControls() {
});
}
if (/\binit\b/.test(opts.path)) {
var conf = {};
var fresh;
if (!opts.body) {
res.statusCode = 422;
res.end('{"error":{"message":"needs more arguments"}}');
return;
}
// relay, email, agree_tos, servernames, ports
//
opts.body.forEach(function (opt) {
var parts = opt.split(/,/);
conf[parts[0]] = parts[1];
});
if (!state.config.relay || !state.config.email || !state.config.agreeTos) {
fresh = true;
}
// TODO camelCase query
state.config.email = conf.email || state.config.email || '';
if ('undefined' !== typeof conf.agreeTos) {
state.config.agreeTos = conf.agree_tos;
}
state.config.relay = conf.relay || state.config.relay || '';
state.config.token = conf.token || state.config.token || null;
state.config.secret = conf.secret || state.config.secret || null;
if ('undefined' !== typeof conf.newsletter) {
state.config.newsletter = conf.newsletter;
}
if ('undefined' !== typeof conf.community_member) {
state.config.communityMember = conf.community_member;
}
if ('undefined' !== typeof conf.telemetry) {
state.config.telemetry = conf.telemetry;
}
conf.servernames.forEach(function (key) {
if (!state.config.servernames[key]) {
state.config.servernames[key] = {};
}
});
conf.ports.forEach(function (key) {
if (!state.config.ports[key]) {
state.config.ports[key] = {};
}
});
if (!state.config.relay || !state.config.email || !state.config.agreeTos) {
res.statusCode = 400;
res.end('{"error":{"code":"E_CONFIG","message":"Missing important config file params. Please run \'telebit init\'"}}');
return;
}
if (tun) {
tun.end(function () {
tun = rawTunnel();
});
tun = null;
setTimeout(function () {
if (!tun) { tun = rawTunnel(); }
}, 3000);
} else {
tun = rawTunnel();
}
fs.writeFile(confpath, YAML.safeDump(snakeCopy(state.config)), function (err) {
if (err) {
res.statusCode = 500;
res.end('{"error":{"message":"Could not save config file after init: ' + err.message.replace(/"/g, "'")
+ '.\nPerhaps check that the file exists and your user has permissions to write it?"}}');
return;
}
// TODO check for message from remote about email
if (/\btelebit\.cloud\b/i.test(conf.relay) && state.config.email && !state.token) {
res.statusCode = 200;
res.end('{"success":true,"code":"AWAIT_AUTH","message":"Check your email. You must verify your email address to activate this device."}');
} else {
res.statusCode = 200;
res.end('{"success":true}');
}
});
return;
}
if (!state.config.relay || !state.config.email || !state.config.agreeTos) {
res.statusCode = 400;
res.end('{"error":{"code":"E_CONFIG","message":"Invalid config file. Please run \'telebit init\'"}}');

0
etc/.gitkeep

16
lib/cli-common.js

@ -7,7 +7,7 @@ var mkdirp = require('mkdirp');
var os = require('os');
var homedir = os.homedir();
var localshare = '.local/share/telebit/var';
var localshare = '.local/share/telebit';
var localconf = '.config/telebit';
common.pipename = function (config) {
@ -17,10 +17,20 @@ common.pipename = function (config) {
}
return pipename;
};
common.DEFAULT_SOCK_NAME = path.join(homedir, localshare, 'telebit.sock');
common.DEFAULT_SOCK_NAME = path.join(homedir, localshare, 'var', 'telebit.sock');
try {
mkdirp.sync(path.join(homedir, localshare));
mkdirp.sync(path.join(__dirname, '..', 'var', 'log'));
mkdirp.sync(path.join(__dirname, '..', 'var', 'run'));
mkdirp.sync(path.join(__dirname, '..', 'etc'));
} catch(e) {
console.error(e);
}
try {
mkdirp.sync(path.join(homedir, localshare, 'var', 'log'));
mkdirp.sync(path.join(homedir, localshare, 'var', 'run'));
//mkdirp.sync(path.join(homedir, localshare, 'etc'));
mkdirp.sync(path.join(homedir, localconf));
} catch(e) {
console.error(e);

4
lib/remote.js

@ -520,7 +520,7 @@ function _connect(state) {
var connPromise;
return {
end: function() {
end: function(cb) {
tokens.length = 0;
if (timeoutId) {
clearTimeout(timeoutId);
@ -529,7 +529,7 @@ function _connect(state) {
if (wstunneler) {
try {
wstunneler.close();
wstunneler.close(cb);
} catch(e) {
console.error("[error] wstunneler.close()");
console.error(e);

153
usr/share/install_helper.sh

@ -285,134 +285,28 @@ else
fi
sleep 2
echo ""
echo ""
echo "==============================================="
echo " Service Configuration "
echo "==============================================="
echo ""
if [ -z "${my_email}" ]; then
echo ""
echo ""
echo "Telebit uses Greenlock for free automated ssl through Let's Encrypt."
echo ""
echo "To accept the Terms of Service for Telebit, Greenlock and Let's Encrypt,"
echo "please enter your email."
echo ""
$read_cmd -p "email: " my_email
echo ""
# UX - just want a smooth transition
sleep 0.25
fi
if [ -z "${my_relay}" ]; then
echo "What relay will you be using? (press enter for default)"
echo ""
$read_cmd -p "relay [default: telebit.cloud]: " my_relay
echo ""
my_relay=${my_relay:-telebit.cloud}
# UX - just want a smooth transition
sleep 0.25
fi
if [ -n "$my_relay" ] && [ "$my_relay" != "telebit.cloud" ]; then
if [ -z "${my_servernames}" ]; then
#echo "What servername(s) will you be relaying here? (press enter for default)"
echo "What servername(s) will you be relaying here?"
echo ""
#$read_cmd -p "domain [default: <random>.telebit.cloud]: " my_servernames
$read_cmd -p "domain: " my_servernames
echo ""
# UX - just want a smooth transition
sleep 0.25
fi
if [ -z "${my_secret}" ]; then
#echo "What's your authorization for the relay server? (press enter for default)"
echo "What's your authorization for the relay server?"
echo ""
#$read_cmd -p "auth [default: new account]: " my_secret
$read_cmd -p "secret: " my_secret
echo ""
# UX - just want a smooth transition
sleep 0.25
fi
fi
# TODO don't create this in TMP_PATH if it exists in TELEBIT_PATH
my_config="$TELEBIT_PATH/etc/$my_daemon.yml"
mkdir -p "$(dirname $my_config)"
if [ ! -e "$my_config" ]; then
#$rsync_cmd examples/$my_app.yml "$my_config"
if [ -n "$my_email" ]; then
echo "email: $my_email" >> "$my_config"
echo "agree_tos: true" >> "$my_config"
else
echo "#email: jon@example.com # used for Automated HTTPS and Telebit.Cloud registrations" >> "$my_config"
echo "#agree_tos: true # must be enabled to use Automated HTTPS and Telebit.Cloud" >> "$my_config"
fi
echo "sock: $TELEBIT_PATH/var/telebit.sock" >> "$my_config"
if [ -n "$my_relay" ]; then
echo "relay: $my_relay" >> "$my_config"
if [ -n "$my_secret" ]; then
echo "secret: $my_secret" >> "$my_config"
fi
if [ -n "$my_servernames" ]; then
# TODO could use printf or echo -e,
# just not sure how portable they are
echo "servernames:" >> "$my_config"
echo " $my_servernames: {}" >> "$my_config"
fi
else
echo "relay: telebit.cloud # the relay server to use" >> "$my_config"
fi
#echo "dynamic_ports:\n []" >> "$my_config"
echo "sock: $TELEBIT_PATH/var/run/telebit.sock" >> "$my_config"
cat $TELEBIT_PATH/usr/share/$my_daemon.tpl.yml >> "$my_config"
fi
#my_config_link="/etc/$my_app/$my_app.yml"
#if [ ! -e "$my_config_link" ]; then
# echo "${sudo_cmde}ln -sf '$my_config' '$my_config_link'"
# #$sudo_cmd mkdir -p /etc/$my_app
# $sudo_cmd ln -sf "$my_config" "$my_config_link"
#fi
my_config="$HOME/.config/$my_app/$my_app.yml"
mkdir -p "$(dirname $my_config)"
if [ ! -e "$my_config" ]; then
echo "cli: true" >> "$my_config"
echo "sock: $TELEBIT_PATH/var/telebit.sock" >> "$my_config"
if [ -n "$my_email" ]; then
echo "email: $my_email" >> "$my_config"
echo "agree_tos: true" >> "$my_config"
else
echo "#email: jon@example.com # used for Automated HTTPS and Telebit.Cloud registrations" >> "$my_config"
echo "#agree_tos: true # must be enabled to use Automated HTTPS and Telebit.Cloud" >> "$my_config"
fi
if [ -n "$my_relay" ]; then
echo "relay: $my_relay" >> "$my_config"
echo "sock: $TELEBIT_PATH/var/run/telebit.sock" >> "$my_config"
if [ -n "$my_secret" ]; then
echo "secret: $my_secret" >> "$my_config"
fi
else
echo "relay: telebit.cloud # the relay server to use" >> "$my_config"
fi
fi
#echo "${sudo_cmde}chown -R $my_user '$TELEBIT_PATH'
$sudo_cmd chown -R $my_user "$TELEBIT_PATH"
###############################
# Actually Launch the Service #
###############################
@ -425,45 +319,8 @@ if [ "systemd" == "$my_system_launcher" ]; then
$sudo_cmd systemctl restart $my_app
fi
# TODO run 'telebit status'
echo "telebit init"
sleep 2
if [ "telebit.cloud" == $my_relay ]; then
echo ""
echo ""
echo "=============================================="
echo " Hey, Listen! "
echo "=============================================="
echo ""
echo "GO CHECK YOUR EMAIL"
echo ""
echo "You MUST verify your email address to activate this device."
echo "(if the activation link expires, just run 'telebit restart' and check your email again)"
echo ""
$read_cmd -p "hit [enter] once you've clicked the verification" my_ignore
fi
sleep 2
echo ""
echo ""
echo ""
echo "=============================================="
echo " Privacy Settings "
echo "=============================================="
echo ""
echo "Privacy settings are managed in the config files:"
echo ""
echo " $TELEBIT_PATH/etc/$my_app.yml"
echo " $HOME/.config/$my_app/$my_app.yml"
echo ""
echo "Your current settings:"
echo ""
echo " telemetry: true # You ARE contributing project telemetry"
echo " community: true # You ARE receiving important email updates"
echo " newsletter: false # You ARE NOT receiving regular emails"
echo ""
echo "Please edit the config file to meet your needs before starting."
echo ""
sleep 1
echo ""
sleep 1
$TELEBIT_PATH/bin/node $TELEBIT_PATH/bin/telebit.js init
$TELEBIT_PATH/bin/node $TELEBIT_PATH/bin/telebit.js enable

3
usr/share/telebitd.tpl.yml

@ -1,4 +1,5 @@
#agree_tos: true # agree to the Telebit, Greenlock, and Let's Encrypt TOSes
community_member: true # receive infrequent relevant updates
telemetry: true # contribute to project telemetric data
ssh_auto: 22 # forward ssh-looking packets, from any connection, to port 22
newsletter: false # contribute to project telemetric data
ssh_auto: false # forward ssh-looking packets, from any connection, to port 22

0
var/log/.gitkeep

0
var/run/.gitkeep

Loading…
Cancel
Save