# @serve.zone/dcrouter

`dcrouter` is the serve.zone datacenter gateway runtime: a TypeScript control plane that brings HTTP/HTTPS/TCP routing, email ingress, authoritative DNS, RADIUS, VPN access control, remote ingress tunnels, certificate operations, metrics, and an Ops dashboard into one process.

## Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.

## Why It Exists

Modern infrastructure often has too many tiny edge tools: a proxy here, a DNS daemon there, a separate cert worker, another dashboard, and a tunnel process bolted on later. `dcrouter` is designed as a cohesive gateway layer for operators who want one audited place to define public routes, domains, edge tunnels, access policy, and operational state.

Highlights:

- 🌐 SmartProxy-backed HTTP, HTTPS, TCP, TLS/SNI, source-policy rate limits/challenges, and optional HTTP/3 route handling
- 📬 SmartMTA-backed SMTP ingress and email-domain operations
- 🧭 SmartDNS-backed authoritative DNS plus generated DNS-over-HTTPS routes
- 🔐 ACME with managed-domain DNS-01 support, certificate state, API tokens, users, source profiles, and target profiles
- 🛡️ RADIUS, VLAN assignment, VPN-protected routes, IP intelligence, block rules, and remote ingress firewall snapshots
- 🖥️ Browser Ops dashboard and TypedRequest API served by the built-in OpsServer

## Runtime Areas

| Area | What dcrouter manages |
| --- | --- |
| Proxying | SmartProxy routes for HTTP, HTTPS, TCP, SNI, TLS termination, passthrough, backend forwarding, source policies, rate limits, and browser challenges |
| Route ownership | Constructor routes, generated email/DNS routes, and API-created routes with explicit origins |
| DNS | Authoritative scopes, generated NS records, static DNS records, provider-backed domains, and DoH endpoints |
| Email | UnifiedEmailServer startup, email-domain management, route-backed delivery actions, received mail operations |
| Certificates | ACME config, managed-domain DNS-01 challenges, HTTP-01 fallback, stored certificate metadata, provisioning backoff, and certificate status reporting |
| Edge access | Remote ingress hub, edge registrations, derived edge ports, pushed firewall rules, VPN-only route access |
| Network auth | RADIUS clients, MAC Authentication Bypass, VLAN mapping, and accounting sessions |
| Security policy | DB-backed block rules, public-IP intelligence, compiled SmartProxy deny policy, RemoteIngress firewall snapshots, and audit events |
| Operations | Dashboard views, TypedRequest handlers, metrics, logs, health, API tokens, users, and configuration views |

## Install

Install the CLI/runtime on a Linux gateway host with the released self-extracting binary:

```bash
curl -sSL https://code.foss.global/serve.zone/dcrouter/raw/branch/main/install.sh | sudo bash
```

The installer downloads `dcrouter-linux-x64` or `dcrouter-linux-arm64` from the latest Gitea release, installs it under `/opt/dcrouter`, and links `/usr/local/bin/dcrouter`. Use `--version vX.Y.Z` to pin a release, `--install-dir /path` to change the target directory, or `--source` to clone the tag and build the NodeNext package locally.

```bash
curl -sSL https://code.foss.global/serve.zone/dcrouter/raw/branch/main/install.sh | sudo bash -s -- --source
```

Use the package as a TypeScript library:

```bash
pnpm add @serve.zone/dcrouter
```

## Quick Start

This starts the gateway on unprivileged ports and stores data under the default `~/.serve.zone/dcrouter` base directory.

```typescript
import { DcRouter } from '@serve.zone/dcrouter';

const router = new DcRouter({
  smartProxyConfig: {
    routes: [
      {
        name: 'local-app',
        match: {
          domains: ['localhost'],
          ports: [18080],
        },
        action: {
          type: 'forward',
          targets: [{ host: '127.0.0.1', port: 3001 }],
        },
      },
    ],
  },
  dbConfig: {
    enabled: true,
  },
  opsServerPort: 3000,
});

await router.start();
```

