apertodns 1.2.5 → 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.
Files changed (2) hide show
  1. package/index.js +125 -130
  2. package/package.json +1 -1
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,37 +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 = args.includes("--enable") ? args[args.indexOf("--enable") + 1] : null;
153
- const disableTokenId = args.includes("--disable") ? args[args.indexOf("--disable") + 1] : null;
154
- const toggleTokenId = args.includes("--toggle") ? args[args.indexOf("--toggle") + 1] : null;
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 = args.includes("--add-domain") ? args[args.indexOf("--add-domain") + 1] : null;
159
- const deleteDomainArg = args.includes("--delete-domain") ? args[args.indexOf("--delete-domain") + 1] : null;
160
- const showStats = args.includes("--stats");
161
- const showLogs = args.includes("--logs");
162
- const testDns = args.includes("--test") ? args[args.indexOf("--test") + 1] : null;
163
- const showDashboard = args.includes("--dashboard");
164
- const listWebhooks = args.includes("--webhooks");
165
- const listApiKeys = args.includes("--api-keys");
166
- const createApiKeyArg = args.includes("--create-api-key") ? args[args.indexOf("--create-api-key") + 1] : null;
167
- const deleteApiKeyArg = args.includes("--delete-api-key") ? args[args.indexOf("--delete-api-key") + 1] : null;
168
- const showScopes = args.includes("--scopes");
169
- const useApiKey = args.includes("--api-key") ? args[args.indexOf("--api-key") + 1] : null;
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 = args.includes("--interval") ? parseInt(args[args.indexOf("--interval") + 1]) : 300;
173
- const showMyIp = args.includes("--my-ip") || args.includes("--ip");
174
- const logout = args.includes("--logout");
175
-
176
- // Standalone update command (like docker updater)
177
- const standaloneUpdate = args.includes("--update");
178
- const updateDomain = args.includes("--domain") ? args[args.indexOf("--domain") + 1] : null;
179
- const updateToken = args.includes("--token") ? args[args.indexOf("--token") + 1] : null;
180
- const updateIp = args.includes("--ip") && args.indexOf("--ip") !== args.indexOf("--my-ip")
181
- ? args[args.indexOf("--ip") + 1]
182
- : null;
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";
183
186
 
184
187
  // JSON output helper
