# pipenet Expose your local server to the public internet instantly ## Installation ```bash npm install pipenet ``` ## CLI Usage ```bash # Expose local port 3000 to the internet npx pipenet client --port 3000 # Request a specific subdomain npx pipenet client --port 3000 --subdomain myapp # Use a custom tunnel server npx pipenet client --port 3000 --host https://your-tunnel-server.com ``` ## API The pipenet client is also usable through an API (for test integration, automation, etc) ### pipenet(port [,options][,callback]) Creates a new pipenet tunnel to the specified local `port`. Will return a Promise that resolves once you have been assigned a public tunnel url. `options` can be used to request a specific `subdomain`. A `callback` function can be passed, in which case it won't return a Promise. This exists for backwards compatibility with the old Node-style callback API. You may also pass a single options object with `port` as a property. ```js import { pipenet } from 'pipenet'; const tunnel = await pipenet({ port: 3000, host: 'https://pipenet.dev' }); // the assigned public url for your tunnel // i.e. https://abcdefgjhij.pipenet.dev tunnel.url; tunnel.on('close', () => { // tunnels are closed }); ``` #### options - `port` (number) [required] The local port number to expose through pipenet. - `host` (string) URL for the upstream proxy server. Defaults to `https://pipenet.dev`. - `subdomain` (string) Request a specific subdomain on the proxy server. **Note** You may not actually receive this name depending on availability. - `localHost` (string) Proxy to this hostname instead of `localhost`. This will also cause the `Host` header to be re-written to this value in proxied requests. - `localHttps` (boolean) Enable tunneling to local HTTPS server. - `localCert` (string) Path to certificate PEM file for local HTTPS server. - `localKey` (string) Path to certificate key file for local HTTPS server. - `localCa` (string) Path to certificate authority file for self-signed certificates. - `allowInvalidCert` (boolean) Disable certificate checks for your local HTTPS server (ignore cert/key/ca options). Refer to [tls.createSecureContext](https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options) for details on the certificate options. ### Tunnel The `tunnel` instance returned to your callback emits the following events | event | args | description | | ------- | ---- | ------------------------------------------------------------------------------------ | | request | info | fires when a request is processed by the tunnel, contains _method_ and _path_ fields | | error | err | fires when an error happens on the tunnel | | close | | fires when the tunnel has closed | The `tunnel` instance has the following methods | method | args | description | | ------ | ---- | ---------------- | | close | | close the tunnel | ## Server This package includes both the client and server components. You can run your own pipenet server. ### Running the Server ```bash # Using the CLI npx pipenet server --port 3000 # With a custom domain npx pipenet server --port 3000 --domain tunnel.example.com # With multiple domains npx pipenet server --port 3000 --domain tunnel.example.com --domain tunnel.example.org # For cloud deployments (single tunnel port mode) npx pipenet server --port 3000 --tunnel-port 3001 --domain tunnel.example.com # Or programmatically ``` ```js import { createServer } from 'pipenet/server'; const server = createServer({ domains: ['tunnel.example.com'], // Optional: custom domain(s) secure: false, // Optional: require HTTPS landing: 'https://pipenet.dev', // Optional: landing page URL maxTcpSockets: 10, // Optional: max sockets per client tunnelPort: 3001, // Optional: shared tunnel port for cloud deployments // Lifecycle hooks for tracking tunnels and requests onTunnelCreated: (tunnel) => { console.log(`Tunnel created: ${tunnel.id} at ${tunnel.url}`); }, onTunnelClosed: (tunnel) => { console.log(`Tunnel closed: ${tunnel.id}`); }, onRequest: (request) => { console.log(`Request: ${request.method} ${request.path} via ${request.tunnelId}`); }, }); // Start tunnel server if using shared tunnel port if (server.tunnelServer) { await server.tunnelServer.listen(3001); } server.listen(3000, () => { console.log('pipenet server listening on port 3000'); }); ``` ### Server Options - `domains` (string[]) Custom domain(s) for the tunnel server. - `secure` (boolean) Require HTTPS connections - `landing` (string) URL to redirect root requests to - `maxTcpSockets` (number) Maximum number of TCP sockets per client (default: 10) - `tunnelPort` (number) Shared tunnel port for cloud deployments (enables single-port mode) ### Server Hooks The server supports lifecycle hooks for tracking tunnels and requests: - `onTunnelCreated(tunnel)` - Called when a new tunnel is created. Receives `{ id, url, domain }`. - `onTunnelClosed(tunnel)` - Called when a tunnel is closed. Receives `{ id, url, domain }`. - `onRequest(request)` - Called when a request is proxied through a tunnel. Receives `{ method, path, tunnelId, headers, remoteAddress }`. The `domain` field identifies which configured domain was used when the tunnel was created, which is useful when running a server with multiple domains. The `onRequest` hook provides access to request headers and the client's remote IP address, useful for logging, rate limiting, or authentication. ### Server API Endpoints - `GET /api/status` - Server status and tunnel count - `GET /api/tunnels/:id/status` - Status of a specific tunnel - `GET /:id` - Request a new tunnel with the specified ID ### Cloud Deployments When deploying pipenet server to cloud platforms like fly.io, Docker, or Kubernetes, you typically can only expose a limited number of ports. By default, pipenet creates a random TCP port for each tunnel client, which doesn't work well in these environments. Use the `--tunnel-port` option to enable single-port mode, where all tunnel clients connect to a single shared port: ```bash # fly.io example pipenet server --port 8080 --tunnel-port 8081 --domain tunnel.example.com --secure ``` Then expose both ports in your deployment configuration. For fly.io: ```toml [[services]] internal_port = 8080 protocol = "tcp" [[services.ports]] port = 80 handlers = ["http"] [[services.ports]] port = 443 handlers = ["http", "tls"] [[services]] internal_port = 8081 protocol = "tcp" [[services.ports]] port = 8081 ``` ## Why pipenet? pipenet was developed by [glama.ai](https://glama.ai) to enable local [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers to connect with remote AI clients (e.g., to give AI assistants access to your local file system). This capability is now integrated into [mcp-proxy](https://github.com/punkpeye/mcp-proxy). ## pipenet vs localtunnel pipenet is a modernized fork of [localtunnel](https://github.com/localtunnel/localtunnel) with several improvements: | Feature | pipenet | localtunnel | | ------- | ------- | ----------- | | Cloud deployment support | ✅ Single-port mode via `--tunnel-port` | ❌ Requires random ports | | Multiple domains | ✅ `--domain` can be specified multiple times | ❌ Single domain only | | TypeScript | ✅ Written in TypeScript with full type definitions | ❌ JavaScript only | | ESM support | ✅ Native ES modules | ❌ CommonJS only | | Active maintenance | ✅ Actively maintained | ⚠️ Limited maintenance | | WebSocket support | ✅ Full WebSocket proxying | ✅ Full WebSocket proxying | ### Key Differences **Cloud Deployment Support**: localtunnel creates a random TCP port for each tunnel client, which doesn't work in containerized environments like Docker, fly.io, or Kubernetes where only specific ports are exposed. pipenet solves this with the `--tunnel-port` option, enabling all clients to connect through a single shared port. **Modern JavaScript**: pipenet uses ES modules and is written in TypeScript, providing better IDE support, type safety, and compatibility with modern JavaScript tooling. ## Acknowledgments pipenet is based on [localtunnel](https://github.com/localtunnel/localtunnel). Development of pipenet is sponsored by [glama.ai](https://glama.ai).