Compare commits

..

78 Commits

Author SHA1 Message Date
f6e1951aa2 v11.8.1
Some checks failed
Docker (tags) / security (push) Failing after 2s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-19 22:01:50 +00:00
76fd563e21 fix(dcrouter): use constructor routes for remote ingress setup and bump smartproxy dependency 2026-03-19 22:01:50 +00:00
ee831ea057 v11.8.0
Some checks failed
Docker (tags) / security (push) Failing after 21s
Docker (tags) / test (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
Docker (tags) / release (push) Has been skipped
2026-03-19 21:30:06 +00:00
a65c2ec096 feat(remoteingress): add UDP listen port derivation and edge configuration support 2026-03-19 21:30:06 +00:00
65822278d5 v11.7.1
Some checks failed
Docker (tags) / security (push) Failing after 2s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-19 20:43:47 +00:00
aa3955fc67 fix(deps): bump @push.rocks/smartproxy to ^25.16.0 2026-03-19 20:43:47 +00:00
d4605062bb v11.7.0
Some checks failed
Docker (tags) / security (push) Failing after 2s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-19 19:10:33 +00:00
cd3f08d55f feat(readme): document HTTP/3 QUIC support and configuration options 2026-03-19 19:10:33 +00:00
6d447f0086 v11.6.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 19:06:15 +00:00
c7de3873d8 feat(http3): add automatic HTTP/3 route augmentation for qualifying HTTPS routes 2026-03-19 19:06:15 +00:00
6d4e30e8a9 v11.5.1
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:54:39 +00:00
0e308b692b fix(project): no changes to commit 2026-03-19 16:54:39 +00:00
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
ef741d84fb v11.2.53
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-17 16:49:17 +00:00
b0ea97b922 fix(deps): bump @push.rocks/smartproxy and @serve.zone/remoteingress patch versions 2026-03-17 16:49:17 +00:00
d1560811f5 v11.2.52
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-17 15:51:31 +00:00
5e872c4e6a fix(deps): bump @serve.zone/remoteingress to ^4.8.13 2026-03-17 15:51:31 +00:00
3620e4549a v11.2.51
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 13:28:33 +00:00
b32865e790 fix(deps): bump @serve.zone/remoteingress to ^4.8.12 2026-03-17 13:28:33 +00:00
ebe71a2a94 v11.2.50
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 13:06:39 +00:00
877a2ad0ee fix(deps): bump @serve.zone/remoteingress to ^4.8.11 2026-03-17 13:06:39 +00:00
7be1aaedb3 v11.2.49
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-17 12:50:44 +00:00
05eb8e9723 fix(deps): bump @serve.zone/remoteingress to ^4.8.10 2026-03-17 12:50:44 +00:00
d95d89ea6f v11.2.48
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-17 12:36:14 +00:00
5d1b988579 fix(deps): bump @serve.zone/remoteingress to ^4.8.9 2026-03-17 12:36:14 +00:00
bae85eea9e v11.2.47
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-17 12:24:37 +00:00
2be7974991 fix(deps): bump @push.rocks/smartproxy to ^25.11.23 2026-03-17 12:24:37 +00:00
ac03b1f081 v11.2.46
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-17 12:13:49 +00:00
5ca209dd5a fix(deps): bump @push.rocks/smartproxy to ^25.11.22 2026-03-17 12:13:49 +00:00
867e93b246 v11.2.45
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-17 12:05:41 +00:00
aa9c4c1c28 fix(deps): bump @push.rocks/smartproxy and @serve.zone/remoteingress dependencies 2026-03-17 12:05:41 +00:00
207f21cb77 v11.2.44
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-17 11:18:11 +00:00
96a47ef588 fix(deps): bump @serve.zone/remoteingress to ^4.8.3 2026-03-17 11:18:11 +00:00
3bac80eb41 v11.2.43
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-17 10:35:25 +00:00
19d67a644c fix(deps): bump @serve.zone/remoteingress to ^4.8.2 2026-03-17 10:35:25 +00:00
341e4113bd v11.2.42
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-17 01:51:04 +00:00
81eb19a9ab fix(deps): bump @serve.zone/remoteingress to ^4.8.1 2026-03-17 01:51:04 +00:00
e0221c0d05 v11.2.41
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-17 01:33:47 +00:00
e6a1f23c84 fix(deps): bump @push.rocks/smartproxy to ^25.11.20 2026-03-17 01:33:47 +00:00
f77772e1c0 v11.2.40
Some checks failed
Docker (tags) / security (push) Failing after 4s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-17 01:00:41 +00:00
0a706c03c4 fix(deps): bump @serve.zone/remoteingress to ^4.8.0 2026-03-17 01:00:41 +00:00
df5547fda0 v11.2.39
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-17 00:43:03 +00:00
a677ee081c fix(repository): no changes to commit 2026-03-17 00:43:03 +00:00
7339a91513 v11.2.38
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-17 00:41:16 +00:00
cd27645489 fix(deps): bump @serve.zone/remoteingress to ^4.7.2 2026-03-17 00:41:16 +00:00
71f2d53e45 v11.2.37
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-16 23:36:55 +00:00
f74fcc68de fix(deps): bump @serve.zone/remoteingress to ^4.7.0 2026-03-16 23:36:55 +00:00
abcaf9c858 v11.2.36
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-16 20:58:17 +00:00
c9f185dd82 fix(deps): bump @push.rocks/smartproxy to ^25.11.19 2026-03-16 20:58:17 +00:00
5e3186d311 v11.2.35
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-16 20:01:59 +00:00
db35c01a09 fix(deps): bump @push.rocks/smartproxy, @serve.zone/catalog, and @serve.zone/remoteingress dependencies 2026-03-16 20:01:59 +00:00
3cf6c84a60 v11.2.34
Some checks failed
Docker (tags) / security (push) Failing after 2s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-03-16 14:32:19 +00:00
d570d5c916 fix(deps): bump @push.rocks/smartproxy and @serve.zone/catalog patch versions 2026-03-16 14:32:19 +00:00
4f6afb62f3 v11.2.33
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-16 14:12:00 +00:00
e5edfdc052 fix(deps): bump smartproxy and remoteingress dependencies 2026-03-16 14:12:00 +00:00
55cbb92a00 v11.2.32
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-16 13:49:26 +00:00
928be6a5c6 fix(deps): bump smartproxy and remoteingress dependencies 2026-03-16 13:49:26 +00:00
dff3e3c80d v11.2.31
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-16 13:03:33 +00:00
8db2118a78 fix(deps): bump @push.rocks/smartproxy to ^25.11.11 2026-03-16 13:03:33 +00:00
dc6da4ce34 v11.2.30
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-16 12:35:27 +00:00
63eb99ae5d fix(deps): bump @push.rocks/smartproxy and @serve.zone/catalog dependencies 2026-03-16 12:35:27 +00:00
6b533fba9f v11.2.29
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-16 11:31:30 +00:00
d3e102b6d2 fix(deps): bump @serve.zone/remoteingress to ^4.5.9 2026-03-16 11:31:30 +00:00
2bc9761d21 v11.2.28
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-16 10:53:17 +00:00
d60b9fb8e2 fix(deps): bump @serve.zone/remoteingress to ^4.5.8 2026-03-16 10:53:17 +00:00
b2705f3e88 v11.2.27
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-16 09:45:33 +00:00
4dea263cb0 fix(deps): bump smartproxy and remoteingress dependencies 2026-03-16 09:45:33 +00:00
30 changed files with 2051 additions and 986 deletions

View File

@@ -6,7 +6,7 @@ on:
- '**' - '**'
env: 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_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}} NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}} NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}

