Compare commits

...

12 Commits

Author SHA1 Message Date
9f74b6e063 v11.5.0
Some checks failed
Docker (tags) / security (push) Failing after 3s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-19 16:41:16 +00:00
1d0f47f256 feat(opsserver): add configurable OpsServer port and update related tests and documentation 2026-03-19 16:41:16 +00:00
4e9301ae2a v11.4.0
Some checks failed
Docker (tags) / security (push) Failing after 2m0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-19 14:58:08 +00:00
7e2142ce53 feat(docs): document OCI container deployment and enable verbose docker build scripts 2026-03-19 14:58:08 +00:00
67190605a6 v11.3.0
Some checks failed
Docker (tags) / security (push) Failing after 13s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-18 16:22:02 +00:00
9479a07ddf feat(docker): add OCI container startup configuration and migrate Docker release pipeline to tsdocker 2026-03-18 16:22:02 +00:00
fbed56092f v11.2.56
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-18 00:13:58 +00:00
547b82b35b fix(deps): bump @serve.zone/remoteingress to ^4.9.0 2026-03-18 00:13:58 +00:00
3dc63fa02e v11.2.55
Some checks failed
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-17 23:29:58 +00:00
e0154f5b70 fix(deps): bump @serve.zone/catalog to ^2.7.0 and @serve.zone/remoteingress to ^4.8.18 2026-03-17 23:29:58 +00:00
b268409897 v11.2.54
Some checks failed
Docker (tags) / security (push) Failing after 0s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-17 19:14:26 +00:00
f3a9fd12c5 fix(deps): bump @serve.zone/remoteingress to ^4.8.16 2026-03-17 19:14:26 +00:00
25 changed files with 1200 additions and 979 deletions

View File

@@ -6,7 +6,7 @@ on:
- '**'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
IMAGE: code.foss.global/host.today/ht-docker-node:szci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}

View File

@@ -6,7 +6,7 @@ on:
- '*'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
IMAGE: code.foss.global/host.today/ht-docker-node:szci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
@@ -74,7 +74,7 @@ jobs:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: registry.gitlab.com/hosttoday/ht-docker-dbase:npmci
image: code.foss.global/host.today/ht-docker-node:dbase_dind
steps:
- uses: actions/checkout@v3
@@ -82,15 +82,13 @@ jobs:
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
pnpm install -g @git.zone/tsdocker
- name: Release
run: |
npmci docker login
npmci docker build
npmci docker test
# npmci docker push gitea.lossless.digital
npmci docker push dockerregistry.lossless.digital
tsdocker login
tsdocker build
tsdocker push
metadata:
needs: test

View File

@@ -1,44 +1,24 @@
# gitzone dockerfile_service
## STAGE 1 // BUILD
FROM registry.gitlab.com/hosttoday/ht-docker-node:npmci as node1
FROM code.foss.global/host.today/ht-docker-node:lts AS build
COPY ./ /app
WORKDIR /app
ARG NPMCI_TOKEN_NPM2
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
RUN npmci npm prepare
RUN pnpm config set store-dir .pnpm-store
RUN rm -rf node_modules && pnpm install
RUN pnpm run build
RUN rm -rf .pnpm-store node_modules && pnpm install --prod
## STAGE 2 // PRODUCTION
FROM code.foss.global/host.today/ht-docker-node:alpine-node AS production
# gcompat + libstdc++ for glibc-linked Rust binaries (smartproxy, smartmta, remoteingress)
RUN apk add --no-cache gcompat libstdc++
# gitzone dockerfile_service
## STAGE 2 // install production
FROM registry.gitlab.com/hosttoday/ht-docker-node:npmci as node2
WORKDIR /app
COPY --from=node1 /app /app
RUN rm -rf .pnpm-store
ARG NPMCI_TOKEN_NPM2
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
RUN npmci npm prepare
RUN pnpm config set store-dir .pnpm-store
RUN rm -rf node_modules/ && pnpm install --prod
COPY --from=build /app /app
ENV DCROUTER_MODE=OCI_CONTAINER
## STAGE 3 // rebuild dependencies for alpine
FROM registry.gitlab.com/hosttoday/ht-docker-node:alpinenpmci as node3
WORKDIR /app
COPY --from=node2 /app /app
ARG NPMCI_TOKEN_NPM2
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
RUN npmci npm prepare
RUN pnpm config set store-dir .pnpm-store
RUN pnpm rebuild -r
## STAGE 4 // the final production image with all dependencies in place
FROM registry.gitlab.com/hosttoday/ht-docker-node:alpine as node4
WORKDIR /app
COPY --from=node3 /app /app
### Healthchecks
RUN pnpm install -g @servezone/healthy
HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD [ "healthy" ]

