apertodns 2.0.1 → 2.0.3
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 +89 -13
- package/index.js +148 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/apertodns)
|
|
4
4
|
[](https://www.npmjs.com/package/apertodns)
|
|
5
|
-
[](https://github.com/
|
|
5
|
+
[](https://github.com/apertodns/apertodns/blob/main/LICENSE)
|
|
6
6
|
[](https://nodejs.org)
|
|
7
7
|
|
|
8
8
|
**Dynamic DNS management from your terminal.** Manage domains, tokens, API keys, and DNS updates with style.
|
|
@@ -11,16 +11,19 @@ ApertoDNS is a free Dynamic DNS service that lets you point a subdomain to your
|
|
|
11
11
|
|
|
12
12
|
## Why ApertoDNS?
|
|
13
13
|
|
|
14
|
-
| Feature | ApertoDNS | No-IP | DuckDNS |
|
|
15
|
-
|
|
16
|
-
| Free
|
|
17
|
-
|
|
|
14
|
+
| Feature | ApertoDNS | Dyn (Oracle) | No-IP | DuckDNS |
|
|
15
|
+
|---------|-----------|--------------|-------|---------|
|
|
16
|
+
| Free plan | Yes | No ($55/yr) | Yes | Yes |
|
|
17
|
+
| Free subdomains | Unlimited | 0 | 1 | 5 |
|
|
18
|
+
| API Keys with scopes | Yes | No | No | No |
|
|
18
19
|
| CLI tool | Yes | No | No | No |
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
20
|
+
| Docker images | Yes | No | No | No |
|
|
21
|
+
| IPv6 support | Yes | Paid | Paid | Yes |
|
|
22
|
+
| No forced renewal | Yes | N/A | 30 days | Yes |
|
|
23
|
+
| DynDNS2 compatible | Yes | Yes | Yes | No |
|
|
22
24
|
| Webhooks | Yes | No | No | No |
|
|
23
25
|
| Team sharing | Yes | No | No | No |
|
|
26
|
+
| Open Source | Yes | No | No | No |
|
|
24
27
|
|
|
25
28
|
## Features
|
|
26
29
|
|
|
@@ -33,6 +36,7 @@ ApertoDNS is a free Dynamic DNS service that lets you point a subdomain to your
|
|
|
33
36
|
- **IPv4 & IPv6** - Full dual-stack support
|
|
34
37
|
- **Real-time Stats** - View usage statistics and logs
|
|
35
38
|
- **Router/NAS Compatible** - Works with Synology, QNAP, and DynDNS2-compatible routers
|
|
39
|
+
- **Docker Images** - Official [apertodns/cli](https://hub.docker.com/r/apertodns/cli) and [apertodns/updater](https://hub.docker.com/r/apertodns/updater) images
|
|
36
40
|
|
|
37
41
|
## Requirements
|
|
38
42
|
|
|
@@ -94,6 +98,27 @@ apertodns --force
|
|
|
94
98
|
| `--add-domain <name>` | Create a new subdomain |
|
|
95
99
|
| `--delete-domain` | Delete a domain (interactive) |
|
|
96
100
|
| `--test <domain>` | Test DNS resolution |
|
|
101
|
+
| `update <domain>` | Update a specific domain's IP (use with `--api-key`) |
|
|
102
|
+
|
|
103
|
+
### TXT Records (ACME DNS-01)
|
|
104
|
+
|
|
105
|
+
| Command | Description |
|
|
106
|
+
|---------|-------------|
|
|
107
|
+
| `--txt-set <host> <name> <value>` | Set a TXT record |
|
|
108
|
+
| `--txt-delete <host> <name>` | Delete a TXT record |
|
|
109
|
+
|
|
110
|
+
Perfect for Let's Encrypt DNS-01 challenges:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Set TXT record for certificate validation
|
|
114
|
+
apertodns --txt-set example.apertodns.com _acme-challenge "validation-token"
|
|
115
|
+
|
|
116
|
+
# Delete TXT record after certificate issuance
|
|
117
|
+
apertodns --txt-delete example.apertodns.com _acme-challenge
|
|
118
|
+
|
|
119
|
+
# Use with API key for automation
|
|
120
|
+
apertodns --api-key apertodns_live_xxx... --txt-set example.apertodns.com _acme-challenge "token" --json
|
|
121
|
+
```
|
|
97
122
|
|
|
98
123
|
### Token Management
|
|
99
124
|
|
|
@@ -123,7 +148,15 @@ apertodns --force
|
|
|
123
148
|
| `--config` | Edit configuration |
|
|
124
149
|
| `--logout` | Remove local configuration |
|
|
125
150
|
| `--force` | Force DNS update now |
|
|
126
|
-
|
|
151
|
+
|
|
152
|
+
### Standalone Update (DynDNS2)
|
|
153
|
+
|
|
154
|
+
| Command | Description |
|
|
155
|
+
|---------|-------------|
|
|
156
|
+
| `--update` | Standalone DynDNS2 update (no config required) |
|
|
157
|
+
| `--domain <fqdn>` | Domain to update (with --update) |
|
|
158
|
+
| `--token <token>` | Token for authentication (with --update) |
|
|
159
|
+
| `--ip <address>` | Custom IP address (optional, auto-detected if omitted) |
|
|
127
160
|
|
|
128
161
|
### Daemon Mode
|
|
129
162
|
|
|
@@ -170,7 +203,7 @@ apertodns --domains --json
|
|
|
170
203
|
apertodns --my-ip --json
|
|
171
204
|
|
|
172
205
|
# Combine with API key for scripting
|
|
173
|
-
apertodns --api-key
|
|
206
|
+
apertodns --api-key apertodns_live_xxx... --domains --json
|
|
174
207
|
```
|
|
175
208
|
|
|
176
209
|
## Daemon Mode
|
|
@@ -185,6 +218,49 @@ apertodns --daemon
|
|
|
185
218
|
apertodns --daemon --interval 60
|
|
186
219
|
```
|
|
187
220
|
|
|
221
|
+
## Standalone Update
|
|
222
|
+
|
|
223
|
+
Update DNS without any saved configuration - perfect for scripts and one-off updates:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# Auto-detect IP and update
|
|
227
|
+
apertodns --update --domain myserver.apertodns.com --token YOUR_TOKEN
|
|
228
|
+
|
|
229
|
+
# Specify custom IP
|
|
230
|
+
apertodns --update --domain myserver.apertodns.com --token YOUR_TOKEN --ip 203.0.113.42
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Docker
|
|
234
|
+
|
|
235
|
+
Run without installing Node.js:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# Run CLI via Docker
|
|
239
|
+
docker run --rm apertodns/cli --help
|
|
240
|
+
|
|
241
|
+
# Interactive setup (persisted config)
|
|
242
|
+
docker run -it -v apertodns_config:/root/.config/apertodns apertodns/cli --setup
|
|
243
|
+
|
|
244
|
+
# List domains
|
|
245
|
+
docker run -v apertodns_config:/root/.config/apertodns apertodns/cli --domains
|
|
246
|
+
|
|
247
|
+
# Standalone update (no config needed)
|
|
248
|
+
docker run --rm apertodns/cli --update --domain myhost.apertodns.com --token YOUR_TOKEN
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
For continuous updates, use the dedicated updater image:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
docker run -d \
|
|
255
|
+
--name apertodns-updater \
|
|
256
|
+
--restart unless-stopped \
|
|
257
|
+
-e TOKEN=your_token \
|
|
258
|
+
-e DOMAINS=myhost.apertodns.com \
|
|
259
|
+
apertodns/updater
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
See [Docker Hub](https://hub.docker.com/r/apertodns/cli) for more options.
|
|
263
|
+
|
|
188
264
|
## Automatic Updates (Cron)
|
|
189
265
|
|
|
190
266
|
Set up automatic IP updates with cron:
|
|
@@ -295,7 +371,7 @@ apertodns --add-domain newserver.apertodns.com
|
|
|
295
371
|
apertodns --domains --json
|
|
296
372
|
|
|
297
373
|
# Use API key for automation
|
|
298
|
-
APERTODNS_API_KEY=
|
|
374
|
+
APERTODNS_API_KEY=apertodns_live_xxx... apertodns --domains --json
|
|
299
375
|
```
|
|
300
376
|
|
|
301
377
|
## Links
|
|
@@ -303,12 +379,12 @@ APERTODNS_API_KEY=ak_xxx... apertodns --domains --json
|
|
|
303
379
|
- **Website**: [apertodns.com](https://apertodns.com)
|
|
304
380
|
- **Dashboard**: [apertodns.com/dashboard](https://apertodns.com/dashboard)
|
|
305
381
|
- **Documentation**: [apertodns.com/docs](https://apertodns.com/docs)
|
|
306
|
-
- **Issues**: [GitHub Issues](https://github.com/
|
|
382
|
+
- **Issues**: [GitHub Issues](https://github.com/apertodns/apertodns/issues)
|
|
307
383
|
|
|
308
384
|
## Support
|
|
309
385
|
|
|
310
386
|
Need help?
|
|
311
|
-
- Open an issue on [GitHub](https://github.com/
|
|
387
|
+
- Open an issue on [GitHub](https://github.com/apertodns/apertodns/issues)
|
|
312
388
|
- Email: support@apertodns.com
|
|
313
389
|
|
|
314
390
|
## License
|
package/index.js
CHANGED
|
@@ -184,6 +184,19 @@ const daemonInterval = getOption("--interval") ? parseInt(getOption("--interval"
|
|
|
184
184
|
const showMyIp = args.includes("--my-ip") || args.includes("--ip") || subcommand === "ip" || subcommand === "my-ip";
|
|
185
185
|
const logout = args.includes("--logout") || subcommand === "logout";
|
|
186
186
|
|
|
187
|
+
// TXT record commands (ACME DNS-01 challenges)
|
|
188
|
+
const txtSetIdx = args.indexOf("--txt-set");
|
|
189
|
+
const txtSetArgs = txtSetIdx !== -1 ? {
|
|
190
|
+
hostname: args[txtSetIdx + 1],
|
|
191
|
+
name: args[txtSetIdx + 2],
|
|
192
|
+
value: args[txtSetIdx + 3]
|
|
193
|
+
} : null;
|
|
194
|
+
const txtDeleteIdx = args.indexOf("--txt-delete");
|
|
195
|
+
const txtDeleteArgs = txtDeleteIdx !== -1 ? {
|
|
196
|
+
hostname: args[txtDeleteIdx + 1],
|
|
197
|
+
name: args[txtDeleteIdx + 2]
|
|
198
|
+
} : null;
|
|
199
|
+
|
|
187
200
|
// JSON output helper
|
|
188
201
|
const jsonOutput = (data) => {
|
|
189
202
|
if (showJson) {
|
|
@@ -214,6 +227,10 @@ ${chalk.bold("GESTIONE DOMINI:")}
|
|
|
214
227
|
${cyan("--delete-domain")} Elimina un dominio (interattivo)
|
|
215
228
|
${cyan("--test")} <domain> Testa risoluzione DNS di un dominio
|
|
216
229
|
|
|
230
|
+
${chalk.bold("TXT RECORDS (ACME DNS-01):")}
|
|
231
|
+
${cyan("--txt-set")} <host> <name> <val> Imposta record TXT
|
|
232
|
+
${cyan("--txt-delete")} <host> <name> Elimina record TXT
|
|
233
|
+
|
|
217
234
|
${chalk.bold("GESTIONE TOKEN:")}
|
|
218
235
|
${cyan("--enable")} <id> Attiva un token
|
|
219
236
|
${cyan("--disable")} <id> Disattiva un token
|
|
@@ -361,9 +378,15 @@ const fetchDomains = async () => {
|
|
|
361
378
|
const token = await getAuthToken();
|
|
362
379
|
const spin = !showJson ? spinner("Caricamento domini...").start() : null;
|
|
363
380
|
try {
|
|
381
|
+
const controller = new AbortController();
|
|
382
|
+
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
383
|
+
|
|
364
384
|
const res = await fetch(`${API_BASE}/domains`, {
|
|
365
|
-
headers: getAuthHeaders(token)
|
|
385
|
+
headers: getAuthHeaders(token),
|
|
386
|
+
signal: controller.signal
|
|
366
387
|
});
|
|
388
|
+
clearTimeout(timeout);
|
|
389
|
+
|
|
367
390
|
spin?.stop();
|
|
368
391
|
if (!res.ok) {
|
|
369
392
|
const err = await res.json().catch(() => ({}));
|
|
@@ -371,7 +394,7 @@ const fetchDomains = async () => {
|
|
|
371
394
|
}
|
|
372
395
|
return await res.json();
|
|
373
396
|
} catch (err) {
|
|
374
|
-
spin?.fail("Errore caricamento domini");
|
|
397
|
+
spin?.fail(err.name === 'AbortError' ? "Timeout caricamento domini" : "Errore caricamento domini");
|
|
375
398
|
throw err;
|
|
376
399
|
}
|
|
377
400
|
};
|
|
@@ -636,6 +659,127 @@ const testDnsResolution = async (domain) => {
|
|
|
636
659
|
}
|
|
637
660
|
};
|
|
638
661
|
|
|
662
|
+
// ==================== TXT RECORDS ====================
|
|
663
|
+
|
|
664
|
+
const IETF_BASE = "https://api.apertodns.com/.well-known/apertodns/v1";
|
|
665
|
+
|
|
666
|
+
const setTxtRecord = async (hostname, name, value) => {
|
|
667
|
+
if (!hostname || !name || !value) {
|
|
668
|
+
if (showJson) {
|
|
669
|
+
console.log(JSON.stringify({ error: "Uso: --txt-set <hostname> <name> <value>" }));
|
|
670
|
+
} else {
|
|
671
|
+
console.log(red("\n❌ Uso: --txt-set <hostname> <name> <value>"));
|
|
672
|
+
console.log(gray(" Esempio: --txt-set mio.apertodns.com _acme-challenge abc123\n"));
|
|
673
|
+
}
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const token = await getAuthToken();
|
|
678
|
+
const spin = !showJson ? spinner(`Impostazione TXT ${name}.${hostname}...`).start() : null;
|
|
679
|
+
|
|
680
|
+
try {
|
|
681
|
+
const controller = new AbortController();
|
|
682
|
+
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
683
|
+
|
|
684
|
+
const res = await fetch(`${IETF_BASE}/update`, {
|
|
685
|
+
method: "POST",
|
|
686
|
+
headers: {
|
|
687
|
+
"Content-Type": "application/json",
|
|
688
|
+
...getAuthHeaders(token)
|
|
689
|
+
},
|
|
690
|
+
body: JSON.stringify({
|
|
691
|
+
hostname,
|
|
692
|
+
txt: {
|
|
693
|
+
name,
|
|
694
|
+
value,
|
|
695
|
+
action: "set"
|
|
696
|
+
}
|
|
697
|
+
}),
|
|
698
|
+
signal: controller.signal
|
|
699
|
+
});
|
|
700
|
+
clearTimeout(timeout);
|
|
701
|
+
|
|
702
|
+
const data = await res.json();
|
|
703
|
+
|
|
704
|
+
if (res.ok && data.success !== false) {
|
|
705
|
+
spin?.succeed(`TXT record impostato: ${name}.${hostname}`);
|
|
706
|
+
if (showJson) {
|
|
707
|
+
console.log(JSON.stringify({ success: true, hostname, txt: { name, value, action: "set" }, response: data }, null, 2));
|
|
708
|
+
} else {
|
|
709
|
+
console.log(gray(` Nome: ${cyan(name)}`));
|
|
710
|
+
console.log(gray(` Valore: ${cyan(value)}`));
|
|
711
|
+
console.log();
|
|
712
|
+
}
|
|
713
|
+
} else {
|
|
714
|
+
spin?.fail(`Errore: ${data.error || data.message || 'TXT non supportato'}`);
|
|
715
|
+
if (showJson) {
|
|
716
|
+
console.log(JSON.stringify({ error: data.error || data.message }));
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
} catch (err) {
|
|
720
|
+
spin?.fail(err.name === 'AbortError' ? "Timeout" : err.message);
|
|
721
|
+
if (showJson) {
|
|
722
|
+
console.log(JSON.stringify({ error: err.message }));
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
const deleteTxtRecord = async (hostname, name) => {
|
|
728
|
+
if (!hostname || !name) {
|
|
729
|
+
if (showJson) {
|
|
730
|
+
console.log(JSON.stringify({ error: "Uso: --txt-delete <hostname> <name>" }));
|
|
731
|
+
} else {
|
|
732
|
+
console.log(red("\n❌ Uso: --txt-delete <hostname> <name>"));
|
|
733
|
+
console.log(gray(" Esempio: --txt-delete mio.apertodns.com _acme-challenge\n"));
|
|
734
|
+
}
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const token = await getAuthToken();
|
|
739
|
+
const spin = !showJson ? spinner(`Eliminazione TXT ${name}.${hostname}...`).start() : null;
|
|
740
|
+
|
|
741
|
+
try {
|
|
742
|
+
const controller = new AbortController();
|
|
743
|
+
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
744
|
+
|
|
745
|
+
const res = await fetch(`${IETF_BASE}/update`, {
|
|
746
|
+
method: "POST",
|
|
747
|
+
headers: {
|
|
748
|
+
"Content-Type": "application/json",
|
|
749
|
+
...getAuthHeaders(token)
|
|
750
|
+
},
|
|
751
|
+
body: JSON.stringify({
|
|
752
|
+
hostname,
|
|
753
|
+
txt: {
|
|
754
|
+
name,
|
|
755
|
+
action: "delete"
|
|
756
|
+
}
|
|
757
|
+
}),
|
|
758
|
+
signal: controller.signal
|
|
759
|
+
});
|
|
760
|
+
clearTimeout(timeout);
|
|
761
|
+
|
|
762
|
+
const data = await res.json();
|
|
763
|
+
|
|
764
|
+
if (res.ok && data.success !== false) {
|
|
765
|
+
spin?.succeed(`TXT record eliminato: ${name}.${hostname}`);
|
|
766
|
+
if (showJson) {
|
|
767
|
+
console.log(JSON.stringify({ success: true, hostname, txt: { name, action: "delete" }, response: data }, null, 2));
|
|
768
|
+
}
|
|
769
|
+
} else {
|
|
770
|
+
spin?.fail(`Errore: ${data.error || data.message || 'TXT non supportato'}`);
|
|
771
|
+
if (showJson) {
|
|
772
|
+
console.log(JSON.stringify({ error: data.error || data.message }));
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
} catch (err) {
|
|
776
|
+
spin?.fail(err.name === 'AbortError' ? "Timeout" : err.message);
|
|
777
|
+
if (showJson) {
|
|
778
|
+
console.log(JSON.stringify({ error: err.message }));
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
|
|
639
783
|
// ==================== TOKENS ====================
|
|
640
784
|
|
|
641
785
|
const fetchTokens = async () => {
|
|
@@ -1799,6 +1943,8 @@ const interactiveMode = async () => {
|
|
|
1799
1943
|
const main = async () => {
|
|
1800
1944
|
try {
|
|
1801
1945
|
if (logout) await runLogout();
|
|
1946
|
+
else if (txtSetArgs) await setTxtRecord(txtSetArgs.hostname, txtSetArgs.name, txtSetArgs.value);
|
|
1947
|
+
else if (txtDeleteArgs) await deleteTxtRecord(txtDeleteArgs.hostname, txtDeleteArgs.name);
|
|
1802
1948
|
else if (showMyIp) await showMyIpCommand();
|
|
1803
1949
|
else if (runDaemon) await runDaemonMode();
|
|
1804
1950
|
else if (enableTokenId) await updateTokenState(enableTokenId, true);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apertodns",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "ApertoDNS CLI - Dynamic DNS management from your terminal. Manage domains, tokens, API keys and DNS updates with style.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -30,15 +30,15 @@
|
|
|
30
30
|
"qnap",
|
|
31
31
|
"router"
|
|
32
32
|
],
|
|
33
|
-
"author": "
|
|
33
|
+
"author": "Andrea Ferro <support@apertodns.com>",
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"repository": {
|
|
36
36
|
"type": "git",
|
|
37
|
-
"url": "https://github.com/
|
|
37
|
+
"url": "https://github.com/apertodns/apertodns.git"
|
|
38
38
|
},
|
|
39
39
|
"homepage": "https://apertodns.com",
|
|
40
40
|
"bugs": {
|
|
41
|
-
"url": "https://github.com/
|
|
41
|
+
"url": "https://github.com/apertodns/apertodns/issues"
|
|
42
42
|
},
|
|
43
43
|
"engines": {
|
|
44
44
|
"node": ">=18.0.0"
|