xynginc 1.0.95 → 1.0.96

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);
@@ -35,6 +35,8 @@ export async function installRequirementsHandler(binaryPath, sudoCmd) {
35
35
  });
36
36
  });
37
37
  }
38
+ const fs = __sys__.fs;
39
+ const path = __sys__.path;
38
40
  /**
39
41
  * Applies the configuration using the xynginc binary.
40
42
  *
@@ -52,8 +54,8 @@ export async function applyConfig(binaryPath, config, sudoCmd) {
52
54
  port: d.port,
53
55
  ssl: d.ssl,
54
56
  email: d.email,
55
- host: d.host,
56
- max_body_size: d.maxBodySize,
57
+ host: d.host || "127.0.0.1",
58
+ max_body_size: d.maxBodySize || "10M",
57
59
  })),
58
60
  };
59
61
  const configJson = XStringify(mappedConfig, {
@@ -63,6 +65,9 @@ export async function applyConfig(binaryPath, config, sudoCmd) {
63
65
  truncateStrings: 1000000, // 1MB limit per string (pour le HTML)
64
66
  reportCircularPath: true,
65
67
  });
68
+ const tmpDir = typeof fs.tempDir === "function" ? fs.tempDir() : "/tmp";
69
+ const tempConfigFile = path.join(tmpDir, `.xynginc-config-${Date.now()}-${Math.random().toString(36).substring(2, 8)}.json`);
70
+ fs.writeFileSync(tempConfigFile, configJson);
66
71
  try {
67
72
  // Test nginx BEFORE applying new config
68
73
  Logger.info("[XyNginC] Testing current nginx config...");
@@ -70,21 +75,8 @@ export async function applyConfig(binaryPath, config, sudoCmd) {
70
75
  if (!testResult) {
71
76
  Logger.warn("[XyNginC] ⚠️ Current nginx config has errors. Attempting to fix...");
72
77
  }
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
- }
78
+ // Apply configuration using the temporary file to avoid stdin collision with sudo
79
+ await execStream(`${sudoCmd} ${binaryPath} apply --config ${tempConfigFile}`);
88
80
  }
89
81
  catch (error) {
90
82
  // If it fails, show more helpful error
@@ -93,6 +85,12 @@ export async function applyConfig(binaryPath, config, sudoCmd) {
93
85
  Logger.info("[XyNginC] Check: /etc/nginx/sites-enabled/");
94
86
  throw new Error(`Failed to apply configuration: ${error.message}`);
95
87
  }
88
+ finally {
89
+ try {
90
+ fs.rmIfExists(tempConfigFile);
91
+ }
92
+ catch { }
93
+ }
96
94
  }
97
95
  /**
98
96
  * Adds a new domain configuration using the binary.
@@ -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,47 @@ 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
+ await checkRequirements(binary, getSudo(sudoPassword));
63
+ }
64
+ else if (!requirementsOk) {
65
+ throw new Error("[XyNginC] System requirements not satisfied. Install with 'installRequirements: true' or run: sudo xynginc install");
66
+ }
67
+ // 3. Apply configuration
68
+ Logger.info("[XyNginC] Applying configuration...");
69
+ await applyConfig(binary, {
70
+ domains,
71
+ auto_reload: autoReload,
72
+ auto_fix_firewall: autoFixFirewall,
73
+ }, getSudo(sudoPassword));
74
+ appliedConfigKey = configKey;
75
+ Logger.success("[XyNginC] Configuration applied successfully!");
76
+ })();
77
+ }
78
+ try {
79
+ await activeInitPromise;
80
+ }
81
+ finally {
82
+ activeInitPromise = null;
83
+ }
55
84
  }
56
- else if (!requirementsOk) {
57
- throw new Error("[XyNginC] System requirements not satisfied. Install with 'installRequirements: true' or run: sudo xynginc install");
85
+ else {
86
+ Logger.info("[XyNginC] Configuration already applied for this multi-server group.");
58
87
  }
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
88
  // Expose CLI helper methods on server
68
89
  const sUtil = {
69
90
  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.96",
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.96
3
3
  Min-Engine: 1.0.81
4
- Fingerprint: sha256:29164ca30e93b4db58ec04a683f11e643738961ff5539561b0cbc02b0fc77216
4
+ Fingerprint: sha256:e7e5a57bf5e0a262062baa0c78a5c59c91044c00da42aabc6af91edffc80abbe
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:07:58Z
8
8
  Revision: sha256:none
9
9
  --- BEGIN CRYPTOGRAPHIC PROOF ---
10
- base64:xPDNo3n8nqha9OGhhNRkMKdJxiMGy5ZddvDGcuv314t/imBXHpBjhrAM+AVWpsKEsP8gJC5jximCDzvaGFeDDg==
10
+ base64:VSQiC693lzyQXwMlaho4Hlequr97rUQCbWIx2KhaGMNBRiiEMjn1RBSFPGCskwaA3nUmmcBMd23LTAxDpERaDw==
11
11
  --- END XYPRISS SIGNATURE ---