move most user config into node

This commit is contained in:
AJ ONeal 2018-06-12 04:36:37 -06:00
parent a0f8b78cc2
commit 7d8916f645
9 changed files with 364 additions and 160 deletions

View File

@ -33,10 +33,12 @@ function help() {
console.info(''); console.info('');
console.info('Usage:'); console.info('Usage:');
console.info(''); console.info('');
console.info('\ttelebit [--config <path>] <module> <module-option>'); console.info('\ttelebit [--config <path>] <module> <module-options>');
console.info(''); console.info('');
console.info('Examples:'); console.info('Examples:');
console.info(''); console.info('');
//console.info('\ttelebit init # bootstrap the config files');
//console.info('');
console.info('\ttelebit status # whether enabled or disabled'); console.info('\ttelebit status # whether enabled or disabled');
console.info('\ttelebit enable # disallow incoming connections'); console.info('\ttelebit enable # disallow incoming connections');
console.info('\ttelebit disable # allow incoming connections'); console.info('\ttelebit disable # allow incoming connections');
@ -82,8 +84,202 @@ if (!confpath || /^--/.test(confpath)) {
process.exit(1); process.exit(1);
} }
function askForConfig() { function askForConfig(answers, mainCb) {
console.log("Please create a config file at '" + confpath + "' or specify --config /path/to/config"); 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) { function parseConfig(err, text) {
@ -94,7 +290,7 @@ function parseConfig(err, text) {
if ('ENOENT' === err.code) { if ('ENOENT' === err.code) {
text = 'relay: \'\''; text = 'relay: \'\'';
} }
askForConfig(); //askForConfig();
} }
try { try {
@ -125,12 +321,15 @@ function parseConfig(err, text) {
console.warn("'" + service + "' may have failed." console.warn("'" + service + "' may have failed."
+ " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log"); + " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log");
console.warn(resp.statusCode, body); console.warn(resp.statusCode, body);
//cb(new Error("not okay"), body);
} else { } else {
if (body) { if (body) {
console.info('Response'); console.info('Response');
console.info(body); console.info(body);
//cb(null, body);
} else { } else {
console.info("👌"); console.info("👌");
//cb(null, "");
} }
} }
} }
@ -179,6 +378,47 @@ function parseConfig(err, text) {
return true; 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)) { if ([ 'status', 'enable', 'disable', 'restart', 'list', 'save' ].some(makeRpc)) {
return; return;
} }

View File

@ -47,6 +47,10 @@ function help() {
var verstr = '' + pkg.name + ' v' + pkg.version; var verstr = '' + pkg.name + ' v' + pkg.version;
if (-1 === confIndex) { 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'); confpath = path.join(state.homedir, '.config/telebit/telebitd.yml');
verstr += ' (--config "' + confpath + '")'; verstr += ' (--config "' + confpath + '")';
} }
@ -87,11 +91,18 @@ function serveControls() {
} }
function listSuccess() { function listSuccess() {
res.end(YAML.safeDump({ var dumpy = {
servernames: state.servernames servernames: state.servernames
, ports: state.ports , ports: state.ports
, ssh: state.config.sshAuto || 'disabled' , 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() { 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) { if (!state.config.relay || !state.config.email || !state.config.agreeTos) {
res.statusCode = 400; res.statusCode = 400;
res.end('{"error":{"code":"E_CONFIG","message":"Invalid config file. Please run \'telebit init\'"}}'); res.end('{"error":{"code":"E_CONFIG","message":"Invalid config file. Please run \'telebit init\'"}}');

0
etc/.gitkeep Normal file
View File

View File

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

View File

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

View File

@ -285,134 +285,28 @@ else
fi fi
sleep 2 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 # TODO don't create this in TMP_PATH if it exists in TELEBIT_PATH
my_config="$TELEBIT_PATH/etc/$my_daemon.yml" my_config="$TELEBIT_PATH/etc/$my_daemon.yml"
mkdir -p "$(dirname $my_config)" mkdir -p "$(dirname $my_config)"
if [ ! -e "$my_config" ]; then if [ ! -e "$my_config" ]; then
#$rsync_cmd examples/$my_app.yml "$my_config" echo "sock: $TELEBIT_PATH/var/run/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
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"
cat $TELEBIT_PATH/usr/share/$my_daemon.tpl.yml >> "$my_config" cat $TELEBIT_PATH/usr/share/$my_daemon.tpl.yml >> "$my_config"
fi 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" my_config="$HOME/.config/$my_app/$my_app.yml"
mkdir -p "$(dirname $my_config)" mkdir -p "$(dirname $my_config)"
if [ ! -e "$my_config" ]; then if [ ! -e "$my_config" ]; then
echo "cli: true" >> "$my_config" echo "sock: $TELEBIT_PATH/var/run/telebit.sock" >> "$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"
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 fi
#echo "${sudo_cmde}chown -R $my_user '$TELEBIT_PATH' #echo "${sudo_cmde}chown -R $my_user '$TELEBIT_PATH'
$sudo_cmd chown -R $my_user "$TELEBIT_PATH" $sudo_cmd chown -R $my_user "$TELEBIT_PATH"
############################### ###############################
# Actually Launch the Service # # Actually Launch the Service #
############################### ###############################
@ -425,45 +319,8 @@ if [ "systemd" == "$my_system_launcher" ]; then
$sudo_cmd systemctl restart $my_app $sudo_cmd systemctl restart $my_app
fi fi
# TODO run 'telebit status' echo "telebit init"
sleep 2 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 $TELEBIT_PATH/bin/node $TELEBIT_PATH/bin/telebit.js init
echo "" $TELEBIT_PATH/bin/node $TELEBIT_PATH/bin/telebit.js enable
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

View File

@ -1,4 +1,5 @@
#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
community_member: true # receive infrequent relevant updates community_member: true # receive infrequent relevant updates
telemetry: true # contribute to project telemetric data 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 Normal file
View File

0
var/run/.gitkeep Normal file
View File