diff --git a/config/default.yaml b/config/default.yaml index abe05dd..4e3936e 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -1,11 +1,3 @@ -notify: - services: - pushsafer: - module: "@uci-utils/notify-pushsafer-plugin" - email: - module: "@uci-utils/notify-email-plugin" - subject: 'A Public IP Address has changed' -cron: 10 # interval in minutes to check to see if public ip has changed, comment to run just once or set to falsey lookup: interval: 10 # seconds to keep trying lookup of changed value timeout: 2 # minutes before giving up - failed set diff --git a/config/dev-debug.yaml b/config/dev-debug.yaml new file mode 100644 index 0000000..d985717 --- /dev/null +++ b/config/dev-debug.yaml @@ -0,0 +1,2 @@ +debug: true +verbose: vv diff --git a/config/dev.yaml b/config/dev.yaml index f2254ed..b0b8487 100644 --- a/config/dev.yaml +++ b/config/dev.yaml @@ -1,5 +1,5 @@ network: 'testing kebler network' domain: 'test.kebler.net' dev: true -announce: true +verbose: v cron: 1 diff --git a/config/production.yaml b/config/production.yaml new file mode 100644 index 0000000..2d67af1 --- /dev/null +++ b/config/production.yaml @@ -0,0 +1,6 @@ +announce: false +pro: true +cron: 30 # interval in minutes to check to see if public ip has changed, comment to run just once or set to falsey +lookup: + interval: 10 # seconds to keep trying lookup of changed value + timeout: 2 # minutes before giving up - failed set diff --git a/index.js b/index.js index b326a95..faaf43f 100644 --- a/index.js +++ b/index.js @@ -7,15 +7,18 @@ import { promisify } from 'util' const lookup = promisify(clookup) import { CronJob } from 'cron' -console.log(JSON.stringify(opts, null, 3)) const rname = process.argv[2] || opts.domain if (!rname) { - console.log('exiting, must supply subdomain record name to check') + console.log('exiting, must supply (sub)domain record name to check') + console.log(JSON.stringify(opts, null, 3)) process.exit() } -const interval = (opts.lookup || {}).interval * 1000 || 10000 -const timeout = (opts.lookup || {}).timeout * 1000 * 60 || 120000 +const verbose = (opts.verbose ||'').length +const interval = (opts.lookup || {}).interval || 10 // seconds +const timeout = (opts.lookup || {}).timeout || 5 // minutes + +if (verbose) console.log(JSON.stringify(opts, null, 3)) let notify=()=>{} // noop const route53 = new Route53(opts.aws ? opts.aws : {profile:'default'}) @@ -25,8 +28,7 @@ const route53 = new Route53(opts.aws ? opts.aws : {profile:'default'}) const notifier = await Notifier.create(opts.notify) notify = notifier.send.bind(notifier) } - - if (opts.cron) { + if (opts.cron && !opts.debug) { console.log('setting up check cron job every', opts.cron,'minutes') const cronstr = `0 */${opts.cron} * * * *` const job = new CronJob(cronstr, checkIP) @@ -34,7 +36,7 @@ const route53 = new Route53(opts.aws ? opts.aws : {profile:'default'}) job.start() } else { console.log('running check a single time') - checkIP + checkIP() } })().catch(err => { @@ -52,24 +54,41 @@ async function checkIP () { curr = ip.join('.') } const saved = await route53.getZoneRecordValue(rname) - if (opts.announce) notify('checking for change in public ip') + if (opts.debug) notify('checking for change in public ip',timestamp()) if (curr !== saved) { - let msg=`For Network:${opts.network}, a new public ip has been detected for ${rname} from ${saved} to ${curr}` - console.log(msg) - notify(msg) - msg=JSON.stringify(await route53.updateZoneRecordValue(rname,curr),null,3) + let msg=`${timestamp()} Network:${opts.network}, a new public ip has been detected for ${rname} from ${saved} to ${curr}` console.log(msg) notify(msg) + const res=JSON.stringify(await route53.updateZoneRecordValue(rname,curr),null,3) + console.log(res) + notify(res) + const fail = setTimeout(()=>{ + clearInterval(check) + msg=`${timestamp()}, ${opts.network}: FATAL the pending change of ip address was not realized in ${timeout*(opts.debug ? 10 : 60)} seconds. Human attention required` + console.log(msg) + notify(msg) + },timeout*1000*(opts.debug ? 10 : 60)) const check = setInterval(async ()=>{ if (curr === (await lookup(rname)).address) { msg=`Update Succeeded, lookup of ${rname} is now ${curr}` clearInterval(check) - } else msg=`Update Pending, will check again in ${(opts.lookup || {}).interval || 10 } seconds` - console.log(msg) - notify(msg) + clearTimeout(fail) + console.log(msg) + notify(msg) + } else { + msg=`Update Pending, will check again in ${interval} seconds` + if (verbose || opts.debug ) { + notify(msg) + console.log(msg) + } + } } - ,(opts.lookup || {}).interval * 1000 || 10000) - // const fail = setTimeout(,timeout) + ,interval*1000) + } else console.log('Public IP has not changed, nothing to do', saved) } + +function timestamp() { + return new Date().toLocaleString().replace('T', ' ').substring(0, 19) +} diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..e59b813 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,6 @@ +{ + "ignoreRoot": [".git"], + "watch": ["config/","node_modules/@uci/","node_modules/@uci-utils/","src/","index.js"], + "ignore": [".git"], + "ext" : "yaml, js" +} diff --git a/package.json b/package.json index 4e2e0c3..75e6925 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "src/route53.js", "scripts": { "start": "node -r esm index.js", - "dev": "NODE_ENV=dev nodemon -r esm index.js" + "dev": "NODE_ENV=dev nodemon -r esm index.js", + "dev:debug": "NODE_APP_INSTANCE=debug npm run dev" }, "author": "David Kebler", "license": "MIT", @@ -22,7 +23,7 @@ "homepage": "https://github.com/uCOMmandIt/uci-utils#readme", "dependencies": { "@uci-utils/notify": "^0.1.5", - "@uci-utils/notify-email-plugin": "^0.1.5", + "@uci-utils/notify-email-plugin": "^0.1.6", "@uci-utils/notify-pushsafer-plugin": "^0.1.2", "@uci-utils/route53": "^0.1.2", "config": "^3.3.2", diff --git a/run b/run new file mode 100755 index 0000000..56cbb81 --- /dev/null +++ b/run @@ -0,0 +1,16 @@ +#!/bin/bash +CONF=$1 +if [ -z "$CONF" ]; +then + echo missing configuration argument, aborting + exit 1 +fi +DIR="$(dirname "$(readlink -f "$0")")" +# echo $DIR +cd $DIR || exit +NODE=$(which node) +export NODE_ENV=production +export NODE_APP_INSTANCE=$CONF +export NODE_ENV=$2 +# echo $NODE +$NODE -r esm $DIR/index.js diff --git a/systemd.service b/systemd/README.md similarity index 100% rename from systemd.service rename to systemd/README.md diff --git a/systemd/install b/systemd/install new file mode 100755 index 0000000..d16e806 --- /dev/null +++ b/systemd/install @@ -0,0 +1,7 @@ +#!/bin/bash +# install template system space +DIR="$(dirname "$(readlink -f "$0")")" +sudo \cp -f $DIR/update-public-ip@.service /etc/systemd/system +echo "update-public-ip@.service copied to systemd" +. start +. persist diff --git a/systemd/log b/systemd/log new file mode 100755 index 0000000..672c473 --- /dev/null +++ b/systemd/log @@ -0,0 +1,4 @@ +#!/bin/bash +CONF=$1 +sudo journalctl --unit=update-public-ip@$CONF.service -n 50 --no-pager +exit 1 diff --git a/systemd/persist b/systemd/persist new file mode 100755 index 0000000..d28aa50 --- /dev/null +++ b/systemd/persist @@ -0,0 +1,3 @@ +#!/bin/bash +CONF=$1 +sudo systemctl enable update-public-ip@$CONF diff --git a/systemd/start b/systemd/start new file mode 100755 index 0000000..0906c2a --- /dev/null +++ b/systemd/start @@ -0,0 +1,11 @@ +#!/bin/bash +CONF=$1 +if [ -z "$CONF" ]; +then + echo missing configuration argument, aborting + exit 1 +fi +sudo systemctl daemon-reload +sudo systemctl stop update-public-ip@$CONF +sudo systemctl start update-public-ip@$CONF +sudo systemctl status update-public-ip@$CONF diff --git a/systemd/status b/systemd/status new file mode 100755 index 0000000..3c62ba6 --- /dev/null +++ b/systemd/status @@ -0,0 +1,8 @@ +#!/bin/bash +CONF=$1 +if [ -z "$CONF" ]; +then + echo missing configuration argument, aborting + exit 1 +fi +sudo systemctl status update-public-ip@$CONF diff --git a/systemd/stop b/systemd/stop new file mode 100755 index 0000000..20c627c --- /dev/null +++ b/systemd/stop @@ -0,0 +1,10 @@ +#!/bin/bash +CONF=$1 +if [ -z "$CONF" ]; +then + echo missing configuration argument, aborting + exit 1 +fi +sudo systemctl stop update-public-ip@$CONF +sudo systemctl status update-public-ip@$CONF +sudo journalctl --unit=update-public-ip@$CONF.service -n 100 --no-pager diff --git a/systemd/update-public-ip@.service b/systemd/update-public-ip@.service new file mode 100644 index 0000000..021e40b --- /dev/null +++ b/systemd/update-public-ip@.service @@ -0,0 +1,17 @@ +[Unit] +Description=Update DNS record at Route53 with LAN public IP +After=network-online.target +[Service] +# if using aws profile files must be in .aws under user home given below. +User=sysadmin +Group=sysadmin +# NODE_ENV and NODE_INSTANCE will determine configuration file. see config directory in repo +Environment=NODE_ENV=production +Environment=NODE_APP_INSTANCE=%i +WorkingDirectory=/opt/update-public-ip +ExecStart=/usr/bin/node -r esm /opt/update-public-ip/index.js +Restart=always +RestartSec=3 + +[Install] +WantedBy=default.target