After startup:

- open the dashboard at `http://localhost:3000`
- complete the first-admin bootstrap flow if no persisted admin account exists yet
- send proxied traffic to `http://localhost:18080`
- stop gracefully with `await router.stop()`

## Initial Admin Bootstrap

When DB-backed persistence is enabled and no persisted admin exists, dcrouter does not auto-create an admin account. The Ops dashboard exposes a non-cancelable first-admin bootstrap flow that must be completed explicitly.

Bootstrap behavior:

- `getAdminBootstrapStatus` reports whether persistence is ready and whether a first admin is required.
- The temporary env/config admin identity is only used to authorize bootstrap access while no persisted admin exists.
- `createInitialAdminUser` creates the first persisted admin with normalized email and local password authentication.
- Optional `idp.global` authentication can be enabled for that local account. The hosted `https://idp.global` endpoint is used by default, `adminAuth.idpGlobalUrl` or `DCROUTER_IDP_GLOBAL_URL` only override it, and the local dcrouter role remains authoritative.
- After a persisted admin exists, temporary bootstrap admin login is rejected and normal persisted-account authentication is used.

## Configuration Model

`DcRouter` is configured with `IDcRouterOptions` from `@serve.zone/dcrouter`.

| Option | Purpose |
| --- | --- |
| `baseDir` | Root directory for dcrouter runtime data. Defaults to `~/.serve.zone/dcrouter`. |
| `smartProxyConfig` | Main SmartProxy route configuration for HTTP/HTTPS/TCP/SNI traffic. |
| `emailConfig` | UnifiedEmailServer configuration: hostname, ports, domains, and mail routes. |
| `emailPortConfig` | External-to-internal email port mapping and received-email storage path. |
| `tls` | Legacy/static TLS and ACME contact settings used to seed certificate config. |
| `dnsNsDomains` | Nameserver hostnames used for generated NS records and DoH routes. |
| `dnsScopes` | Authoritative domains served by the embedded DNS server. |
| `dnsRecords` | Constructor-defined DNS records. |
| `publicIp` / `proxyIps` | IPs used for generated A records and proxy-aware DNS exposure. |
| `dbConfig` | Smartdata persistence via embedded LocalSmartDb or external MongoDB. |
| `radiusConfig` | RADIUS authentication, accounting, and VLAN assignment. |
| `remoteIngressConfig` | Remote ingress hub configuration for edge tunnel nodes. |
| `vpnConfig` | VPN server/client definitions and VPN-only routing behavior. |
| `http3` | HTTP/3 augmentation settings for qualifying HTTPS routes. |
| `opsServerPort` | Port for the Ops dashboard and `/typedrequest` API. Defaults to `3000`. |

Important runtime behavior:

- `dbConfig.enabled` defaults to enabled. Without `mongoDbUrl`, dcrouter uses embedded LocalSmartDb.
- If the DB is disabled, constructor-defined proxy traffic can still run, but persistent API routes, tokens, managed domains, and stored certificate state are unavailable.
- Qualifying HTTPS forward routes on port `443` are HTTP/3-augmented unless `http3.enabled === false` or the route opts out.
- DNS-over-HTTPS routes are generated on the first `dnsNsDomains` entry at `/dns-query` and `/resolve`.
- Email listener ports can be remapped internally, for example public `25`, `587`, and `465` to unprivileged internal ports.

## Route Ownership

dcrouter keeps generated and operator-created routes separate so automation can reconcile safely.

| Origin | Source | Mutability |
| --- | --- | --- |
| `config` | Constructor `smartProxyConfig.routes` and seed data | Toggle only |
| `email` | Email listener and email-domain generated routes | Toggle only |
| `dns` | Generated DNS-over-HTTPS and DNS-related routes | Toggle only |
| `api` | Ops UI or API client | Full CRUD |

System routes are persisted with stable `systemKey` values. API-created routes are the editable route layer intended for operators and automation.

