From 7a0ac68038328273c9a21258abd44bcb48a9257c Mon Sep 17 00:00:00 2001 From: Yuri Arabadji Date: Thu, 7 May 2020 22:29:33 +0300 Subject: [PATCH 1/2] override watch behavior by ENV vars HARAKA_CONFIG_CUSTOM_DIR sets custom config dir HARAKA_CONFIG_SLOWWATCH selects slow watch behavior --- config.js | 2 +- configfile.js | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/config.js b/config.js index 41d1dbd..95f08dd 100644 --- a/config.js +++ b/config.js @@ -116,7 +116,7 @@ class Config { } module_config (defaults_path, overrides_path) { - const cfg = new Config(path.join(defaults_path, 'config'), true); + const cfg = new Config(process.env.HARAKA_CONFIG_CUSTOM_DIR || path.join(defaults_path, 'config'), true); if (overrides_path) { cfg.overrides_path = path.join(overrides_path, 'config'); } diff --git a/configfile.js b/configfile.js index 6b2f347..c0c0707 100644 --- a/configfile.js +++ b/configfile.js @@ -35,9 +35,17 @@ class cfreader { is_truth: /^(?:true|yes|ok|enabled|on|1)$/i, is_array: /(.+)\[\]$/, } + + let watch_interval = process.env.HARAKA_CONFIG_SLOWWATCH + if (watch_interval) console.error(`configfile.js: Requested to use SLOW config watchers with POLL interval ${watch_interval} secs`) } get_path_to_config_dir () { + if (process.env.HARAKA_CONFIG_CUSTOM_DIR) { + this.config_path = process.env.HARAKA_CONFIG_CUSTOM_DIR + return + } + if (process.env.HARAKA) { // console.log(`process.env.HARAKA: ${process.env.HARAKA}`); this.config_path = path.join(process.env.HARAKA, 'config'); @@ -103,6 +111,25 @@ class cfreader { } } + on_slowwatch_event (name, type, options, cb) { + return (fstat_cur, fstat_prev) => { + // skipping initial missing file watch reload + if (fstat_prev.mtimeMs === 0 && fstat_cur.mtimeMs === 0) return + + console.log(`WATCH EVENT: ${fstat_prev.mtime} -> ${fstat_cur.mtime}, will trigger reload in 5secs`) + + if (this._sedation_timers[name]) { + clearTimeout(this._sedation_timers[name]); + } + this._sedation_timers[name] = setTimeout(() => { + console.log(`Reloading file: ${name}`); + this.load_config(name, type, options); + delete this._sedation_timers[name]; + if (typeof cb === 'function') cb(); + }, 5 * 1000); + } + } + watch_dir () { // NOTE: Has OS platform limitations: // https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener @@ -155,6 +182,14 @@ class cfreader { } } + slowwatch_file (name, type, cb, options) { + if (this._watchers[name] || (options && options.no_watch)) return; + + this._watchers[name] = fs.watchFile(name, + { persistent: false, interval: process.env.HARAKA_CONFIG_SLOWWATCH * 1000 }, + this.on_slowwatch_event(name, type, options, cb)) + } + get_cache_key (name, options) { // Ignore options etc. if this is an overriden value @@ -198,6 +233,12 @@ class cfreader { const result = this.load_config(name, type, options); if (!this.watch_files) return result; + if (process.env.HARAKA_CONFIG_SLOWWATCH) { + this.slowwatch_file(name, type, cb, options) + + return result + } + // We can watch the directory on these platforms which // allows us to notice when files are newly created. switch (process.platform) { @@ -284,10 +325,12 @@ class cfreader { } let cfrType = this.get_filetype_reader(type); + const cache_key = this.get_cache_key(name, options); if (!fs.existsSync(name)) { if (!/\.h?json$/.test(name)) { - return cfrType.empty(options, type); + // console.error(`ENOENT ${name}`) + return this._config_cache[cache_key] = cfrType.empty(options, type) } const yaml_name = name.replace(/\.h?json$/, '.yaml'); @@ -299,7 +342,6 @@ class cfreader { cfrType = this.get_filetype_reader(type); } - const cache_key = this.get_cache_key(name, options); try { switch (type) { case 'ini': From 75f06f1b7070335c5938e512ad97d3157663ce54 Mon Sep 17 00:00:00 2001 From: Yuri Arabadji Date: Fri, 8 May 2020 17:53:27 +0300 Subject: [PATCH 2/2] docs --- Changes.md | 2 ++ README.md | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/Changes.md b/Changes.md index 21458d5..4a81297 100644 --- a/Changes.md +++ b/Changes.md @@ -1,6 +1,8 @@ ## 1.N.NN - 20YY-MM-DD +- configfile: allow selecting slow watch behavior via env var + ## 1.0.19 - 2019-MM-DD - configfile: convert to es6 class diff --git a/README.md b/README.md index 47eb135..9842a31 100644 --- a/README.md +++ b/README.md @@ -329,3 +329,8 @@ in a plugins register() function are read *before* Haraka drops privileges. Be sure that Haraka's user/group has permission to read these files else Haraka will be unable to update them after changes. +### Network filesystems + +Auto-reload is not supported on an FS that doesn't implement proper file changes notify mechanism. + +Imagine you've got several Haraka nodes that keep their config dir on a network FS. You have updated a configuration file on the network server and would like to let all Haraka nodes know your file has been updated. For that, you have to login to all your Haraka nodes and `touch` the relevant config file manually. Obviously, this really resembles plain old `scp` copy, except that you only touch files instead of copying data. It almost makes no point of using a network FS then. So, for those people who desire all this drama to be irrelevant, we've made a special option available that allows to poll files for changes periodically instead of relying on notify mechanism. It is very slow and triggers traffic on network FS during the poll, but it's the only way around. Set `HARAKA_CONFIG_SLOWWATCH` env var to desired poll interval (> 10 recommended) and you'll have your config files reloaded (in > 15 secs) after the file update.