Skip to content

Commit ec9eb0d

Browse files
committed
Refactor and integrate ddns resolution with nginx module
Refactored ddns resolver so that no patching is done. nginx.js will automatically resolve ddns addresses if needed. Added dedicated logger scope for ddns resovler.
1 parent 5586709 commit ec9eb0d

File tree

5 files changed

+80
-82
lines changed

5 files changed

+80
-82
lines changed

backend/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ async function appStart () {
99
const apiValidator = require('./lib/validator/api');
1010
const internalCertificate = require('./internal/certificate');
1111
const internalIpRanges = require('./internal/ip_ranges');
12-
const ddnsResolver = require('./lib/ddns_resolver/ddns_resolver');
12+
const ddnsResolver = require('./lib/ddns_resolver');
1313

1414
return migrate.latest()
1515
.then(setup)

backend/internal/nginx.js

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const logger = require('../logger').nginx;
44
const config = require('../lib/config');
55
const utils = require('../lib/utils');
66
const error = require('../lib/error');
7+
const ddnsResolver = require('../lib/ddns_resolver');
78

89
const internalNginx = {
910

@@ -131,6 +132,31 @@ const internalNginx = {
131132
return '/data/nginx/' + internalNginx.getFileFriendlyHostType(host_type) + '/' + host_id + '.conf';
132133
},
133134

135+
/**
136+
* Resolves any ddns addresses that need to be resolved for clients in the host's access list.
137+
* @param {Object} host
138+
* @returns {Promise}
139+
*/
140+
resolveDDNSAddresses: (host) => {
141+
const promises = [];
142+
if (typeof host.access_list !== 'undefined' && typeof host.access_list.clients !== 'undefined') {
143+
for (const client of host.access_list.clients) {
144+
if (ddnsResolver.requiresResolution(client.address)) {
145+
const p = ddnsResolver.resolveAddress(client.address)
146+
.then((resolvedIP) => {
147+
client.address = `${resolvedIP}; # ${client.address}`;
148+
return Promise.resolve();
149+
});
150+
promises.push(p);
151+
}
152+
}
153+
}
154+
if (promises.length) {
155+
return Promise.all(promises);
156+
}
157+
return Promise.resolve();
158+
},
159+
134160
/**
135161
* Generates custom locations
136162
* @param {Object} host
@@ -201,6 +227,12 @@ const internalNginx = {
201227
return;
202228
}
203229

230+
// Resolve ddns addresses if needed
231+
let resolverPromise = Promise.resolve();
232+
if (host_type === 'proxy_host') {
233+
resolverPromise = internalNginx.resolveDDNSAddresses(host);
234+
}
235+
204236
let locationsPromise;
205237
let origLocations;
206238

@@ -215,8 +247,10 @@ const internalNginx = {
215247
if (host.locations) {
216248
//logger.info ('host.locations = ' + JSON.stringify(host.locations, null, 2));
217249
origLocations = [].concat(host.locations);
218-
locationsPromise = internalNginx.renderLocations(host).then((renderedLocations) => {
219-
host.locations = renderedLocations;
250+
locationsPromise = resolverPromise.then(() => {
251+
return internalNginx.renderLocations(host).then((renderedLocations) => {
252+
host.locations = renderedLocations;
253+
});
220254
});
221255

222256
// Allow someone who is using / custom location path to use it, and skip the default / location
@@ -227,7 +261,7 @@ const internalNginx = {
227261
});
228262

229263
} else {
230-
locationsPromise = Promise.resolve();
264+
locationsPromise = resolverPromise;
231265
}
232266

233267
// Set the IPv6 setting for the host

backend/lib/ddns_resolver/ddns_resolver.js renamed to backend/lib/ddns_resolver.js

Lines changed: 8 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,7 @@
1-
const error = require('../error')
2-
const logger = require('../../logger').global;
3-
const internalAccessList = require('../../internal/access-list');
4-
const internalNginx = require('../../internal/nginx');
5-
const spawn = require('child_process').spawn;
6-
7-
const cmdHelper = {
8-
/**
9-
* Run the given command. Safer than using exec since args are passed as a list instead of in shell mode as a single string.
10-
* @param {string} cmd The command to run
11-
* @param {string} args The args to pass to the command
12-
* @returns Promise that resolves to stdout or an object with error code and stderr if there's an error
13-
*/
14-
run: (cmd, args) => {
15-
return new Promise((resolve, reject) => {
16-
let stdout = '';
17-
let stderr = '';
18-
const proc = spawn(cmd, args);
19-
proc.stdout.on('data', (data) => {
20-
stdout += data;
21-
});
22-
proc.stderr.on('data', (data) => {
23-
stderr += data;
24-
});
25-
26-
proc.on('close', (exitCode) => {
27-
if (!exitCode) {
28-
resolve(stdout.trim());
29-
} else {
30-
reject({
31-
exitCode: exitCode,
32-
stderr: stderr
33-
});
34-
}
35-
});
36-
});
37-
}
38-
};
1+
const error = require('./error')
2+
const logger = require('../logger').ddns;
3+
const internalAccessList = require('../internal/access-list');
4+
const utils = require('./utils');
395

406
const ddnsResolver = {
417
/**
@@ -99,15 +65,13 @@ const ddnsResolver = {
9965
/** Private **/
10066
// Properties
10167
_initialized: false,
102-
_updateIntervalMs: 1000 * 60 * 60, // 1 hr default (overriden with $DDNS_UPDATE_INTERVAL env var)
68+
_updateIntervalMs: 60 * 60 * 1000, // 1 hr default (overriden with $DDNS_UPDATE_INTERVAL env var)
10369
/**
10470
* cache mapping host to (ip address, last updated time)
10571
*/
10672
_cache: new Map(),
10773
_interval: null, // reference to created interval id
10874
_processingDDNSUpdate: false,
109-
110-
_originalGenerateConfig: null, // Used for patching config generation to resolve hosts
11175

11276
// Methods
11377

@@ -126,16 +90,6 @@ const ddnsResolver = {
12690
logger.warn(`[DDNS] invalid value for update interval: '${process.env.DDNS_UPDATE_INTERVAL}'`);
12791
}
12892
}
129-
130-
// Patch nginx config generation if needed (check env var)
131-
if (typeof process.env.DDNS_UPDATE_PATCH !== 'undefined') {
132-
const enabled = Number(process.env.DDNS_UPDATE_PATCH.toLowerCase());
133-
if (!isNaN(enabled) && enabled) {
134-
logger.info('Patching nginx config generation');
135-
ddnsResolver._originalGenerateConfig = internalNginx.generateConfig;
136-
internalNginx.generateConfig = ddnsResolver._patchedGenerateConfig;
137-
}
138-
}
13993
ddnsResolver._initialized = true;
14094
},
14195