View File

@@ -1,5 +1,43 @@
# Changelog
## 2026-03-19 - 11.5.0 - feat(opsserver)
add configurable OpsServer port and update related tests and documentation
- introduces an optional `opsServerPort` configuration that overrides the default OpsServer port 3000
- updates OpsServer startup logic to use the configured port
- adjusts integration tests to run against dedicated OpsServer ports to avoid conflicts
- documents the new OpsServer port option in the README and TypeScript docs
- includes dependency updates and a remote ingress port range type refinement
## 2026-03-19 - 11.4.0 - feat(docs)
document OCI container deployment and enable verbose docker build scripts
- adds a new README section covering Docker/OCI container deployment, environment variables, and image build/push commands
- updates docker build and release npm scripts to pass the --verbose flag for more detailed output
## 2026-03-18 - 11.3.0 - feat(docker)
add OCI container startup configuration and migrate Docker release pipeline to tsdocker
- adds OCI container mode startup that reads DcRouter options from environment variables and an optional JSON config file
- simplifies the Docker image to a two-stage build with production dependencies only and Alpine runtime compatibility packages
- updates Gitea workflows and npm scripts to use tsdocker for image build and release
## 2026-03-18 - 11.2.56 - fix(deps)
bump @serve.zone/remoteingress to ^4.9.0
- Updates @serve.zone/remoteingress from ^4.8.18 to ^4.9.0 in package.json
## 2026-03-17 - 11.2.55 - fix(deps)
bump @serve.zone/catalog to ^2.7.0 and @serve.zone/remoteingress to ^4.8.18
- updates @serve.zone/catalog from ^2.6.2 to ^2.7.0
- updates @serve.zone/remoteingress from ^4.8.16 to ^4.8.18
## 2026-03-17 - 11.2.54 - fix(deps)
bump @serve.zone/remoteingress to ^4.8.16
- Updates @serve.zone/remoteingress from ^4.8.14 to ^4.8.16 in package.json.
## 2026-03-17 - 11.2.53 - fix(deps)
bump @push.rocks/smartproxy and @serve.zone/remoteingress patch versions

View File

@@ -72,9 +72,14 @@
"dockerRegistryRepoMap": {
"registry.gitlab.com": "code.foss.global/serve.zone/dcrouter"
},
"dockerBuildargEnvMap": {
"NPMCI_TOKEN_NPM2": "NPMCI_TOKEN_NPM2"
},
"npmRegistryUrl": "verdaccio.lossless.digital"
},
"@git.zone/tsdocker": {
"registries": ["code.foss.global"],
"registryRepoMap": {
"code.foss.global": "serve.zone/dcrouter",
"dockerregistry.lossless.digital": "serve.zone/dcrouter"
},
"platforms": ["linux/amd64", "linux/arm64"]
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "@serve.zone/dcrouter",
"private": false,
"version": "11.2.53",
"version": "11.5.0",
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
"type": "module",
"exports": {
@@ -16,6 +16,8 @@
"start": "(node --max_old_space_size=250 ./cli.js)",
"startTs": "(node cli.ts.js)",
"build": "(tsbuild tsfolders --allowimplicitany && npm run bundle)",
"build:docker": "tsdocker build --verbose",
"release:docker": "tsdocker push --verbose",
"bundle": "(tsbundle)",
"watch": "tswatch"
},
@@ -23,7 +25,7 @@
"@git.zone/tsbuild": "^4.3.0",
"@git.zone/tsbundle": "^2.9.1",
"@git.zone/tsrun": "^2.0.1",
"@git.zone/tstest": "^3.3.2",
"@git.zone/tstest": "^3.5.0",
"@git.zone/tswatch": "^3.3.0",
"@types/node": "^25.5.0"
},
@@ -38,7 +40,7 @@
"@push.rocks/lik": "^6.3.1",
"@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/qenv": "^6.1.3",
"@push.rocks/smartacme": "^9.1.3",
"@push.rocks/smartacme": "^9.3.0",
"@push.rocks/smartdata": "^7.1.0",
"@push.rocks/smartdns": "^7.9.0",
"@push.rocks/smartfile": "^13.1.2",
@@ -51,15 +53,15 @@
"@push.rocks/smartnetwork": "^4.4.0",
"@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartproxy": "^25.11.24",
"@push.rocks/smartproxy": "^25.14.1",
"@push.rocks/smartradius": "^1.1.1",
"@push.rocks/smartrequest": "^5.0.1",
"@push.rocks/smartrx": "^3.0.10",
"@push.rocks/smartstate": "^2.2.0",
"@push.rocks/smartunique": "^3.0.9",
"@serve.zone/catalog": "^2.6.2",
"@serve.zone/catalog": "^2.9.0",
"@serve.zone/interfaces": "^5.3.0",
"@serve.zone/remoteingress": "^4.8.14",
"@serve.zone/remoteingress": "^4.13.0",
"@tsclass/tsclass": "^9.4.0",
"lru-cache": "^11.2.7",
"uuid": "^13.0.0"

