dynroute
Version:
Dynamic DNS for your domain using Amazon Route53
274 lines (227 loc) • 7.64 kB
JavaScript
var awssum = require('awssum'),
amazon = awssum.load('amazon/amazon'),
Route53 = awssum.load('amazon/route53').Route53,
_ = require('underscore'),
async = require('async'),
fmt = require('fmt'),
utils = require('./lib/utils'),
growl = require('growl'),
request = require('request'),
env = process.env,
lastKnownIP, hostedZones, opts, r53;
function searchRecord(ip) {
r53.ListHostedZones(function(err, data) {
if (err) {
if (opts.debug) {
utils.debugBlock('Error while listing hosted zones', err);
}
return;
}
hostedZones = data.Body.ListHostedZonesResponse.HostedZones.HostedZone;
// dirty little hack for next map function
if (!utils.isArray(hostedZones)) { hostedZones = [hostedZones]; }
hostedZones = _.map(hostedZones, function(zone) {
return {
id : zone.Id.replace('/hostedzone/', ''),
name : zone.Name.slice(0, zone.Name.length - 1)
};
});
if (opts.debug) {
utils.debugBlock('Hosted Zones', hostedZones);
}
async.map(hostedZones, function(zone, callback) {
// skip listing record sets and return an empty array unless we care about this specific zone
if (opts.zone && opts.zone !== zone.id) {
callback(null, {
id: zone.id,
records: []
});
return;
}
r53.ListResourceRecordSets({ HostedZoneId : zone.id }, function(err, data) {
var resp, records;
if (err) { return callback(err); }
records = data.Body.ListResourceRecordSetsResponse.ResourceRecordSets.ResourceRecordSet;
records = _.filter(records, function(record) {
return record.Type === 'A';
});
records.forEach(function(record, index, records){
records[index].Name = record.Name.replace(/\\052/g, '*');
});
resp = {
id : zone.id,
records : records
};
callback(null, resp);
});
}, function(err, zones) {
if (err) {
if (opts.debug) {
utils.debugBlock('Error listing resource record sets', err);
}
return;
}
if (opts.debug) {
utils.debugBlock('Zones with A records', zones);
}
for (j = 0; j < opts.domain.length; j++) {
(function iterate(domain, iterator) {
if (opts.debug) {
utils.debugBlock('Iterating Domain', domain);
}
var zone, i, len, record, rootZone;
// validating domain
if (!utils.isValidDomain(domain)) {
if (opts.debug) {
utils.debugBlock('Error validating domain name', domain);
}
utils.print('Correct domain name form: domain.tld, subdomain.domain.tld, or any number of additional subdomains.');
utils.exit('Error validating domain name');
}
zone = zones[iterator];
// current zone has A type records, iterate through them until
// domain is found
if (zone.records.length) {
// TODO: make simple forEach async
for (i = 0, len = zone.records.length; i < len; i++) {
if (zone.records[i].Name === (domain + '.')) {
return updateDns(ip, domain, {
zoneId : zone.id,
record : zone.records[i]
});
}
}
}
// proceed to next zone from the array
if (iterator) {
process.nextTick(function() {
iterate(domain, --iterator);
});
} else {
if (opts.debug) {
utils.debugBlock("Record doesn't exist, create it", hostedZones);
}
// no such record was found, iterate through hostedZones
// and check for the root domain
hostedZones.forEach(function(zone) {
if (domain.indexOf(zone.name) > -1) {
rootZone = zone;
}
});
if (opts.debug) {
utils.debugBlock('RootZone', domain, rootZone, zone);
}
return updateDns(ip, domain, {
zoneId : rootZone.id
});
}
}(opts.domain[j], zones.length - 1));
}
});
});
}
function updateDns(ip, domain, details) {
var args, oldIps;
if (opts.debug) {
utils.debugBlock('updateDns() called', {
ip : ip,
details : details
});
}
args = {
HostedZoneId: '/' + details.zoneId,
Comment: 'Updated local ip at: ' + Date.now(),
Changes: []
};
// if record already exists in Route53 - delete & re-create it
if (details.record) {
oldIps = details.record.ResourceRecords.ResourceRecord;
// dirty little hack for next map function
if (!utils.isArray(oldIps)) { oldIps = [oldIps]; }
oldIps = _.map(oldIps, function(ip) {
return ip.Value;
});
// IP still the same, no change needed
if (oldIps.length === 1 && oldIps[0] === ip) {
lastKnownIP = ip;
if (opts.debug) {
utils.debugBlock('Ip unchanged', 'No request sent!');
}
return;
}
args.Changes.push({
Action : 'DELETE',
Name : details.record.Name,
Type : 'A',
Ttl : details.record.TTL,
ResourceRecords : oldIps
});
}
// Record either doesnt exist or has been deleted, create it
args.Changes.push({
Action : 'CREATE',
Name : domain,
Type : 'A',
Ttl : opts.ttl,
ResourceRecords : [ip]
});
if (opts.debug) {
utils.debugBlock('Sent ip change request to Route53', args);
}
r53.ChangeResourceRecordSets(args, function(err, data) {
if (err) {
if (opts.debug) {
utils.debugBlock('Error while updating ip in Route53', err);
}
return;
}
if (opts.debug) {
utils.debugBlock('ChangeResourceRecordSetsResponse', data);
}
if (!err && data &&
((data.Body.ChangeResourceRecordSetsResponse.ChangeInfo.Status === 'PENDING') ||
(data.Body.ChangeResourceRecordSetsResponse.ChangeInfo.Status === 'CREATE'))) {
if (opts.debug && opts.growl) {
utils.debugBlock('IP change successful', 'Sending growl notification');
}
if (opts.growl) {
growl(domain + ' : ' + ip, { title: 'Dynamic DNS Update' });
}
lastKnownIP = ip;
}
});
};
function checkIpUpdate(credentials, configs) {
opts = configs;
// initialize once
r53 = r53 || new Route53(credentials);
request(opts.ipserver, function (error, response, body) {
var ip;
if (!error && response.statusCode == 200) {
try {
ip = JSON.parse(body.trim()).ip;
if (opts.debug) {
utils.debugBlock('Your IP', ip);
}
// no point in querying Route53 if we know the last ip is correct right?
if (ip !== lastKnownIP) {
searchRecord(ip);
} else {
if (opts.debug) {
utils.debugBlock('IP the same as last time', 'No update needed');
}
}
}
catch(err) {
if (opts.debug) {
utils.debugBlock('Error getting ip from server', err);
}
}
} else {
if (opts.debug) {
utils.debugBlock('Error getting ip from server', error, response.statusCode);
}
}
});
};
module.exports = checkIpUpdate;