@@ -146,7 +100,7 @@ const ddnsResolver = {
146100
*/
147101
_queryHost: (host) => {
148102
logger.info('Looking up IP for ', host);
149-
return cmdHelper.run('getent', ['hosts', host])
103+
return utils.execSafe('getent', ['hosts', host])
150104
.then((result) => {
151105
if (result.length < 8) {
152106
logger.error('IP lookup returned invalid output: ', result);
@@ -162,35 +116,12 @@ const ddnsResolver = {
162116
});
163117
},
164118

165-
_patchedGenerateConfig: (host_type, host) => {
166-
const promises = [];
167-
if (host_type === 'proxy_host') {
168-
if (typeof host.access_list !== 'undefined' && typeof host.access_list.clients !== 'undefined') {
169-
for (const client of host.access_list.clients) {
170-
if (ddnsResolver.requiresResolution(client.address)) {
171-
const p = ddnsResolver.resolveAddress(client.address)
172-
.then((resolvedIP) => {
173-
client.address = `${resolvedIP}; # ${client.address}`;
174-
return Promise.resolve();
175-
});
176-
promises.push(p);
177-
}
178-
}
179-
}
180-
}
181-
if (promises.length) {
182-
return Promise.all(promises)
183-
.then(() => {
184-
return ddnsResolver._originalGenerateConfig(host_type, host);
185-
});
186-
}
187-
return ddnsResolver._originalGenerateConfig(host_type, host);
188-
},
189-
190119
/**
191120
* Triggered by a timer, will check for and update ddns hosts in access list clients
192121
*/
193122
_checkForDDNSUpdates: () => {
123+
const internalNginx = require('../internal/nginx'); // Prevent circular import
124+
194125
logger.info('Checking for DDNS updates...');
195126
if (!ddnsResolver._processingDDNSUpdate) {
196127
ddnsResolver._processingDDNSUpdate = true;

backend/lib/utils.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const execFile = require('child_process').execFile;
44
const { Liquid } = require('liquidjs');
55
const logger = require('../logger').global;
66
const error = require('./error');
7+
const spawn = require('child_process').spawn;
78

89
module.exports = {
910

@@ -26,6 +27,37 @@ module.exports = {
2627
return stdout;
2728
},
2829

30+
/**
31+
* Run the given command. Safer than using exec since args are passed as a list instead of in shell mode as a single string.
32+
* @param {string} cmd The command to run
33+
* @param {string} args The args to pass to the command
34+
* @returns Promise that resolves to stdout or an object with error code and stderr if there's an error
35+
*/
36+
execSafe: (cmd, args) => {
37+
return new Promise((resolve, reject) => {
38+
let stdout = '';
39+
let stderr = '';
40+
const proc = spawn(cmd, args);
41+
proc.stdout.on('data', (data) => {
42+
stdout += data;
43+
});
44+
proc.stderr.on('data', (data) => {
45+
stderr += data;
46+
});
47+
48+
proc.on('close', (exitCode) => {
49+
if (!exitCode) {
50+
resolve(stdout.trim());
51+
} else {
52+
reject({
53+
exitCode: exitCode,
54+
stderr: stderr
55+
});
56+
}
57+
});
58+
});
59+
},
60+
2961
/**
3062
* @param {String} cmd
3163
* @param {Array} args

backend/logger.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ module.exports = {
1010
certbot: new Signale({scope: 'Certbot '}),
1111
import: new Signale({scope: 'Importer '}),
1212
setup: new Signale({scope: 'Setup '}),
13-
ip_ranges: new Signale({scope: 'IP Ranges'})
13+
ip_ranges: new Signale({scope: 'IP Ranges'}),
14+
ddns: new Signale({scope: 'DDNS '})
1415
};

0 commit comments

Comments
 (0)