apertodns 2.0.0 → 2.0.1
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 +8 -63
- package/index.js +123 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,19 +11,16 @@ 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 |
|
|
15
|
-
|
|
16
|
-
| Free
|
|
17
|
-
|
|
|
18
|
-
| API Keys with scopes | Yes | No | No | No |
|
|
14
|
+
| Feature | ApertoDNS | No-IP | DuckDNS | Dynu |
|
|
15
|
+
|---------|-----------|-------|---------|------|
|
|
16
|
+
| Free subdomains | Unlimited | 1 (free) | 5 | 4 |
|
|
17
|
+
| API Keys with scopes | Yes | No | No | Limited |
|
|
19
18
|
| CLI tool | Yes | No | No | No |
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
| DynDNS2 compatible | Yes | Yes | Yes | No |
|
|
19
|
+
| IPv6 support | Yes | Paid | Yes | Yes |
|
|
20
|
+
| No forced renewal | Yes | 30 days | Yes | Yes |
|
|
21
|
+
| DynDNS2 compatible | Yes | Yes | No | Yes |
|
|
24
22
|
| Webhooks | Yes | No | No | No |
|
|
25
23
|
| Team sharing | Yes | No | No | No |
|
|
26
|
-
| Open Source | Yes | No | No | No |
|
|
27
24
|
|
|
28
25
|
## Features
|
|
29
26
|
|
|
@@ -36,7 +33,6 @@ ApertoDNS is a free Dynamic DNS service that lets you point a subdomain to your
|
|
|
36
33
|
- **IPv4 & IPv6** - Full dual-stack support
|
|
37
34
|
- **Real-time Stats** - View usage statistics and logs
|
|
38
35
|
- **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
|
|
40
36
|
|
|
41
37
|
## Requirements
|
|
42
38
|
|
|
@@ -127,15 +123,7 @@ apertodns --force
|
|
|
127
123
|
| `--config` | Edit configuration |
|
|
128
124
|
| `--logout` | Remove local configuration |
|
|
129
125
|
| `--force` | Force DNS update now |
|
|
130
|
-
|
|
131
|
-
### Standalone Update (DynDNS2)
|
|
132
|
-
|
|
133
|
-
| Command | Description |
|
|
134
|
-
|---------|-------------|
|
|
135
|
-
| `--update` | Standalone DynDNS2 update (no config required) |
|
|
136
|
-
| `--domain <fqdn>` | Domain to update (with --update) |
|
|
137
|
-
| `--token <token>` | Token for authentication (with --update) |
|
|
138
|
-
| `--ip <address>` | Custom IP address (optional, auto-detected if omitted) |
|
|
126
|
+
| `--update` | Standalone DynDNS2 update (with `--domain` and `--token`) |
|
|
139
127
|
|
|
140
128
|
### Daemon Mode
|
|
141
129
|
|
|
@@ -197,49 +185,6 @@ apertodns --daemon
|
|
|
197
185
|
apertodns --daemon --interval 60
|
|
198
186
|
```
|
|
199
187
|
|
|
200
|
-
## Standalone Update
|
|
201
|
-
|
|
202
|
-
Update DNS without any saved configuration - perfect for scripts and one-off updates:
|
|
203
|
-
|
|
204
|
-
```bash
|
|
205
|
-
# Auto-detect IP and update
|
|
206
|
-
apertodns --update --domain myserver.apertodns.com --token YOUR_TOKEN
|
|
207
|
-
|
|
208
|
-
# Specify custom IP
|
|
209
|
-
apertodns --update --domain myserver.apertodns.com --token YOUR_TOKEN --ip 203.0.113.42
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
## Docker
|
|
213
|
-
|
|
214
|
-
Run without installing Node.js:
|
|
215
|
-
|
|
216
|
-
```bash
|
|
217
|
-
# Run CLI via Docker
|
|
218
|
-
docker run --rm apertodns/cli --help
|
|
219
|
-
|
|
220
|
-
# Interactive setup (persisted config)
|
|
221
|
-
docker run -it -v apertodns_config:/root/.config/apertodns apertodns/cli --setup
|
|
222
|
-
|
|
223
|
-
# List domains
|
|
224
|
-
docker run -v apertodns_config:/root/.config/apertodns apertodns/cli --domains
|
|
225
|
-
|
|
226
|
-
# Standalone update (no config needed)
|
|
227
|
-
docker run --rm apertodns/cli --update --domain myhost.apertodns.com --token YOUR_TOKEN
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
For continuous updates, use the dedicated updater image:
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
docker run -d \
|
|
234
|
-
--name apertodns-updater \
|
|
235
|
-
--restart unless-stopped \
|
|
236
|
-
-e TOKEN=your_token \
|
|
237
|
-
-e DOMAINS=myhost.apertodns.com \
|
|
238
|
-
apertodns/updater
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
See [Docker Hub](https://hub.docker.com/r/apertodns/cli) for more options.
|
|
242
|
-
|
|
243
188
|
## Automatic Updates (Cron)
|
|
244
189
|
|
|
245
190
|
Set up automatic IP updates with cron:
|
package/index.js
CHANGED
|
@@ -141,6 +141,16 @@ if (isCron) {
|
|
|
141
141
|
process.argv.push("--json");
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
// Subcommand detection (new style: domains list, update domain.com, etc.)
|
|
145
|
+
const subcommand = args[0] && !args[0].startsWith('-') ? args[0] : null;
|
|
146
|
+
const subcommandArg = args[1] && !args[1].startsWith('-') ? args[1] : null;
|
|
147
|
+
|
|
148
|
+
// Helper to get option value from args (works anywhere in args array)
|
|
149
|
+
const getOption = (name) => {
|
|
150
|
+
const idx = args.indexOf(name);
|
|
151
|
+
return idx !== -1 && args[idx + 1] && !args[idx + 1].startsWith('-') ? args[idx + 1] : null;
|
|
152
|
+
};
|
|
153
|
+
|
|
144
154
|
const isQuiet = args.includes("--quiet");
|
|
145
155
|
const showHelp = args.includes("--help") || args.includes("-h");
|
|
146
156
|
const showVersion = args.includes("--version") || args.includes("-v");
|
|
@@ -149,29 +159,30 @@ const runVerify = args.includes("--verify");
|
|
|
149
159
|
const runSetup = args.includes("--setup");
|
|
150
160
|
const showStatus = args.includes("--status") || args.includes("--show");
|
|
151
161
|
const forceUpdate = args.includes("--force");
|
|
152
|
-
const enableTokenId =
|
|
153
|
-
const disableTokenId =
|
|
154
|
-
const toggleTokenId =
|
|
162
|
+
const enableTokenId = getOption("--enable");
|
|
163
|
+
const disableTokenId = getOption("--disable");
|
|
164
|
+
const toggleTokenId = getOption("--toggle");
|
|
155
165
|
const runConfigEdit = args.includes("--config");
|
|
156
|
-
const listDomains = args.includes("--domains");
|
|
157
|
-
const listTokens = args.includes("--tokens");
|
|
158
|
-
const addDomainArg =
|
|
159
|
-
const deleteDomainArg =
|
|
160
|
-
const showStats = args.includes("--stats");
|
|
161
|
-
const showLogs = args.includes("--logs");
|
|
162
|
-
const testDns =
|
|
163
|
-
const showDashboard = args.includes("--dashboard");
|
|
164
|
-
const listWebhooks = args.includes("--webhooks");
|
|
165
|
-
const listApiKeys = args.includes("--api-keys");
|
|
166
|
-
const createApiKeyArg =
|
|
167
|
-
const deleteApiKeyArg =
|
|
168
|
-
const showScopes = args.includes("--scopes");
|
|
169
|
-
const useApiKey =
|
|
166
|
+
const listDomains = args.includes("--domains") || (subcommand === "domains" && (!subcommandArg || subcommandArg === "list"));
|
|
167
|
+
const listTokens = args.includes("--tokens") || (subcommand === "tokens" && (!subcommandArg || subcommandArg === "list"));
|
|
168
|
+
const addDomainArg = getOption("--add-domain") || (subcommand === "domains" && subcommandArg === "add" ? args[2] : null);
|
|
169
|
+
const deleteDomainArg = getOption("--delete-domain") || (subcommand === "domains" && subcommandArg === "delete" ? args[2] : null);
|
|
170
|
+
const showStats = args.includes("--stats") || subcommand === "stats";
|
|
171
|
+
const showLogs = args.includes("--logs") || subcommand === "logs";
|
|
172
|
+
const testDns = getOption("--test") || (subcommand === "test" ? subcommandArg : null);
|
|
173
|
+
const showDashboard = args.includes("--dashboard") || subcommand === "dashboard";
|
|
174
|
+
const listWebhooks = args.includes("--webhooks") || subcommand === "webhooks";
|
|
175
|
+
const listApiKeys = args.includes("--api-keys") || (subcommand === "api-keys" && (!subcommandArg || subcommandArg === "list"));
|
|
176
|
+
const createApiKeyArg = getOption("--create-api-key") || (subcommand === "api-keys" && subcommandArg === "create" ? args[2] : null);
|
|
177
|
+
const deleteApiKeyArg = getOption("--delete-api-key") || (subcommand === "api-keys" && subcommandArg === "delete" ? args[2] : null);
|
|
178
|
+
const showScopes = args.includes("--scopes") || subcommand === "scopes";
|
|
179
|
+
const useApiKey = getOption("--api-key");
|
|
180
|
+
const updateDomainArg = subcommand === "update" ? subcommandArg : null;
|
|
170
181
|
const runInteractive = args.length === 0;
|
|
171
|
-
const runDaemon = args.includes("--daemon");
|
|
172
|
-
const daemonInterval =
|
|
173
|
-
const showMyIp = args.includes("--my-ip") || args.includes("--ip");
|
|
174
|
-
const logout = args.includes("--logout");
|
|
182
|
+
const runDaemon = args.includes("--daemon") || subcommand === "daemon";
|
|
183
|
+
const daemonInterval = getOption("--interval") ? parseInt(getOption("--interval")) : 300;
|
|
184
|
+
const showMyIp = args.includes("--my-ip") || args.includes("--ip") || subcommand === "ip" || subcommand === "my-ip";
|
|
185
|
+
const logout = args.includes("--logout") || subcommand === "logout";
|
|
175
186
|
|
|
176
187
|
// JSON output helper
|
|
177
188
|
const jsonOutput = (data) => {
|
|
@@ -1534,6 +1545,96 @@ const runUpdate = async () => {
|
|
|
1534
1545
|
}
|
|
1535
1546
|
};
|
|
1536
1547
|
|
|
1548
|
+
// ==================== UPDATE SINGLE DOMAIN ====================
|
|
1549
|
+
|
|
1550
|
+
const updateSingleDomain = async (domainName) => {
|
|
1551
|
+
const token = await getAuthToken();
|
|
1552
|
+
const spin = !showJson ? spinner(`Rilevamento IP per ${domainName}...`).start() : null;
|
|
1553
|
+
|
|
1554
|
+
try {
|
|
1555
|
+
// Get current IP
|
|
1556
|
+
const currentIP = await getCurrentIP('https://api.ipify.org').catch(() => null);
|
|
1557
|
+
const currentIPv6 = await getCurrentIPv6('https://api6.ipify.org').catch(() => null);
|
|
1558
|
+
|
|
1559
|
+
if (!currentIP && !currentIPv6) {
|
|
1560
|
+
spin?.fail("Nessun IP rilevato");
|
|
1561
|
+
if (showJson) {
|
|
1562
|
+
console.log(JSON.stringify({ error: "Nessun IP rilevato" }));
|
|
1563
|
+
}
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
if (spin) spin.text = `Ricerca dominio ${domainName}...`;
|
|
1568
|
+
|
|
1569
|
+
// First, get domain list to find the domain ID
|
|
1570
|
+
const listRes = await fetch(`${API_BASE}/domains`, {
|
|
1571
|
+
headers: getAuthHeaders(token)
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
if (!listRes.ok) {
|
|
1575
|
+
const errData = await listRes.json().catch(() => ({}));
|
|
1576
|
+
spin?.fail(`Errore autenticazione: ${errData.error || errData.message || 'Token non valido'}`);
|
|
1577
|
+
if (showJson) {
|
|
1578
|
+
console.log(JSON.stringify({ error: errData.error || errData.message || 'Autenticazione fallita' }));
|
|
1579
|
+
}
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
const listData = await listRes.json();
|
|
1584
|
+
const domains = listData.domains || listData;
|
|
1585
|
+
const domain = domains.find(d => d.name === domainName || d.name.toLowerCase() === domainName.toLowerCase());
|
|
1586
|
+
|
|
1587
|
+
if (!domain) {
|
|
1588
|
+
spin?.fail(`Dominio "${domainName}" non trovato nel tuo account`);
|
|
1589
|
+
if (showJson) {
|
|
1590
|
+
console.log(JSON.stringify({ error: `Dominio "${domainName}" non trovato` }));
|
|
1591
|
+
}
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
if (spin) spin.text = `Aggiornamento DNS per ${domainName}...`;
|
|
1596
|
+
|
|
1597
|
+
// Use PATCH /domains/:id to update (supports API Key)
|
|
1598
|
+
const updateBody = { ip: currentIP };
|
|
1599
|
+
if (currentIPv6) updateBody.ipv6 = currentIPv6;
|
|
1600
|
+
|
|
1601
|
+
const res = await fetch(`${API_BASE}/domains/${domain.id}`, {
|
|
1602
|
+
method: "PATCH",
|
|
1603
|
+
headers: {
|
|
1604
|
+
"Content-Type": "application/json",
|
|
1605
|
+
...getAuthHeaders(token)
|
|
1606
|
+
},
|
|
1607
|
+
body: JSON.stringify(updateBody)
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
const data = await res.json();
|
|
1611
|
+
|
|
1612
|
+
if (res.ok) {
|
|
1613
|
+
spin?.succeed(`DNS aggiornato! ${domainName} → ${currentIP}`);
|
|
1614
|
+
if (showJson) {
|
|
1615
|
+
console.log(JSON.stringify({
|
|
1616
|
+
success: true,
|
|
1617
|
+
domain: domainName,
|
|
1618
|
+
ip: currentIP,
|
|
1619
|
+
ipv6: currentIPv6 || null,
|
|
1620
|
+
previousIp: domain.ip,
|
|
1621
|
+
result: data
|
|
1622
|
+
}, null, 2));
|
|
1623
|
+
}
|
|
1624
|
+
} else {
|
|
1625
|
+
spin?.fail(`Errore: ${data.error || data.details || data.message}`);
|
|
1626
|
+
if (showJson) {
|
|
1627
|
+
console.log(JSON.stringify({ error: data.error || data.details || data.message }));
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
} catch (err) {
|
|
1631
|
+
spin?.fail(err.message);
|
|
1632
|
+
if (showJson) {
|
|
1633
|
+
console.log(JSON.stringify({ error: err.message }));
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
|
|
1537
1638
|
// ==================== DAEMON MODE ====================
|
|
1538
1639
|
|
|
1539
1640
|
const runDaemonMode = async () => {
|
|
@@ -1707,6 +1808,7 @@ const main = async () => {
|
|
|
1707
1808
|
else if (listDomains) await showDomainsList();
|
|
1708
1809
|
else if (addDomainArg) await addDomain(addDomainArg);
|
|
1709
1810
|
else if (deleteDomainArg) await deleteDomain(deleteDomainArg);
|
|
1811
|
+
else if (updateDomainArg) await updateSingleDomain(updateDomainArg);
|
|
1710
1812
|
else if (testDns) await testDnsResolution(testDns);
|
|
1711
1813
|
else if (listTokens) await showTokensList();
|
|
1712
1814
|
else if (showStats) await showStatsCommand();
|
package/package.json
CHANGED