## Route Source Bindings

API-created route records pass ordered `metadata.sourceBindings[]` alongside the SmartProxy route config to express source and path policy variants without duplicating whole routes by hand. Each binding points at a source profile id through `sourceProfileRef`. Dashboard presets resolve seeded profile names to ids before saving.

Source profiles store reusable defaults only. A source profile is not enforced globally by itself; dcrouter compiles it into SmartProxy routes only when a route references it through `metadata.sourceBindings[]`.

Runtime behavior:

- Source matching uses the referenced `SourceProfile.security.ipAllowList`.
- Bindings are evaluated in order and the first matching source profile wins.
- A matched binding that exceeds its configured rate or connection limit is terminal; dcrouter does not fall through to later bindings. Rate limits normally return `429`, or trigger a browser challenge when `rateLimit.onExceeded.type === 'challenge'`.
- Source-binding rate limits are always keyed by source IP; dcrouter ignores `path` and `header` keying on source-binding and path-policy overrides.
- Source-binding and path-policy `rateLimit` and `challenge` fields use tri-state semantics: omitted means inherit, `null` means explicitly clear inherited protection, and an object means custom protection.
- Effective protection precedence is path policy, then route binding, then source profile, then parent source profile, then no protection.
- When `sourceBindings[]` are present, dcrouter compiles source-policy variants from source profile, binding, and path policy protection. Base route `rateLimit` and `challenge` values are not part of that inheritance chain; routes without source bindings use normal SmartProxy route security.
- Direct `challenge` protection applies to matching HTTP-visible requests. `rateLimit.onExceeded.type === 'challenge'` configures a browser challenge for rate-limit exceed events.
- Binding-level and path-policy `onExceeded` is only for `429` message handling. Browser challenge-on-exceeded behavior belongs on `rateLimit.onExceeded`.
- Private-only binding lists are valid. dcrouter adds a same-match terminal deny fallback so unmatched sources fail closed.
- A public or wildcard binding is optional. When present, it must be last and must use `*`, or both `0.0.0.0/0` and `::/0`, in `security.ipAllowList`.
- Create/update paths reject source bindings with missing source profiles, source profiles without source matches, or any all-source binding that shadows later bindings; persisted invalid bindings fail closed at compile time.
- Server-side caps bound policy expansion to 16 source bindings, 12 path policies per binding, 64 path patterns per path policy, 256 characters and 8 wildcards per custom path pattern, 512 compiled SmartProxy route-port variants per stored route, and enough priority headroom above the stored route priority for generated source-binding variants.

Path policies let a source binding override rate limits, browser challenges, or connection limits for specific path classes. dcrouter currently ships Gitea-oriented classes: `git-smart-http`, `static`, `normal-html`, `expensive-html`, `raw`, and `archive`. Path-specific variants win over the same binding's fallback; if every path policy is path-specific, dcrouter adds a source-level fallback route for unmatched paths so normal browsing cannot fall through to a later source binding. The Gitea preset keeps `git-smart-http` high-limit and separate from HTML crawling paths so normal `git clone`, `git fetch`, `git push`, and Git LFS traffic are not subject to the lower HTML crawler limits.