View File

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

View File

@@ -1,44 +1,24 @@
# gitzone dockerfile_service # gitzone dockerfile_service
## STAGE 1 // BUILD ## 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 COPY ./ /app
WORKDIR /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 pnpm config set store-dir .pnpm-store
RUN rm -rf node_modules && pnpm install RUN rm -rf node_modules && pnpm install
RUN pnpm run build 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 WORKDIR /app
COPY --from=node1 /app /app COPY --from=build /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
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 RUN pnpm install -g @servezone/healthy
HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD [ "healthy" ] HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD [ "healthy" ]

View File

@@ -1,5 +1,223 @@
# Changelog # Changelog
## 2026-03-19 - 11.8.1 - fix(dcrouter)
use constructor routes for remote ingress setup and bump smartproxy dependency
- Switch remote ingress initialization to use constructorRoutes instead of smartProxyConfig routes so derived edge ports are based on the active route set.
- Update @push.rocks/smartproxy from ^25.16.2 to ^25.16.3.
## 2026-03-19 - 11.8.0 - feat(remoteingress)
add UDP listen port derivation and edge configuration support
- derive UDP ports from remote ingress routes using transport 'udp' or 'all'
- expose effective UDP listen ports in allowed edge payloads and remote ingress interfaces
- update @push.rocks/smartproxy to ^25.16.2
## 2026-03-19 - 11.7.1 - fix(deps)
bump @push.rocks/smartproxy to ^25.16.0
- updates the smartproxy dependency from ^25.15.0 to ^25.16.0
## 2026-03-19 - 11.7.0 - feat(readme)
document HTTP/3 QUIC support and configuration options
- Add a dedicated README section explaining default HTTP/3 route augmentation, qualification rules, and opt-out behavior.
- Document the new global `http3` configuration shape and re-exported `IHttp3Config` type.
- Update TypeScript module documentation to include the built-in HTTP/3 augmentation module and exports.
## 2026-03-19 - 11.6.0 - feat(http3)
add automatic HTTP/3 route augmentation for qualifying HTTPS routes
- introduce configurable HTTP/3 augmentation utilities for eligible SmartProxy routes on port 443
- apply HTTP/3 settings to both constructor-defined and stored programmatic routes, with global and per-route opt-out support
- export the HTTP/3 config type and add test coverage for qualification, augmentation behavior, and defaults
- bump @push.rocks/smartproxy to ^25.15.0 for HTTP/3-related support
## 2026-03-19 - 11.5.1 - fix(project)
no changes to commit
## 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
- update @push.rocks/smartproxy from ^25.11.23 to ^25.11.24
- update @serve.zone/remoteingress from ^4.8.13 to ^4.8.14
## 2026-03-17 - 11.2.52 - fix(deps)
bump @serve.zone/remoteingress to ^4.8.13
- Updates the @serve.zone/remoteingress dependency from ^4.8.12 to ^4.8.13.
## 2026-03-17 - 11.2.51 - fix(deps)
bump @serve.zone/remoteingress to ^4.8.12
- Updates @serve.zone/remoteingress from ^4.8.11 to ^4.8.12 in package.json
## 2026-03-17 - 11.2.50 - fix(deps)
bump @serve.zone/remoteingress to ^4.8.11
- updates @serve.zone/remoteingress from ^4.8.10 to ^4.8.11
## 2026-03-17 - 11.2.49 - fix(deps)
bump @serve.zone/remoteingress to ^4.8.10
- Updates @serve.zone/remoteingress from ^4.8.9 to ^4.8.10 in package.json
## 2026-03-17 - 11.2.48 - fix(deps)
bump @serve.zone/remoteingress to ^4.8.9
- Updates @serve.zone/remoteingress from ^4.8.7 to ^4.8.9 in package.json
## 2026-03-17 - 11.2.47 - fix(deps)
bump @push.rocks/smartproxy to ^25.11.23
- Updates the @push.rocks/smartproxy dependency from ^25.11.22 to ^25.11.23 in package.json
## 2026-03-17 - 11.2.46 - fix(deps)
bump @push.rocks/smartproxy to ^25.11.22
- Updates the @push.rocks/smartproxy dependency from ^25.11.21 to ^25.11.22 in package.json.
## 2026-03-17 - 11.2.45 - fix(deps)
bump @push.rocks/smartproxy and @serve.zone/remoteingress dependencies
- update @push.rocks/smartproxy from ^25.11.20 to ^25.11.21
- update @serve.zone/remoteingress from ^4.8.3 to ^4.8.7
## 2026-03-17 - 11.2.44 - fix(deps)
bump @serve.zone/remoteingress to ^4.8.3
- Updates @serve.zone/remoteingress from ^4.8.2 to ^4.8.3 in package.json
## 2026-03-17 - 11.2.43 - fix(deps)
bump @serve.zone/remoteingress to ^4.8.2
- Updates the @serve.zone/remoteingress dependency from ^4.8.1 to ^4.8.2.
## 2026-03-17 - 11.2.42 - fix(deps)
bump @serve.zone/remoteingress to ^4.8.1
- Updates @serve.zone/remoteingress from ^4.8.0 to ^4.8.1 in package.json
## 2026-03-17 - 11.2.41 - fix(deps)
bump @push.rocks/smartproxy to ^25.11.20
- Updates the @push.rocks/smartproxy dependency from ^25.11.19 to ^25.11.20 in package.json.
## 2026-03-17 - 11.2.40 - fix(deps)
bump @serve.zone/remoteingress to ^4.8.0
- Updates the @serve.zone/remoteingress dependency from ^4.7.2 to ^4.8.0 in package.json.
## 2026-03-17 - 11.2.39 - fix(repository)
no changes to commit
## 2026-03-17 - 11.2.38 - fix(deps)
bump @serve.zone/remoteingress to ^4.7.2
- Updates @serve.zone/remoteingress from ^4.7.0 to ^4.7.2 in package.json
## 2026-03-16 - 11.2.37 - fix(deps)
bump @serve.zone/remoteingress to ^4.7.0
- updates the @serve.zone/remoteingress dependency from ^4.6.0 to ^4.7.0
## 2026-03-16 - 11.2.36 - fix(deps)
bump @push.rocks/smartproxy to ^25.11.19
- Updates the @push.rocks/smartproxy dependency from ^25.11.18 to ^25.11.19 in package.json
## 2026-03-16 - 11.2.35 - fix(deps)
bump @push.rocks/smartproxy, @serve.zone/catalog, and @serve.zone/remoteingress dependencies
- updates @push.rocks/smartproxy from ^25.11.17 to ^25.11.18
- updates @serve.zone/catalog from ^2.6.1 to ^2.6.2
- updates @serve.zone/remoteingress from ^4.5.11 to ^4.6.0
## 2026-03-16 - 11.2.34 - fix(deps)
bump @push.rocks/smartproxy and @serve.zone/catalog patch versions
- updates @push.rocks/smartproxy from ^25.11.16 to ^25.11.17
- updates @serve.zone/catalog from ^2.6.0 to ^2.6.1
## 2026-03-16 - 11.2.33 - fix(deps)
bump smartproxy and remoteingress dependencies
- update @push.rocks/smartproxy from ^25.11.14 to ^25.11.16
- update @serve.zone/remoteingress from ^4.5.10 to ^4.5.11
## 2026-03-16 - 11.2.32 - fix(deps)
bump smartproxy and remoteingress dependencies
- update @push.rocks/smartproxy from ^25.11.11 to ^25.11.14
- update @serve.zone/remoteingress from ^4.5.9 to ^4.5.10
## 2026-03-16 - 11.2.31 - fix(deps)
bump @push.rocks/smartproxy to ^25.11.11
- Updates the @push.rocks/smartproxy dependency from ^25.11.10 to ^25.11.11 in package.json.
## 2026-03-16 - 11.2.30 - fix(deps)
bump @push.rocks/smartproxy and @serve.zone/catalog dependencies
- update @push.rocks/smartproxy from ^25.11.9 to ^25.11.10
- update @serve.zone/catalog from ^2.5.0 to ^2.6.0
## 2026-03-16 - 11.2.29 - fix(deps)
bump @serve.zone/remoteingress to ^4.5.9
- Updates @serve.zone/remoteingress from ^4.5.8 to ^4.5.9 in package.json
## 2026-03-16 - 11.2.28 - fix(deps)
bump @serve.zone/remoteingress to ^4.5.8
- Updates the @serve.zone/remoteingress dependency from ^4.5.7 to ^4.5.8.
## 2026-03-16 - 11.2.27 - fix(deps)
bump smartproxy and remoteingress dependencies
- update @push.rocks/smartproxy from ^25.11.8 to ^25.11.9
- update @serve.zone/remoteingress from ^4.5.5 to ^4.5.7
## 2026-03-16 - 11.2.26 - fix(deps) ## 2026-03-16 - 11.2.26 - fix(deps)
bump @serve.zone/remoteingress to ^4.5.5 bump @serve.zone/remoteingress to ^4.5.5

