2015-06-24 21:45:54 +00:00
#!/usr/bin/env node
2015-06-24 21:36:17 +00:00
'use strict' ;
2016-10-07 04:40:16 +00:00
//var PromiseA = global.Promise;
var PromiseA = require ( 'bluebird' ) ;
2016-10-11 23:20:10 +00:00
var tls = require ( 'tls' ) ;
2016-10-06 23:09:21 +00:00
var https = require ( 'httpolyglot' ) ;
2015-07-08 06:43:46 +00:00
var http = require ( 'http' ) ;
var fs = require ( 'fs' ) ;
2015-06-24 21:36:17 +00:00
var path = require ( 'path' ) ;
2016-10-06 22:42:38 +00:00
var DDNS = require ( 'ddns-cli' ) ;
2016-10-06 23:09:21 +00:00
var httpPort = 80 ;
var httpsPort = 443 ;
2016-10-07 19:12:46 +00:00
var lrPort = 35729 ;
2016-09-13 23:08:08 +00:00
var portFallback = 8443 ;
var insecurePortFallback = 4080 ;
function showError ( err , port ) {
if ( 'EACCES' === err . code ) {
console . error ( err ) ;
console . warn ( "You do not have permission to use '" + port + "'." ) ;
console . warn ( "You can probably fix that by running as Administrator or root." ) ;
}
else if ( 'EADDRINUSE' === err . code ) {
console . warn ( "Another server is already running on '" + port + "'." ) ;
console . warn ( "You can probably fix that by rebooting your comupter (or stopping it if you know what it is)." ) ;
}
}
2015-06-24 21:36:17 +00:00
2015-07-08 06:43:46 +00:00
function createInsecureServer ( port , pubdir , opts ) {
2016-10-11 16:58:18 +00:00
return new PromiseA ( function ( realResolve ) {
2016-09-13 23:08:08 +00:00
var server = http . createServer ( ) ;
2016-10-11 16:58:18 +00:00
function resolve ( ) {
realResolve ( server ) ;
}
2016-09-13 23:08:08 +00:00
server . on ( 'error' , function ( err ) {
if ( opts . errorInsecurePort || opts . manualInsecurePort ) {
showError ( err , port ) ;
process . exit ( 1 ) ;
return ;
}
2015-07-08 06:43:46 +00:00
2016-09-13 23:08:08 +00:00
opts . errorInsecurePort = err . toString ( ) ;
2015-07-08 06:43:46 +00:00
2016-09-13 23:08:08 +00:00
return createInsecureServer ( insecurePortFallback , pubdir , opts ) . then ( resolve ) ;
} ) ;
2015-07-08 06:43:46 +00:00
2016-10-11 16:58:18 +00:00
server . on ( 'request' , opts . redirectApp ) ;
2016-09-13 23:08:08 +00:00
server . listen ( port , function ( ) {
opts . insecurePort = port ;
resolve ( ) ;
} ) ;
2015-07-08 06:43:46 +00:00
} ) ;
}
function createServer ( port , pubdir , content , opts ) {
2016-10-06 22:42:38 +00:00
function approveDomains ( params , certs , cb ) {
// This is where you check your database and associated
// email addresses with domains and agreements and such
var domains = params . domains ;
//var p;
console . log ( 'approveDomains' ) ;
console . log ( domains ) ;
// The domains being approved for the first time are listed in opts.domains
// Certs being renewed are listed in certs.altnames
if ( certs ) {
params . domains = certs . altnames ;
//p = PromiseA.resolve();
}
else {
//params.email = opts.email;
if ( ! opts . agreeTos ) {
console . error ( "You have not previously registered '" + domains + "' so you must specify --agree-tos to agree to both the Let's Encrypt and Daplie DNS terms of service." ) ;
process . exit ( 1 ) ;
return ;
}
params . agreeTos = opts . agreeTos ;
}
// ddns.token(params.email, domains[0])
params . email = opts . email ;
params . refreshToken = opts . refreshToken ;
params . challengeType = 'dns-01' ;
params . cli = opts . argv ;
cb ( null , { options : params , certs : certs } ) ;
}
2016-10-11 16:58:18 +00:00
return new PromiseA ( function ( realResolve ) {
2016-10-27 04:41:26 +00:00
var app = require ( '../lib/app.js' ) ;
2016-09-13 23:08:08 +00:00
var directive = { public : pubdir , content : content , livereload : opts . livereload
, servername : opts . servername , expressApp : opts . expressApp } ;
2016-10-11 16:58:18 +00:00
var insecureServer ;
function resolve ( ) {
realResolve ( {
plainServer : insecureServer
, server : server
} ) ;
}
2016-09-13 23:08:08 +00:00
2016-10-06 22:42:38 +00:00
// returns an instance of node-letsencrypt with additional helper methods
var webrootPath = require ( 'os' ) . tmpdir ( ) ;
var leChallengeFs = require ( 'le-challenge-fs' ) . create ( { webrootPath : webrootPath } ) ;
2016-10-11 23:20:10 +00:00
//var leChallengeSni = require('le-challenge-sni').create({ webrootPath: webrootPath });
2016-10-19 20:09:10 +00:00
var leChallengeDdns = require ( 'le-challenge-ddns' ) . create ( { ttl : 1 } ) ;
2016-10-06 22:42:38 +00:00
var lex = require ( 'letsencrypt-express' ) . create ( {
// set to https://acme-v01.api.letsencrypt.org/directory in production
server : opts . debug ? 'staging' : 'https://acme-v01.api.letsencrypt.org/directory'
// If you wish to replace the default plugins, you may do so here
//
, challenges : {
'http-01' : leChallengeFs
2016-10-11 23:20:10 +00:00
, 'tls-sni-01' : leChallengeFs // leChallengeSni
2016-10-19 20:09:10 +00:00
, 'dns-01' : leChallengeDdns
2016-10-06 22:42:38 +00:00
}
2016-10-11 23:20:10 +00:00
, challengeType : ( opts . tunnel ? 'http-01' : 'dns-01' )
2016-10-27 09:00:40 +00:00
, store : require ( 'le-store-certbot' ) . create ( {
webrootPath : webrootPath
, configDir : path . join ( ( opts . homedir || '~' ) , 'letsencrypt' , 'etc' )
, homedir : opts . homedir
} )
2016-10-06 22:42:38 +00:00
, webrootPath : webrootPath
// You probably wouldn't need to replace the default sni handler
// See https://github.com/Daplie/le-sni-auto if you think you do
//, sni: require('le-sni-auto').create({})
, approveDomains : approveDomains
} ) ;
2016-10-11 23:20:10 +00:00
var secureContext ;
opts . httpsOptions . SNICallback = function ( servername , cb ) {
console . log ( '[https] servername' , servername ) ;
if ( 'localhost.daplie.com' === servername ) {
if ( ! secureContext ) {
secureContext = tls . createSecureContext ( opts . httpsOptions ) ;
}
cb ( null , secureContext ) ;
return ;
}
lex . httpsOptions . SNICallback ( servername , cb ) ;
} ;
2016-10-06 22:42:38 +00:00
var server = https . createServer ( opts . httpsOptions ) ;
2016-09-13 23:08:08 +00:00
server . on ( 'error' , function ( err ) {
if ( opts . errorPort || opts . manualPort ) {
showError ( err , port ) ;
process . exit ( 1 ) ;
return ;
}
2015-12-06 06:43:33 +00:00
2016-09-13 23:08:08 +00:00
opts . errorPort = err . toString ( ) ;
2015-12-06 06:43:33 +00:00
2016-09-13 23:08:08 +00:00
return createServer ( portFallback , pubdir , content , opts ) . then ( resolve ) ;
} ) ;
2015-06-24 21:36:17 +00:00
2016-09-13 23:08:08 +00:00
server . listen ( port , function ( ) {
opts . port = port ;
2016-10-11 16:58:18 +00:00
opts . redirectOptions . port = port ;
2015-07-08 06:43:46 +00:00
2016-10-07 19:12:46 +00:00
if ( opts . livereload ) {
opts . lrPort = opts . lrPort || lrPort ;
var livereload = require ( 'livereload' ) ;
var server2 = livereload . createServer ( {
https : opts . httpsOptions
, port : opts . lrPort
2016-10-17 23:40:55 +00:00
, exclusions : [ 'node_modules' ]
2016-10-07 19:12:46 +00:00
} ) ;
console . info ( "[livereload] watching " + pubdir ) ;
console . warn ( "WARNING: If CPU usage spikes to 100% it's because too many files are being watched" ) ;
server2 . watch ( pubdir ) ;
}
2016-09-09 05:10:04 +00:00
2016-10-27 08:44:14 +00:00
// if we haven't disabled insecure port
2016-10-27 08:45:08 +00:00
if ( 'false' !== opts . insecurePort ) {
2016-10-27 08:44:14 +00:00
// and both ports are the default
if ( ( httpsPort === opts . port && httpPort === opts . insecurePort )
// or other case
|| ( httpPort !== opts . insecurePort && opts . port !== opts . insecurePort )
) {
return createInsecureServer ( opts . insecurePort , pubdir , opts ) . then ( function ( _server ) {
insecureServer = _server ;
resolve ( ) ;
} ) ;
}
2016-09-13 23:08:08 +00:00
}
2016-10-27 08:44:14 +00:00
opts . insecurePort = opts . port ;
resolve ( ) ;
return ;
2016-09-13 23:08:08 +00:00
} ) ;
2016-09-09 05:10:04 +00:00
2016-09-13 23:08:08 +00:00
if ( 'function' === typeof app ) {
app = app ( directive ) ;
} else if ( 'function' === typeof app . create ) {
app = app . create ( directive ) ;
}
2016-09-09 05:10:04 +00:00
2016-09-13 23:08:08 +00:00
server . on ( 'request' , function ( req , res ) {
2016-10-06 22:42:38 +00:00
console . log ( '[' + req . method + '] ' + req . url ) ;
2016-10-11 19:41:29 +00:00
if ( ! req . socket . encrypted && ! /\/\.well-known\/acme-challenge\// . test ( req . url ) ) {
2016-10-11 16:58:18 +00:00
opts . redirectApp ( req , res ) ;
2016-10-06 23:09:21 +00:00
return ;
}
2016-10-06 22:42:38 +00:00
2016-09-13 23:08:08 +00:00
if ( 'function' === typeof app ) {
app ( req , res ) ;
return ;
2016-09-09 05:10:04 +00:00
}
2015-06-24 21:36:17 +00:00
2016-09-13 23:08:08 +00:00
res . end ( 'not ready' ) ;
} ) ;
2015-06-24 21:36:17 +00:00
2016-09-13 23:08:08 +00:00
return PromiseA . resolve ( app ) . then ( function ( _app ) {
app = _app ;
} ) ;
2015-06-24 21:36:17 +00:00
} ) ;
}
module . exports . createServer = createServer ;
function run ( ) {
2016-09-13 23:08:08 +00:00
var defaultServername = 'localhost.daplie.com' ;
2015-06-24 21:36:17 +00:00
var minimist = require ( 'minimist' ) ;
var argv = minimist ( process . argv . slice ( 2 ) ) ;
2016-10-06 23:09:21 +00:00
var port = parseInt ( argv . p || argv . port || argv . _ [ 0 ] , 10 ) || httpsPort ;
2015-12-06 06:43:33 +00:00
var livereload = argv . livereload ;
2015-06-24 21:36:17 +00:00
var pubdir = path . resolve ( argv . d || argv . _ [ 1 ] || process . cwd ( ) ) ;
2015-06-30 23:11:01 +00:00
var content = argv . c ;
2015-07-08 07:27:14 +00:00
var letsencryptHost = argv [ 'letsencrypt-certs' ] ;
2015-07-08 06:43:46 +00:00
2016-10-07 21:28:46 +00:00
if ( argv . V || argv . version || argv . v ) {
if ( argv . v ) {
console . warn ( "flag -v is reserved for future use. Use -V or --version for version information." ) ;
}
2016-10-27 04:41:26 +00:00
console . info ( 'v' + require ( '../package.json' ) . version ) ;
2016-10-07 21:28:46 +00:00
return ;
}
2016-09-13 23:08:08 +00:00
// letsencrypt
2016-10-07 19:26:53 +00:00
var httpsOptions = require ( 'localhost.daplie.com-certificates' ) . merge ( { } ) ;
2016-10-07 18:00:21 +00:00
var secureContext ;
2016-09-13 23:08:08 +00:00
2015-07-08 06:43:46 +00:00
var opts = {
2016-10-06 22:42:38 +00:00
agreeTos : argv . agreeTos || argv [ 'agree-tos' ]
, debug : argv . debug
2016-10-19 20:09:10 +00:00
, device : argv . device
2016-10-06 22:42:38 +00:00
, email : argv . email
, httpsOptions : {
2016-10-07 19:26:53 +00:00
key : httpsOptions . key
, cert : httpsOptions . cert
//, ca: httpsOptions.ca
2016-10-07 19:29:50 +00:00
}
2016-10-27 08:07:15 +00:00
, homedir : argv . homedir
2016-10-06 22:42:38 +00:00
, argv : argv
2015-07-08 06:43:46 +00:00
} ;
2015-07-13 23:46:44 +00:00
var peerCa ;
2016-10-06 22:42:38 +00:00
var p ;
2015-07-08 06:43:46 +00:00
2016-10-11 23:20:10 +00:00
opts . PromiseA = PromiseA ;
2016-10-06 22:42:38 +00:00
opts . httpsOptions . SNICallback = function ( servername , cb ) {
2016-10-07 18:00:21 +00:00
if ( ! secureContext ) {
secureContext = tls . createSecureContext ( opts . httpsOptions ) ;
}
cb ( null , secureContext ) ;
2016-09-13 23:08:08 +00:00
return ;
} ;
2015-07-08 07:27:14 +00:00
if ( letsencryptHost ) {
argv . key = argv . key || '/etc/letsencrypt/live/' + letsencryptHost + '/privkey.pem' ;
2015-07-13 23:46:44 +00:00
argv . cert = argv . cert || '/etc/letsencrypt/live/' + letsencryptHost + '/fullchain.pem' ;
2015-12-11 05:30:54 +00:00
argv . root = argv . root || argv . chain || '' ;
2015-07-08 07:27:14 +00:00
argv . servername = argv . servername || letsencryptHost ;
2015-07-13 23:46:44 +00:00
argv [ 'serve-root' ] = argv [ 'serve-root' ] || argv [ 'serve-chain' ] ;
2016-08-06 18:34:15 +00:00
// argv[express-app]
2015-07-08 07:27:14 +00:00
}
2015-07-13 23:46:44 +00:00
if ( argv [ 'serve-root' ] && ! argv . root ) {
console . error ( "You must specify bath --root to use --serve-root" ) ;
return ;
}
if ( argv . key || argv . cert || argv . root ) {
if ( ! argv . key || ! argv . cert ) {
console . error ( "You must specify bath --key and --cert, and optionally --root (required with serve-root)" ) ;
2015-07-08 06:43:46 +00:00
return ;
}
2015-07-13 23:46:44 +00:00
if ( ! Array . isArray ( argv . root ) ) {
argv . root = [ argv . root ] ;
2015-07-08 06:43:46 +00:00
}
2016-10-06 22:42:38 +00:00
opts . httpsOptions . key = fs . readFileSync ( argv . key ) ;
opts . httpsOptions . cert = fs . readFileSync ( argv . cert ) ;
2015-07-13 23:46:44 +00:00
2015-07-08 06:43:46 +00:00
// turn multiple-cert pemfile into array of cert strings
2015-07-13 23:46:44 +00:00
peerCa = argv . root . reduce ( function ( roots , fullpath ) {
if ( ! fs . existsSync ( fullpath ) ) {
return roots ;
}
return roots . concat ( fs . readFileSync ( fullpath , 'ascii' )
2015-07-08 06:43:46 +00:00
. split ( '-----END CERTIFICATE-----' )
. filter ( function ( ca ) {
return ca . trim ( ) ;
} ) . map ( function ( ca ) {
return ( ca + '-----END CERTIFICATE-----' ) . trim ( ) ;
} ) ) ;
} , [ ] ) ;
2015-07-08 07:27:14 +00:00
2015-07-13 23:46:44 +00:00
// TODO * `--verify /path/to/root.pem` require peers to present certificates from said authority
if ( argv . verify ) {
2016-10-06 22:42:38 +00:00
opts . httpsOptions . ca = peerCa ;
opts . httpsOptions . requestCert = true ;
opts . httpsOptions . rejectUnauthorized = true ;
2015-07-13 23:46:44 +00:00
}
2015-12-11 05:30:54 +00:00
if ( argv [ 'serve-root' ] ) {
content = peerCa . join ( '\r\n' ) ;
}
2015-07-08 06:43:46 +00:00
}
2016-09-13 23:08:08 +00:00
opts . servername = defaultServername ;
2015-07-08 06:43:46 +00:00
if ( argv . servername ) {
opts . servername = argv . servername ;
}
2016-09-13 23:08:08 +00:00
if ( argv . p || argv . port || argv . _ [ 0 ] ) {
opts . manualPort = true ;
}
2016-10-07 16:44:25 +00:00
if ( argv . t || argv . tunnel ) {
opts . tunnel = true ;
}
2016-09-13 23:08:08 +00:00
if ( argv . i || argv [ 'insecure-port' ] ) {
opts . manualInsecurePort = true ;
}
2016-10-06 23:09:21 +00:00
opts . insecurePort = parseInt ( argv . i || argv [ 'insecure-port' ] , 10 )
|| argv . i || argv [ 'insecure-port' ]
|| httpPort
;
2015-12-06 06:43:33 +00:00
opts . livereload = livereload ;
2015-07-08 06:43:46 +00:00
2016-08-06 18:34:15 +00:00
if ( argv [ 'express-app' ] ) {
opts . expressApp = require ( path . resolve ( process . cwd ( ) , argv [ 'express-app' ] ) ) ;
}
2016-10-06 22:42:38 +00:00
if ( opts . email || opts . servername ) {
if ( ! opts . agreeTos ) {
console . warn ( "You may need to specify --agree-tos to agree to both the Let's Encrypt and Daplie DNS terms of service." ) ;
}
if ( ! opts . email ) {
// TODO store email in .ddnsrc.json
console . warn ( "You may need to specify --email to register with both the Let's Encrypt and Daplie DNS." ) ;
}
p = DDNS . refreshToken ( {
email : opts . email
, silent : true
2016-10-27 08:07:15 +00:00
, homedir : opts . homedir
2016-10-06 22:42:38 +00:00
} , {
debug : false
, email : opts . argv . email
} ) . then ( function ( refreshToken ) {
opts . refreshToken = refreshToken ;
} ) ;
}
else {
p = PromiseA . resolve ( ) ;
}
return p . then ( function ( ) {
2016-10-11 17:02:01 +00:00
2016-10-11 16:58:18 +00:00
// can be changed to tunnel external port
opts . redirectOptions = {
port : opts . port
} ;
opts . redirectApp = require ( 'redirect-https' ) ( opts . redirectOptions ) ;
2016-10-11 23:20:10 +00:00
return createServer ( port , pubdir , content , opts ) . then ( function ( servers ) {
2016-09-13 23:08:08 +00:00
var p ;
var httpsUrl ;
2016-10-27 08:53:49 +00:00
var httpUrl ;
2016-09-13 23:08:08 +00:00
var promise ;
2016-10-27 08:53:49 +00:00
console . info ( '' ) ;
console . info ( 'Serving ' + pubdir + ' at ' ) ;
console . info ( '' ) ;
2016-09-13 23:08:08 +00:00
// Port
httpsUrl = 'https://' + opts . servername ;
p = opts . port ;
2016-10-06 23:09:21 +00:00
if ( httpsPort !== p ) {
2016-09-13 23:08:08 +00:00
httpsUrl += ':' + p ;
}
console . info ( '\t' + httpsUrl ) ;
// Insecure Port
2016-10-27 08:53:49 +00:00
httpUrl = 'http://' + opts . servername ;
2016-10-27 08:47:11 +00:00
p = opts . insecurePort ;
2016-10-06 23:09:21 +00:00
if ( httpPort !== p ) {
2016-10-27 08:53:49 +00:00
httpUrl += ':' + p ;
2016-09-13 23:08:08 +00:00
}
2016-10-27 08:53:49 +00:00
console . info ( '\t' + httpUrl + ' (redirecting to https)' ) ;
2016-09-13 23:08:08 +00:00
console . info ( '' ) ;
if ( ! ( argv . servername && defaultServername !== argv . servername && ! ( argv . key && argv . cert ) ) ) {
// ifaces
2016-10-27 04:41:26 +00:00
opts . ifaces = require ( '../lib/local-ip.js' ) . find ( ) ;
2016-09-13 23:08:08 +00:00
promise = PromiseA . resolve ( ) ;
} else {
console . info ( "Attempting to resolve external connection for '" + argv . servername + "'" ) ;
try {
2016-10-27 04:41:26 +00:00
promise = require ( '../lib/match-ips.js' ) . match ( argv . servername , opts ) ;
2016-09-13 23:08:08 +00:00
} catch ( e ) {
console . warn ( "Upgrade to version 2.x to use automatic certificate issuance for '" + argv . servername + "'" ) ;
promise = PromiseA . resolve ( ) ;
}
}
return promise . then ( function ( matchingIps ) {
if ( matchingIps ) {
if ( ! matchingIps . length ) {
2016-10-06 22:42:38 +00:00
console . info ( "Neither the attached nor external interfaces match '" + argv . servername + "'" ) ;
2016-09-13 23:08:08 +00:00
}
}
2016-10-07 04:28:05 +00:00
opts . matchingIps = matchingIps || [ ] ;
2016-09-13 23:08:08 +00:00
if ( opts . matchingIps . length ) {
console . info ( '' ) ;
console . info ( 'External IPs:' ) ;
console . info ( '' ) ;
opts . matchingIps . forEach ( function ( ip ) {
if ( 'IPv4' === ip . family ) {
httpsUrl = 'https://' + ip . address ;
2016-10-06 23:09:21 +00:00
if ( httpsPort !== opts . port ) {
2016-09-13 23:08:08 +00:00
httpsUrl += ':' + opts . port ;
}
console . info ( '\t' + httpsUrl ) ;
}
else {
httpsUrl = 'https://[' + ip . address + ']' ;
2016-10-06 23:09:21 +00:00
if ( httpsPort !== opts . port ) {
2016-09-13 23:08:08 +00:00
httpsUrl += ':' + opts . port ;
}
console . info ( '\t' + httpsUrl ) ;
}
} ) ;
}
2016-10-11 23:20:10 +00:00
else if ( ! opts . tunnel ) {
console . info ( "External IP address does not match local IP address." ) ;
console . info ( "Use --tunnel to allow the people of the Internet to access your server." ) ;
}
if ( opts . tunnel ) {
2016-10-27 04:41:26 +00:00
require ( '../lib/tunnel.js' ) . create ( opts , servers ) ;
2016-10-11 19:41:29 +00:00
}
2016-10-17 23:40:55 +00:00
else if ( opts . ddns ) {
2016-10-27 04:41:26 +00:00
require ( '../lib/ddns.js' ) . create ( opts , servers ) ;
2016-10-17 23:40:55 +00:00
}
2016-09-13 23:08:08 +00:00
Object . keys ( opts . ifaces ) . forEach ( function ( iname ) {
var iface = opts . ifaces [ iname ] ;
if ( iface . ipv4 . length ) {
console . info ( '' ) ;
console . info ( iname + ':' ) ;
httpsUrl = 'https://' + iface . ipv4 [ 0 ] . address ;
2016-10-06 23:09:21 +00:00
if ( httpsPort !== opts . port ) {
2016-09-13 23:08:08 +00:00
httpsUrl += ':' + opts . port ;
}
console . info ( '\t' + httpsUrl ) ;
if ( iface . ipv6 . length ) {
2016-10-06 22:42:38 +00:00
httpsUrl = 'https://[' + iface . ipv6 [ 0 ] . address + ']' ;
2016-10-07 04:40:16 +00:00
if ( httpsPort !== opts . port ) {
2016-10-06 22:42:38 +00:00
httpsUrl += ':' + opts . port ;
}
2016-09-13 23:08:08 +00:00
console . info ( '\t' + httpsUrl ) ;
}
}
} ) ;
console . info ( '' ) ;
} ) ;
} ) ;
2016-10-06 22:42:38 +00:00
} ) ;
2015-06-24 21:36:17 +00:00
}
if ( require . main === module ) {
run ( ) ;
}