apertodns 2.0.2 → 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.
Files changed (3) hide show
  1. package/README.md +23 -3
  2. package/index.js +148 -2
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/apertodns.svg)](https://www.npmjs.com/package/apertodns)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/apertodns.svg)](https://www.npmjs.com/package/apertodns)
5
- [![license](https://img.shields.io/npm/l/apertodns.svg)](https://github.com/1r0n3d3v3l0per/apertodns/blob/main/LICENSE)
5
+ [![license](https://img.shields.io/npm/l/apertodns.svg)](https://github.com/apertodns/apertodns/blob/main/LICENSE)
6
6
  [![node](https://img.shields.io/node/v/apertodns.svg)](https://nodejs.org)
7
7
 
8
8
  **Dynamic DNS management from your terminal.** Manage domains, tokens, API keys, and DNS updates with style.
@@ -100,6 +100,26 @@ apertodns --force
100
100
  | `--test <domain>` | Test DNS resolution |
101
101
  | `update <domain>` | Update a specific domain's IP (use with `--api-key`) |
102
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
+ ```
122
+
103
123
  ### Token Management
104
124
 
105
125
  | Command | Description |
@@ -359,12 +379,12 @@ APERTODNS_API_KEY=apertodns_live_xxx... apertodns --domains --json
359
379
  - **Website**: [apertodns.com](https://apertodns.com)
360
380
  - **Dashboard**: [apertodns.com/dashboard](https://apertodns.com/dashboard)
361
381
  - **Documentation**: [apertodns.com/docs](https://apertodns.com/docs)
362
- - **Issues**: [GitHub Issues](https://github.com/1r0n3d3v3l0per/apertodns/issues)
382
+ - **Issues**: [GitHub Issues](https://github.com/apertodns/apertodns/issues)
363
383
 
364
384
  ## Support
365
385
 
366
386
  Need help?
367
- - Open an issue on [GitHub](https://github.com/1r0n3d3v3l0per/apertodns/issues)
387
+ - Open an issue on [GitHub](https://github.com/apertodns/apertodns/issues)
368
388
  - Email: support@apertodns.com
369
389
 
370
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.2",
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": "Aperto Network <info@apertodns.com>",
33
+ "author": "Andrea Ferro <support@apertodns.com>",
34
34
  "license": "MIT",
35
35
  "repository": {
36
36
  "type": "git",
37
- "url": "https://github.com/1r0n3d3v3l0per/apertodns.git"
37
+ "url": "https://github.com/apertodns/apertodns.git"
38
38
  },
39
39
  "homepage": "https://apertodns.com",
40
40
  "bugs": {
41
- "url": "https://github.com/1r0n3d3v3l0per/apertodns/issues"
41
+ "url": "https://github.com/apertodns/apertodns/issues"
42
42
  },
43
43
  "engines": {
44
44
  "node": ">=18.0.0"