View File

@@ -72,9 +72,14 @@
"dockerRegistryRepoMap": { "dockerRegistryRepoMap": {
"registry.gitlab.com": "code.foss.global/serve.zone/dcrouter" "registry.gitlab.com": "code.foss.global/serve.zone/dcrouter"
}, },
"dockerBuildargEnvMap": {
"NPMCI_TOKEN_NPM2": "NPMCI_TOKEN_NPM2"
},
"npmRegistryUrl": "verdaccio.lossless.digital" "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", "name": "@serve.zone/dcrouter",
"private": false, "private": false,
"version": "11.2.26", "version": "11.8.1",
"description": "A multifaceted routing service handling mail and SMS delivery functions.", "description": "A multifaceted routing service handling mail and SMS delivery functions.",
"type": "module", "type": "module",
"exports": { "exports": {
@@ -16,6 +16,8 @@
"start": "(node --max_old_space_size=250 ./cli.js)", "start": "(node --max_old_space_size=250 ./cli.js)",
"startTs": "(node cli.ts.js)", "startTs": "(node cli.ts.js)",
"build": "(tsbuild tsfolders --allowimplicitany && npm run bundle)", "build": "(tsbuild tsfolders --allowimplicitany && npm run bundle)",
"build:docker": "tsdocker build --verbose",
"release:docker": "tsdocker push --verbose",
"bundle": "(tsbundle)", "bundle": "(tsbundle)",
"watch": "tswatch" "watch": "tswatch"
}, },
@@ -23,7 +25,7 @@
"@git.zone/tsbuild": "^4.3.0", "@git.zone/tsbuild": "^4.3.0",
"@git.zone/tsbundle": "^2.9.1", "@git.zone/tsbundle": "^2.9.1",
"@git.zone/tsrun": "^2.0.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", "@git.zone/tswatch": "^3.3.0",
"@types/node": "^25.5.0" "@types/node": "^25.5.0"
}, },
@@ -38,7 +40,7 @@
"@push.rocks/lik": "^6.3.1", "@push.rocks/lik": "^6.3.1",
"@push.rocks/projectinfo": "^5.0.2", "@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/qenv": "^6.1.3", "@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/smartdata": "^7.1.0",
"@push.rocks/smartdns": "^7.9.0", "@push.rocks/smartdns": "^7.9.0",
"@push.rocks/smartfile": "^13.1.2", "@push.rocks/smartfile": "^13.1.2",
@@ -51,15 +53,15 @@
"@push.rocks/smartnetwork": "^4.4.0", "@push.rocks/smartnetwork": "^4.4.0",
"@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartpromise": "^4.2.3", "@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartproxy": "^25.11.8", "@push.rocks/smartproxy": "^25.16.3",
"@push.rocks/smartradius": "^1.1.1", "@push.rocks/smartradius": "^1.1.1",
"@push.rocks/smartrequest": "^5.0.1", "@push.rocks/smartrequest": "^5.0.1",
"@push.rocks/smartrx": "^3.0.10", "@push.rocks/smartrx": "^3.0.10",
"@push.rocks/smartstate": "^2.2.0", "@push.rocks/smartstate": "^2.2.0",
"@push.rocks/smartunique": "^3.0.9", "@push.rocks/smartunique": "^3.0.9",
"@serve.zone/catalog": "^2.5.0", "@serve.zone/catalog": "^2.9.0",
"@serve.zone/interfaces": "^5.3.0", "@serve.zone/interfaces": "^5.3.0",
"@serve.zone/remoteingress": "^4.5.5", "@serve.zone/remoteingress": "^4.13.0",
"@tsclass/tsclass": "^9.4.0", "@tsclass/tsclass": "^9.4.0",
"lru-cache": "^11.2.7", "lru-cache": "^11.2.7",
"uuid": "^13.0.0" "uuid": "^13.0.0"

1811
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

179
readme.md
View File

