@lopatnov/conduit 0.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 ADDED
@@ -0,0 +1,99 @@
1
+ # @lopatnov/conduit
2
+
3
+ [![npm](https://img.shields.io/npm/v/@lopatnov/conduit.svg)](https://www.npmjs.com/package/@lopatnov/conduit)
4
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/lopatnov/conduit/blob/main/LICENSE)
5
+
6
+ **High-performance reverse proxy and static file server** — powered by [Cloudflare Pingora](https://github.com/cloudflare/pingora). Runs as a native Rust binary, distributed via npm for convenience.
7
+
8
+ - **Single binary** — no Node.js runtime needed after install
9
+ - **One JSON file** describes your entire server
10
+ - Static file server, reverse proxy, TLS termination, HTTP/2, IP filtering, redirects
11
+ - Hot-reload in dev and Auto-TLS (Let's Encrypt) in production — [coming soon](https://github.com/lopatnov/conduit#implementation-status)
12
+ - Drop-in upgrade path from `express-reverse-proxy`
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ # Use without installing
18
+ npx @lopatnov/conduit
19
+
20
+ # Or install globally
21
+ npm install -g @lopatnov/conduit
22
+ ```
23
+
24
+ The `postinstall` script automatically downloads the correct pre-built binary for your
25
+ platform from [GitHub Releases](https://github.com/lopatnov/conduit/releases).
26
+
27
+ To skip the download (e.g., you built from source and placed the binary yourself):
28
+
29
+ ```bash
30
+ CONDUIT_SKIP_DOWNLOAD=1 npm install @lopatnov/conduit
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ```bash
36
+ # Interactive setup wizard
37
+ conduit init
38
+
39
+ # Start the server
40
+ conduit
41
+
42
+ # Validate config without starting
43
+ conduit validate
44
+ ```
45
+
46
+ Minimal `conduit.json`:
47
+
48
+ ```json
49
+ {
50
+ "port": 3000,
51
+ "static": "./dist",
52
+ "proxy": { "/api": "http://localhost:4000" }
53
+ }
54
+ ```
55
+
56
+ ## CLI
57
+
58
+ ```bash
59
+ conduit start server (reads conduit.json)
60
+ conduit -c <file> use a specific config file
61
+ conduit init interactive wizard
62
+ conduit validate validate config (exit 0 = OK)
63
+ conduit probe HEAD to every upstream
64
+ conduit fmt [--write] pretty-print config
65
+ conduit reload hot-reload config (no restart)
66
+ conduit status server status
67
+ conduit upstreams upstream health and latency
68
+ conduit shutdown graceful shutdown
69
+ conduit --version
70
+ ```
71
+
72
+ ## Supported Platforms
73
+
74
+ | Platform | Architecture | Supported |
75
+ | -------- | --------------------- | --------- |
76
+ | Linux | x86-64 | ✅ |
77
+ | Linux | ARM64 | ✅ |
78
+ | macOS | x86-64 (Intel) | ✅ |
79
+ | macOS | ARM64 (Apple Silicon) | ✅ |
80
+ | Windows | x86-64 | ✅ |
81
+
82
+ ## Alternatives
83
+
84
+ If the binary download fails or your platform is not supported, install from source:
85
+
86
+ ```bash
87
+ cargo install lopatnov-conduit
88
+ ```
89
+
90
+ Or download a pre-built binary directly from
91
+ [GitHub Releases](https://github.com/lopatnov/conduit/releases).
92
+
93
+ ## Documentation
94
+
95
+ Full documentation: <https://github.com/lopatnov/conduit>
96
+
97
+ ## License
98
+
99
+ [Apache 2.0](https://github.com/lopatnov/conduit/blob/main/LICENSE)
package/bin/conduit.js ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Conduit — platform wrapper.
4
+ *
5
+ * Resolves the native binary for the current OS/arch and spawns it,
6
+ * passing all CLI arguments and stdio through transparently.
7
+ */
8
+
9
+ import { spawn } from "node:child_process";
10
+ import { existsSync } from "node:fs";
11
+ import { fileURLToPath } from "node:url";
12
+ import { dirname, join } from "node:path";
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+
16
+ // --------------------------------------------------------------------------
17
+ // Platform → binary name mapping (matches GitHub Release asset names)
18
+ // --------------------------------------------------------------------------
19
+ function getPlatformBinary() {
20
+ const { platform, arch } = process;
21
+
22
+ const map = {
23
+ "linux-x64": "conduit-x86_64-unknown-linux-gnu",
24
+ "linux-arm64": "conduit-aarch64-unknown-linux-gnu",
25
+ "darwin-x64": "conduit-x86_64-apple-darwin",
26
+ "darwin-arm64":"conduit-aarch64-apple-darwin",
27
+ "win32-x64": "conduit-x86_64-pc-windows-msvc.exe",
28
+ };
29
+
30
+ const key = `${platform}-${arch}`;
31
+ const name = map[key];
32
+
33
+ if (!name) {
34
+ console.error(
35
+ `[conduit] Unsupported platform: ${key}\n` +
36
+ `Supported: ${Object.keys(map).join(", ")}\n` +
37
+ `Install from source: cargo install conduit-proxy`
38
+ );
39
+ process.exit(1);
40
+ }
41
+
42
+ return name;
43
+ }
44
+
45
+ // --------------------------------------------------------------------------
46
+ // Locate binary
47
+ // --------------------------------------------------------------------------
48
+ const nativeDir = join(__dirname, "native");
49
+ const binaryName = getPlatformBinary();
50
+ const binaryPath = join(nativeDir, binaryName);
51
+
52
+ if (!existsSync(binaryPath)) {
53
+ console.error(
54
+ `[conduit] Native binary not found: ${binaryPath}\n` +
55
+ `Try reinstalling: npm install @lopatnov/conduit\n` +
56
+ `Or install from source: cargo install conduit-proxy`
57
+ );
58
+ process.exit(1);
59
+ }
60
+
61
+ // --------------------------------------------------------------------------
62
+ // Spawn
63
+ // --------------------------------------------------------------------------
64
+ const child = spawn(binaryPath, process.argv.slice(2), {
65
+ stdio: "inherit",
66
+ env: process.env,
67
+ });
68
+
69
+ child.on("error", (err) => {
70
+ console.error(`[conduit] Failed to start binary: ${err.message}`);
71
+ process.exit(1);
72
+ });
73
+
74
+ child.on("exit", (code, signal) => {
75
+ if (signal) {
76
+ process.kill(process.pid, signal);
77
+ } else {
78
+ process.exit(code ?? 0);
79
+ }
80
+ });
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Conduit — postinstall downloader.
4
+ *
5
+ * Downloads the native binary for the current platform from GitHub Releases.
6
+ * Runs automatically after `npm install`.
7
+ *
8
+ * Skipped when:
9
+ * - CONDUIT_SKIP_DOWNLOAD=1 (opt-out for custom installs)
10
+ * - npm lifecycle is "ci" (avoid re-downloading in production installs
11
+ * where the binary is vendored or built locally)
12
+ */
13
+
14
+ import { createWriteStream, existsSync, mkdirSync, chmodSync, unlinkSync } from "node:fs";
15
+ import { get } from "node:https";
16
+ import { fileURLToPath } from "node:url";
17
+ import { dirname, join } from "node:path";
18
+ import { readFileSync } from "node:fs";
19
+
20
+ // --------------------------------------------------------------------------
21
+ // Skip conditions
22
+ // --------------------------------------------------------------------------
23
+ if (process.env.CONDUIT_SKIP_DOWNLOAD === "1") {
24
+ process.exit(0);
25
+ }
26
+
27
+ // --------------------------------------------------------------------------
28
+ // Config
29
+ // --------------------------------------------------------------------------
30
+ const __dirname = dirname(fileURLToPath(import.meta.url));
31
+ const pkgPath = join(__dirname, "..", "package.json");
32
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
33
+ const VERSION = pkg.version;
34
+ const REPO = "lopatnov/conduit";
35
+ const NATIVE_DIR = join(__dirname, "native");
36
+
37
+ // --------------------------------------------------------------------------
38
+ // Platform → asset name
39
+ // --------------------------------------------------------------------------
40
+ function getAssetName() {
41
+ const { platform, arch } = process;
42
+
43
+ const map = {
44
+ "linux-x64": "conduit-x86_64-unknown-linux-gnu",
45
+ "linux-arm64": "conduit-aarch64-unknown-linux-gnu",
46
+ "darwin-x64": "conduit-x86_64-apple-darwin",
47
+ "darwin-arm64": "conduit-aarch64-apple-darwin",
48
+ "win32-x64": "conduit-x86_64-pc-windows-msvc.exe",
49
+ };
50
+
51
+ const key = `${platform}-${arch}`;
52
+ const name = map[key];
53
+
54
+ if (!name) {
55
+ console.warn(
56
+ `[conduit] Skipping binary download — unsupported platform: ${key}\n` +
57
+ `Install from source: cargo install conduit-proxy`
58
+ );
59
+ process.exit(0);
60
+ }
61
+
62
+ return name;
63
+ }
64
+
65
+ // --------------------------------------------------------------------------
66
+ // Download helper (follows redirects, no external dependencies)
67
+ // --------------------------------------------------------------------------
68
+ const MAX_REDIRECTS = 10;
69
+
70
+ function download(url, dest) {
71
+ return new Promise((resolve, reject) => {
72
+ const file = createWriteStream(dest);
73
+ let redirectCount = 0;
74
+
75
+ function request(url) {
76
+ get(url, { headers: { "User-Agent": `conduit-npm/${VERSION}` } }, (res) => {
77
+ if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307 || res.statusCode === 308) {
78
+ if (++redirectCount > MAX_REDIRECTS) {
79
+ file.close();
80
+ try { unlinkSync(dest); } catch { /* ignore */ }
81
+ reject(new Error(`Too many redirects (>${MAX_REDIRECTS}) for ${url}`));
82
+ return;
83
+ }
84
+ if (!res.headers.location) {
85
+ file.close();
86
+ try { unlinkSync(dest); } catch { /* ignore */ }
87
+ reject(new Error(`Redirect with no Location header from ${url}`));
88
+ return;
89
+ }
90
+ // Follow redirect (resolve relative URLs against current URL).
91
+ request(new URL(res.headers.location, url).href);
92
+ return;
93
+ }
94
+
95
+ if (res.statusCode !== 200) {
96
+ file.close();
97
+ try { unlinkSync(dest); } catch { /* ignore */ }
98
+ reject(new Error(`HTTP ${res.statusCode} for ${url}`));
99
+ return;
100
+ }
101
+
102
+ const total = parseInt(res.headers["content-length"] || "0", 10);
103
+ let received = 0;
104
+ let lastPct = -1;
105
+
106
+ res.on("data", (chunk) => {
107
+ received += chunk.length;
108
+ if (total > 0) {
109
+ const pct = Math.floor((received / total) * 100);
110
+ if (pct !== lastPct && pct % 10 === 0) {
111
+ process.stdout.write(`\r[conduit] Downloading... ${pct}%`);
112
+ lastPct = pct;
113
+ }
114
+ }
115
+ });
116
+
117
+ res.pipe(file);
118
+ file.on("finish", () => {
119
+ file.close(() => {
120
+ process.stdout.write("\r[conduit] Downloading... done \n");
121
+ resolve();
122
+ });
123
+ });
124
+ }).on("error", (err) => {
125
+ file.close();
126
+ try { unlinkSync(dest); } catch { /* ignore */ }
127
+ reject(err);
128
+ });
129
+ }
130
+
131
+ request(url);
132
+ });
133
+ }
134
+
135
+ // --------------------------------------------------------------------------
136
+ // Main
137
+ // --------------------------------------------------------------------------
138
+ async function main() {
139
+ const assetName = getAssetName();
140
+ const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${assetName}`;
141
+ const dest = join(NATIVE_DIR, assetName);
142
+
143
+ // Skip if already downloaded (idempotent)
144
+ if (existsSync(dest)) {
145
+ return;
146
+ }
147
+
148
+ mkdirSync(NATIVE_DIR, { recursive: true });
149
+
150
+ console.log(`[conduit] Downloading v${VERSION} for ${process.platform}/${process.arch}`);
151
+ console.log(`[conduit] Source: ${url}`);
152
+
153
+ try {
154
+ await download(url, dest);
155
+ } catch (err) {
156
+ console.error(`\n[conduit] Download failed: ${err.message}`);
157
+ console.error(`[conduit] You can install from source: cargo install conduit-proxy`);
158
+ // Exit 0 so npm install doesn't fail for the whole project
159
+ process.exit(0);
160
+ }
161
+
162
+ // Make executable on Unix
163
+ if (process.platform !== "win32") {
164
+ chmodSync(dest, 0o755);
165
+ }
166
+
167
+ console.log(`[conduit] Binary installed to ${dest}`);
168
+ }
169
+
170
+ main();
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@lopatnov/conduit",
3
+ "version": "0.2.0",
4
+ "description": "High-performance reverse proxy and static file server powered by Cloudflare Pingora",
5
+ "keywords": [
6
+ "reverse-proxy",
7
+ "proxy",
8
+ "static-server",
9
+ "http",
10
+ "https",
11
+ "http2",
12
+ "load-balancer",
13
+ "rust",
14
+ "pingora"
15
+ ],
16
+ "homepage": "https://github.com/lopatnov/conduit#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/lopatnov/conduit/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/lopatnov/conduit.git"
23
+ },
24
+ "license": "Apache-2.0",
25
+ "author": "lopatnov",
26
+ "type": "module",
27
+ "bin": {
28
+ "conduit": "./bin/conduit.js"
29
+ },
30
+ "files": [
31
+ "bin/",
32
+ "Readme.md"
33
+ ],
34
+ "scripts": {
35
+ "postinstall": "node bin/download.js"
36
+ },
37
+ "engines": {
38
+ "node": ">=18.0.0"
39
+ }
40
+ }