```typescript
const trustedProfileId = 'source-profile-id-trusted';
const publicProfileId = 'source-profile-id-public';

const createRoutePayload = {
  route: {
    name: 'public-gitea',
    match: { domains: ['code.example.com'], ports: [443] },
    action: {
      type: 'forward',
      targets: [{ host: '10.10.0.20', port: 3000 }],
      tls: { mode: 'terminate', certificate: 'auto' },
    },
  },
  metadata: {
    sourceBindings: [
      {
        sourceProfileRef: trustedProfileId,
        rateLimit: null,
        maxConnections: 5000,
        onExceeded: { type: '429' },
      },
      {
        sourceProfileRef: publicProfileId,
        onExceeded: { type: '429' },
        pathPolicies: [
          {
            pathClass: 'git-smart-http',
            rateLimit: { enabled: true, maxRequests: 1200, window: 60, keyBy: 'ip' },
          },
          {
            pathClass: 'static',
            rateLimit: { enabled: true, maxRequests: 600, window: 60, keyBy: 'ip' },
          },
          {
            pathClass: 'raw',
            rateLimit: { enabled: true, maxRequests: 120, window: 60, keyBy: 'ip' },
          },
          {
            pathClass: 'archive',
            rateLimit: { enabled: true, maxRequests: 30, window: 60, keyBy: 'ip' },
          },
          {
            pathClass: 'expensive-html',
            rateLimit: { enabled: true, maxRequests: 30, window: 60, keyBy: 'ip' },
          },
          {
            pathClass: 'normal-html',
            rateLimit: {
              enabled: true,
              maxRequests: 120,
              window: 60,
              keyBy: 'ip',
              onExceeded: {
                type: 'challenge',
                challenge: {
                  providerId: 'smartchallenge',
                  challengeType: 'wait',
                  applyTo: { methods: ['GET'], browserNavigationsOnly: true },
                  clearance: { ttlSeconds: 300, bindToHost: true, bindToRoute: true },
                },
                clearanceEffect: 'bypass-rate-limit',
              },
            },
          },
        ],
      },
    ],
  },
};
```

### Source Profiles

Source profiles are reusable source-side security defaults. They can carry `ipAllowList`, `ipBlockList`, `maxConnections`, `rateLimit`, authentication fields, VPN fields, and `challenge`. Profiles can extend parent profiles; IP allow/block lists are unioned and scalar/object protection fields such as `maxConnections`, `rateLimit`, and `challenge` are overridden by the more specific profile.

When `dbConfig.seedOnEmpty` seeds an empty database, dcrouter's built-in profile set includes:

| Profile | Default purpose |
| --- | --- |
| `TRUSTED NETWORKS` | Private networks, localhost, and high connection allowance |
| `AI CRAWLERS` | Placeholder crawler profile with low per-IP request limits until verified crawler CIDRs are added |
| `PUBLIC` | Public fallback profile with per-IP request limiting |
| `STANDARD` | Standard private-network access profile |

The source profile dashboard supports protection modes for rate limits and browser challenges: inherit/unset, none/clear inherited, or custom. Custom challenge settings expose the provider id, challenge type, and clearance TTL used by source-policy route compilation.

### Challenge Support

dcrouter registers the `smartchallenge` provider with the `wait` challenge type when SmartProxy starts. Source policies can apply challenges in two ways:

- `challenge` on a source profile, route binding, or path policy protects matching HTTP-visible traffic directly.
- `rateLimit.onExceeded.type: 'challenge'` configures the rate-limit exceeded path to issue the configured challenge.

Challenge-aware source-policy variants and HTTP/3-augmented challenged routes force HTTP protocol matching where needed so SmartProxy can process browser challenges correctly.

### ACME And Certificate Challenges

DB-backed ACME configuration drives SmartProxy certificate provisioning. When ACME is enabled and dcrouter has managed domains, `DnsManager` builds the DNS-01 provider used by SmartAcme. That provider creates and removes challenge TXT records through the same DNS record path used for dcrouter-hosted zones and provider-managed domains.

SmartAcme is configured with DNS-01 priority for managed domains. If SmartAcme is still starting or retrying account setup, the certificate provision callback falls back to HTTP-01 for that request. Issued certificates are stored through the proxy certificate store, and certificate status is tracked from both newly issued and store-loaded certificate events.

### Security Policy And IP Intelligence

When DB-backed persistence is enabled, `SecurityPolicyManager` maintains global deny policy from block rules and observed public-IP intelligence. Rule types are `ip`, `cidr`, `asn`, and `organization`; organization rules support `exact` or `contains` matching against enriched ASN and registrant organization data.