1811
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -30,6 +30,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
- [API Reference](#api-reference)
- [Sub-Modules](#sub-modules)
- [Testing](#testing)
- [Docker / OCI Container Deployment](#docker--oci-container-deployment)
- [License and Legal Information](#license-and-legal-information)
## Features
@@ -343,7 +344,7 @@ graph TB
DcRouter acts purely as an **orchestrator** — it doesn't implement protocols itself. Instead, it wires together best-in-class packages for each protocol:
1. **On `start()`**: DcRouter initializes OpsServer (port 3000), then spins up SmartProxy, smartmta, SmartDNS, SmartRadius, and RemoteIngress based on which configs are provided.
1. **On `start()`**: DcRouter initializes OpsServer (default port 3000, configurable via `opsServerPort`), then spins up SmartProxy, smartmta, SmartDNS, SmartRadius, and RemoteIngress based on which configs are provided.
2. **During operation**: Each service handles its own protocol independently. SmartProxy uses a Rust-powered engine for maximum throughput. smartmta uses a hybrid TypeScript + Rust architecture for reliable email delivery. RemoteIngress runs a Rust data plane for edge tunnel networking. SmartAcme v9 handles all certificate operations with built-in concurrency control and rate limiting.
3. **On `stop()`**: All services are gracefully shut down in parallel, including cleanup of HTTP agents and DNS clients.
@@ -424,6 +425,10 @@ interface IDcRouterOptions {
};
};
// ── OpsServer ────────────────────────────────────────────────
/** Port for the OpsServer web dashboard (default: 3000) */
opsServerPort?: number;
// ── TLS & Certificates ────────────────────────────────────────
tls?: {
contactEmail: string;
@@ -1015,7 +1020,7 @@ action: {
## OpsServer Dashboard
The OpsServer provides a web-based management interface served on port 3000. It's built with modern web components using [@design.estate/dees-catalog](https://code.foss.global/design.estate/dees-catalog).
The OpsServer provides a web-based management interface served on port 3000 by default (configurable via `opsServerPort`). It's built with modern web components using [@design.estate/dees-catalog](https://code.foss.global/design.estate/dees-catalog).
### Dashboard Views
@@ -1278,6 +1283,49 @@ tstest test/test.opsserver-api.ts --verbose --timeout 60
| `test.protected-endpoint.ts` | Admin auth, identity verification, public endpoints | 8 |
| `test.storagemanager.ts` | Memory, filesystem, custom backends, concurrency | 8 |
## Docker / OCI Container Deployment
DcRouter ships with a `Dockerfile` and supports environment-variable-driven configuration for OCI container deployments. When `DCROUTER_MODE=OCI_CONTAINER` is set, DcRouter automatically reads configuration from environment variables (and optionally from a JSON config file).
### Running with Docker
```bash
docker run -d \
-e DCROUTER_MODE=OCI_CONTAINER \
-e DCROUTER_TLS_EMAIL=admin@example.com \
-e DCROUTER_PUBLIC_IP=203.0.113.1 \
-e DCROUTER_DNS_NS_DOMAINS=ns1.example.com,ns2.example.com \
-e DCROUTER_DNS_SCOPES=example.com \
-p 80:80 -p 443:443 -p 25:25 -p 53:53/udp -p 3000:3000 \
code.foss.global/serve.zone/dcrouter:latest
```
### Environment Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `DCROUTER_MODE` | Set to `OCI_CONTAINER` to enable container mode | `OCI_CONTAINER` |
| `DCROUTER_CONFIG_PATH` | Path to a JSON config file (loaded as base, env vars override) | `/config/dcrouter.json` |
| `DCROUTER_BASE_DIR` | Override base data directory | `/data/dcrouter` |
| `DCROUTER_TLS_EMAIL` | ACME contact email | `admin@example.com` |
| `DCROUTER_TLS_DOMAIN` | Primary TLS domain | `example.com` |
| `DCROUTER_PUBLIC_IP` | Public IP for DNS records | `203.0.113.1` |
| `DCROUTER_PROXY_IPS` | Comma-separated ingress proxy IPs | `198.51.100.1,198.51.100.2` |
| `DCROUTER_DNS_NS_DOMAINS` | Comma-separated nameserver domains | `ns1.example.com,ns2.example.com` |
| `DCROUTER_DNS_SCOPES` | Comma-separated authoritative domains | `example.com,other.com` |
| `DCROUTER_EMAIL_HOSTNAME` | SMTP server hostname | `mail.example.com` |
| `DCROUTER_EMAIL_PORTS` | Comma-separated email ports | `25,587,465` |
| `DCROUTER_CACHE_ENABLED` | Enable/disable cache database | `true` |
### Building the Image
```bash
pnpm run build:docker # Build the container image
pnpm run release:docker # Push to registry
```
The Docker build supports multi-platform (`linux/amd64`, `linux/arm64`) via [tsdocker](https://code.foss.global/git.zone/tsdocker).
## 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.
@@ -1292,7 +1340,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
### Company Information
Task Venture Capital GmbH
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.

View File

@@ -129,6 +129,7 @@ tap.test('DcRouter class - Email config with domains and routes', async () => {
tls: {
contactEmail: 'test@example.com'
},
opsServerPort: 3104,
cacheConfig: {
enabled: false,
}

View File

@@ -9,6 +9,7 @@ tap.test('should NOT instantiate DNS server when dnsNsDomains is not set', async
smartProxyConfig: {
routes: []
},
opsServerPort: 3100,
cacheConfig: { enabled: false }
});

View File

@@ -9,6 +9,7 @@ let identity: interfaces.data.IIdentity;
tap.test('should start DCRouter with OpsServer', async () => {
testDcRouter = new DcRouter({
// Minimal config for testing
opsServerPort: 3102,
cacheConfig: { enabled: false },
});
@@ -18,7 +19,7 @@ tap.test('should start DCRouter with OpsServer', async () => {
tap.test('should login with admin credentials and receive JWT', async () => {
const loginRequest = new TypedRequest<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
'http://localhost:3000/typedrequest',
'http://localhost:3102/typedrequest',
'adminLoginWithUsernameAndPassword'
);
@@ -41,7 +42,7 @@ tap.test('should login with admin credentials and receive JWT', async () => {
tap.test('should verify valid JWT identity', async () => {
const verifyRequest = new TypedRequest<interfaces.requests.IReq_VerifyIdentity>(
'http://localhost:3000/typedrequest',
'http://localhost:3102/typedrequest',
'verifyIdentity'
);
@@ -57,7 +58,7 @@ tap.test('should verify valid JWT identity', async () => {
tap.test('should reject invalid JWT', async () => {
const verifyRequest = new TypedRequest<interfaces.requests.IReq_VerifyIdentity>(
'http://localhost:3000/typedrequest',
'http://localhost:3102/typedrequest',
'verifyIdentity'
);
@@ -74,7 +75,7 @@ tap.test('should reject invalid JWT', async () => {
tap.test('should verify JWT matches identity data', async () => {
const verifyRequest = new TypedRequest<interfaces.requests.IReq_VerifyIdentity>(
'http://localhost:3000/typedrequest',
'http://localhost:3102/typedrequest',
'verifyIdentity'
);
@@ -91,7 +92,7 @@ tap.test('should verify JWT matches identity data', async () => {
tap.test('should handle logout', async () => {
const logoutRequest = new TypedRequest<interfaces.requests.IReq_AdminLogout>(
'http://localhost:3000/typedrequest',
'http://localhost:3102/typedrequest',
'adminLogout'
);
@@ -105,7 +106,7 @@ tap.test('should handle logout', async () => {
tap.test('should reject wrong credentials', async () => {
const loginRequest = new TypedRequest<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
'http://localhost:3000/typedrequest',
'http://localhost:3102/typedrequest',
'adminLoginWithUsernameAndPassword'
);

View File

@@ -9,6 +9,7 @@ let adminIdentity: interfaces.data.IIdentity;
tap.test('should start DCRouter with OpsServer', async () => {
testDcRouter = new DcRouter({
// Minimal config for testing
opsServerPort: 3101,
cacheConfig: { enabled: false },
});
@@ -18,7 +19,7 @@ tap.test('should start DCRouter with OpsServer', async () => {
tap.test('should login as admin', async () => {
const loginRequest = new TypedRequest<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
'http://localhost:3000/typedrequest',
'http://localhost:3101/typedrequest',
'adminLoginWithUsernameAndPassword'
);
@@ -33,7 +34,7 @@ tap.test('should login as admin', async () => {
tap.test('should respond to health status request', async () => {
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
'http://localhost:3000/typedrequest',
'http://localhost:3101/typedrequest',
'getHealthStatus'
);
@@ -49,7 +50,7 @@ tap.test('should respond to health status request', async () => {
tap.test('should respond to server statistics request', async () => {
const statsRequest = new TypedRequest<interfaces.requests.IReq_GetServerStatistics>(
'http://localhost:3000/typedrequest',
'http://localhost:3101/typedrequest',
'getServerStatistics'
);
@@ -66,7 +67,7 @@ tap.test('should respond to server statistics request', async () => {
tap.test('should respond to configuration request', async () => {
const configRequest = new TypedRequest<interfaces.requests.IReq_GetConfiguration>(
'http://localhost:3000/typedrequest',
'http://localhost:3101/typedrequest',
'getConfiguration'
);
@@ -87,7 +88,7 @@ tap.test('should respond to configuration request', async () => {
tap.test('should handle log retrieval request', async () => {
const logsRequest = new TypedRequest<interfaces.requests.IReq_GetRecentLogs>(
'http://localhost:3000/typedrequest',
'http://localhost:3101/typedrequest',
'getRecentLogs'
);
@@ -104,7 +105,7 @@ tap.test('should handle log retrieval request', async () => {
tap.test('should reject unauthenticated requests', async () => {
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
'http://localhost:3000/typedrequest',
'http://localhost:3101/typedrequest',
'getHealthStatus'
);

View File

@@ -9,6 +9,7 @@ let adminIdentity: interfaces.data.IIdentity;
tap.test('should start DCRouter with OpsServer', async () => {
testDcRouter = new DcRouter({
// Minimal config for testing
opsServerPort: 3103,
cacheConfig: { enabled: false },
});
@@ -18,7 +19,7 @@ tap.test('should start DCRouter with OpsServer', async () => {
tap.test('should login as admin', async () => {
const loginRequest = new TypedRequest<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
'http://localhost:3000/typedrequest',
'http://localhost:3103/typedrequest',
'adminLoginWithUsernameAndPassword'
);
@@ -34,7 +35,7 @@ tap.test('should login as admin', async () => {
tap.test('should allow admin to verify identity', async () => {
const verifyRequest = new TypedRequest<interfaces.requests.IReq_VerifyIdentity>(
'http://localhost:3000/typedrequest',
'http://localhost:3103/typedrequest',
'verifyIdentity'
);
@@ -49,7 +50,7 @@ tap.test('should allow admin to verify identity', async () => {
tap.test('should reject verify identity without identity', async () => {
const verifyRequest = new TypedRequest<interfaces.requests.IReq_VerifyIdentity>(
'http://localhost:3000/typedrequest',
'http://localhost:3103/typedrequest',
'verifyIdentity'
);
@@ -64,7 +65,7 @@ tap.test('should reject verify identity without identity', async () => {
tap.test('should reject verify identity with invalid JWT', async () => {
const verifyRequest = new TypedRequest<interfaces.requests.IReq_VerifyIdentity>(
'http://localhost:3000/typedrequest',
'http://localhost:3103/typedrequest',
'verifyIdentity'
);
@@ -84,7 +85,7 @@ tap.test('should reject verify identity with invalid JWT', async () => {
tap.test('should reject protected endpoints without auth', async () => {
const healthRequest = new TypedRequest<interfaces.requests.IReq_GetHealthStatus>(
'http://localhost:3000/typedrequest',
'http://localhost:3103/typedrequest',
'getHealthStatus'
);
@@ -100,7 +101,7 @@ tap.test('should reject protected endpoints without auth', async () => {
tap.test('should allow authenticated access to protected endpoints', async () => {
const configRequest = new TypedRequest<interfaces.requests.IReq_GetConfiguration>(
'http://localhost:3000/typedrequest',
'http://localhost:3103/typedrequest',
'getConfiguration'
);

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/dcrouter',
version: '11.2.53',
version: '11.5.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
}

View File

@@ -163,6 +163,9 @@ export interface IDcRouterOptions {
* Remote Ingress configuration for edge tunnel nodes
* Enables edge nodes to accept incoming connections and tunnel them to this DcRouter
*/
/** Port for the OpsServer web UI (default: 3000) */
opsServerPort?: number;
remoteIngressConfig?: {
/** Enable remote ingress hub (default: false) */
enabled?: boolean;

View File

@@ -5,6 +5,7 @@ export { UnifiedEmailServer } from '@push.rocks/smartmta';
export type { IUnifiedEmailServerOptions, IEmailRoute, IEmailDomainConfig } from '@push.rocks/smartmta';
// DcRouter
import { DcRouter } from './classes.dcrouter.js';
export * from './classes.dcrouter.js';
// RADIUS module
@@ -13,4 +14,24 @@ export * from './radius/index.js';
// Remote Ingress module
export * from './remoteingress/index.js';
export const runCli = async () => {};
export const runCli = async () => {
let options: import('./classes.dcrouter.js').IDcRouterOptions = {};
if (process.env.DCROUTER_MODE === 'OCI_CONTAINER') {
const { getOciContainerConfig } = await import('../ts_oci_container/index.js');
options = getOciContainerConfig();
console.log('[DCRouter] Starting in OCI Container mode...');
}
const dcRouter = new DcRouter(options);
await dcRouter.start();
console.log('[DCRouter] Running. Send SIGTERM or SIGINT to stop.');
const shutdown = async () => {
console.log('[DCRouter] Shutting down...');
await dcRouter.stop();
process.exit(0);
};
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
};

View File

@@ -50,7 +50,7 @@ export class OpsServer {
// Set up handlers
await this.setupHandlers();
await this.server.start(3000);
await this.server.start(this.dcRouterRef.options.opsServerPort ?? 3000);
}
/**

View File

@@ -37,7 +37,7 @@ const router = new DcRouter({
});
await router.start();
// OpsServer dashboard at http://localhost:3000
// OpsServer dashboard at http://localhost:3000 (configurable via opsServerPort)
// Graceful shutdown
await router.stop();
@@ -71,7 +71,10 @@ ts/
│ ├── email.handler.ts # Email operations
│ ├── certificate.handler.ts # Certificate management
│ ├── radius.handler.ts # RADIUS management
── remoteingress.handler.ts # Remote ingress edge + token management
── remoteingress.handler.ts # Remote ingress edge + token management
│ ├── route-management.handler.ts # Programmatic route CRUD
│ ├── api-token.handler.ts # API token management
│ └── security.handler.ts # Security metrics + connections
├── radius/ # RADIUS server integration
├── remoteingress/ # Remote ingress hub integration
│ ├── classes.remoteingress-manager.ts # Edge CRUD + port derivation

View File

@@ -7,7 +7,7 @@ const STORAGE_PREFIX = '/remote-ingress/';
/**
* Flatten a port range (number | number[] | Array<{from, to}>) to a sorted unique number array.
*/
function extractPorts(portRange: number | number[] | Array<{ from: number; to: number }>): number[] {
function extractPorts(portRange: number | Array<number | { from: number; to: number }>): number[] {
const ports = new Set<number>();
if (typeof portRange === 'number') {
ports.add(portRange);

View File

@@ -271,7 +271,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
### Company Information
Task Venture Capital GmbH
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.

View File

@@ -292,7 +292,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
### Company Information
Task Venture Capital GmbH
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.

100
ts_oci_container/index.ts Normal file
View File

@@ -0,0 +1,100 @@
import * as plugins from './plugins.js';
import type { IDcRouterOptions } from '../ts/classes.dcrouter.js';
/**
* Parses a comma-separated env var into a string array.
* Returns undefined if the env var is not set or empty.
*/
function parseCommaSeparated(envVar: string | undefined): string[] | undefined {
if (!envVar || envVar.trim() === '') return undefined;
return envVar.split(',').map((s) => s.trim()).filter(Boolean);
}
/**
* Parses a comma-separated env var into a number array.
* Returns undefined if the env var is not set or empty.
*/
function parseCommaSeparatedNumbers(envVar: string | undefined): number[] | undefined {
const parts = parseCommaSeparated(envVar);
if (!parts) return undefined;
return parts.map((s) => parseInt(s, 10)).filter((n) => !isNaN(n));
}
/**
* Builds IDcRouterOptions from environment variables for OCI container mode.
*
* If DCROUTER_CONFIG_PATH is set and the file exists, it is loaded as a JSON base config.
* Individual env vars are then applied as overrides on top.
*/
export function getOciContainerConfig(): IDcRouterOptions {
let options: IDcRouterOptions = {};
// Load JSON config file if specified
const configPath = process.env.DCROUTER_CONFIG_PATH;
if (configPath && plugins.fs.existsSync(configPath)) {
const raw = plugins.fs.readFileSync(configPath, 'utf8');
options = JSON.parse(raw);
console.log(`[OCI Container] Loaded config from ${configPath}`);
}
// Apply env var overrides
if (process.env.DCROUTER_BASE_DIR) {
options.baseDir = process.env.DCROUTER_BASE_DIR;
}
// TLS config
const tlsEmail = process.env.DCROUTER_TLS_EMAIL;
const tlsDomain = process.env.DCROUTER_TLS_DOMAIN;
if (tlsEmail || tlsDomain) {
options.tls = {
...options.tls,
contactEmail: tlsEmail || options.tls?.contactEmail || '',
...(tlsDomain ? { domain: tlsDomain } : {}),
};
}
// Network config
if (process.env.DCROUTER_PUBLIC_IP) {
options.publicIp = process.env.DCROUTER_PUBLIC_IP;
}
const proxyIps = parseCommaSeparated(process.env.DCROUTER_PROXY_IPS);
if (proxyIps) {
options.proxyIps = proxyIps;
}
// DNS config
const nsDomains = parseCommaSeparated(process.env.DCROUTER_DNS_NS_DOMAINS);
if (nsDomains) {
options.dnsNsDomains = nsDomains;
}
const dnsScopes = parseCommaSeparated(process.env.DCROUTER_DNS_SCOPES);
if (dnsScopes) {
options.dnsScopes = dnsScopes;
}
// Email config
const emailHostname = process.env.DCROUTER_EMAIL_HOSTNAME;
const emailPorts = parseCommaSeparatedNumbers(process.env.DCROUTER_EMAIL_PORTS);
if (emailHostname || emailPorts) {
options.emailConfig = {
...options.emailConfig,
...(emailHostname ? { hostname: emailHostname } : {}),
...(emailPorts ? { ports: emailPorts } : {}),
domains: options.emailConfig?.domains || [],
routes: options.emailConfig?.routes || [],
} as IDcRouterOptions['emailConfig'];
}
// Cache config
const cacheEnabled = process.env.DCROUTER_CACHE_ENABLED;
if (cacheEnabled !== undefined) {
options.cacheConfig = {
...options.cacheConfig,
enabled: cacheEnabled === 'true',
};
}
return options;
}

View File

@@ -0,0 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
export {
fs,
path,
};

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/dcrouter',
version: '11.2.53',
version: '11.5.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
}

View File

@@ -249,7 +249,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
### Company Information
Task Venture Capital GmbH
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.