185
188
  const jsonOutput = (data) => {
@@ -234,12 +237,6 @@ ${chalk.bold("CONFIGURAZIONE:")}
234
237
  ${cyan("--logout")} Rimuovi configurazione locale
235
238
  ${cyan("--force")} Forza aggiornamento DNS
236
239
 
237
- ${chalk.bold("AGGIORNAMENTO STANDALONE:")}
238
- ${cyan("--update")} Aggiorna DNS via DynDNS2 (richiede --domain e --token)
239
- ${cyan("--domain")} <name> Dominio da aggiornare (es: myhost.apertodns.com)
240
- ${cyan("--token")} <token> Token DDNS per l'autenticazione
241
- ${cyan("--ip")} <ip> IP da impostare (opzionale, auto-detect se omesso)
242
-
243
240
  ${chalk.bold("DAEMON MODE:")}
244
241
  ${cyan("--daemon")} Avvia in modalità daemon (aggiornamento continuo)
245
242
  ${cyan("--interval")} <sec> Intervallo aggiornamento daemon (default: 300s)
@@ -267,8 +264,6 @@ ${gray("Esempi:")}
267
264
  ${gray("$")} apertodns --test mioserver.apertodns.com
268
265
  ${gray("$")} apertodns --daemon --interval 60
269
266
  ${gray("$")} apertodns --api-key ak_xxx... --domains --json
270
- ${gray("$")} apertodns --update --domain myhost.apertodns.com --token abc123
271
- ${gray("$")} apertodns --update --domain myhost.apertodns.com --token abc123 --ip 1.2.3.4
272
267
 
273
268
  ${gray("Docs: https://apertodns.com/docs")}
274
269
  `);
@@ -318,8 +313,8 @@ const spinner = (text) => ora({ text, spinner: "dots", color: "yellow" });
318
313
 
319
314
  // Helper: get auth headers (supports both JWT and API Key)
320
315
  const getAuthHeaders = (token) => {
321
- // API Keys start with "ak_"
322
- if (token && token.startsWith('ak_')) {
316
+ // API Keys start with "ak_" or "apertodns_live_" or "apertodns_test_"
317
+ if (token && (token.startsWith('ak_') || token.startsWith('apertodns_live_') || token.startsWith('apertodns_test_'))) {
323
318
  return { 'X-API-Key': token };
324
319
  }
325
320
  return { Authorization: `Bearer ${token}` };
@@ -1550,6 +1545,96 @@ const runUpdate = async () => {
1550
1545
  }
1551
1546
  };
1552
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
+
1553
1638
  // ==================== DAEMON MODE ====================
1554
1639
 
1555
1640
  const runDaemonMode = async () => {
@@ -1602,96 +1687,6 @@ const runDaemonMode = async () => {
1602
1687
  setInterval(update, daemonInterval * 1000);
1603
1688
  };
1604
1689
 
1605
- // ==================== STANDALONE UPDATE (DynDNS2) ====================
1606
-
1607
- const runStandaloneUpdate = async (domain, token, customIp) => {
1608
- // Validate required parameters
1609
- if (!domain || !token) {
1610
- if (showJson) {
1611
- console.log(JSON.stringify({
1612
- error: "Parametri mancanti. Usa: --update --domain <domain> --token <token> [--ip <ip>]"
1613
- }));
1614
- } else {
1615
- console.log(red("\n❌ Parametri mancanti."));
1616
- console.log(gray(" Uso: apertodns --update --domain <domain> --token <token> [--ip <ip>]\n"));
1617
- }
1618
- process.exit(1);
1619
- }
1620
-
1621
- const spin = !showJson ? spinner("Aggiornamento DNS via DynDNS2...").start() : null;
1622
-
1623
- try {
1624
- // Get current IP if not provided
1625
- let ipToUse = customIp;
1626
- if (!ipToUse) {
1627
- if (spin) spin.text = "Rilevamento IP pubblico...";
1628
- ipToUse = await getCurrentIP('https://api.ipify.org');
1629
- if (!ipToUse) {
1630
- throw new Error("Impossibile rilevare IP pubblico");
1631
- }
1632
- ipToUse = ipToUse.trim();
1633
- }
1634
-
1635
- if (spin) spin.text = `Aggiornamento ${domain} → ${ipToUse}...`;
1636
-
1637
- // Build DynDNS2 URL
1638
- const updateUrl = `https://api.apertodns.com/nic/update?hostname=${encodeURIComponent(domain)}&myip=${encodeURIComponent(ipToUse)}`;
1639
-
1640
- // Make request with Basic Auth (DynDNS2 protocol)
1641
- const authHeader = 'Basic ' + Buffer.from(`${domain}:${token}`).toString('base64');
1642
-
1643
- const res = await fetch(updateUrl, {
1644
- method: 'GET',
1645
- headers: {
1646
- 'Authorization': authHeader,
1647
- 'User-Agent': `ApertoDNS-CLI/${CURRENT_VERSION}`
1648
- }
1649
- });
1650
-
1651
- const responseText = await res.text();
1652
-
1653
- // Parse DynDNS2 response
1654
- const isSuccess = responseText.startsWith('good') || responseText.startsWith('nochg');
1655
-
1656
- if (isSuccess) {
1657
- spin?.succeed(`DNS aggiornato! ${domain} → ${ipToUse}`);
1658
-
1659
- if (showJson) {
1660
- console.log(JSON.stringify({
1661
- success: true,
1662
- domain,
1663
- ip: ipToUse,
1664
- response: responseText.trim(),
1665
- timestamp: new Date().toISOString()
1666
- }, null, 2));
1667
- } else if (!isCron) {
1668
- console.log(` ${gray('Risposta:')} ${green(responseText.trim())}`);
1669
- console.log();
1670
- }
1671
- } else {
1672
- spin?.fail(`Errore aggiornamento: ${responseText.trim()}`);
1673
-
1674
- if (showJson) {
1675
- console.log(JSON.stringify({
1676
- success: false,
1677
- domain,
1678
- ip: ipToUse,
1679
- error: responseText.trim(),
1680
- timestamp: new Date().toISOString()
1681
- }, null, 2));
1682
- }
1683
- process.exit(1);
1684
- }
1685
- } catch (err) {
1686
- spin?.fail(`Errore: ${err.message}`);
1687
-
1688
- if (showJson) {
1689
- console.log(JSON.stringify({ error: err.message }));
1690
- }
1691
- process.exit(1);
1692
- }
1693
- };
1694
-
1695
1690
  // ==================== LOGOUT ====================
1696
1691
 
1697
1692
  const runLogout = async () => {
@@ -1804,7 +1799,6 @@ const interactiveMode = async () => {
1804
1799
  const main = async () => {
1805
1800
  try {
1806
1801
  if (logout) await runLogout();
1807
- else if (standaloneUpdate) await runStandaloneUpdate(updateDomain, updateToken, updateIp);
1808
1802
  else if (showMyIp) await showMyIpCommand();
1809
1803
  else if (runDaemon) await runDaemonMode();
1810
1804
  else if (enableTokenId) await updateTokenState(enableTokenId, true);
@@ -1814,6 +1808,7 @@ const main = async () => {
1814
1808
  else if (listDomains) await showDomainsList();
1815
1809
  else if (addDomainArg) await addDomain(addDomainArg);
1816
1810
  else if (deleteDomainArg) await deleteDomain(deleteDomainArg);
1811
+ else if (updateDomainArg) await updateSingleDomain(updateDomainArg);
1817
1812
  else if (testDns) await testDnsResolution(testDns);
1818
1813
  else if (listTokens) await showTokensList();
1819
1814
  else if (showStats) await showStatsCommand();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apertodns",
3
- "version": "1.2.5",
3
+ "version": "2.0.1",
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",