The compiled policy contains `blockedIps` and `blockedCidrs`. dcrouter merges it into SmartProxy's security policy and also compiles an IPv4 firewall snapshot for RemoteIngress edge synchronization. Security policy changes are audited when block rules are created, updated, or deleted.

The OpsServer exposes these security policy methods through TypedRequest: `listSecurityBlockRules`, `createSecurityBlockRule`, `updateSecurityBlockRule`, `deleteSecurityBlockRule`, `listIpIntelligence`, `refreshIpIntelligence`, `getCompiledSecurityPolicy`, and `listSecurityPolicyAudit`.

## Production-Flavored Example

```typescript
import { DcRouter } from '@serve.zone/dcrouter';

const router = new DcRouter({
  baseDir: '/var/lib/dcrouter',
  smartProxyConfig: {
    routes: [
      {
        name: 'web-app',
        match: { domains: ['app.example.com'], ports: [443] },
        action: {
          type: 'forward',
          targets: [{ host: '10.10.0.21', port: 8080 }],
          tls: { mode: 'terminate', certificate: 'auto' },
        },
      },
      {
        name: 'internal-admin',
        match: { domains: ['admin.example.com'], ports: [443] },
        action: {
          type: 'forward',
          targets: [{ host: '10.10.0.30', port: 9000 }],
          tls: { mode: 'terminate', certificate: 'auto' },
        },
        vpnOnly: true,
      },
    ],
  },
  emailConfig: {
    hostname: 'mail.example.com',
    ports: [25, 587, 465],
    domains: [{ domain: 'example.com', dnsMode: 'internal-dns' }],
    routes: [
      {
        name: 'inbound-example',
        match: { recipients: '*@example.com' },
        action: {
          type: 'forward',
          forward: { host: 'mail-backend.example.com', port: 25 },
        },
      },
    ],
  },
  dnsNsDomains: ['ns1.example.com', 'ns2.example.com'],
  dnsScopes: ['example.com'],
  publicIp: '203.0.113.10',
  remoteIngressConfig: {
    enabled: true,
    tunnelPort: 8443,
    hubDomain: 'ingress.example.com',
  },
  vpnConfig: {
    enabled: true,
    serverEndpoint: 'vpn.example.com',
    clients: [{ clientId: 'ops-laptop', description: 'Operations laptop' }],
  },
  opsServerPort: 3000,
});

await router.start();
```

## VPN Target Profiles

Target profiles define what a VPN client can reach through `domains`, direct `targets`, and `routeRefs`. Set `allowRoutesByClientSourceIp: true` on a target profile when a VPN client should also be granted to routes whose source policy is meant to evaluate the client's real connecting IP.

dcrouter maps target profiles to SmartProxy VPN client grants. SmartVPN forwards both the real client source IP and authenticated VPN metadata through trusted PROXY v2 headers, so SmartProxy checks source policy and VPN client authorization separately for each connection. Route `security.ipAllowList` and `security.ipBlockList` stay the source of truth for real source-IP policy; `vpnOnly` adds the requirement for authenticated VPN metadata and a matching VPN client grant.

```typescript
const targetProfile = {
  name: 'ops laptop source access',
  allowRoutesByClientSourceIp: true,
};
```

## Automation API

The OpsServer exposes TypedRequest handlers at `/typedrequest`. You can use raw contracts or the object-oriented API client.

```bash
pnpm add @serve.zone/dcrouter-apiclient
```

```typescript
import { DcRouterApiClient } from '@serve.zone/dcrouter-apiclient';

const client = new DcRouterApiClient({
  baseUrl: 'https://dcrouter.example.com',
});

await client.login('admin@example.com', 'strong-password');

const route = await client.routes.build()
  .setName('api-gateway')
  .setMatch({ ports: 443, domains: ['api.example.com'] })
  .setAction({ type: 'forward', targets: [{ host: '127.0.0.1', port: 8081 }] })
  .save();

await route.toggle(true);
```

