diff --git a/README.md b/README.md index 378a0d8..fefbe6d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,18 @@ + + +About Daplie: We're taking back the Internet! +-------------- + +Down with Google, Apple, and Facebook! + +We're re-decentralizing the web and making it read-write again - one home cloud system at a time. + +Tired of serving the Empire? Come join the Rebel Alliance: + +jobs@daplie.com | [Invest in Daplie on Wefunder](https://daplie.com/invest/) | [Pre-order Cloud](https://daplie.com/preorder/), The World's First Home Server for Everyone + + + walnut ====== diff --git a/boot/master.js b/boot/master.js index 7aec33a..5442a39 100644 --- a/boot/master.js +++ b/boot/master.js @@ -9,27 +9,58 @@ console.info('arch:', process.arch); console.info('platform:', process.platform); console.info('\n\n\n[MASTER] Welcome to WALNUT!'); +var path = require('path'); var cluster = require('cluster'); //var minWorkers = 2; var numCores = 2; // Math.max(minWorkers, require('os').cpus().length); var workers = []; -var config = require('../../config'); -var useCaddy = require('fs').existsSync(config.caddy.bin); -var conf = { - localPort: process.argv[2] || (useCaddy ? 4080 : 443) // system / local network -, insecurePort: process.argv[3] || (useCaddy ? 80 : 80) // meh -, externalPort: 443 // world accessible +var config = { + externalPort: 443 // world accessible , externalPortInsecure: 80 // world accessible // TODO externalInsecurePort? , locked: false // TODO XXX , ipcKey: null -, caddyfilepath: config.caddy.conf + // XXX // TODO needs mappings from db -, caddy: config.caddy + // TODO autoconfig Caddy caddy + // XXX +, caddy: { + conf: __dirname + '/Caddyfile' + , bin: '/usr/local/bin/caddy' + , sitespath: path.join(__dirname, 'sites-enabled') + } +, redirects: [ + { "ip": false, "id": "*", "value": false } // default no-www + + , { "ip": false, "id": "daplie.domains", "value": null } + , { "ip": false, "id": "*.daplie.domains", "value": false } + , { "ip": false, "id": "no.daplie.domains", "value": false } + , { "ip": false, "id": "*.no.daplie.domains", "value": false } + , { "ip": false, "id": "ns2.daplie.domains", "value": false } + + , { "ip": true, "id": "maybe.daplie.domains", "value": null } + , { "ip": true, "id": "*.maybe.daplie.domains", "value": null } + + , { "ip": true, "id": "www.daplie.domains", "value": null } + , { "ip": true, "id": "yes.daplie.domains", "value": true } + , { "ip": true, "id": "*.yes.daplie.domains", "value": true } + , { "ip": true, "id": "ns1.daplie.domains", "value": false } + ] + // TODO use sqlite3 or autogenerate ? +, privkey: require('fs').readFileSync(__dirname + '/../../' + '/nsx.redirect-www.org.key.pem', 'ascii') +, pubkey: require('fs').readFileSync(__dirname + '/../../' + '/nsx.redirect-www.org.key.pem.pub', 'ascii') +// keys +// letsencrypt +// com.example.provider +// com.example.consumer }; +var useCaddy = require('fs').existsSync(config.caddy.bin); var state = {}; var caddy; +config.localPort = process.argv[2] || (useCaddy ? 4080 : 443); // system / local network +config.insecurePort = process.argv[3] || (useCaddy ? 80 : 80); // meh + function fork() { if (workers.length < numCores) { workers.push(cluster.fork()); @@ -37,29 +68,26 @@ function fork() { } cluster.on('online', function (worker) { - // TODO XXX Should these be configurable? If so, where? - var certPaths = config.certPaths; - var info; - conf.ddns = config.ddns; - conf.redirects = config.redirects; - console.info('[MASTER] Worker ' + worker.process.pid + ' is online'); fork(); + // TODO XXX Should these be configurable? If so, where? + var certPaths = [ + path.join(__dirname, 'certs', 'live') + , path.join(__dirname, 'letsencrypt', 'live') + ]; // TODO communicate config with environment vars? - info = { + var info = { type: 'walnut.init' , conf: { protocol: useCaddy ? 'http' : 'https' - , externalPort: conf.externalPort - , localPort: conf.localPort - , insecurePort: conf.insecurePort + , externalPort: config.externalPort + , localPort: config.localPort + , insecurePort: config.insecurePort , trustProxy: useCaddy ? true : false , certPaths: useCaddy ? null : certPaths , ipcKey: null // TODO let this load after server is listening - , redirects: config.redirects - , ddns: config.ddns , 'org.oauth3.consumer': config['org.oauth3.consumer'] , 'org.oauth3.provider': config['org.oauth3.provider'] , keys: config.keys @@ -77,20 +105,14 @@ cluster.on('online', function (worker) { // calls init if init has not been called state.caddy = caddy; state.workers = workers; - require('../lib/master').touch(conf, state).then(function () { + require('../lib/master').touch(config, state).then(function () { info.type = 'walnut.webserver.onrequest'; - info.conf.ipcKey = conf.ipcKey; - info.conf.memstoreSock = conf.memstoreSock; - info.conf.sqlite3Sock = conf.sqlite3Sock; - // TODO get this from db config instead - var config = require('../../config'); - info.conf.primaryNameserver = config.ddns.primaryNameserver; - info.conf.nameservers = config.ddns.nameservers; + info.conf.ipcKey = config.ipcKey; + info.conf.memstoreSock = config.memstoreSock; + info.conf.sqlite3Sock = config.sqlite3Sock; // TODO get this from db config instead info.conf.privkey = config.privkey; info.conf.pubkey = config.pubkey; - info.conf.redirects = config.redirects; - info.conf.ddns = config.ddns; worker.send(info); }); } @@ -116,7 +138,7 @@ cluster.on('exit', function (worker, code, signal) { fork(); if (useCaddy) { - caddy = require('../lib/spawn-caddy').create(conf); + caddy = require('../lib/spawn-caddy').create(config); // relies on { localPort, locked } - caddy.spawn(conf); + caddy.spawn(config); } diff --git a/etc/init/walnut.conf b/etc/init/walnut.conf new file mode 100644 index 0000000..6b10255 --- /dev/null +++ b/etc/init/walnut.conf @@ -0,0 +1,21 @@ +description "WALNUT, by Daplie" +version "0.7" +author "Daplie Inc" + +# Upstart has nothing in $PATH by default +env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +# Keep the server running on crash or machine reboot +respawn +respawn limit 10 120 +start on runlevel [2345] + +# Start the server using spark and redirect output to log files +script + DATE=`date '+%F_%H-%M-%S'` + cd /srv/walnut + mkdir -p logs + exec node ./core/bin/walnut \ + > "./logs/access.${DATE}.log" \ + 2> "./logs/error.${DATE}.log" +end script diff --git a/etc/letsencrypt/live/.gitkeep b/etc/letsencrypt/live/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/etc/letsencrypt/live/www.example.com/fullchain.pem b/etc/letsencrypt/live/www.example.com/fullchain.pem new file mode 100644 index 0000000..3845f76 --- /dev/null +++ b/etc/letsencrypt/live/www.example.com/fullchain.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSTCCAjECCQCyFV5BDXN2ADANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJV +UzENMAsGA1UECBMEVXRhaDEOMAwGA1UEBxMFUHJvdm8xIzAhBgNVBAoTGkFDTUUg +U2lnbmluZyBBdXRob3JpdHkgSW5jMRQwEgYDVQQDEwtleGFtcGxlLmNvbTAeFw0x +NTAxMjgwMjQyMjNaFw0xODAxMjcwMjQyMjNaMGYxCzAJBgNVBAYTAlVTMQ0wCwYD +VQQIEwRVdGFoMQ4wDAYDVQQHEwVQcm92bzEVMBMGA1UEChMMQUNNRSBTZXJ2aWNl +MSEwHwYDVQQDExhsb2NhbC5oZWxsb3dvcmxkMzAwMC5jb20wggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIRtMZnPXfMT8ejvdndHUx0pBh0ToOyLgtXP6q +PlziXUN8GJoQ1mxT2QNtkFHDoB5Nl2g3HDtEAGxNXlm8qMFrsbL772qidUN45oz0 +8dqOgOxENAwVqeA70YqqLdDhqW1qe8aRG5QyA43WGTiv18xZUZ6YG4lgHloqwNfK +7KObACzTA5i8zuhl7z1eHm5va06pkXIdAbxNQwk9YLVM71B2pOF2jqIcsBZ/qrvD +TdKQsXLIy9NBMHykNuD6uPTNuBnOxzo59RqlQMzaVxqHYzCkWszlXLC05I+Xhcg5 +JKXbnvl1A+aMqEnqjY8H2t4S6bDfeA1v6z1oL77/t6S4u2LhAgMBAAEwDQYJKoZI +hvcNAQEFBQADggEBAAOlX8d05CbPjVeS91zfQBz8Ev0sXjdIElm6DMEmX9OzCuk+ ++j5Ptn3g3bFraFqcDO9U9lgbYooiulCnlrYX46eyuCNfojF0Q1lGu3OGK1TYKmmK +DUTLcZ/5NL3azhZJ4iOFw8Kv0OvwTvGRBoKM+VsRkX+wtTLyY9b5+C4ON4htu6Qm +V0Tm25qRZQZx/kVZQolfYiR9mL2RBr+sQfJ4mfZJoFu+m99LPKgAc5qcbS1MmwNq +l2INMkFtPxPWXzt1INdLtLHfA7LpMZBLsBfj7mPcr1xLBLpiFBT+Pvsgc+Y+LrGz +QruRjat3F7YKt/M5Y1jSanvvyAKZNhTZoJeVQhI= +-----END CERTIFICATE----- diff --git a/etc/letsencrypt/live/www.example.com/privkey.pem b/etc/letsencrypt/live/www.example.com/privkey.pem new file mode 100644 index 0000000..0d6a52b --- /dev/null +++ b/etc/letsencrypt/live/www.example.com/privkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAyEbTGZz13zE/Ho73Z3R1MdKQYdE6Dsi4LVz+qj5c4l1DfBia +ENZsU9kDbZBRw6AeTZdoNxw7RABsTV5ZvKjBa7Gy++9qonVDeOaM9PHajoDsRDQM +FangO9GKqi3Q4altanvGkRuUMgON1hk4r9fMWVGemBuJYB5aKsDXyuyjmwAs0wOY +vM7oZe89Xh5ub2tOqZFyHQG8TUMJPWC1TO9QdqThdo6iHLAWf6q7w03SkLFyyMvT +QTB8pDbg+rj0zbgZzsc6OfUapUDM2lcah2MwpFrM5VywtOSPl4XIOSSl2575dQPm +jKhJ6o2PB9reEumw33gNb+s9aC++/7ekuLti4QIDAQABAoIBAQCaEvKAN+JnbEl0 +xuPKMSufwq/UsNOLYBtgurQoYIWwl7M9Gugbn/oCn2cluOtOJjZp1tsT2qI1dGXd +N4Ktwt3abgCWFN/iT3gy4Jr8LbvH+aLJ7YiGegPJwTB+L3UMxf0o+EQQijj+KQHK +/ehATzIkXMg3mKeczHzteyIIOPi8Wo+2r2cRsHv+7GUV5iMWp0UF1DbuA0yqqLw8 +8tMHcl4+IAamvaNhwWGvCVathu/TIIQCI4ZYT/+KZmzEMZ5Q+uZdtoj6jd8KGi/c +EBZYi2X+DViej0kGuQncn3evMyfcSsyU70ytkFYgO9X/lk3al7vl6FjLiD8tb8T5 +axSuRdhhAoGBAOUGUCuW5mMkGPDDAkvvRlc5QxJk7Q4y1e7nFdaQ+Dz5a9KhGhav +7ylC/+wxTqdKS4n+4rIFkPkphFULSmM1SNH/V3efjvJlZ5Xwaf9yNkr4f+A3oj4+ +BGFVt6zXI5pHsonWDf4mhJfNOAax+K8dC9hMCcuODGFbEpjp1fHOmu+NAoGBAN/d +r0jJiivLMWvCoJpjQFYRiueVQf0cMjqsKzTg7DEB7aw0HAg5VZ7eMGmuBEKSCq4m +Wt1Q/3MY2mt3Xb03/uMb82B/0Z7fDdFxVakhDElQ450njKZTkOrJy6Xau9z/yakX +C5dGj+PKW2F29Qi+AWWCn2y/Y4B+knApMkTbWTGlAoGBAM33poP9GscQgKmOB3O5 +BZ6N7ecOAy9gwosvIbGtAML9YV206tx6bvDBw51sHptOsq6xHrkRTnb2Qn+lPsoH +8qyBIe1//rO6RLVQ2FMWKQO1hnNkNTIa3h+XUi9L8EXgsw1JB56Ij3hh5EAJMGh0 +C+IDSE/bOQEZQZlpurHTp/1FAoGAcIkvYWa3B7bYfDRVqea0489jxXD5wDRZZ7c+ +7REYirZOdR+o69VunjNRoP5yc8iYh90OFm1uTLy0qAtUWxd4EVDuspbumG/GsX/I +sQHE/GImWc0U3lPp6K1Sq2hewgTvqgnEsOIu2TLTZxZ2rVNevlnArNk1OtXl5W5A +L+8YUmUCgYAHavun7/NKAoydxI/2/Uok4/eQ6BlwtfDG2Tlz1GZctkD3wju2ongA +WFLckyZ1Y4eV77NgyxiVRzSDY6rGLtK/INK+GrP6D9AqWMS/JKVhwZYe5rz9TWuO +ZwkbdVr+uwLwP+3DQltHev0yuVtRa+qLUssQdW6VRJZgQ2T9ue4MzQ== +-----END RSA PRIVATE KEY----- diff --git a/holepunch/beacon.js b/holepunch/beacon.js deleted file mode 100644 index c5124bc..0000000 --- a/holepunch/beacon.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -var PromiseA = require('bluebird').Promise; -var updateIp = require('./helpers/update-ip.js').update; -var request = PromiseA.promisifyAll(require('request')); -var requestAsync = PromiseA.promisify(require('request')); -var upnpForward = require('./helpers/upnp-forward').upnpForward; -var pmpForward = require('./helpers/pmp-forward').pmpForward; -var loopbackHttps = require('./loopback-https'); -//var checkip = require('check-ip-address'); - -function openPort(ip, port) { - if (!/tcp|https|http/.test(port.protocol || 'tcp')) { - throw new Error('not yet supported \'' + port.protocol + '\''); - } - - if (false === port.testable) { - return PromiseA.resolve(); - } - - return loopbackHttps.create(ip, port.private, port.public).then(function () { - console.log('success'); - }).catch(function (err) { - // TODO test err - return upnpForward(port).catch(function (err) { - console.error('[ERROR] UPnP Port Forward'); - console.error(err); - // TODO test err - return pmpForward(port); - }).then(function () { - return loopbackHttps.create(ip, port.private, port.public); - }); - }); -} - -// 1. update dyndns -// 1.5. check ip every 5 min -// 2. loopback test on ip for http / https / ssh -// 3. if needed: discover gateway, map ports -function beacon(hostnames, ports) { - // test with - // dig -p 53 @redirect-www.org pi.nadal.daplie.com A - return updateIp({ - updater: 'redirect-www.org' - , port: 65443 - , ddns: hostnames.map(function (hostname) { - return { "name": hostname /*, "value": ipaddress, "type": "A"*/ }; - }) - }).then(function (data) { - var promises = []; - - console.log("Updated DynDNS"); - console.log(data); - - ports.forEach(function (port) { - promises.push(openPort(JSON.parse(data)[0].answers[0] || hostname, port)); - }); - - return PromiseA.all(promises); - }).then(function () { - console.log('opened ports'); - }); - -/* - request.getAsync('http://checkip.hellabit.com').spread(function (resp, data) { - console.log("External IP is", data); - }).then(function () { - return upnpForward().catch(function (err) { - console.error('ERROR: UPnP failure:'); - console.error(err); - }); - }).then(function () { - return pmpForward().catch(function (err) { - console.error('TODO: Notify user that their router is not compatible'); - }); - }); - - // TODO test roundtrip -*/ -} - -//setInterval(beacon, 5 * 60 * 1000); -exports.run = beacon; diff --git a/holepunch/helpers/pmp-forward.js b/holepunch/helpers/pmp-forward.js deleted file mode 100644 index c105bf3..0000000 --- a/holepunch/helpers/pmp-forward.js +++ /dev/null @@ -1,81 +0,0 @@ -'use strict'; - -var PromiseA = require('bluebird').Promise; -var natpmp = require('nat-pmp'); -var exec = require('child_process').exec; - -exports.pmpForward = function (port) { - return new PromiseA(function (resolve, reject) { - exec('ip route show default', function (err, stdout, stderr) { - var gw; - - if (err || stderr) { reject(err || stderr); return; } - - // default via 192.168.1.1 dev eth0 - gw = stdout.replace(/^default via (\d+\.\d+\.\d+\.\d+) dev[\s\S]+/m, '$1'); - console.log('Possible PMP gateway is', gw); - - // create a "client" instance connecting to your local gateway - var client = natpmp.connect(gw); - - function setPortForward() { - // setup a new port mapping - client.portMapping({ - private: port.private || port.public - , public: port.public || port.private - , ttl: port.ttl || 0 // 600 - }, function (err, info) { - if (err) { - reject(err); - return; - } - - console.log(info); - // { - // type: 'tcp', - // epoch: 8922109, - // private: 22, - // public: 2222, - // ... - // } - resolve(); - }); - } - - // explicitly ask for the current external IP address - setTimeout(function () { - client.externalIp(function (err, info) { - if (err) throw err; - console.log('Current external IP address: %s', info.ip.join('.')); - setPortForward(); - }); - }); - }); - }); -}; - -function usage() { - console.warn(""); - console.warn("node helpers/pmp-forward [public port] [private port] [ttl]"); - console.warn(""); -} - -function run() { - var pubPort = parseInt(process.argv[2], 10) || 0; - var privPort = parseInt(process.argv[3], 10) || pubPort; - var ttl = parseInt(process.argv[4], 10) || 0; - var options = { public: pubPort, private: privPort, ttl: ttl }; - - if (!pubPort) { - usage(); - return; - } - - exports.pmpForward(options).then(function () { - console.log('done'); - }); -} - -if (require.main === module) { - run(); -} diff --git a/holepunch/helpers/upnp-forward.js b/holepunch/helpers/upnp-forward.js deleted file mode 100644 index f42dfe0..0000000 --- a/holepunch/helpers/upnp-forward.js +++ /dev/null @@ -1,81 +0,0 @@ -'use strict'; - -var PromiseA = require('bluebird').Promise; -var natUpnp = require('nat-upnp'); - -exports.upnpForward = function (port) { - return natUpnp.createClient({ timeout: 1800 }).then(function (client) { - return client.portMapping({ - public: port.public, - private: port.private || port.public, - ttl: port.ttl || 0 - })/*.then(function () { - var promitter = client.getMappings(); - - promitter.on('entry', function (entry, i) { - console.log('entry', i); - console.log(entry); - }).then(function (mappings) { - console.log('mappings'); - console.log(mappings); - }); - - return promitter; - })*/; - }); -}; - -/* -client.portUnmapping({ - public: 80 -}); - -.findGateway().then(function (stuff) { - console.log('[a] gateway'); - console.log(stuff.gateway); - console.log('[a] address'); - console.log(stuff.address); - }).then(function () { - return client -*/ - -/* -client.getMappings({ local: true }, function(err, results) { - console.log('local mappings', results); -}); - -client.externalIp(function(err, ip) { - console.log('ext-ip', ip); -}); -*/ - -function usage() { - console.warn(""); - console.warn("node helpers/upnp-forward [public port] [private port] [ttl]"); - console.warn(""); -} - -function run() { - var pubPort = parseInt(process.argv[2], 10) || 0; - var privPort = parseInt(process.argv[3], 10) || pubPort; - var ttl = parseInt(process.argv[4], 10) || 0; - var options = { public: pubPort, private: privPort, ttl: ttl }; - - if (!pubPort) { - usage(); - return; - } - - exports.upnpForward(options).then(function () { - console.log('done'); - }).catch(function (err) { - console.error('ERROR'); - console.error(err); - throw err; - }); -} - -if (require.main === module) { - run(); - return; -} diff --git a/holepunch/loopback-https.js b/holepunch/loopback-https.js deleted file mode 100644 index b5a098f..0000000 --- a/holepunch/loopback-https.js +++ /dev/null @@ -1,124 +0,0 @@ -'use strict'; - -var https = require('https'); -var path = require('path'); -var fs = require('fs'); -var PromiseA = global.Promise || require('bluebird').Promise; - -exports.create = function (ip, localPort, externalPort) { - return new PromiseA(function (resolve, reject) { - var token = Math.random().toString(16).split('.')[1]; - var tokenPath = Math.random().toString(16).split('.')[1]; - var options; - var server; - var options; - var certsPath = path.join(__dirname, 'certs', 'server'); - var caCertsPath = path.join(__dirname, 'certs', 'ca'); - - - function testConnection() { - var awesome = false; - var timetok; - var webreq; - var options = { - // not hostname because we set headers.host on our own - host: ip - , headers: { - // whatever's on the fake cert - 'Host': 'redirect-www.org' - } - , port: externalPort - , path: '/' + tokenPath - , ca: fs.readFileSync(path.join(caCertsPath, 'my-root-ca.crt.pem')) - }; - options.agent = new https.Agent(options); - - timetok = setTimeout(function () { - reject(new Error("timed out while testing NAT loopback for port " + externalPort)); - }, 2000); - - function finishHim(err) { - clearTimeout(timetok); - server.close(function () { - if (!err && awesome) { - resolve(); - } - }); - - if (err || !awesome) { - if (err) { - reject(err); - } - else if (!awesome) { - reject(new Error("loopback failed. Why? here's my best guess: " - + "the ssl cert matched, so you've probably got two boxes and this isn't the right one")); - } - return; - } - } - - webreq = https.request(options, function(res) { - res.on('data', function (resToken) { - if (resToken.toString() === token) { - awesome = true; - return; - } - }); - res.on('error', function (err) { - console.error('[ERROR] https.request.response'); - console.error(err); - finishHim(new Error("loopback failed. Why? here's my best guess: " - + "the connection was interrupted")); - }); - res.on('end', function () { - finishHim(); - }); - }); - - webreq.on('error', function (err) { - console.error('[ERROR] https.request'); - console.error(err); - if (/ssl|cert|chain/i.test(err.message || err.toString())) { - finishHim(new Error("loopback failed. Why? here's my best guess: " - + "the ssl cert validation may have failed (might port-forward to the wrong box)")); - } else { - finishHim(new Error("loopback failed. Why? here's my best guess: " - + "port forwarding isn't configured for " + ip + ":" + externalPort + " to " + localPort)); - } - }); - webreq.end(); - } - - // - // SSL Certificates - // - options = { - key: fs.readFileSync(path.join(certsPath, 'my-server.key.pem')) - , ca: [ fs.readFileSync(path.join(caCertsPath, 'my-root-ca.crt.pem')) ] - , cert: fs.readFileSync(path.join(certsPath, 'my-server.crt.pem')) - , requestCert: false - , rejectUnauthorized: false - }; - - // - // Serve an Express App securely with HTTPS - // - server = https.createServer(options); - function listen(app) { - server.on('request', app); - server.listen(localPort, function () { - localPort = server.address().port; - setTimeout(testConnection, 2000); - }); - } - - listen(function (req, res) { - if (('/' + tokenPath) === req.url) { - res.end(token); - return; - } - - res.end('loopback failure'); - }); - }); -}; diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..f91b31e --- /dev/null +++ b/install.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +sudo mkdir -p /srv/walnut/{certs,core,letsencrypt,lib} +sudo mkdir -p /srv/walnut/packages/{api,pages,services} +sudo chown -R $(whoami):$(whoami) /srv/walnut + +#git clone git@github.com:Daplie/walnut.git +git clone https://github.com/Daplie/walnut.git /srv/walnut/core + +pushd /srv/walnut/core +npm install +sudo rsync -av /srv/walnut/core/etc/init/walnut.conf /etc/init/walnut.conf +rsync -av /srv/walnut/core/etc/letsencrypt/ /srv/walnut/certs/ + +popd +mv /srv/walnut/core/node_modules /srv/walnut + +sudo service walnut stop +sudo service walnut start diff --git a/lib/ddns-updater.js b/lib/ddns-updater.js deleted file mode 100644 index fd94146..0000000 --- a/lib/ddns-updater.js +++ /dev/null @@ -1,82 +0,0 @@ -'use strict'; - -var updateIp = require('ddns-cli').update; - -/* - * @param {string[]} hostnames - A list of hostnames - * @param {Object[]} addresses - A list of { address: , family: <4|6> } - */ -function update(services, hostnames, addresses, ddnsToken) { - // TODO use not-yet-built API to get and store tokens - // TODO use API to add and remove nameservers - var answers = []; - var promises; - var results = []; - var PromiseA; - - hostnames.forEach(function (hostname) { - addresses.forEach(function (address) { - var answer = { - "name": hostname - , "value": address.address - , "type": null - , "priority": null - // token = require('../dyndns-token.js').token; - , "token": ddnsToken - }; - - if (4 === address.family) { - answer.type = 'A'; - } - else if (6 === address.family) { - answer.type = 'AAAA'; - } - else { - console.error('[ERROR] unspported address:'); - console.error(address); - return; - } - - answers.push(answer); - }); - }); - - promises = services.map(function (service, i) { - return updateIp({ - hostname: service.hostname - , port: service.port - , pathname: service.pathname - , cacert: service.cacert - , token: ddnsToken - , ddns: answers - }).then(function (data) { - results[i] = { service: service, data: data }; - return data; - }).error(function (err) { - results[i] = { service: service, error: err }; - }); - }); - - PromiseA = require('bluebird').Promise; - return PromiseA.all(promises).then(function () { - return results; - }); -} - -module.exports.update = function (services, hostnames, ddnsToken) { - return require('./ip-checker').getExternalAddresses().then(function (result) { - //console.log(Object.keys(allMap), result); - //console.log(hostnames) - //console.log(result.addresses); - console.log('[IP CHECKER] hostnames.length', hostnames.length); - console.log('[IP CHECKER] result.addresses.length', result.addresses.length); - return update(services, hostnames, result.addresses, ddnsToken); - }); -}; - -if (require.main === module) { - module.exports.update().then(function (results) { - console.log('results'); - console.log(results); - }); -} diff --git a/lib/insecure-server.js b/lib/insecure-server.js index 359fed9..afb2a28 100644 --- a/lib/insecure-server.js +++ b/lib/insecure-server.js @@ -46,6 +46,10 @@ module.exports.create = function (securePort, insecurePort, info, serverCallback if (/redirect-www.org$/.test(host) && useAppInsecurely(req, res)) { return true; } + if (/^\/.well-known\/acme-challenge/.test(req.url) && useAppInsecurely(req, res)) { + console.log('exception for acme-challenge'); + return true; + } var escapeHtml = require('escape-html'); var newLocation = 'https://' diff --git a/lib/ip-checker.js b/lib/ip-checker.js deleted file mode 100644 index 9a05592..0000000 --- a/lib/ip-checker.js +++ /dev/null @@ -1,126 +0,0 @@ -"use strict"; - -var PromiseA = require('bluebird').Promise; -var ifaces = require('os').networkInterfaces(); -var dns = PromiseA.promisifyAll(require('dns')); -var https = require('https'); - -function getExternalAddresses() { - var iftypes = {}; - - Object.keys(ifaces).forEach(function (ifname) { - ifaces[ifname].forEach(function (iface) { - if (iface.internal) { - return; - } - /* - if (/^(::|f[cde])/.test(iface.address)) { - console.log('non-public ipv6'); - return; - } - */ - - iftypes[iface.family] = true; - }); - }); - - var now = Date.now(); - - return PromiseA.all([ - dns.lookupAsync('api.ipify.org', { family: 4/*, all: true*/ }).then(function (ans) { - iftypes.IPv4 = { address: ans[0], family: ans[1], time: Date.now() - now }; - }).error(function () { - //console.log('no ipv4', Date.now() - now); - iftypes.IPv4 = false; - }) - , dns.lookupAsync('api.ipify.org', { family: 6/*, all: true*/ }).then(function (ans) { - iftypes.IPv6 = { address: ans[0], family: ans[1], time: Date.now() - now }; - }).error(function () { - //console.log('no ipv6', Date.now() - now); - iftypes.IPv6 = false; - }) - ]).then(function () { - var requests = []; - - if (iftypes.IPv4) { - requests.push(new PromiseA(function (resolve) { - var req = https.request({ - method: 'GET' - , hostname: iftypes.IPv4.address - , port: 443 - , headers: { - Host: 'api.ipify.org' - } - , path: '/' - //, family: 4 - // TODO , localAddress: <> - }, function (res) { - var result = ''; - - res.on('error', function (/*err*/) { - resolve(null); - }); - - res.on('data', function (chunk) { - result += chunk.toString('utf8'); - }); - res.on('end', function () { - resolve({ address: result, family: 4/*, wan: result === iftypes.IPv4.localAddress*/, time: iftypes.IPv4.time }); - }); - }); - - req.on('error', function () { - resolve(null); - }); - req.end(); - })); - } - - if (iftypes.IPv6) { - requests.push(new PromiseA(function (resolve) { - var req = https.request({ - method: 'GET' - , hostname: iftypes.IPv6.address - , port: 443 - , headers: { - Host: 'api.ipify.org' - } - , path: '/' - //, family: 6 - // TODO , localAddress: <> - }, function (res) { - var result = ''; - - res.on('error', function (/*err*/) { - resolve(null); - }); - - res.on('data', function (chunk) { - result += chunk.toString('utf8'); - }); - res.on('end', function () { - resolve({ address: result, family: 6/*, wan: result === iftypes.IPv6.localAaddress*/, time: iftypes.IPv4.time }); - }); - }); - - req.on('error', function () { - resolve(null); - }); - req.end(); - })); - } - - return PromiseA.all(requests).then(function (ips) { - ips = ips.filter(function (ip) { - return ip; - }); - - return { - addresses: ips - , time: Date.now() - now - }; - }); - }); -} - -exports.getExternalAddresses = getExternalAddresses; diff --git a/lib/master.js b/lib/master.js index 414146c..80ea606 100644 --- a/lib/master.js +++ b/lib/master.js @@ -4,42 +4,10 @@ var cluster = require('cluster'); var PromiseA = require('bluebird'); var memstore; var sqlstore; -var config; // TODO // var rootMasterKey; -function updateIps() { - console.log('[UPDATE IP]'); - var allMap = {}; - var hostnames = config.redirects.reduce(function (all, redirect) { - if (redirect.ip && !allMap[redirect.id]) { - allMap[redirect.id] = true; - all.push(redirect.id); - } - - return all; - }, []); - - require('./ddns-updater').update( - config.ddns.services - , hostnames - , config.ddns.token - ).then(function (results) { - results.forEach(function (result) { - if (result.error) { - console.error(result); - } else { - console.log('[SUCCESS]', result.service.hostname); - } - }); - }).error(function (err) { - console.error('[UPDATE IP] ERROR'); - console.error(err); - }); -} - function init(conf/*, state*/) { - config = conf; if (!conf.ipcKey) { conf.ipcKey = require('crypto').randomBytes(16).toString('base64'); } @@ -101,11 +69,6 @@ function init(conf/*, state*/) { */ }); - // TODO check the IP every 5 minutes and update it every hour - setInterval(updateIps, 60 * 60 * 1000); - // we don't want this to load right away (extra procesing time) - setTimeout(updateIps, 1); - return promise; } diff --git a/lib/package-server.js b/lib/package-server.js index 7721728..bb5863f 100644 --- a/lib/package-server.js +++ b/lib/package-server.js @@ -89,13 +89,19 @@ function loadPages(pkgConf, packagedPage, req, res, next) { if (!packagedPage._promise_page) { packagedPage._promise_page = new PromiseA(function (resolve, reject) { fs.exists(pkgpath, function (exists) { + var staticServer; + if (!exists) { reject(new Error("package '" + pkgpath + "' is registered but does not exist")); return; } //console.log('[static mount]', pkgpath); - resolve(require('serve-static')(pkgpath)); + // https://github.com/expressjs/serve-static/issues/54 + // https://github.com/pillarjs/send/issues/91 + // https://example.com/.well-known/acme-challenge/xxxxxxxxxxxxxxx + staticServer = require('serve-static')(pkgpath, { dotfiles: undefined }); + resolve(staticServer); }); }); } @@ -415,10 +421,27 @@ function mapToApp(opts, req, res, next) { return; } + // TODO .well-known can be an API (webfinger, letsencrypt, oauth3) + // or static (...???) + if (!router._re_api.test(req.url)) { //console.log('[static router]'); //console.log(router._re_api, req.url); - layerItUp(pkgConf, router, req, res, next); + layerItUp(pkgConf, router, req, res, function (err) { + if (err) { + next(err); + return; + } + + if (/\/\.well-known([\/?]|$)/.test(req.url)) { + console.log('[TODO] handle .well-known as API'); + // rewrite api as /api/org.ietf/.well-known ? + // pass through simply as /.well-known ? + // runApi(opts, router, req, res, next) + } + + next(); + }); return; } diff --git a/lib/worker.js b/lib/worker.js index e165372..b2f9fc7 100644 --- a/lib/worker.js +++ b/lib/worker.js @@ -172,8 +172,6 @@ module.exports.create = function (webserver, info, state) { , vhostPatterns: null , server: webserver , externalPort: info.conf.externalPort - , primaryNameserver: info.conf.primaryNameserver - , nameservers: info.conf.nameservers , privkey: info.conf.privkey , pubkey: info.conf.pubkey , redirects: info.conf.redirects diff --git a/package.json b/package.json index 4a7abed..64dcf91 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ "homepage": "https://github.com/Daplie/walnut", "dependencies": { "accepts": "^1.2.5", - "authcodes": "git://bitbucket.org/daplie/authcodes.git", + "app-scoped-ids": "^1.0.1", + "authcodes": "git://github.com/Daplie/authcodes.git", "authenticator": "^1.0.0", "bluebird": "2.x", "body-parser": "1.x", @@ -81,7 +82,7 @@ "json-storage": "2.x", "jsonwebtoken": "^5.4.0", "lodash": "2.x", - "masterquest-sqlite3": "git://github.com/coolaj86/masterquest-sqlite3.git", + "masterquest-sqlite3": "git://git.daplie.com/coolaj86/node-masterquest-sqlite3.git", "media-typer": "^0.3.0", "methods": "^1.1.1", "mime": "^1.3.4",