| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Manage = module.exports; | 
					
						
							|  |  |  | var sfs = require('safe-replace').create({ tmp: 'tmp', bak: 'bak' }); | 
					
						
							|  |  |  | var promisify = require('util').promisify; | 
					
						
							|  |  |  | var fs = require('fs'); | 
					
						
							|  |  |  | var readFile = promisify(fs.readFile); | 
					
						
							|  |  |  | var statFile = promisify(fs.stat); | 
					
						
							|  |  |  | var chmodFile = promisify(fs.chmod); | 
					
						
							|  |  |  | var homedir = require('os').homedir(); | 
					
						
							|  |  |  | var path = require('path'); | 
					
						
							|  |  |  | var mkdirp = promisify(require('@root/mkdirp')); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Manage.create = function(opts) { | 
					
						
							|  |  |  | 	if (!opts) { | 
					
						
							|  |  |  | 		opts = {}; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (!opts.configFile) { | 
					
						
							|  |  |  | 		opts.configFile = '~/.config/greenlock/manager.json'; | 
					
						
							| 
									
										
										
										
											2019-10-28 02:23:23 -06:00
										 |  |  | 		console.info('Greenlock Manager Config File: ' + opts.configFile); | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	opts.configFile = opts.configFile.replace('~/', homedir + '/'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var manage = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-28 02:23:23 -06:00
										 |  |  | 	manage._txPromise = Promise.resolve(); | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-29 05:08:41 +00:00
										 |  |  | 	manage.defaults = manage.config = function(conf) { | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 		// get / set default site settings such as
 | 
					
						
							|  |  |  | 		// subscriberEmail, store, challenges, renewOffset, renewStagger
 | 
					
						
							|  |  |  | 		return Manage._getLatest(manage, opts).then(function(config) { | 
					
						
							|  |  |  | 			if (!conf) { | 
					
						
							|  |  |  | 				conf = JSON.parse(JSON.stringify(config)); | 
					
						
							|  |  |  | 				delete conf.sites; | 
					
						
							|  |  |  | 				return conf; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// TODO set initial sites
 | 
					
						
							|  |  |  | 			if (conf.sites) { | 
					
						
							|  |  |  | 				throw new Error('cannot set sites as global config'); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// TODO whitelist rather than blacklist?
 | 
					
						
							|  |  |  | 			if ( | 
					
						
							|  |  |  | 				[ | 
					
						
							|  |  |  | 					'subject', | 
					
						
							|  |  |  | 					'altnames', | 
					
						
							|  |  |  | 					'lastAttemptAt', | 
					
						
							|  |  |  | 					'expiresAt', | 
					
						
							|  |  |  | 					'issuedAt', | 
					
						
							|  |  |  | 					'renewAt' | 
					
						
							|  |  |  | 				].some(function(k) { | 
					
						
							|  |  |  | 					if (k in conf) { | 
					
						
							|  |  |  | 						throw new Error( | 
					
						
							|  |  |  | 							'`' + k + '` not allowed as a default setting' | 
					
						
							|  |  |  | 						); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			) { | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Object.keys(conf).forEach(function(k) { | 
					
						
							|  |  |  | 				if (-1 !== ['sites', 'module', 'manager'].indexOf(k)) { | 
					
						
							|  |  |  | 					return; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if ('undefined' === typeof k) { | 
					
						
							|  |  |  | 					throw new Error( | 
					
						
							|  |  |  | 						"'" + | 
					
						
							|  |  |  | 							k + | 
					
						
							|  |  |  | 							"' should be set to a value, or `null`, but not left `undefined`" | 
					
						
							|  |  |  | 					); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if (null === k) { | 
					
						
							|  |  |  | 					delete config[k]; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				config[k] = conf[k]; | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-28 02:23:23 -06:00
										 |  |  | 			return manage._save(config); | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 		}); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	manage.add = function(args) { | 
					
						
							|  |  |  | 		manage._txPromise = manage._txPromise.then(function() { | 
					
						
							|  |  |  | 			// if the fs has changed since we last wrote, get the lastest from disk
 | 
					
						
							|  |  |  | 			return Manage._getLatest(manage, opts).then(function(config) { | 
					
						
							|  |  |  | 				// TODO move to Greenlock.add
 | 
					
						
							| 
									
										
										
										
											2019-10-28 02:23:23 -06:00
										 |  |  | 				var subscriberEmail = args.subscriberEmail; | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 				var subject = args.subject || args.domain; | 
					
						
							|  |  |  | 				var primary = subject; | 
					
						
							| 
									
										
										
										
											2019-10-29 05:08:41 +00:00
										 |  |  | 				var altnames = | 
					
						
							|  |  |  | 					args.servernames || args.altnames || args.domains; | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 				if ('string' !== typeof primary) { | 
					
						
							|  |  |  | 					if (!Array.isArray(altnames) || !altnames.length) { | 
					
						
							|  |  |  | 						throw new Error('there needs to be a subject'); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					primary = altnames.slice(0).sort()[0]; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if (!Array.isArray(altnames) || !altnames.length) { | 
					
						
							|  |  |  | 					altnames = [primary]; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				primary = primary.toLowerCase(); | 
					
						
							|  |  |  | 				altnames = altnames.map(function(name) { | 
					
						
							|  |  |  | 					return name.toLowerCase(); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if (!config.sites) { | 
					
						
							|  |  |  | 					config.sites = {}; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				var site = config.sites[primary]; | 
					
						
							|  |  |  | 				if (!site) { | 
					
						
							|  |  |  | 					site = config.sites[primary] = { altnames: [] }; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// The goal is to make this decently easy to manage by hand without mistakes
 | 
					
						
							|  |  |  | 				// but also reasonably easy to error check and correct
 | 
					
						
							|  |  |  | 				// and to make deterministic auto-corrections
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// TODO added, removed, moved (duplicate), changed
 | 
					
						
							| 
									
										
										
										
											2019-10-28 02:23:23 -06:00
										 |  |  | 				if (subscriberEmail) { | 
					
						
							|  |  |  | 					site.subscriberEmail = subscriberEmail; | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 				site.subject = subject; | 
					
						
							|  |  |  | 				site.altnames = altnames; | 
					
						
							| 
									
										
										
										
											2019-10-28 02:23:23 -06:00
										 |  |  | 				if (!site.issuedAt) { | 
					
						
							|  |  |  | 					site.issuedAt = 0; | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 				site.expiresAt = site.expiresAt || 0; | 
					
						
							|  |  |  | 				site.lastAttemptAt = site.lastAttemptAt || 0; | 
					
						
							|  |  |  | 				// re-add if this was deleted
 | 
					
						
							|  |  |  | 				site.deletedAt = 0; | 
					
						
							|  |  |  | 				if ( | 
					
						
							|  |  |  | 					site.altnames | 
					
						
							|  |  |  | 						.slice(0) | 
					
						
							|  |  |  | 						.sort() | 
					
						
							|  |  |  | 						.join() !== | 
					
						
							|  |  |  | 					altnames | 
					
						
							|  |  |  | 						.slice(0) | 
					
						
							|  |  |  | 						.sort() | 
					
						
							|  |  |  | 						.join() | 
					
						
							|  |  |  | 				) { | 
					
						
							|  |  |  | 					site.expiresAt = 0; | 
					
						
							|  |  |  | 					site.issuedAt = 0; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// These should usually be empty, for most situations
 | 
					
						
							| 
									
										
										
										
											2019-10-28 02:23:23 -06:00
										 |  |  | 				if (args.customerEmail) { | 
					
						
							|  |  |  | 					site.customerEmail = args.customerEmail; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if (args.challenges) { | 
					
						
							|  |  |  | 					site.challenges = args.challenges; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if (args.store) { | 
					
						
							|  |  |  | 					site.store = args.store; | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				return manage._save(config).then(function() { | 
					
						
							|  |  |  | 					return JSON.parse(JSON.stringify(site)); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 		return manage._txPromise; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	manage.find = function(args) { | 
					
						
							| 
									
										
										
										
											2019-10-29 05:08:41 +00:00
										 |  |  | 		var some = _find(args); | 
					
						
							|  |  |  | 		if (!opts.find) { | 
					
						
							|  |  |  | 			return some; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// TODO function to always add
 | 
					
						
							|  |  |  | 		throw new Error('TODO: use the given find'); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	function _find(args) { | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 		return Manage._getLatest(manage, opts).then(function(config) { | 
					
						
							|  |  |  | 			// i.e. find certs more than 30 days old
 | 
					
						
							|  |  |  | 			//args.issuedBefore = Date.now() - 30 * 24 * 60 * 60 * 1000;
 | 
					
						
							|  |  |  | 			// i.e. find certs more that will expire in less than 45 days
 | 
					
						
							|  |  |  | 			//args.expiresBefore = Date.now() + 45 * 24 * 60 * 60 * 1000;
 | 
					
						
							|  |  |  | 			var issuedBefore = args.issuedBefore || 0; | 
					
						
							| 
									
										
										
										
											2019-10-28 02:23:23 -06:00
										 |  |  | 			var expiresBefore = args.expiresBefore || Infinity; //Date.now() + 21 * 24 * 60 * 60 * 1000;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var altnames = (args.altnames || args.domains || []).slice(0); | 
					
						
							|  |  |  | 			if (args.servername && !altnames.includes(args.servername)) { | 
					
						
							|  |  |  | 				altnames.push(args.servername); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (args.subject && !altnames.includes(args.subject)) { | 
					
						
							|  |  |  | 				altnames.push(args.subject); | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// TODO match ANY domain on any cert
 | 
					
						
							| 
									
										
										
										
											2019-10-28 02:23:23 -06:00
										 |  |  | 			var sites = Object.keys(config.sites || {}) | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 				.filter(function(sub) { | 
					
						
							|  |  |  | 					var site = config.sites[sub]; | 
					
						
							|  |  |  | 					if ( | 
					
						
							|  |  |  | 						!site.deletedAt || | 
					
						
							|  |  |  | 						site.expiresAt < expiresBefore || | 
					
						
							|  |  |  | 						site.issuedAt < issuedBefore | 
					
						
							|  |  |  | 					) { | 
					
						
							| 
									
										
										
										
											2019-10-28 02:23:23 -06:00
										 |  |  | 						return (site.altnames || []).some(function(name) { | 
					
						
							|  |  |  | 							return altnames.includes(name); | 
					
						
							|  |  |  | 						}); | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 					} | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 				.map(function(name) { | 
					
						
							|  |  |  | 					var site = config.sites[name]; | 
					
						
							|  |  |  | 					return { | 
					
						
							|  |  |  | 						subject: site.subject, | 
					
						
							|  |  |  | 						altnames: site.altnames, | 
					
						
							|  |  |  | 						issuedAt: site.issuedAt, | 
					
						
							|  |  |  | 						expiresAt: site.expiresAt, | 
					
						
							|  |  |  | 						renewOffset: site.renewOffset, | 
					
						
							|  |  |  | 						renewStagger: site.renewStagger, | 
					
						
							|  |  |  | 						renewAt: site.renewAt, | 
					
						
							|  |  |  | 						subscriberEmail: site.subscriberEmail, | 
					
						
							|  |  |  | 						customerEmail: site.customerEmail, | 
					
						
							|  |  |  | 						challenges: site.challenges, | 
					
						
							|  |  |  | 						store: site.store | 
					
						
							|  |  |  | 					}; | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return sites; | 
					
						
							|  |  |  | 		}); | 
					
						
							| 
									
										
										
										
											2019-10-29 05:08:41 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-29 05:08:41 +00:00
										 |  |  | 	manage.notify = opts.notify || _notify; | 
					
						
							|  |  |  | 	function _notify(ev, args) { | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 		if (!args) { | 
					
						
							|  |  |  | 			args = ev; | 
					
						
							|  |  |  | 			ev = args.event; | 
					
						
							|  |  |  | 			delete args.event; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// TODO define message types
 | 
					
						
							| 
									
										
										
										
											2019-10-28 02:23:23 -06:00
										 |  |  | 		if (!manage._notify_notice) { | 
					
						
							|  |  |  | 			console.info( | 
					
						
							|  |  |  | 				'set greenlockOptions.notify to override the default logger' | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 			manage._notify_notice = true; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		switch (ev) { | 
					
						
							|  |  |  | 			case 'error': | 
					
						
							|  |  |  | 			/* falls through */ | 
					
						
							|  |  |  | 			case 'warning': | 
					
						
							|  |  |  | 				console.error( | 
					
						
							|  |  |  | 					'Error%s:', | 
					
						
							| 
									
										
										
										
											2019-10-29 05:08:41 +00:00
										 |  |  | 					(' ' + (args.context || '')).trimRight() | 
					
						
							| 
									
										
										
										
											2019-10-28 02:23:23 -06:00
										 |  |  | 				); | 
					
						
							|  |  |  | 				console.error(args.message); | 
					
						
							|  |  |  | 				if (args.description) { | 
					
						
							|  |  |  | 					console.error(args.description); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if (args.code) { | 
					
						
							|  |  |  | 					console.error('code:', args.code); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			default: | 
					
						
							|  |  |  | 				if (/status/.test(ev)) { | 
					
						
							|  |  |  | 					console.info( | 
					
						
							|  |  |  | 						ev, | 
					
						
							|  |  |  | 						args.altname || args.subject || '', | 
					
						
							|  |  |  | 						args.status || '' | 
					
						
							|  |  |  | 					); | 
					
						
							|  |  |  | 					if (!args.status) { | 
					
						
							| 
									
										
										
										
											2019-10-29 05:08:41 +00:00
										 |  |  | 						console.info(args); | 
					
						
							| 
									
										
										
										
											2019-10-28 02:23:23 -06:00
										 |  |  | 					} | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				console.info( | 
					
						
							|  |  |  | 					ev, | 
					
						
							|  |  |  | 					'(more info available: ' + Object.keys(args).join(' ') + ')' | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-10-29 05:08:41 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	manage.update = function(args) { | 
					
						
							|  |  |  | 		manage._txPromise = manage.txPromise.then(function() { | 
					
						
							|  |  |  | 			return Manage._getLatest(manage, opts).then(function(config) { | 
					
						
							|  |  |  | 				var site = config.sites[args.subject]; | 
					
						
							|  |  |  | 				site.issuedAt = args.issuedAt; | 
					
						
							|  |  |  | 				site.expiresAt = args.expiresAt; | 
					
						
							|  |  |  | 				site.renewAt = args.renewAt; | 
					
						
							|  |  |  | 				// foo
 | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 		return manage._txPromise; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-29 05:08:41 +00:00
										 |  |  | 	manage.remove = function(args) { | 
					
						
							|  |  |  | 		if (!args.subject) { | 
					
						
							|  |  |  | 			throw new Error('should have a subject for sites to remove'); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		manage._txPromise = manage.txPromise.then(function() { | 
					
						
							|  |  |  | 			return Manage._getLatest(manage, opts).then(function(config) { | 
					
						
							|  |  |  | 				var site = config.sites[args.subject]; | 
					
						
							|  |  |  | 				if (!site) { | 
					
						
							|  |  |  | 					return {}; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				site.deletedAt = Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				return JSON.parse(JSON.stringify(site)); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 		return manage._txPromise; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	manage._lastStat = { | 
					
						
							|  |  |  | 		size: 0, | 
					
						
							|  |  |  | 		mtimeMs: 0 | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	manage._config = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	manage._save = function(config) { | 
					
						
							|  |  |  | 		return mkdirp(path.dirname(opts.configFile)).then(function() { | 
					
						
							|  |  |  | 			return sfs | 
					
						
							|  |  |  | 				.writeFileAsync( | 
					
						
							|  |  |  | 					opts.configFile, | 
					
						
							|  |  |  | 					// pretty-print the config file
 | 
					
						
							|  |  |  | 					JSON.stringify(config, null, 2), | 
					
						
							|  |  |  | 					'utf8' | 
					
						
							|  |  |  | 				) | 
					
						
							|  |  |  | 				.then(function() { | 
					
						
							|  |  |  | 					// this file may contain secrets, so keep it safe
 | 
					
						
							|  |  |  | 					return chmodFile(opts.configFile, parseInt('0600', 8)) | 
					
						
							|  |  |  | 						.catch(function() { | 
					
						
							|  |  |  | 							/*ignore for Windows */ | 
					
						
							|  |  |  | 						}) | 
					
						
							|  |  |  | 						.then(function() { | 
					
						
							|  |  |  | 							return statFile(opts.configFile).then(function( | 
					
						
							|  |  |  | 								stat | 
					
						
							|  |  |  | 							) { | 
					
						
							|  |  |  | 								manage._lastStat.size = stat.size; | 
					
						
							|  |  |  | 								manage._lastStat.mtimeMs = stat.mtimeMs; | 
					
						
							|  |  |  | 							}); | 
					
						
							|  |  |  | 						}); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-21 18:15:34 -06:00
										 |  |  | 	return manage; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Manage._getLatest = function(mng, opts) { | 
					
						
							|  |  |  | 	return statFile(opts.configFile) | 
					
						
							|  |  |  | 		.catch(function(err) { | 
					
						
							|  |  |  | 			if ('ENOENT' === err.code) { | 
					
						
							|  |  |  | 				return { | 
					
						
							|  |  |  | 					size: 0, | 
					
						
							|  |  |  | 					mtimeMs: 0 | 
					
						
							|  |  |  | 				}; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			err.context = 'manager_read'; | 
					
						
							|  |  |  | 			throw err; | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		.then(function(stat) { | 
					
						
							|  |  |  | 			if ( | 
					
						
							|  |  |  | 				stat.size === mng._lastStat.size && | 
					
						
							|  |  |  | 				stat.mtimeMs === mng._lastStat.mtimeMs | 
					
						
							|  |  |  | 			) { | 
					
						
							|  |  |  | 				return mng._config; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return readFile(opts.configFile, 'utf8').then(function(data) { | 
					
						
							|  |  |  | 				mng._lastStat = stat; | 
					
						
							|  |  |  | 				mng._config = JSON.parse(data); | 
					
						
							|  |  |  | 				return mng._config; | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | }; |