xynginc 1.0.95 → 1.0.97

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.
@@ -1,4 +1,4 @@
1
1
  import path from "path";
2
2
  export const BINARY_NAME = "xynginc";
3
3
  export const GITHUB_REPO = "Nehonix-Team/xynginc";
4
- export const BINARY_DIR = path.join(__dirname, "../bin");
4
+ export const BINARY_DIR = path.resolve(__dirname, "../../bin");
@@ -4,6 +4,7 @@ import { BINARY_NAME, BINARY_DIR, GITHUB_REPO } from "./constant";
4
4
  import { __strl__ } from "strulink";
5
5
  const fs = __sys__.fs;
6
6
  const path = __sys__.path;
7
+ let activeDownloadPromise = null;
7
8
  /**
8
9
  * Downloads the xynginc binary from GitHub releases.
9
10
  *
@@ -11,6 +12,18 @@ const path = __sys__.path;
11
12
  * @returns The path to the downloaded binary.
12
13
  */
13
14
  export async function downloadBinary(version) {
15
+ if (activeDownloadPromise) {
16
+ return activeDownloadPromise;
17
+ }
18
+ activeDownloadPromise = doDownload(version);
19
+ try {
20
+ return await activeDownloadPromise;
21
+ }
22
+ finally {
23
+ activeDownloadPromise = null;
24
+ }
25
+ }
26
+ async function doDownload(version) {
14
27
  const platform = __sys__.os.platform();
15
28
  const arch = __sys__.os.arch();
16
29
  if (platform !== "linux") {
@@ -21,35 +34,80 @@ export async function downloadBinary(version) {
21
34
  ? `https://github.com/${GITHUB_REPO}/releases/latest/download/${binaryName}`
22
35
  : `https://github.com/${GITHUB_REPO}/releases/download/${version}/${binaryName}`;
23
36
  Logger.info(`[XyNginC] Downloading from: ${__strl__.createUrl(downloadUrl).hostname}`);
24
- // Create bin directory
25
- fs.writeIfNotExistsSync(BINARY_DIR, { recursive: true });
26
- const localPath = path.join(BINARY_DIR, BINARY_NAME);
37
+ // Ensure bin directory exists (with fallback if root package directory is read-only)
38
+ let targetDir = BINARY_DIR;
39
+ try {
40
+ if (!fs.exists(targetDir)) {
41
+ fs.ensureDir(targetDir);
42
+ }
43
+ }
44
+ catch {
45
+ const home = __sys__.os.homeDir
46
+ ? __sys__.os.homeDir()
47
+ : (typeof process !== "undefined" && process.env.HOME) || "/tmp";
48
+ targetDir = path.join(home, ".xynginc", "bin");
49
+ fs.ensureDir(targetDir);
50
+ }
51
+ const localPath = path.join(targetDir, BINARY_NAME);
27
52
  return new Promise((resolve, reject) => {
28
- function download(url) {
53
+ function download(url, redirectCount = 0) {
54
+ if (redirectCount > 5) {
55
+ reject(new Error("[XyNginC] Too many redirects while downloading binary"));
56
+ return;
57
+ }
29
58
  https
30
- .get(url, (response) => {
59
+ .get(url, {
60
+ headers: {
61
+ "User-Agent": "xynginc",
62
+ Accept: "*/*",
63
+ },
64
+ }, (response) => {
31
65
  if (response.statusCode &&
32
66
  response.statusCode >= 300 &&
33
67
  response.statusCode < 400 &&
34
68
  response.headers.location) {
35
- download(response.headers.location);
69
+ response.resume();
70
+ download(response.headers.location, redirectCount + 1);
36
71
  return;
37
72
  }
38
73
  if (response.statusCode !== 200) {
39
- reject(new Error(`Failed to download binary: HTTP ${response.statusCode}`));
74
+ response.resume();
75
+ reject(new Error(`Failed to download binary from ${url}: HTTP ${response.statusCode}`));
40
76
  return;
41
77
  }
42
78
  const file = fs.createWriteStream(localPath);
79
+ file.on("error", (err) => {
80
+ try {
81
+ fs.rmIfExists(localPath);
82
+ }
83
+ catch { }
84
+ reject(err);
85
+ });
86
+ response.on("error", (err) => {
87
+ try {
88
+ fs.rmIfExists(localPath);
89
+ }
90
+ catch { }
91
+ reject(err);
92
+ });
43
93
  response.pipe(file);
44
94
  file.on("finish", () => {
45
- file.close(); //
46
- fs.chmod(localPath, "755"); // Make executable (octal version: 0o755)
95
+ file.close();
96
+ try {
97
+ fs.chmod(localPath, 0o755);
98
+ }
99
+ catch {
100
+ // Ignore chmod error if any
101
+ }
47
102
  Logger.success("[XyNginC] ✓ Binary downloaded successfully");
48
103
  resolve(localPath);
49
104
  });
50
105
  })
51
106
  .on("error", (err) => {
52
- fs.rmIfExists(localPath);
107
+ try {
108
+ fs.rmIfExists(localPath);
109
+ }
110
+ catch { }
53
111
  reject(err);
54
112
  });
55
113
  }
@@ -30,12 +30,28 @@ export async function ensureBinary(customPath, autoDownload, version) {
30
30
  catch {
31
31
  // Not in PATH
32
32
  }
33
- // 3. Try local bin directory
33
+ // 3. Try local bin directory (<package_root>/bin/xynginc)
34
34
  const localPath = path.join(BINARY_DIR, BINARY_NAME);
35
35
  if (fs.exists(localPath)) {
36
+ try {
37
+ fs.chmod(localPath, 0o755);
38
+ }
39
+ catch { }
36
40
  return localPath;
37
41
  }
38
- // 4. Auto-download if enabled
42
+ // 4. Try user home cache (~/.xynginc/bin/xynginc)
43
+ const home = __sys__.os.homeDir
44
+ ? __sys__.os.homeDir()
45
+ : (typeof process !== "undefined" && process.env.HOME) || "/tmp";
46
+ const userPath = path.join(home, ".xynginc", "bin", BINARY_NAME);
47
+ if (fs.exists(userPath)) {
48
+ try {
49
+ fs.chmod(userPath, 0o755);
50
+ }
51
+ catch { }
52
+ return userPath;
53
+ }
54
+ // 5. Auto-download if enabled
39
55
  if (autoDownload) {
40
56
  Logger.info("[XyNginC] Binary not found, downloading...");
41
57
  return await downloadBinary(version);
@@ -13,9 +13,10 @@ export async function installRequirementsHandler(binaryPath, sudoCmd) {
13
13
  Logger.info("[XyNginC] Launching interactive installer...");
14
14
  Logger.info("[XyNginC] Please respond to any prompts in the terminal.");
15
15
  // Handle process environmental logic if running via non-interactive sudo -S
16
+ const envPrefix = "env XYNC_INSTALL_MODE=non-interactive DEBIAN_FRONTEND=noninteractive";
16
17
  const cmd = sudoCmd.includes("-S")
17
- ? `${sudoCmd} ${binaryPath} install`
18
- : `sudo ${binaryPath} install`;
18
+ ? `${sudoCmd} ${envPrefix} ${binaryPath} install`
19
+ : `sudo ${envPrefix} ${binaryPath} install`;
19
20
  // Spawn the process with inherited stdio for full interactivity
20
21
  const installProcess = spawn(cmd, {
21
22
  stdio: "inherit", // This allows the subprocess to use the parent's stdin/stdout/stderr
@@ -35,6 +36,8 @@ export async function installRequirementsHandler(binaryPath, sudoCmd) {
35
36
  });
36
37
  });
37
38
  }
39
+ const fs = __sys__.fs;
40
+ const path = __sys__.path;
38
41
  /**
39
42
  * Applies the configuration using the xynginc binary.
40
43
  *
@@ -52,8 +55,8 @@ export async function applyConfig(binaryPath, config, sudoCmd) {
52
55
  port: d.port,
53
56
  ssl: d.ssl,
54
57
  email: d.email,
55
- host: d.host,
56
- max_body_size: d.maxBodySize,
58
+ host: d.host || "127.0.0.1",
59
+ max_body_size: d.maxBodySize || "10M",
57
60
  })),
58
61
  };
59
62
  const configJson = XStringify(mappedConfig, {
@@ -63,6 +66,9 @@ export async function applyConfig(binaryPath, config, sudoCmd) {
63
66
  truncateStrings: 1000000, // 1MB limit per string (pour le HTML)
64
67
  reportCircularPath: true,
65
68
  });
69
+ const tmpDir = typeof fs.tempDir === "function" ? fs.tempDir() : "/tmp";
70
+ const tempConfigFile = path.join(tmpDir, `.xynginc-config-${Date.now()}-${Math.random().toString(36).substring(2, 8)}.json`);
71
+ fs.writeFileSync(tempConfigFile, configJson);
66
72
  try {
67
73
  // Test nginx BEFORE applying new config
68
74
  Logger.info("[XyNginC] Testing current nginx config...");
@@ -70,21 +76,8 @@ export async function applyConfig(binaryPath, config, sudoCmd) {
70
76
  if (!testResult) {
71
77
  Logger.warn("[XyNginC] ⚠️ Current nginx config has errors. Attempting to fix...");
72
78
  }
73
- // Pass config via stdin to avoid shell escaping issues
74
- // If using sudo -S, we must pass both the password and the JSON in the same pipe
75
- if (sudoCmd.includes("-S")) {
76
- const pwdMatch = sudoCmd.match(/echo '(.*)' \| sudo -S/);
77
- if (pwdMatch) {
78
- const pwd = pwdMatch[1];
79
- await execStream(`(echo '${pwd}'; echo '${configJson}') | sudo -S ${binaryPath} apply --config -`);
80
- }
81
- else {
82
- await execStream(`echo '${configJson}' | ${sudoCmd} ${binaryPath} apply --config -`);
83
- }
84
- }
85
- else {
86
- await execStream(`echo '${configJson}' | ${sudoCmd} ${binaryPath} apply --config -`);
87
- }
79
+ // Apply configuration using the temporary file to avoid stdin collision with sudo
80
+ await execStream(`${sudoCmd} ${binaryPath} apply --config ${tempConfigFile}`);
88
81
  }
89
82
  catch (error) {
90
83
  // If it fails, show more helpful error
@@ -93,6 +86,12 @@ export async function applyConfig(binaryPath, config, sudoCmd) {
93
86
  Logger.info("[XyNginC] Check: /etc/nginx/sites-enabled/");
94
87
  throw new Error(`Failed to apply configuration: ${error.message}`);
95
88
  }
89
+ finally {
90
+ try {
91
+ fs.rmIfExists(tempConfigFile);
92
+ }
93
+ catch { }
94
+ }
96
95
  }
97
96
  /**
98
97
  * Adds a new domain configuration using the binary.
@@ -209,7 +208,7 @@ export async function checkRequirements(binaryPath, sudoCmd) {
209
208
  try {
210
209
  Logger.info("[XyNginC] Checking system requirements...");
211
210
  const cmd = `${sudoCmd} ${binaryPath} check`;
212
- Logger.info(`[XyNginC] Running: ${cmd}`);
211
+ // Logger.info(`[XyNginC] Running: ${__sys__.utils.str.of(cmd).between("ech")}`);
213
212
  await execStream(cmd);
214
213
  Logger.info("[XyNginC] System requirements checked successfully!");
215
214
  return true;
@@ -26,6 +26,8 @@ const getSudo = (sudoPassword) => {
26
26
  // Return non-interactive sudo to prevent infinite blocking/hanging
27
27
  return "sudo -n";
28
28
  };
29
+ let appliedConfigKey = null;
30
+ let activeInitPromise = null;
29
31
  export async function startXNCPlugin(server, options) {
30
32
  const { binaryPath, autoDownload, version, domains, autoReload, autoFixFirewall, installRequirements, sudoPassword, } = options;
31
33
  Logger.info("[XyNginC] Initializing Nginx Controller...");
@@ -42,28 +44,50 @@ export async function startXNCPlugin(server, options) {
42
44
  binary,
43
45
  },
44
46
  });
45
- // 2. Check system requirements
46
- Logger.info("[XyNginC] Checking system requirements...");
47
- // Check if requirements are satisfied
48
- const requirementsOk = await checkRequirements(binary, getSudo(sudoPassword));
49
- // Install requirements if enabled and needed
50
- if (!requirementsOk && installRequirements) {
51
- Logger.info("[XyNginC] Requirements missing, installing automatically...");
52
- await installRequirementsHandler(binary, getSudo(sudoPassword));
53
- Logger.info("[XyNginC] Requirements installed, re-checking...");
54
- await checkRequirements(binary, getSudo(sudoPassword));
47
+ const configKey = JSON.stringify({
48
+ domains,
49
+ autoReload,
50
+ autoFixFirewall,
51
+ });
52
+ if (appliedConfigKey !== configKey) {
53
+ if (!activeInitPromise) {
54
+ activeInitPromise = (async () => {
55
+ // 2. Check system requirements
56
+ Logger.info("[XyNginC] Checking system requirements...");
57
+ const requirementsOk = await checkRequirements(binary, getSudo(sudoPassword));
58
+ if (!requirementsOk && installRequirements) {
59
+ Logger.info("[XyNginC] Requirements missing, installing automatically...");
60
+ await installRequirementsHandler(binary, getSudo(sudoPassword));
61
+ Logger.info("[XyNginC] Requirements installed, re-checking...");
62
+ const recheckOk = await checkRequirements(binary, getSudo(sudoPassword));
63
+ if (!recheckOk) {
64
+ throw new Error("[XyNginC] System requirements installation failed or was incomplete. Please run 'sudo xynginc install' manually.");
65
+ }
66
+ }
67
+ else if (!requirementsOk) {
68
+ throw new Error("[XyNginC] System requirements not satisfied. Install with 'installRequirements: true' or run: sudo xynginc install");
69
+ }
70
+ // 3. Apply configuration
71
+ Logger.info("[XyNginC] Applying configuration...");
72
+ await applyConfig(binary, {
73
+ domains,
74
+ auto_reload: autoReload,
75
+ auto_fix_firewall: autoFixFirewall,
76
+ }, getSudo(sudoPassword));
77
+ appliedConfigKey = configKey;
78
+ Logger.success("[XyNginC] Configuration applied successfully!");
79
+ })();
80
+ }
81
+ try {
82
+ await activeInitPromise;
83
+ }
84
+ finally {
85
+ activeInitPromise = null;
86
+ }
55
87
  }
56
- else if (!requirementsOk) {
57
- throw new Error("[XyNginC] System requirements not satisfied. Install with 'installRequirements: true' or run: sudo xynginc install");
88
+ else {
89
+ Logger.info("[XyNginC] Configuration already applied for this multi-server group.");
58
90
  }
59
- // 3. Apply configuration
60
- Logger.info("[XyNginC] Applying configuration...");
61
- await applyConfig(binary, {
62
- domains,
63
- auto_reload: autoReload,
64
- auto_fix_firewall: autoFixFirewall,
65
- }, getSudo(sudoPassword));
66
- Logger.success("[XyNginC] Configuration applied successfully!");
67
91
  // Expose CLI helper methods on server
68
92
  const sUtil = {
69
93
  addDomain: (domain, port, ssl = false, email, maxBodySize) => addDomain(binary, domain, port, ssl, email, maxBodySize, getSudo(sudoPassword)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xynginc",
3
- "version": "1.0.95",
3
+ "version": "1.0.97",
4
4
  "description": "XyPriss Nginx Controller - Automatic Nginx & SSL management for XyPriss servers",
5
5
  "author": "Seth Eleazar - iDevo",
6
6
  "license": "NOSL",
@@ -48,7 +48,7 @@
48
48
  "xypriss-security": "^2.1.16"
49
49
  },
50
50
  "peerDependencies": {
51
- "xypriss": ">=9.9.7"
51
+ "xypriss": ">=9.12.69"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^20.19.37",
@@ -27,32 +27,57 @@ if (!fs.existsSync(binDir)) {
27
27
  fs.mkdirSync(binDir, { recursive: true });
28
28
  }
29
29
 
30
- function download(url, dest) {
30
+ function download(url, dest, redirectCount = 0) {
31
31
  return new Promise((resolve, reject) => {
32
+ if (redirectCount > 5) {
33
+ reject(new Error("Too many redirects while downloading binary"));
34
+ return;
35
+ }
36
+
32
37
  https
33
- .get(url, (response) => {
34
- if (
35
- response.statusCode >= 300 &&
36
- response.statusCode < 400 &&
37
- response.headers.location
38
- ) {
39
- // Recurse for redirects
40
- download(response.headers.location, dest).then(resolve).catch(reject);
41
- return;
42
- }
43
-
44
- if (response.statusCode !== 200) {
45
- reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
46
- return;
47
- }
48
-
49
- const file = fs.createWriteStream(dest);
50
- response.pipe(file);
51
- file.on("finish", () => {
52
- file.close();
53
- resolve();
54
- });
55
- })
38
+ .get(
39
+ url,
40
+ {
41
+ headers: {
42
+ "User-Agent": "xynginc",
43
+ Accept: "*/*",
44
+ },
45
+ },
46
+ (response) => {
47
+ if (
48
+ response.statusCode >= 300 &&
49
+ response.statusCode < 400 &&
50
+ response.headers.location
51
+ ) {
52
+ response.resume();
53
+ download(response.headers.location, dest, redirectCount + 1)
54
+ .then(resolve)
55
+ .catch(reject);
56
+ return;
57
+ }
58
+
59
+ if (response.statusCode !== 200) {
60
+ response.resume();
61
+ reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
62
+ return;
63
+ }
64
+
65
+ const file = fs.createWriteStream(dest);
66
+ file.on("error", (err) => {
67
+ fs.unlink(dest, () => {});
68
+ reject(err);
69
+ });
70
+ response.on("error", (err) => {
71
+ fs.unlink(dest, () => {});
72
+ reject(err);
73
+ });
74
+ response.pipe(file);
75
+ file.on("finish", () => {
76
+ file.close();
77
+ resolve();
78
+ });
79
+ },
80
+ )
56
81
  .on("error", (err) => {
57
82
  fs.unlink(dest, () => {});
58
83
  reject(err);
@@ -1,11 +1,11 @@
1
1
  --- XYPRISS SIGNATURE (G3) ---
2
- Manifest: xynginc@1.0.95
2
+ Manifest: xynginc@1.0.97
3
3
  Min-Engine: 1.0.81
4
- Fingerprint: sha256:29164ca30e93b4db58ec04a683f11e643738961ff5539561b0cbc02b0fc77216
4
+ Fingerprint: sha256:bcb7ff347f32fadda4fb97221c8a3a345c829487b96f5b4f9917ce339d3db7c3
5
5
  Identity: ed25519:0e2e1accdbba45480979305256d50d368ae6cc2c0d7ee378c5f1999ae834cf58
6
6
  Privileges: XHS.HOOK.LIFECYCLE.REGISTER,XHS.HOOK.LIFECYCLE.SERVER_START,XHS.HOOK.LIFECYCLE.SERVER_STOP,XHS.HOOK.LIFECYCLE.SERVER_READY
7
- Expires: 2027-09-13T14:10:40Z
7
+ Expires: 2027-09-13T22:23:40Z
8
8
  Revision: sha256:none
9
9
  --- BEGIN CRYPTOGRAPHIC PROOF ---
10
- base64:xPDNo3n8nqha9OGhhNRkMKdJxiMGy5ZddvDGcuv314t/imBXHpBjhrAM+AVWpsKEsP8gJC5jximCDzvaGFeDDg==
10
+ base64:5GY1tW/vThkWfbexGer3o5a+/mZ9gaF+W6Iv85Odb7eH4uzU9/Vhx39WpjT4gytE0ftwsmeSxGjk/SscaDAsDw==
11
11
  --- END XYPRISS SIGNATURE ---