apertodns 1.0.1 → 1.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 +190 -9
- package/index.js +817 -206
- package/package.json +58 -49
- package/utils.js +151 -14
package/package.json
CHANGED
|
@@ -1,51 +1,60 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
2
|
+
"name": "apertodns",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "ApertoDNS CLI - Dynamic DNS management from your terminal. Manage domains, tokens, API keys and DNS updates with style.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"apertodns": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js",
|
|
12
|
+
"test": "node index.js --version"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"ddns",
|
|
16
|
+
"cli",
|
|
17
|
+
"dns",
|
|
18
|
+
"apertodns",
|
|
19
|
+
"dynamic-dns",
|
|
20
|
+
"dyndns",
|
|
21
|
+
"dyndns2",
|
|
22
|
+
"ipv4",
|
|
23
|
+
"ipv6",
|
|
24
|
+
"domain",
|
|
25
|
+
"terminal",
|
|
26
|
+
"api-keys",
|
|
27
|
+
"webhooks",
|
|
28
|
+
"nas",
|
|
29
|
+
"synology",
|
|
30
|
+
"qnap",
|
|
31
|
+
"router"
|
|
32
|
+
],
|
|
33
|
+
"author": "Aperto Network <info@apertodns.com>",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/1r0n3d3v3l0per/apertodns.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://apertodns.com",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/1r0n3d3v3l0per/apertodns/issues"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18.0.0"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"index.js",
|
|
48
|
+
"utils.js",
|
|
49
|
+
"README.md"
|
|
50
|
+
],
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"chalk": "^5.3.0",
|
|
53
|
+
"cli-spinners": "^3.3.0",
|
|
54
|
+
"cli-table3": "^0.6.5",
|
|
55
|
+
"figlet": "^1.6.0",
|
|
56
|
+
"inquirer": "^9.3.8",
|
|
57
|
+
"node-fetch": "^3.3.2",
|
|
58
|
+
"ora": "^9.0.0"
|
|
59
|
+
}
|
|
51
60
|
}
|
package/utils.js
CHANGED
|
@@ -1,35 +1,172 @@
|
|
|
1
1
|
import fetch from "node-fetch";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import os from "os";
|
|
4
5
|
import { fileURLToPath } from "url";
|
|
5
6
|
|
|
6
7
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const dataDir = path.resolve(__dirname, ".data");
|
|
8
|
-
const ipPath = path.join(dataDir, "last_ip_ironDNS.txt");
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
// Get config directory based on platform (XDG compliant)
|
|
10
|
+
export const getConfigDir = () => {
|
|
11
|
+
// Check for XDG config home first (Linux standard)
|
|
12
|
+
if (process.env.XDG_CONFIG_HOME) {
|
|
13
|
+
return path.join(process.env.XDG_CONFIG_HOME, "apertodns");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Platform-specific defaults
|
|
17
|
+
const platform = os.platform();
|
|
18
|
+
|
|
19
|
+
if (platform === "win32") {
|
|
20
|
+
// Windows: use APPDATA
|
|
21
|
+
return path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "apertodns");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// macOS and Linux: use ~/.config/apertodns or ~/.apertodns
|
|
25
|
+
const configDir = path.join(os.homedir(), ".config", "apertodns");
|
|
26
|
+
|
|
27
|
+
// Fallback to ~/.apertodns if .config doesn't exist
|
|
28
|
+
if (!fs.existsSync(path.join(os.homedir(), ".config"))) {
|
|
29
|
+
return path.join(os.homedir(), ".apertodns");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return configDir;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Get data directory for cache and temp files
|
|
36
|
+
export const getDataDir = () => {
|
|
37
|
+
const configDir = getConfigDir();
|
|
38
|
+
const dataDir = path.join(configDir, ".data");
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(dataDir)) {
|
|
41
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return dataDir;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const dataDir = getDataDir();
|
|
48
|
+
const ipPath = path.join(dataDir, "last_ip.txt");
|
|
49
|
+
const ipv6Path = path.join(dataDir, "last_ipv6.txt");
|
|
13
50
|
|
|
14
51
|
export const log = (msg) => {
|
|
15
52
|
const time = new Date().toISOString().replace("T", " ").substring(0, 19);
|
|
16
53
|
console.log(`[${time}] ${msg}`);
|
|
17
54
|
};
|
|
18
55
|
|
|
19
|
-
export const getCurrentIP = async (ipService) => {
|
|
20
|
-
|
|
21
|
-
|
|
56
|
+
export const getCurrentIP = async (ipService = 'https://api.ipify.org') => {
|
|
57
|
+
try {
|
|
58
|
+
const controller = new AbortController();
|
|
59
|
+
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
60
|
+
|
|
61
|
+
const res = await fetch(ipService, { signal: controller.signal });
|
|
62
|
+
clearTimeout(timeout);
|
|
63
|
+
|
|
64
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
65
|
+
const ip = (await res.text()).trim();
|
|
66
|
+
|
|
67
|
+
// Validate IPv4
|
|
68
|
+
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
69
|
+
if (!ipv4Regex.test(ip)) {
|
|
70
|
+
throw new Error("Invalid IPv4 format");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return ip;
|
|
74
|
+
} catch (err) {
|
|
75
|
+
// Try fallback services
|
|
76
|
+
const fallbacks = [
|
|
77
|
+
'https://api.ipify.org',
|
|
78
|
+
'https://ifconfig.me/ip',
|
|
79
|
+
'https://icanhazip.com',
|
|
80
|
+
'https://checkip.amazonaws.com'
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
for (const fallback of fallbacks) {
|
|
84
|
+
if (fallback === ipService) continue;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
89
|
+
|
|
90
|
+
const res = await fetch(fallback, { signal: controller.signal });
|
|
91
|
+
clearTimeout(timeout);
|
|
92
|
+
|
|
93
|
+
if (res.ok) {
|
|
94
|
+
const ip = (await res.text()).trim();
|
|
95
|
+
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
96
|
+
if (ipv4Regex.test(ip)) {
|
|
97
|
+
return ip;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw new Error("Unable to detect IP address");
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const getCurrentIPv6 = async (ipService6 = 'https://api6.ipify.org') => {
|
|
110
|
+
try {
|
|
111
|
+
const controller = new AbortController();
|
|
112
|
+
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
113
|
+
|
|
114
|
+
const res = await fetch(ipService6, { signal: controller.signal });
|
|
115
|
+
clearTimeout(timeout);
|
|
116
|
+
|
|
117
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
118
|
+
const ip = (await res.text()).trim();
|
|
119
|
+
|
|
120
|
+
// Basic IPv6 validation (contains colons)
|
|
121
|
+
if (!ip.includes(':')) {
|
|
122
|
+
throw new Error("Invalid IPv6 format");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return ip;
|
|
126
|
+
} catch (err) {
|
|
127
|
+
// IPv6 might not be available, return null instead of throwing
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
22
130
|
};
|
|
23
131
|
|
|
24
132
|
export const loadLastIP = () => {
|
|
25
|
-
|
|
133
|
+
try {
|
|
134
|
+
return fs.existsSync(ipPath) ? fs.readFileSync(ipPath, "utf-8").trim() : null;
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
26
138
|
};
|
|
27
139
|
|
|
28
140
|
export const saveCurrentIP = (ip) => {
|
|
29
|
-
|
|
141
|
+
try {
|
|
142
|
+
fs.writeFileSync(ipPath, ip);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
// Silently fail - non-critical
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const loadLastIPv6 = () => {
|
|
149
|
+
try {
|
|
150
|
+
return fs.existsSync(ipv6Path) ? fs.readFileSync(ipv6Path, "utf-8").trim() : null;
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
30
154
|
};
|
|
31
155
|
|
|
32
|
-
export const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
156
|
+
export const saveCurrentIPv6 = (ip) => {
|
|
157
|
+
try {
|
|
158
|
+
fs.writeFileSync(ipv6Path, ip);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
// Silently fail - non-critical
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Export package info
|
|
165
|
+
export const getPackageInfo = () => {
|
|
166
|
+
try {
|
|
167
|
+
const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, "package.json"), "utf-8"));
|
|
168
|
+
return { name: pkg.name, version: pkg.version };
|
|
169
|
+
} catch {
|
|
170
|
+
return { name: "apertodns", version: "0.0.0" };
|
|
171
|
+
}
|
|
172
|
+
};
|