zgrzyt 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +2 -1
- package/lib/check.js +33 -15
- package/lib/config.js +11 -1
- package/lib/dns.js +32 -6
- package/package.json +3 -3
package/Readme.md
CHANGED
|
@@ -36,6 +36,7 @@ zgrzyt --force
|
|
|
36
36
|
; list of potential servers configured for this domain CNAME record
|
|
37
37
|
servers[]=alfa.example.net
|
|
38
38
|
servers[]=beta.example.net
|
|
39
|
+
ipv6=true ; by default ipv4 is true and ipv6 is false
|
|
39
40
|
|
|
40
41
|
[cloudflare]
|
|
41
42
|
; cloudflare API token
|
|
@@ -51,7 +52,7 @@ method=HEAD ; optional, HTTP method used by zgrzyt
|
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
If `api.domain` is not specified its value is deduced from `api.url`.
|
|
54
|
-
If `api.method` is not specified zgrzyt will send `HEAD` requests to poll the servers. `api.method` can be set to `GET` if needed. `retry` and `
|
|
55
|
+
If `api.method` is not specified zgrzyt will send `HEAD` requests to poll the servers. `api.method` can be set to `GET` if needed. `retry`, `timeout`, `ipv4`, and `ipv6` can be either configured globally or separately for each API.
|
|
55
56
|
|
|
56
57
|
### Multiple checks
|
|
57
58
|
|
package/lib/check.js
CHANGED
|
@@ -23,25 +23,39 @@ export {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
async function checkService(server, api) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const { ipv4 = true, ipv6 = false } = api;
|
|
27
|
+
const addresses = await resolve(server, { ipv4, ipv6 });
|
|
28
|
+
if (debug.enabled) {
|
|
29
|
+
debug('Resolved %s to %s', server, addresses.map(a => a.address).join(', '));
|
|
30
|
+
}
|
|
31
|
+
const oks = await Promise.all(addresses.map(a => checkApi(api, a)));
|
|
32
|
+
// collate results
|
|
33
|
+
const ok = oks.every(ok => ok);
|
|
29
34
|
const health = await updateHealth(api, server, ok);
|
|
30
35
|
return {
|
|
31
|
-
address,
|
|
36
|
+
address: addresses[0].address,
|
|
32
37
|
server,
|
|
33
38
|
health,
|
|
34
39
|
ok
|
|
35
40
|
};
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
async function checkApi(
|
|
43
|
+
async function checkApi(api, { address, family }) {
|
|
44
|
+
|
|
45
|
+
const {
|
|
46
|
+
url,
|
|
47
|
+
method = 'HEAD',
|
|
48
|
+
timeout = 5000,
|
|
49
|
+
headers = {}
|
|
50
|
+
} = api;
|
|
51
|
+
|
|
39
52
|
debug('Checking %s on %s with timeout %dms', url, address, timeout);
|
|
40
53
|
|
|
41
54
|
const { protocol } = new URL(url);
|
|
42
55
|
const { request } = 'https:' === protocol ? https : http;
|
|
43
56
|
|
|
44
57
|
let result;
|
|
58
|
+
let retry = api.retry || 3;
|
|
45
59
|
do {
|
|
46
60
|
result = await makeRequest();
|
|
47
61
|
if (result) {
|
|
@@ -54,15 +68,15 @@ async function checkApi({ url, timeout, method = 'HEAD', headers = {}, retry = 2
|
|
|
54
68
|
|
|
55
69
|
function makeRequest() {
|
|
56
70
|
return new Promise(resolve => request(url, {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
method,
|
|
72
|
+
family,
|
|
73
|
+
lookup,
|
|
74
|
+
timeout,
|
|
75
|
+
headers: {
|
|
76
|
+
'User-Agent': USER_AGENT,
|
|
77
|
+
...headers
|
|
78
|
+
}
|
|
79
|
+
})
|
|
66
80
|
.on('timeout', function () {
|
|
67
81
|
debug('Timeout for %s on %s', url, address);
|
|
68
82
|
this.destroy(new Error('Request timeout.'));
|
|
@@ -82,7 +96,11 @@ async function checkApi({ url, timeout, method = 'HEAD', headers = {}, retry = 2
|
|
|
82
96
|
}
|
|
83
97
|
|
|
84
98
|
function lookup(domain, options, fn) {
|
|
85
|
-
|
|
99
|
+
debug('lookup', domain, address, options);
|
|
100
|
+
if (options.all) {
|
|
101
|
+
address = [address];
|
|
102
|
+
}
|
|
103
|
+
setImmediate(fn, null, address, options.family);
|
|
86
104
|
}
|
|
87
105
|
}
|
|
88
106
|
|
package/lib/config.js
CHANGED
|
@@ -21,10 +21,16 @@ function prepareConfig(config) {
|
|
|
21
21
|
cluster = {}
|
|
22
22
|
} = config;
|
|
23
23
|
|
|
24
|
-
if (!cloudflare
|
|
24
|
+
if (!cloudflare?.token) {
|
|
25
25
|
console.error('Cloudflare API token not configured');
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
+
if (typeof cloudflare.timeout === 'string') {
|
|
29
|
+
cloudflare.timeout = parseInt(cloudflare.timeout);
|
|
30
|
+
}
|
|
31
|
+
if (typeof cloudflare.retry === 'string') {
|
|
32
|
+
cloudflare.retry = parseInt(cloudflare.retry);
|
|
33
|
+
}
|
|
28
34
|
|
|
29
35
|
// collect all APIs
|
|
30
36
|
const apis = [
|
|
@@ -47,6 +53,8 @@ function prepareConfig(config) {
|
|
|
47
53
|
const retry = parseInt(api.retry || config.retry, 10) || 2;
|
|
48
54
|
const force = 'force' in api ? api.force : config.force;
|
|
49
55
|
const repair = parseInt(api.repair || config.repair, 10) || 5;
|
|
56
|
+
const ipv6 = Boolean(api.ipv6 ?? config.ipv6);
|
|
57
|
+
const ipv4 = Boolean(api.ipv4 ?? config.ipv4 ?? true);
|
|
50
58
|
|
|
51
59
|
const { servers } = api.cluster in cluster ? cluster[api.cluster] : config;
|
|
52
60
|
if (!servers || !servers.length) {
|
|
@@ -72,6 +80,8 @@ function prepareConfig(config) {
|
|
|
72
80
|
timeout,
|
|
73
81
|
retry,
|
|
74
82
|
domain,
|
|
83
|
+
ipv4,
|
|
84
|
+
ipv6,
|
|
75
85
|
zone
|
|
76
86
|
},
|
|
77
87
|
client,
|
package/lib/dns.js
CHANGED
|
@@ -1,22 +1,48 @@
|
|
|
1
1
|
import { Resolver } from 'node:dns/promises';
|
|
2
2
|
import makeDebug from 'debug';
|
|
3
3
|
|
|
4
|
-
const resolver = new Resolver();
|
|
5
4
|
const debug = makeDebug('dns:zgrzyt');
|
|
6
5
|
|
|
7
6
|
const cache = Object.create(null);
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
const resolver = new Resolver();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolve domain to IP addresses
|
|
12
|
+
*
|
|
13
|
+
* @param {String} domain
|
|
14
|
+
* @param { ipv4, ipv6 } type of addresses to resolve
|
|
15
|
+
* @returns {Promise<Array<{address: String, family: String}>>} list of resolved addresses
|
|
16
|
+
*/
|
|
17
|
+
export async function resolve(domain, { ipv4, ipv6 }) {
|
|
10
18
|
let p = cache[domain];
|
|
11
19
|
if (!p) {
|
|
12
20
|
cache[domain] = p = doResolve(domain);
|
|
13
21
|
}
|
|
14
|
-
|
|
22
|
+
const addresses = await p;
|
|
23
|
+
return addresses.filter(
|
|
24
|
+
a => (a.family === 4 && ipv4) || (a.family === 6 && ipv6)
|
|
25
|
+
);
|
|
15
26
|
}
|
|
16
27
|
|
|
28
|
+
const RRTYPE_TO_FAMILY = {
|
|
29
|
+
A: 4,
|
|
30
|
+
AAAA: 6
|
|
31
|
+
};
|
|
32
|
+
|
|
17
33
|
async function doResolve(domain) {
|
|
18
34
|
debug('Resolving %s', domain);
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
return
|
|
35
|
+
|
|
36
|
+
const addresses = await Promise.all(['A', 'AAAA'].map(resolveWithFamily));
|
|
37
|
+
return addresses.flat();
|
|
38
|
+
|
|
39
|
+
async function resolveWithFamily(rrtype) {
|
|
40
|
+
try {
|
|
41
|
+
const addresses = await resolver.resolve(domain, rrtype);
|
|
42
|
+
return addresses.map(address => ({ address, family: RRTYPE_TO_FAMILY[rrtype] }));
|
|
43
|
+
} catch (error) {
|
|
44
|
+
debug('Failed to resolve %s with type %s: %s', domain, rrtype, error);
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
22
48
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zgrzyt",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Poor man's load balancing DNS switcher.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": {
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"debug": "~4",
|
|
30
|
-
"got": "~
|
|
31
|
-
"parse-domain": "~
|
|
30
|
+
"got": "~14",
|
|
31
|
+
"parse-domain": "~8",
|
|
32
32
|
"rc": "^1.2.8",
|
|
33
33
|
"sprintfjs": "^1.2.16"
|
|
34
34
|
},
|