@@ -18,6 +18,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
- [Architecture](#architecture) - [Architecture](#architecture)
- [Configuration Reference](#configuration-reference) - [Configuration Reference](#configuration-reference)
- [HTTP/HTTPS & TCP/SNI Routing](#httphttps--tcpsni-routing) - [HTTP/HTTPS & TCP/SNI Routing](#httphttps--tcpsni-routing)
- [HTTP/3 (QUIC) Support](#http3-quic-support)
- [Email System](#email-system) - [Email System](#email-system)
- [DNS Server](#dns-server) - [DNS Server](#dns-server)
- [RADIUS Server](#radius-server) - [RADIUS Server](#radius-server)
@@ -30,12 +31,14 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
- [API Reference](#api-reference) - [API Reference](#api-reference)
- [Sub-Modules](#sub-modules) - [Sub-Modules](#sub-modules)
- [Testing](#testing) - [Testing](#testing)
- [Docker / OCI Container Deployment](#docker--oci-container-deployment)
- [License and Legal Information](#license-and-legal-information) - [License and Legal Information](#license-and-legal-information)
## Features ## Features
### 🌐 Universal Traffic Router ### 🌐 Universal Traffic Router
- **HTTP/HTTPS routing** with domain matching, path-based forwarding, and automatic TLS - **HTTP/HTTPS routing** with domain matching, path-based forwarding, and automatic TLS
- **HTTP/3 (QUIC) enabled by default** — qualifying HTTPS routes automatically get QUIC/H3 support with zero configuration
- **TCP/SNI proxy** for any protocol with TLS termination or passthrough - **TCP/SNI proxy** for any protocol with TLS termination or passthrough
- **DNS server** (Rust-powered via [SmartDNS](https://code.foss.global/push.rocks/smartdns)) with authoritative zones, dynamic record management, and DNS-over-HTTPS - **DNS server** (Rust-powered via [SmartDNS](https://code.foss.global/push.rocks/smartdns)) with authoritative zones, dynamic record management, and DNS-over-HTTPS
- **Multi-protocol support** on the same infrastructure via [SmartProxy](https://code.foss.global/push.rocks/smartproxy) - **Multi-protocol support** on the same infrastructure via [SmartProxy](https://code.foss.global/push.rocks/smartproxy)
@@ -343,7 +346,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: 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. 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. 3. **On `stop()`**: All services are gracefully shut down in parallel, including cleanup of HTTP agents and DNS clients.
@@ -424,6 +427,31 @@ interface IDcRouterOptions {
}; };
}; };
// ── HTTP/3 (QUIC) ────────────────────────────────────────────
/** HTTP/3 config — enabled by default on qualifying HTTPS routes */
http3?: {
enabled?: boolean; // default: true
quicSettings?: {
maxIdleTimeout?: number; // default: 30000ms
maxConcurrentBidiStreams?: number; // default: 100
maxConcurrentUniStreams?: number; // default: 100
initialCongestionWindow?: number;
};
altSvc?: {
port?: number; // default: listening port
maxAge?: number; // default: 86400s
};
udpSettings?: {
sessionTimeout?: number; // default: 60000ms
maxSessionsPerIP?: number; // default: 1000
maxDatagramSize?: number; // default: 65535
};
};
// ── OpsServer ────────────────────────────────────────────────
/** Port for the OpsServer web dashboard (default: 3000) */
opsServerPort?: number;
// ── TLS & Certificates ──────────────────────────────────────── // ── TLS & Certificates ────────────────────────────────────────
tls?: { tls?: {
contactEmail: string; contactEmail: string;
@@ -511,6 +539,102 @@ DcRouter uses [SmartProxy](https://code.foss.global/push.rocks/smartproxy) for a
} }
``` ```
## HTTP/3 (QUIC) Support
DcRouter ships with **HTTP/3 enabled by default** 🚀. All qualifying HTTPS routes on port 443 are automatically augmented with QUIC/H3 configuration — no extra setup needed. Under the hood, SmartProxy's native HTTP/3 support (via `IRouteQuic`) handles QUIC transport, Alt-Svc advertisement, and HTTP/3 negotiation.
### How It Works
When DcRouter assembles routes in `setupSmartProxy()`, it automatically augments qualifying routes with:
- `match.transport: 'all'` — listen on both TCP (HTTP/1.1 + HTTP/2) and UDP (QUIC/HTTP/3) on the same port
- `action.udp.quic` — QUIC configuration with `enableHttp3: true` and `altSvcMaxAge: 86400`
Browsers that support HTTP/3 will discover it via the `Alt-Svc` header on initial TCP responses, then upgrade to QUIC for subsequent requests.
### What Gets Augmented
A route qualifies for HTTP/3 augmentation when **all** of these are true:
- Port includes **443** (single number, array, or range)
- Action type is **`forward`** (not `socket-handler`)
- **TLS is enabled** (passthrough, terminate, or terminate-and-reencrypt)
- Route is **not** an email route (ports 25/587/465)
- Route doesn't already have `transport: 'all'` or existing `udp.quic` config
### Zero-Config (Default Behavior)
```typescript
// HTTP/3 is ON by default — this route automatically gets QUIC/H3:
const router = new DcRouter({
smartProxyConfig: {
routes: [{
name: 'web-app',
match: { domains: ['example.com'], ports: [443] },
action: {
type: 'forward',
targets: [{ host: '192.168.1.10', port: 8080 }],
tls: { mode: 'terminate', certificate: 'auto' }
}
}]
}
});
```
### Per-Route Opt-Out
Disable HTTP/3 on a specific route using `action.options.http3`:
```typescript
{
name: 'legacy-app',
match: { domains: ['legacy.example.com'], ports: [443] },
action: {
type: 'forward',
targets: [{ host: '192.168.1.50', port: 8080 }],
tls: { mode: 'terminate', certificate: 'auto' },
options: { http3: false } // ← This route stays TCP-only
}
}
```
### Global Opt-Out
Disable HTTP/3 across all routes:
```typescript
const router = new DcRouter({
http3: { enabled: false },
smartProxyConfig: { routes: [/* ... */] }
});
```
### Custom QUIC Settings
Fine-tune QUIC parameters globally:
```typescript
const router = new DcRouter({
http3: {
quicSettings: {
maxIdleTimeout: 60000, // 60s idle timeout
maxConcurrentBidiStreams: 200, // More parallel streams
maxConcurrentUniStreams: 50,
},
altSvc: {
maxAge: 3600, // 1 hour Alt-Svc cache
},
udpSettings: {
sessionTimeout: 120000, // 2 min UDP session timeout
maxSessionsPerIP: 500,
}
},
smartProxyConfig: { routes: [/* ... */] }
});
```
### Programmatic Routes
Routes added at runtime via the Route Management API also get HTTP/3 augmentation automatically — the `RouteConfigManager` applies the same augmentation logic when merging programmatic routes.
## Email System ## Email System
The email system is powered by [`@push.rocks/smartmta`](https://code.foss.global/push.rocks/smartmta), a TypeScript + Rust hybrid MTA. DcRouter configures and orchestrates smartmta's **UnifiedEmailServer**, which handles SMTP sessions, route matching, delivery queuing, DKIM signing, and all email processing. The email system is powered by [`@push.rocks/smartmta`](https://code.foss.global/push.rocks/smartmta), a TypeScript + Rust hybrid MTA. DcRouter configures and orchestrates smartmta's **UnifiedEmailServer**, which handles SMTP sessions, route matching, delivery queuing, DKIM signing, and all email processing.
@@ -1015,7 +1139,7 @@ action: {
## OpsServer Dashboard ## 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 ### Dashboard Views
@@ -1216,7 +1340,7 @@ const router = new DcRouter(options: IDcRouterOptions);
### Re-exported Types ### Re-exported Types
DcRouter re-exports key types from smartmta for convenience: DcRouter re-exports key types for convenience:
```typescript ```typescript
import { import {
@@ -1226,6 +1350,7 @@ import {
type IUnifiedEmailServerOptions, type IUnifiedEmailServerOptions,
type IEmailRoute, type IEmailRoute,
type IEmailDomainConfig, type IEmailDomainConfig,
type IHttp3Config,
} from '@serve.zone/dcrouter'; } from '@serve.zone/dcrouter';
``` ```
@@ -1272,12 +1397,56 @@ tstest test/test.opsserver-api.ts --verbose --timeout 60
| `test.dns-server-config.ts` | DNS record parsing, grouping, extraction | 5 | | `test.dns-server-config.ts` | DNS record parsing, grouping, extraction | 5 |
| `test.dns-socket-handler.ts` | DNS socket handler and route generation | 6 | | `test.dns-socket-handler.ts` | DNS socket handler and route generation | 6 |
| `test.errors.ts` | Error classes, handler, retry utilities | 5 | | `test.errors.ts` | Error classes, handler, retry utilities | 5 |
| `test.http3-augmentation.ts` | HTTP/3 route augmentation, qualification, opt-in/out, QUIC settings | 20 |
| `test.ipreputationchecker.ts` | IP reputation, DNSBL, caching, risk classification | 10 | | `test.ipreputationchecker.ts` | IP reputation, DNSBL, caching, risk classification | 10 |
| `test.jwt-auth.ts` | JWT login, verification, logout, invalid credentials | 8 | | `test.jwt-auth.ts` | JWT login, verification, logout, invalid credentials | 8 |
| `test.opsserver-api.ts` | Health, statistics, configuration, log APIs | 6 | | `test.opsserver-api.ts` | Health, statistics, configuration, log APIs | 8 |
| `test.protected-endpoint.ts` | Admin auth, identity verification, public endpoints | 8 | | `test.protected-endpoint.ts` | Admin auth, identity verification, public endpoints | 8 |
| `test.storagemanager.ts` | Memory, filesystem, custom backends, concurrency | 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 ## 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. 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 +1461,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
### Company Information ### Company Information
Task Venture Capital GmbH Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany 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. 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: { tls: {
contactEmail: 'test@example.com' contactEmail: 'test@example.com'
}, },
opsServerPort: 3104,
cacheConfig: { cacheConfig: {
enabled: false, enabled: false,
} }

View File

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

View File

@@ -0,0 +1,304 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import {
routeQualifiesForHttp3,
augmentRouteWithHttp3,
augmentRoutesWithHttp3,
type IHttp3Config,
} from '../ts/http3/index.js';
import type * as plugins from '../ts/plugins.js';
// Helper to create a basic HTTPS forward route on port 443
function makeRoute(
overrides: Partial<plugins.smartproxy.IRouteConfig> = {},
): plugins.smartproxy.IRouteConfig {
return {
match: { ports: 443, ...overrides.match },
action: {
type: 'forward',
targets: [{ host: 'localhost', port: 8080 }],
tls: { mode: 'terminate', certificate: 'auto' },
...overrides.action,
},
name: overrides.name ?? 'test-https-route',
...Object.fromEntries(
Object.entries(overrides).filter(([k]) => !['match', 'action', 'name'].includes(k)),
),
} as plugins.smartproxy.IRouteConfig;
}
const defaultConfig: IHttp3Config = { enabled: true };
// ──────────────────────────────────────────────────────────────────────────────
// Qualification tests
// ──────────────────────────────────────────────────────────────────────────────
tap.test('should augment qualifying HTTPS route on port 443', async () => {
const route = makeRoute();
const result = augmentRouteWithHttp3(route, defaultConfig);
expect(result.match.transport).toEqual('all');
expect(result.action.udp).toBeTruthy();
expect(result.action.udp!.quic).toBeTruthy();
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
expect(result.action.udp!.quic!.altSvcMaxAge).toEqual(86400);
});
tap.test('should NOT augment route on non-443 port', async () => {
const route = makeRoute({ match: { ports: 8080 } });
const result = augmentRouteWithHttp3(route, defaultConfig);
expect(result.match.transport).toBeUndefined();
expect(result.action.udp).toBeUndefined();
});
tap.test('should NOT augment socket-handler type route', async () => {
const route = makeRoute({
action: {
type: 'socket-handler' as any,
socketHandler: (() => {}) as any,
},
});
const result = augmentRouteWithHttp3(route, defaultConfig);
expect(result.match.transport).toBeUndefined();
});
tap.test('should NOT augment route without TLS', async () => {
const route: plugins.smartproxy.IRouteConfig = {
match: { ports: 443 },
action: {
type: 'forward',
targets: [{ host: 'localhost', port: 8080 }],
},
name: 'no-tls-route',
};
const result = augmentRouteWithHttp3(route, defaultConfig);
expect(result.match.transport).toBeUndefined();
});
tap.test('should NOT augment email routes', async () => {
const emailNames = ['smtp-route', 'submission-route', 'smtps-route', 'email-port-2525-route'];
for (const name of emailNames) {
const route = makeRoute({ name });
const result = augmentRouteWithHttp3(route, defaultConfig);
expect(result.match.transport).toBeUndefined();
}
});
tap.test('should respect per-route opt-out (options.http3 = false)', async () => {
const route = makeRoute({
action: {
type: 'forward',
targets: [{ host: 'localhost', port: 8080 }],
tls: { mode: 'terminate', certificate: 'auto' },
options: { http3: false },
},
});
const result = augmentRouteWithHttp3(route, defaultConfig);
expect(result.match.transport).toBeUndefined();
expect(result.action.udp).toBeUndefined();
});
tap.test('should respect per-route opt-in when global is disabled', async () => {
const route = makeRoute({
action: {
type: 'forward',
targets: [{ host: 'localhost', port: 8080 }],
tls: { mode: 'terminate', certificate: 'auto' },
options: { http3: true },
},
});
const result = augmentRouteWithHttp3(route, { enabled: false });
expect(result.match.transport).toEqual('all');
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
});
tap.test('should NOT double-augment routes with transport: all', async () => {
const route = makeRoute({
match: { ports: 443, transport: 'all' as any },
});
const result = augmentRouteWithHttp3(route, defaultConfig);
// Should be the exact same object (no augmentation)
expect(result).toEqual(route);
});
tap.test('should NOT double-augment routes with existing udp.quic', async () => {
const route = makeRoute({
action: {
type: 'forward',
targets: [{ host: 'localhost', port: 8080 }],
tls: { mode: 'terminate', certificate: 'auto' },
udp: { quic: { enableHttp3: true } },
},
});
const result = augmentRouteWithHttp3(route, defaultConfig);
expect(result).toEqual(route);
});
tap.test('should augment route with port range including 443', async () => {
const route = makeRoute({
match: { ports: [{ from: 400, to: 500 }] },
});
const result = augmentRouteWithHttp3(route, defaultConfig);
expect(result.match.transport).toEqual('all');
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
});
tap.test('should augment route with port array including 443', async () => {
const route = makeRoute({
match: { ports: [80, 443] },
});
const result = augmentRouteWithHttp3(route, defaultConfig);
expect(result.match.transport).toEqual('all');
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
});
tap.test('should NOT augment route with port range NOT including 443', async () => {
const route = makeRoute({
match: { ports: [{ from: 8000, to: 9000 }] },
});
const result = augmentRouteWithHttp3(route, defaultConfig);
expect(result.match.transport).toBeUndefined();
});
tap.test('should augment TLS passthrough routes', async () => {
const route = makeRoute({
action: {
type: 'forward',
targets: [{ host: 'localhost', port: 8080 }],
tls: { mode: 'passthrough' },
},
});
const result = augmentRouteWithHttp3(route, defaultConfig);
expect(result.match.transport).toEqual('all');
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
});
tap.test('should augment terminate-and-reencrypt routes', async () => {
const route = makeRoute({
action: {
type: 'forward',
targets: [{ host: 'localhost', port: 8080 }],
tls: { mode: 'terminate-and-reencrypt', certificate: 'auto' },
},
});
const result = augmentRouteWithHttp3(route, defaultConfig);
expect(result.match.transport).toEqual('all');
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
});
// ──────────────────────────────────────────────────────────────────────────────
// Configuration tests
// ──────────────────────────────────────────────────────────────────────────────
tap.test('should apply default QUIC settings when none provided', async () => {
const route = makeRoute();
const result = augmentRouteWithHttp3(route, defaultConfig);
expect(result.action.udp!.quic!.altSvcMaxAge).toEqual(86400);
// Undefined means SmartProxy will use its own defaults
expect(result.action.udp!.quic!.maxIdleTimeout).toBeUndefined();
expect(result.action.udp!.quic!.altSvcPort).toBeUndefined();
});
tap.test('should apply custom QUIC settings', async () => {
const route = makeRoute();
const config: IHttp3Config = {
enabled: true,
quicSettings: {
maxIdleTimeout: 60000,
maxConcurrentBidiStreams: 200,
maxConcurrentUniStreams: 50,
initialCongestionWindow: 65536,
},
altSvc: {
port: 8443,
maxAge: 3600,
},
udpSettings: {
sessionTimeout: 120000,
maxSessionsPerIP: 500,
maxDatagramSize: 32768,
},
};
const result = augmentRouteWithHttp3(route, config);
expect(result.action.udp!.quic!.maxIdleTimeout).toEqual(60000);
expect(result.action.udp!.quic!.maxConcurrentBidiStreams).toEqual(200);
expect(result.action.udp!.quic!.maxConcurrentUniStreams).toEqual(50);
expect(result.action.udp!.quic!.initialCongestionWindow).toEqual(65536);
expect(result.action.udp!.quic!.altSvcPort).toEqual(8443);
expect(result.action.udp!.quic!.altSvcMaxAge).toEqual(3600);
expect(result.action.udp!.sessionTimeout).toEqual(120000);
expect(result.action.udp!.maxSessionsPerIP).toEqual(500);
expect(result.action.udp!.maxDatagramSize).toEqual(32768);
});
tap.test('should not mutate the original route', async () => {
const route = makeRoute();
const originalTransport = route.match.transport;
const originalUdp = route.action.udp;
augmentRouteWithHttp3(route, defaultConfig);
expect(route.match.transport).toEqual(originalTransport);
expect(route.action.udp).toEqual(originalUdp);
});
// ──────────────────────────────────────────────────────────────────────────────
// Batch augmentation
// ──────────────────────────────────────────────────────────────────────────────
tap.test('should augment multiple routes in a batch', async () => {
const routes = [
makeRoute({ name: 'web-app' }),
makeRoute({ name: 'smtp-route', match: { ports: 25 } }),
makeRoute({ name: 'api-gateway' }),
makeRoute({
name: 'dns-query',
action: { type: 'socket-handler' as any, socketHandler: (() => {}) as any },
}),
];
const results = augmentRoutesWithHttp3(routes, defaultConfig);
// web-app and api-gateway should be augmented
expect(results[0].match.transport).toEqual('all');
expect(results[2].match.transport).toEqual('all');
// smtp and dns should NOT be augmented
expect(results[1].match.transport).toBeUndefined();
expect(results[3].match.transport).toBeUndefined();
});
// ──────────────────────────────────────────────────────────────────────────────
// Default enabled behavior
// ──────────────────────────────────────────────────────────────────────────────
tap.test('should treat undefined enabled as true (default on)', async () => {
const route = makeRoute();
const result = augmentRouteWithHttp3(route, {}); // no enabled field at all
expect(result.match.transport).toEqual('all');
expect(result.action.udp!.quic!.enableHttp3).toBeTrue();
});
tap.test('should disable when enabled is explicitly false', async () => {
const route = makeRoute();
const result = augmentRouteWithHttp3(route, { enabled: false });
expect(result.match.transport).toBeUndefined();
expect(result.action.udp).toBeUndefined();
});
export default tap.start();

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,6 +24,7 @@ import { RadiusServer, type IRadiusServerConfig } from './radius/index.js';
import { RemoteIngressManager, TunnelManager } from './remoteingress/index.js'; import { RemoteIngressManager, TunnelManager } from './remoteingress/index.js';
import { RouteConfigManager, ApiTokenManager } from './config/index.js'; import { RouteConfigManager, ApiTokenManager } from './config/index.js';
import { SecurityLogger, ContentScanner, IPReputationChecker } from './security/index.js'; import { SecurityLogger, ContentScanner, IPReputationChecker } from './security/index.js';
import { type IHttp3Config, augmentRoutesWithHttp3 } from './http3/index.js';
export interface IDcRouterOptions { export interface IDcRouterOptions {
/** Base directory for all dcrouter data. Defaults to ~/.serve.zone/dcrouter */ /** Base directory for all dcrouter data. Defaults to ~/.serve.zone/dcrouter */
@@ -163,6 +164,17 @@ export interface IDcRouterOptions {
* Remote Ingress configuration for edge tunnel nodes * Remote Ingress configuration for edge tunnel nodes
* Enables edge nodes to accept incoming connections and tunnel them to this DcRouter * Enables edge nodes to accept incoming connections and tunnel them to this DcRouter
*/ */
/**
* HTTP/3 (QUIC) configuration for HTTPS routes.
* Enabled by default — qualifying HTTPS routes on port 443 are automatically
* augmented with QUIC/H3 fields. Set { enabled: false } to disable globally.
* Individual routes can opt out via action.options.http3 = false.
*/
http3?: IHttp3Config;
/** Port for the OpsServer web UI (default: 3000) */
opsServerPort?: number;
remoteIngressConfig?: { remoteIngressConfig?: {
/** Enable remote ingress hub (default: false) */ /** Enable remote ingress hub (default: false) */
enabled?: boolean; enabled?: boolean;
@@ -294,6 +306,7 @@ export class DcRouter {
this.storageManager, this.storageManager,
() => this.getConstructorRoutes(), () => this.getConstructorRoutes(),
() => this.smartProxy, () => this.smartProxy,
() => this.options.http3,
); );
this.apiTokenManager = new ApiTokenManager(this.storageManager); this.apiTokenManager = new ApiTokenManager(this.storageManager);
await this.apiTokenManager.initialize(); await this.apiTokenManager.initialize();
@@ -466,6 +479,13 @@ export class DcRouter {
challengeHandlers.push(dns01Handler); challengeHandlers.push(dns01Handler);
} }
// HTTP/3 augmentation (enabled by default unless explicitly disabled)
if (this.options.http3?.enabled !== false) {
const http3Config: IHttp3Config = { enabled: true, ...this.options.http3 };
routes = augmentRoutesWithHttp3(routes, http3Config);
logger.log('info', 'HTTP/3: Augmented qualifying HTTPS routes with QUIC/H3 configuration');
}
// Cache constructor routes for RouteConfigManager // Cache constructor routes for RouteConfigManager
this.constructorRoutes = [...routes]; this.constructorRoutes = [...routes];
@@ -1733,7 +1753,7 @@ export class DcRouter {
await this.remoteIngressManager.initialize(); await this.remoteIngressManager.initialize();
// Pass current routes so the manager can derive edge ports from remoteIngress-tagged routes // Pass current routes so the manager can derive edge ports from remoteIngress-tagged routes
const currentRoutes = this.options.smartProxyConfig?.routes || []; const currentRoutes = this.constructorRoutes;
this.remoteIngressManager.setRoutes(currentRoutes as any[]); this.remoteIngressManager.setRoutes(currentRoutes as any[]);
// Resolve TLS certs for tunnel: explicit paths > ACME for hubDomain > self-signed (Rust default) // Resolve TLS certs for tunnel: explicit paths > ACME for hubDomain > self-signed (Rust default)

View File

@@ -7,6 +7,7 @@ import type {
IMergedRoute, IMergedRoute,
IRouteWarning, IRouteWarning,
} from '../../ts_interfaces/data/route-management.js'; } from '../../ts_interfaces/data/route-management.js';
import { type IHttp3Config, augmentRouteWithHttp3 } from '../http3/index.js';
const ROUTES_PREFIX = '/config-api/routes/'; const ROUTES_PREFIX = '/config-api/routes/';
const OVERRIDES_PREFIX = '/config-api/overrides/'; const OVERRIDES_PREFIX = '/config-api/overrides/';
@@ -20,6 +21,7 @@ export class RouteConfigManager {
private storageManager: StorageManager, private storageManager: StorageManager,
private getHardcodedRoutes: () => plugins.smartproxy.IRouteConfig[], private getHardcodedRoutes: () => plugins.smartproxy.IRouteConfig[],
private getSmartProxy: () => plugins.smartproxy.SmartProxy | undefined, private getSmartProxy: () => plugins.smartproxy.SmartProxy | undefined,
private getHttp3Config?: () => IHttp3Config | undefined,
) {} ) {}
/** /**
@@ -258,10 +260,15 @@ export class RouteConfigManager {
enabledRoutes.push(route); enabledRoutes.push(route);
} }
// Add enabled programmatic routes // Add enabled programmatic routes (with HTTP/3 augmentation if enabled)
const http3Config = this.getHttp3Config?.();
for (const stored of this.storedRoutes.values()) { for (const stored of this.storedRoutes.values()) {
if (stored.enabled) { if (stored.enabled) {
enabledRoutes.push(stored.route); if (http3Config && http3Config.enabled !== false) {
enabledRoutes.push(augmentRouteWithHttp3(stored.route, { enabled: true, ...http3Config }));
} else {
enabledRoutes.push(stored.route);
}
} }
} }

View File

@@ -0,0 +1,153 @@
import type * as plugins from '../plugins.js';
/**
* Configuration for HTTP/3 (QUIC) route augmentation.
* HTTP/3 is enabled by default on all qualifying HTTPS routes.
*/
export interface IHttp3Config {
/** Enable HTTP/3 augmentation on qualifying routes (default: true) */
enabled?: boolean;
/** QUIC-specific settings applied to all augmented routes */
quicSettings?: {
/** QUIC connection idle timeout in ms (default: 30000) */
maxIdleTimeout?: number;
/** Max concurrent bidirectional streams per connection (default: 100) */
maxConcurrentBidiStreams?: number;
/** Max concurrent unidirectional streams per connection (default: 100) */
maxConcurrentUniStreams?: number;
/** Initial congestion window size in bytes */
initialCongestionWindow?: number;
};
/** Alt-Svc header settings */
altSvc?: {
/** Port advertised in Alt-Svc header (default: same as listening port) */
port?: number;
/** Max age for Alt-Svc advertisement in seconds (default: 86400) */
maxAge?: number;
};
/** UDP session settings */
udpSettings?: {
/** Idle timeout for UDP sessions in ms (default: 60000) */
sessionTimeout?: number;
/** Max concurrent UDP sessions per source IP (default: 1000) */
maxSessionsPerIP?: number;
/** Max accepted datagram size in bytes (default: 65535) */
maxDatagramSize?: number;
};
}
type TPortRange = plugins.smartproxy.IRouteConfig['match']['ports'];
/**
* Check whether a TPortRange includes port 443.
*/
function portRangeIncludes443(ports: TPortRange): boolean {
if (typeof ports === 'number') return ports === 443;
if (Array.isArray(ports)) {
return ports.some((p) => {
if (typeof p === 'number') return p === 443;
return p.from <= 443 && p.to >= 443;
});
}
return false;
}
/**
* Check if a route name indicates an email route that should not get HTTP/3.
*/
function isEmailRoute(route: plugins.smartproxy.IRouteConfig): boolean {
const name = route.name?.toLowerCase() || '';
return (
name.startsWith('smtp-') ||
name.startsWith('submission-') ||
name.startsWith('smtps-') ||
name.startsWith('email-')
);
}
/**
* Determine if a route qualifies for HTTP/3 augmentation.
*/
export function routeQualifiesForHttp3(
route: plugins.smartproxy.IRouteConfig,
globalConfig: IHttp3Config,
): boolean {
// Check global enable + per-route override
const globalEnabled = globalConfig.enabled !== false; // default true
const perRouteOverride = route.action.options?.http3;
// If per-route explicitly set, use that; otherwise use global
const shouldAugment =
perRouteOverride !== undefined ? perRouteOverride : globalEnabled;
if (!shouldAugment) return false;
// Must be forward type
if (route.action.type !== 'forward') return false;
// Must include port 443
if (!portRangeIncludes443(route.match.ports)) return false;
// Must have TLS
if (!route.action.tls) return false;
// Skip email routes
if (isEmailRoute(route)) return false;
// Skip if already configured with transport 'all' or 'udp'
if (route.match.transport === 'all' || route.match.transport === 'udp') return false;
// Skip if already has QUIC config
if (route.action.udp?.quic) return false;
return true;
}
/**
* Augment a single route with HTTP/3 fields.
* Returns a new route object (does not mutate the original).
*/
export function augmentRouteWithHttp3(
route: plugins.smartproxy.IRouteConfig,
config: IHttp3Config,
): plugins.smartproxy.IRouteConfig {
if (!routeQualifiesForHttp3(route, config)) {
return route;
}
return {
...route,
match: {
...route.match,
transport: 'all' as const,
},
action: {
...route.action,
udp: {
...(route.action.udp || {}),
sessionTimeout: config.udpSettings?.sessionTimeout,
maxSessionsPerIP: config.udpSettings?.maxSessionsPerIP,
maxDatagramSize: config.udpSettings?.maxDatagramSize,
quic: {
enableHttp3: true,
maxIdleTimeout: config.quicSettings?.maxIdleTimeout,
maxConcurrentBidiStreams: config.quicSettings?.maxConcurrentBidiStreams,
maxConcurrentUniStreams: config.quicSettings?.maxConcurrentUniStreams,
altSvcPort: config.altSvc?.port,
altSvcMaxAge: config.altSvc?.maxAge ?? 86400,
initialCongestionWindow: config.quicSettings?.initialCongestionWindow,
},
},
},
};
}
/**
* Augment all qualifying routes in an array.
* Returns a new array (does not mutate originals).
*/
export function augmentRoutesWithHttp3(
routes: plugins.smartproxy.IRouteConfig[],
config: IHttp3Config,
): plugins.smartproxy.IRouteConfig[] {
return routes.map((route) => augmentRouteWithHttp3(route, config));
}

1
ts/http3/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './http3-route-augmentation.js';

View File

@@ -5,6 +5,7 @@ export { UnifiedEmailServer } from '@push.rocks/smartmta';
export type { IUnifiedEmailServerOptions, IEmailRoute, IEmailDomainConfig } from '@push.rocks/smartmta'; export type { IUnifiedEmailServerOptions, IEmailRoute, IEmailDomainConfig } from '@push.rocks/smartmta';
// DcRouter // DcRouter
import { DcRouter } from './classes.dcrouter.js';
export * from './classes.dcrouter.js'; export * from './classes.dcrouter.js';
// RADIUS module // RADIUS module
@@ -13,4 +14,27 @@ export * from './radius/index.js';
// Remote Ingress module // Remote Ingress module
export * from './remoteingress/index.js'; export * from './remoteingress/index.js';
export const runCli = async () => {}; // HTTP/3 module
export type { IHttp3Config } from './http3/index.js';
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 // Set up handlers
await this.setupHandlers(); 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(); await router.start();
// OpsServer dashboard at http://localhost:3000 // OpsServer dashboard at http://localhost:3000 (configurable via opsServerPort)
// Graceful shutdown // Graceful shutdown
await router.stop(); await router.stop();
@@ -60,6 +60,9 @@ ts/
│ └── documents/ # Cached document models │ └── documents/ # Cached document models
├── config/ # Configuration utilities ├── config/ # Configuration utilities
├── errors/ # Error classes and retry logic ├── errors/ # Error classes and retry logic
├── http3/ # HTTP/3 (QUIC) route augmentation
│ ├── index.ts # Barrel export
│ └── http3-route-augmentation.ts # Pure utility: augmentRoutesWithHttp3(), IHttp3Config
├── monitoring/ # MetricsManager (SmartMetrics integration) ├── monitoring/ # MetricsManager (SmartMetrics integration)
├── opsserver/ # OpsServer dashboard + API handlers ├── opsserver/ # OpsServer dashboard + API handlers
│ ├── classes.opsserver.ts # HTTP server + TypedRouter setup │ ├── classes.opsserver.ts # HTTP server + TypedRouter setup
@@ -71,7 +74,10 @@ ts/
│ ├── email.handler.ts # Email operations │ ├── email.handler.ts # Email operations
│ ├── certificate.handler.ts # Certificate management │ ├── certificate.handler.ts # Certificate management
│ ├── radius.handler.ts # RADIUS 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 ├── radius/ # RADIUS server integration
├── remoteingress/ # Remote ingress hub integration ├── remoteingress/ # Remote ingress hub integration
│ ├── classes.remoteingress-manager.ts # Edge CRUD + port derivation │ ├── classes.remoteingress-manager.ts # Edge CRUD + port derivation
@@ -96,6 +102,9 @@ export { RadiusServer, IRadiusServerConfig } from './radius/index.js';
// Remote Ingress // Remote Ingress
export { RemoteIngressManager, TunnelManager } from './remoteingress/index.js'; export { RemoteIngressManager, TunnelManager } from './remoteingress/index.js';
// HTTP/3
export type { IHttp3Config } from './http3/index.js';
``` ```
## Key Classes ## Key Classes
@@ -112,6 +121,7 @@ The central orchestrator. Accepts `IDcRouterOptions` and manages the lifecycle o
| `radiusConfig` | RadiusServer (auth + accounting) | `@push.rocks/smartradius` | | `radiusConfig` | RadiusServer (auth + accounting) | `@push.rocks/smartradius` |
| `remoteIngressConfig` | RemoteIngressManager + TunnelManager | `@serve.zone/remoteingress` | | `remoteIngressConfig` | RemoteIngressManager + TunnelManager | `@serve.zone/remoteingress` |
| `tls` + `dnsChallenge` | SmartAcme (ACME cert provisioning) | `@push.rocks/smartacme` | | `tls` + `dnsChallenge` | SmartAcme (ACME cert provisioning) | `@push.rocks/smartacme` |
| `http3` | HTTP/3 route augmentation (enabled by default) | built-in |
| `cacheConfig` | CacheDb (embedded MongoDB) | `@push.rocks/smartdata` | | `cacheConfig` | CacheDb (embedded MongoDB) | `@push.rocks/smartdata` |
| *(always)* | OpsServer (dashboard + API) | `@api.global/typedserver` | | *(always)* | OpsServer (dashboard + API) | `@api.global/typedserver` |
| *(always)* | MetricsManager | `@push.rocks/smartmetrics` | | *(always)* | MetricsManager | `@push.rocks/smartmetrics` |

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. * 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>(); const ports = new Set<number>();
if (typeof portRange === 'number') { if (typeof portRange === 'number') {
ports.add(portRange); ports.add(portRange);
@@ -94,6 +94,38 @@ export class RemoteIngressManager {
return [...ports].sort((a, b) => a - b); return [...ports].sort((a, b) => a - b);
} }
/**
* Derive UDP listen ports for an edge from routes with transport 'udp' or 'all'.
* These ports need UDP listeners on the edge (e.g. for QUIC/HTTP3).
*/
public deriveUdpPortsForEdge(edgeId: string, edgeTags?: string[]): number[] {
const ports = new Set<number>();
for (const route of this.routes) {
if (!route.remoteIngress?.enabled) continue;
// Apply edge filter if present
const filter = route.remoteIngress.edgeFilter;
if (filter && filter.length > 0) {
const idMatch = filter.includes(edgeId);
const tagMatch = edgeTags?.some((tag) => filter.includes(tag)) ?? false;
if (!idMatch && !tagMatch) continue;
}
// Only include ports from routes that listen on UDP
const transport = route.match?.transport;
if (transport === 'udp' || transport === 'all') {
if (route.match?.ports) {
for (const p of extractPorts(route.match.ports)) {
ports.add(p);
}
}
}
}
return [...ports].sort((a, b) => a - b);
}
/** /**
* Get the effective listen ports for an edge. * Get the effective listen ports for an edge.
* Manual ports are always included. Auto-derived ports are added (union) when autoDerivePorts is true. * Manual ports are always included. Auto-derived ports are added (union) when autoDerivePorts is true.
@@ -106,6 +138,18 @@ export class RemoteIngressManager {
return [...new Set([...manualPorts, ...derivedPorts])].sort((a, b) => a - b); return [...new Set([...manualPorts, ...derivedPorts])].sort((a, b) => a - b);
} }
/**
* Get the effective UDP listen ports for an edge.
* Manual UDP ports are always included. Auto-derived UDP ports are added when autoDerivePorts is true.
*/
public getEffectiveListenPortsUdp(edge: IRemoteIngress): number[] {
const manualPorts = edge.listenPortsUdp || [];
const shouldDerive = edge.autoDerivePorts !== false;
if (!shouldDerive) return [...manualPorts].sort((a, b) => a - b);
const derivedPorts = this.deriveUdpPortsForEdge(edge.id, edge.tags);
return [...new Set([...manualPorts, ...derivedPorts])].sort((a, b) => a - b);
}
/** /**
* Get manual and derived port breakdown for an edge (used in API responses). * Get manual and derived port breakdown for an edge (used in API responses).
* Derived ports exclude any ports already present in the manual list. * Derived ports exclude any ports already present in the manual list.
@@ -241,15 +285,18 @@ export class RemoteIngressManager {
/** /**
* Get the list of allowed edges (enabled only) for the Rust hub. * Get the list of allowed edges (enabled only) for the Rust hub.
* Includes listenPortsUdp when routes with transport 'udp' or 'all' are present.
*/ */
public getAllowedEdges(): Array<{ id: string; secret: string; listenPorts: number[] }> { public getAllowedEdges(): Array<{ id: string; secret: string; listenPorts: number[]; listenPortsUdp?: number[] }> {
const result: Array<{ id: string; secret: string; listenPorts: number[] }> = []; const result: Array<{ id: string; secret: string; listenPorts: number[]; listenPortsUdp?: number[] }> = [];
for (const edge of this.edges.values()) { for (const edge of this.edges.values()) {
if (edge.enabled) { if (edge.enabled) {
const listenPortsUdp = this.getEffectiveListenPortsUdp(edge);
result.push({ result.push({
id: edge.id, id: edge.id,
secret: edge.secret, secret: edge.secret,
listenPorts: this.getEffectiveListenPorts(edge), listenPorts: this.getEffectiveListenPorts(edge),
...(listenPortsUdp.length > 0 ? { listenPortsUdp } : {}),
}); });
} }
} }

View File

@@ -271,7 +271,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
### Company Information ### Company Information
Task Venture Capital GmbH Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany 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. For any legal inquiries or further information, please contact us via email at hello@task.vc.

View File

@@ -8,6 +8,8 @@ export interface IRemoteIngress {
name: string; name: string;
secret: string; secret: string;
listenPorts: number[]; listenPorts: number[];
/** UDP listen ports (e.g. for QUIC/HTTP3). Derived from routes with transport 'udp' or 'all'. */
listenPortsUdp?: number[];
enabled: boolean; enabled: boolean;
/** Whether to auto-derive ports from remoteIngress-tagged routes. Defaults to true. */ /** Whether to auto-derive ports from remoteIngress-tagged routes. Defaults to true. */
autoDerivePorts: boolean; autoDerivePorts: boolean;
@@ -20,6 +22,8 @@ export interface IRemoteIngress {
manualPorts?: number[]; manualPorts?: number[];
/** Ports auto-derived from route configs — only present in API responses. */ /** Ports auto-derived from route configs — only present in API responses. */
derivedPorts?: number[]; derivedPorts?: number[];
/** Effective UDP ports (union of manual + derived) — only present in API responses. */
effectiveListenPortsUdp?: number[];
} }
/** /**

View File

@@ -292,7 +292,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
### Company Information ### Company Information
Task Venture Capital GmbH Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany 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. 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 = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '11.2.26', version: '11.8.1',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' 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 ### Company Information
Task Venture Capital GmbH Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany 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. For any legal inquiries or further information, please contact us via email at hello@task.vc.