Use `@serve.zone/dcrouter/interfaces` or `@serve.zone/dcrouter-interfaces` when you want the raw TypedRequest contracts instead of resource managers.

## OCI / Container Bootstrap

`runCli()` supports an environment-driven container mode when `DCROUTER_MODE=OCI_CONTAINER`.

```typescript
import { runCli } from '@serve.zone/dcrouter';

await runCli();
```

Supported environment overrides include:

| Variable | Purpose |
| --- | --- |
| `DCROUTER_CONFIG_PATH` | JSON file loaded as the base `IDcRouterOptions` object. |
| `DCROUTER_BASE_DIR` | Runtime data root. |
| `DCROUTER_TLS_EMAIL` / `DCROUTER_TLS_DOMAIN` | TLS/ACME seed settings. |
| `DCROUTER_PUBLIC_IP` / `DCROUTER_PROXY_IPS` | Public/proxy IP exposure settings. |
| `DCROUTER_DNS_NS_DOMAINS` / `DCROUTER_DNS_SCOPES` | DNS nameserver and authoritative scope settings. |
| `DCROUTER_EMAIL_HOSTNAME` / `DCROUTER_EMAIL_PORTS` | Email server seed settings. |
| `DCROUTER_CACHE_ENABLED` | Enables or disables DB-backed persistence. |
| `DCROUTER_MAX_CONNECTIONS`, `DCROUTER_MAX_CONNECTIONS_PER_IP`, `DCROUTER_CONNECTION_RATE_LIMIT` | SmartProxy capacity and rate-limit overrides. |

## Docker Image

Release builds publish a multi-arch OCI image at `code.foss.global/serve.zone/dcrouter:latest` for `linux/amd64` and `linux/arm64`. The image sets `DCROUTER_MODE=OCI_CONTAINER` and starts `node ./cli.js`.

```bash
docker run --rm --name dcrouter \
  --network host \
  -v dcrouter-data:/data \
  -e DCROUTER_BASE_DIR=/data \
  -e DCROUTER_TLS_EMAIL=ops@example.com \
  code.foss.global/serve.zone/dcrouter:latest
```

Host networking is the simplest container mode for a gateway that owns HTTP/S, SMTP, DNS, RADIUS, remote ingress, and dynamic proxy ports. For narrower deployments, publish only the ports you enable in `IDcRouterOptions` or via the `DCROUTER_*` environment overrides.

## Published Modules

This repository intentionally publishes multiple module boundaries from one codebase.

| Module | Purpose | Docs |
| --- | --- | --- |
| `@serve.zone/dcrouter` | Main runtime and orchestrator | `./readme.md` |
| `@serve.zone/dcrouter/interfaces` | Shared contracts as a subpath export | `./ts_interfaces/readme.md` |
| `@serve.zone/dcrouter/apiclient` | API client as a subpath export | `./ts_apiclient/readme.md` |
| `@serve.zone/dcrouter-interfaces` | Standalone contracts package | `./ts_interfaces/readme.md` |
| `@serve.zone/dcrouter-apiclient` | Standalone OO API client package | `./ts_apiclient/readme.md` |
| `@serve.zone/dcrouter-migrations` | Standalone migration runner package | `./ts_migrations/readme.md` |
| `@serve.zone/dcrouter-web` | Dashboard frontend module boundary | `./ts_web/readme.md` |

## Development

```bash
pnpm run build
pnpm test
pnpm run watch
```

Useful source entry points:

- `ts/index.ts` exports `DcRouter`, `runCli()`, and public module surfaces.
- `ts/classes.dcrouter.ts` owns service startup, dependency ordering, and `IDcRouterOptions`.
- `ts/opsserver/classes.opsserver.ts` wires the dashboard server and TypedRequest handlers.
- `ts/remoteingress/` integrates `@serve.zone/remoteingress` with stored edge registrations.
- `ts_migrations/index.ts` contains all DB schema migration steps.

## License and Legal Information

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.

**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

### Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

### Company Information

Task Venture Capital GmbH  
Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
