From fa96a41e68df77a06781f38044351a0925886ed1 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Thu, 26 Mar 2026 16:21:45 +0000 Subject: [PATCH] feat(docker,cache,proxy): improve container runtime defaults and add configurable connection limits --- Dockerfile | 14 +++- changelog.md | 8 +++ package.json | 10 +-- pnpm-lock.yaml | 122 +++++++++++++++++++---------------- readme.md | 61 ++++++++++++------ ts/00_commitinfo_data.ts | 2 +- ts/cache/classes.cachedb.ts | 26 ++++---- ts/classes.dcrouter.ts | 47 +++++++++++++- ts/plugins.ts | 8 +-- ts_oci_container/index.ts | 22 +++++++ ts_web/00_commitinfo_data.ts | 2 +- 11 files changed, 218 insertions(+), 104 deletions(-) diff --git a/Dockerfile b/Dockerfile index ed3bc41..791b002 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,9 +18,17 @@ WORKDIR /app COPY --from=build /app /app ENV DCROUTER_MODE=OCI_CONTAINER +ENV DCROUTER_HEAP_SIZE=512 +ENV UV_THREADPOOL_SIZE=16 RUN pnpm install -g @servezone/healthy -HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD [ "healthy" ] +HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=3 CMD [ "healthy" ] -EXPOSE 80 -CMD ["npm", "start"] +LABEL org.opencontainers.image.title="dcrouter" \ + org.opencontainers.image.description="Multi-service datacenter gateway" \ + org.opencontainers.image.source="https://code.foss.global/serve.zone/dcrouter" + +# HTTP/HTTPS, SMTP/Submission/SMTPS, DNS, RADIUS, OpsServer, RemoteIngress, dynamic range +EXPOSE 80 443 25 587 465 53/tcp 53/udp 1812/udp 1813/udp 3000 8443 29000-30000 + +CMD ["sh", "-c", "node --max_old_space_size=${DCROUTER_HEAP_SIZE} ./cli.js"] diff --git a/changelog.md b/changelog.md index 5469a30..05750e6 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2026-03-26 - 11.11.0 - feat(docker,cache,proxy) +improve container runtime defaults and add configurable connection limits + +- replace the embedded cache backend integration from smartmongo LocalTsmDb to smartdb LocalSmartDb +- add OCI container settings for heap size, threadpool size, expanded exposed ports, image metadata, and a direct node startup command +- introduce startup checks for file descriptor limits and warn when container nofile limits are too low for production +- set gateway-oriented SmartProxy default limits and allow max connections, per-IP connections, and rate limits to be configured through OCI environment variables + ## 2026-03-26 - 11.10.7 - fix(sms) update sms service to use async ProjectInfo initialization diff --git a/package.json b/package.json index 6883911..f4d4920 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "license": "MIT", "scripts": { "test": "(tstest test/ --logfile --timeout 60)", - "start": "(node --max_old_space_size=250 ./cli.js)", + "start": "(node ./cli.js)", "startTs": "(node cli.ts.js)", "build": "(tsbuild tsfolders --allowimplicitany && npm run bundle)", "build:docker": "tsdocker build --verbose", @@ -42,18 +42,18 @@ "@push.rocks/qenv": "^6.1.3", "@push.rocks/smartacme": "^9.3.0", "@push.rocks/smartdata": "^7.1.3", + "@push.rocks/smartdb": "^1.0.1", "@push.rocks/smartdns": "^7.9.0", - "@push.rocks/smartfile": "^13.1.2", + "@push.rocks/smartfs": "^1.5.0", "@push.rocks/smartguard": "^3.1.0", "@push.rocks/smartjwt": "^2.2.1", "@push.rocks/smartlog": "^3.2.1", "@push.rocks/smartmetrics": "^3.0.3", - "@push.rocks/smartmongo": "^5.1.1", "@push.rocks/smartmta": "^5.3.1", - "@push.rocks/smartnetwork": "^4.4.0", + "@push.rocks/smartnetwork": "^4.5.2", "@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpromise": "^4.2.3", - "@push.rocks/smartproxy": "^26.2.4", + "@push.rocks/smartproxy": "^26.3.0", "@push.rocks/smartradius": "^1.1.1", "@push.rocks/smartrequest": "^5.0.1", "@push.rocks/smartrx": "^3.0.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 38e93ca..0bf43ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,12 +44,15 @@ importers: '@push.rocks/smartdata': specifier: ^7.1.3 version: 7.1.3(socks@2.8.7) + '@push.rocks/smartdb': + specifier: ^1.0.1 + version: 1.0.1 '@push.rocks/smartdns': specifier: ^7.9.0 version: 7.9.0 - '@push.rocks/smartfile': - specifier: ^13.1.2 - version: 13.1.2 + '@push.rocks/smartfs': + specifier: ^1.5.0 + version: 1.5.0 '@push.rocks/smartguard': specifier: ^3.1.0 version: 3.1.0 @@ -62,15 +65,12 @@ importers: '@push.rocks/smartmetrics': specifier: ^3.0.3 version: 3.0.3 - '@push.rocks/smartmongo': - specifier: ^5.1.1 - version: 5.1.1(socks@2.8.7) '@push.rocks/smartmta': specifier: ^5.3.1 version: 5.3.1 '@push.rocks/smartnetwork': - specifier: ^4.4.0 - version: 4.4.0 + specifier: ^4.5.2 + version: 4.5.2 '@push.rocks/smartpath': specifier: ^6.0.0 version: 6.0.0 @@ -78,8 +78,8 @@ importers: specifier: ^4.2.3 version: 4.2.3 '@push.rocks/smartproxy': - specifier: ^26.2.4 - version: 26.2.4 + specifier: ^26.3.0 + version: 26.3.0 '@push.rocks/smartradius': specifier: ^1.1.1 version: 1.1.1 @@ -1125,6 +1125,9 @@ packages: '@push.rocks/smartdata@7.1.3': resolution: {integrity: sha512-7vQJ9pdRk450yn2m9tmGPdSRlQVmxFPZjHD4sGYsfqCQPg+GLFusu+H16zpf+jKzAq4F2ZBMPaYymJHXvXiVcw==} + '@push.rocks/smartdb@1.0.1': + resolution: {integrity: sha512-m79Gzn7rHBMRYDohy1Mwm3niyRrre0g1ev3NxrIO6dnjo584pa3b0vTKwN+8R+tURJPppiMWfkPscpeKtrwIIg==} + '@push.rocks/smartdelay@3.0.5': resolution: {integrity: sha512-mUuI7kj2f7ztjpic96FvRIlf2RsKBa5arw81AHNsndbxO6asRcxuWL8dTVxouEIK8YsBUlj0AsrCkHhMbLQdHw==} @@ -1230,8 +1233,11 @@ packages: '@push.rocks/smartmustache@3.0.2': resolution: {integrity: sha512-G3LyRXoJhyM+iQhkvP/MR/2WYMvC9U7zc2J44JxUM5tPdkQ+o3++FbfRtnZj6rz5X/A7q03//vsxPitVQwoi2Q==} - '@push.rocks/smartnetwork@4.4.0': - resolution: {integrity: sha512-OvFtz41cvQ7lcXwaIOhghNUUlNoMxvwKDctbDvMyuZyEH08SpLjhyv2FuKbKL/mgwA/WxakTbohoC8SW7t+kiw==} + '@push.rocks/smartnetwork@4.5.2': + resolution: {integrity: sha512-lbMMyc2f/WWd5+qzZyF1ynXndjCtasxPWmj/d8GUuis9rDrW7sLIT1PlAPC2F6Qsy4H/K32JrYU+01d/6sWObg==} + + '@push.rocks/smartnftables@1.0.1': + resolution: {integrity: sha512-o822GH4J8dlEBvNLbm+CwU4h6isMUEh03tf2ZnOSWXc5iewRDdKdOCDwI/e+WdnGYWyv7gvH0DHztCmne6rTCg==} '@push.rocks/smartnpm@2.0.6': resolution: {integrity: sha512-7anKDOjX6gXWs1IAc+YWz9ZZ8gDsTwaLh+CxRnGHjAawOmK788NrrgVCg2Fb3qojrPnoxecc46F8Ivp1BT7Izw==} @@ -1251,14 +1257,11 @@ packages: '@push.rocks/smartpdf@4.2.0': resolution: {integrity: sha512-+egzby5QKJGO10MDvWp+N69cJ8i5M354l9ntc+uLRpxuq/FEY9kigpRwMvRYF5qwOBTuGTLqvmvILzlLtboAQg==} - '@push.rocks/smartping@1.0.8': - resolution: {integrity: sha512-Fvx1Db6hSsDOI6pdiCuS9GjtOX8ugx865YQrPg5vK2iw6Qj/srwyXcWLFYt+19WVKtvtWDJIAKbW+q3bXFsCeA==} - '@push.rocks/smartpromise@4.2.3': resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==} - '@push.rocks/smartproxy@26.2.4': - resolution: {integrity: sha512-TPFqcPIuNiU5suGCkF6A8gK10DEFfr43FOMzIVjZcF1mSbjvHFKKiT+zsy90YYPpio9bLIMmkWu7C6OtL1D4nQ==} + '@push.rocks/smartproxy@26.3.0': + resolution: {integrity: sha512-pdq2l/2vfaZGpNKDo/fG6+9qTcGv3pWzkYcBo3rSHhQvRLuYpYeFDxTKp7+cQpn0j+H2/X5SlECQtTQNnkF4qw==} '@push.rocks/smartpuppeteer@2.0.5': resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==} @@ -2034,9 +2037,6 @@ packages: '@types/node@25.5.0': resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} - '@types/ping@0.4.4': - resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==} - '@types/randomatic@3.1.5': resolution: {integrity: sha512-VCwCTw6qh1pRRw+5rNTAwqPmf6A+hdrkdM7dBpZVmhl7g+em3ONXlYK/bWPVKqVGMWgP0d1bog8Vc/X6zRwRRQ==} @@ -2919,9 +2919,6 @@ packages: resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} engines: {node: '>=20'} - isopen@1.3.0: - resolution: {integrity: sha512-AN6Q9J0UlqHFl1fN/2xJCHCBLCBCFDjZhpGBO1gh3wzgRPsFSFBUL36I2Lbfd9qkuoj58axmE7j83iejTQsk8Q==} - jackspeak@4.2.3: resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} engines: {node: 20 || >=22} @@ -3088,6 +3085,10 @@ packages: math-random@1.0.4: resolution: {integrity: sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==} + maxmind@5.0.5: + resolution: {integrity: sha512-1lcH2kMjbBpCFhuHaMU32vz8CuOsKttRcWMQyXvtlklopCzN7NNHSVR/h9RYa8JPuFTGmkn2vYARm+7cIGuqDw==} + engines: {node: '>=12', npm: '>=6'} + mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -3270,6 +3271,10 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mmdb-lib@3.0.2: + resolution: {integrity: sha512-7e87vk0DdWT647wjcfEtWeMtjm+zVGqNohN/aeIymbUfjHQ2T4Sx5kM+1irVDBSloNC3CkGKxswdMoo8yhqTDg==} + engines: {node: '>=10', npm: '>=6'} + monaco-editor@0.55.1: resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} @@ -3559,10 +3564,6 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - ping@0.4.4: - resolution: {integrity: sha512-56ZMC0j7SCsMMLdOoUg12VZCfj/+ZO+yfOSjaNCRrmZZr6GLbN2X/Ui56T15dI8NhiHckaw5X2pvyfAomanwqQ==} - engines: {node: '>=4.0.0'} - pixelmatch@5.3.0: resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} hasBin: true @@ -3938,12 +3939,6 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - systeminformation@5.31.4: - resolution: {integrity: sha512-lZppDyQx91VdS5zJvAyGkmwe+Mq6xY978BDUG2wRkWE+jkmUF5ti8cvOovFQoN5bvSFKCXVkyKEaU5ec3SJiRg==} - engines: {node: '>=8.0.0'} - os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] - hasBin: true - tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} @@ -3966,6 +3961,10 @@ packages: through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + tiny-lru@11.4.7: + resolution: {integrity: sha512-w/Te7uMUVeH0CR8vZIjr+XiN41V+30lkDdK+NRIDCUYKKuL9VcmaUEmaPISuwGhLlrTGh5yu18lENtR9axSxYw==} + engines: {node: '>=12'} + tiny-worker@2.3.0: resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==} @@ -5094,7 +5093,7 @@ snapshots: '@push.rocks/smartjson': 6.0.0 '@push.rocks/smartlog': 3.2.1 '@push.rocks/smartmongo': 5.1.1(socks@2.8.7) - '@push.rocks/smartnetwork': 4.4.0 + '@push.rocks/smartnetwork': 4.5.2 '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartrequest': 5.0.1 @@ -5856,7 +5855,7 @@ snapshots: '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartdns': 7.9.0 '@push.rocks/smartlog': 3.2.1 - '@push.rocks/smartnetwork': 4.4.0 + '@push.rocks/smartnetwork': 4.5.2 '@push.rocks/smartstring': 4.1.0 '@push.rocks/smarttime': 4.2.3 '@push.rocks/smartunique': 3.0.9 @@ -5866,11 +5865,14 @@ snapshots: - '@aws-sdk/credential-providers' - '@mongodb-js/zstd' - '@nuxt/kit' + - bare-abort-controller + - bare-buffer - encoding - gcp-metadata - kerberos - mongodb-client-encryption - react + - react-native-b4a - snappy - socks - supports-color @@ -6013,6 +6015,15 @@ snapshots: - supports-color - vue + '@push.rocks/smartdb@1.0.1': + dependencies: + '@push.rocks/smartfs': 1.5.0 + '@push.rocks/smartpath': 6.0.0 + '@push.rocks/smartpromise': 4.2.3 + '@push.rocks/smartrx': 3.0.10 + bson: 7.2.0 + mingo: 7.2.0 + '@push.rocks/smartdelay@3.0.5': dependencies: '@push.rocks/smartpromise': 4.2.3 @@ -6284,17 +6295,19 @@ snapshots: dependencies: handlebars: 4.7.8 - '@push.rocks/smartnetwork@4.4.0': + '@push.rocks/smartnetwork@4.5.2': dependencies: '@push.rocks/smartdns': 7.9.0 - '@push.rocks/smartping': 1.0.8 - '@push.rocks/smartpromise': 4.2.3 - '@push.rocks/smartstring': 4.1.0 - isopen: 1.3.0 - systeminformation: 5.31.4 + '@push.rocks/smartrust': 1.3.2 + maxmind: 5.0.5 transitivePeerDependencies: - supports-color + '@push.rocks/smartnftables@1.0.1': + dependencies: + '@push.rocks/smartlog': 3.2.1 + '@push.rocks/smartpromise': 4.2.3 + '@push.rocks/smartnpm@2.0.6': dependencies: '@push.rocks/consolecolor': 2.0.3 @@ -6346,7 +6359,7 @@ snapshots: '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartfs': 1.5.0 '@push.rocks/smartjimp': 1.2.0 - '@push.rocks/smartnetwork': 4.4.0 + '@push.rocks/smartnetwork': 4.5.2 '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartpuppeteer': 2.0.5(typescript@6.0.2) @@ -6368,17 +6381,13 @@ snapshots: - utf-8-validate - vue - '@push.rocks/smartping@1.0.8': - dependencies: - '@types/ping': 0.4.4 - ping: 0.4.4 - '@push.rocks/smartpromise@4.2.3': {} - '@push.rocks/smartproxy@26.2.4': + '@push.rocks/smartproxy@26.3.0': dependencies: '@push.rocks/smartcrypto': 2.0.4 '@push.rocks/smartlog': 3.2.1 + '@push.rocks/smartnftables': 1.0.1 '@push.rocks/smartrust': 1.3.2 '@tsclass/tsclass': 9.5.0 minimatch: 10.2.4 @@ -7413,8 +7422,6 @@ snapshots: dependencies: undici-types: 7.18.2 - '@types/ping@0.4.4': {} - '@types/randomatic@3.1.5': {} '@types/relateurl@0.2.33': {} @@ -8346,8 +8353,6 @@ snapshots: isexe@4.0.0: {} - isopen@1.3.0: {} - jackspeak@4.2.3: dependencies: '@isaacs/cliui': 9.0.0 @@ -8550,6 +8555,11 @@ snapshots: math-random@1.0.4: {} + maxmind@5.0.5: + dependencies: + mmdb-lib: 3.0.2 + tiny-lru: 11.4.7 + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -8911,6 +8921,8 @@ snapshots: mitt@3.0.1: {} + mmdb-lib@3.0.2: {} + monaco-editor@0.55.1: dependencies: dompurify: 3.2.7 @@ -9169,8 +9181,6 @@ snapshots: picomatch@4.0.3: {} - ping@0.4.4: {} - pixelmatch@5.3.0: dependencies: pngjs: 6.0.0 @@ -9687,8 +9697,6 @@ snapshots: symbol-tree@3.2.4: {} - systeminformation@5.31.4: {} - tagged-tag@1.0.0: {} tar-fs@3.1.2: @@ -9742,6 +9750,8 @@ snapshots: dependencies: readable-stream: 3.6.2 + tiny-lru@11.4.7: {} + tiny-worker@2.3.0: dependencies: esm: 3.2.25 diff --git a/readme.md b/readme.md index 3bd5ded..40cbed7 100644 --- a/readme.md +++ b/readme.md @@ -83,7 +83,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community ### 💾 Persistent Storage & Caching - **Multiple storage backends**: filesystem, custom functions, or in-memory -- **Embedded cache database** via smartdata + LocalTsmDb (MongoDB-compatible) +- **Embedded cache database** via smartdata + smartdb (MongoDB-compatible) - **Automatic TTL-based cleanup** for cached emails and IP reputation data ### 🖥️ OpsServer Dashboard @@ -340,7 +340,7 @@ graph TB | **OpsServer** | `@api.global/typedserver` | Web dashboard + TypedRequest API for monitoring and management | | **MetricsManager** | `@push.rocks/smartmetrics` | Real-time metrics collection (CPU, memory, email, DNS, security) | | **StorageManager** | built-in | Pluggable key-value storage (filesystem, custom, or in-memory) | -| **CacheDb** | `@push.rocks/smartdata` | Embedded MongoDB-compatible database (LocalTsmDb) for persistent caching | +| **CacheDb** | `@push.rocks/smartdb` | Embedded MongoDB-compatible database (LocalSmartDb) for persistent caching | ### How It Works @@ -1066,7 +1066,7 @@ Used for: TLS certificates, DKIM keys, email routes, bounce/suppression lists, I ### Cache Database -An embedded MongoDB-compatible database (via smartdata + LocalTsmDb) for persistent caching with automatic TTL cleanup: +An embedded MongoDB-compatible database (via smartdata + smartdb) for persistent caching with automatic TTL cleanup: ```typescript cacheConfig: { @@ -1406,37 +1406,58 @@ tstest test/test.opsserver-api.ts --verbose --timeout 60 ## 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). +DcRouter ships with a production-ready `Dockerfile` and supports environment-variable-driven configuration for OCI container deployments. The container image includes tini as PID 1 (via the base image), proper health checks, and configurable resource limits. 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 \ + --ulimit nofile=65536:65536 \ -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 \ + -p 80:80 -p 443:443 -p 25:25 -p 587:587 -p 465:465 \ + -p 53:53/udp -p 3000:3000 -p 8443:8443 \ code.foss.global/serve.zone/dcrouter:latest ``` +> ⚡ **Production tip:** Always set `--ulimit nofile=65536:65536` for production deployments. DcRouter will log a warning at startup if the file descriptor limit is below 65536. + ### 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` | +| Variable | Description | Default | Example | +|----------|-------------|---------|---------| +| `DCROUTER_MODE` | Container mode (set automatically in image) | `OCI_CONTAINER` | — | +| `DCROUTER_CONFIG_PATH` | Path to JSON config file (env vars override) | — | `/config/dcrouter.json` | +| `DCROUTER_BASE_DIR` | Base data directory | `~/.serve.zone/dcrouter` | `/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` | `false` | +| `DCROUTER_HEAP_SIZE` | Node.js V8 heap size in MB | `512` | `1024` | +| `DCROUTER_MAX_CONNECTIONS` | Global max concurrent connections | `50000` | `100000` | +| `DCROUTER_MAX_CONNECTIONS_PER_IP` | Max connections per source IP | `100` | `200` | +| `DCROUTER_CONNECTION_RATE_LIMIT` | Max new connections/min per IP | `600` | `1200` | + +### Exposed Ports + +The container exposes all service ports: + +| Port(s) | Protocol | Service | +|---------|----------|---------| +| 80, 443 | TCP | HTTP/HTTPS (SmartProxy) | +| 25, 587, 465 | TCP | SMTP, Submission, SMTPS | +| 53 | TCP/UDP | DNS | +| 1812, 1813 | UDP | RADIUS auth/acct | +| 3000 | TCP | OpsServer dashboard | +| 8443 | TCP | Remote ingress tunnels | +| 29000–30000 | TCP | Dynamic port range | ### Building the Image diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 89cfdd8..676e1f1 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '11.10.7', + version: '11.11.0', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts/cache/classes.cachedb.ts b/ts/cache/classes.cachedb.ts index 722f055..0fe29b6 100644 --- a/ts/cache/classes.cachedb.ts +++ b/ts/cache/classes.cachedb.ts @@ -15,15 +15,15 @@ export interface ICacheDbOptions { } /** - * CacheDb - Wrapper around LocalTsmDb and smartdata + * CacheDb - Wrapper around LocalSmartDb and smartdata * * Provides persistent caching using smartdata as the ORM layer - * and LocalTsmDb as the embedded database engine. + * and LocalSmartDb as the embedded database engine. */ export class CacheDb { private static instance: CacheDb | null = null; - private localTsmDb!: plugins.smartmongo.LocalTsmDb; + private localSmartDb!: plugins.smartdb.LocalSmartDb; private smartdataDb!: plugins.smartdata.SmartdataDb; private options: Required; private isStarted: boolean = false; @@ -55,8 +55,8 @@ export class CacheDb { /** * Start the cache database - * - Initializes LocalTsmDb with file persistence - * - Connects smartdata to the LocalTsmDb via Unix socket + * - Initializes LocalSmartDb with file persistence + * - Connects smartdata to the LocalSmartDb via Unix socket */ public async start(): Promise { if (this.isStarted) { @@ -68,16 +68,16 @@ export class CacheDb { // Ensure storage directory exists await plugins.fsUtils.ensureDir(this.options.storagePath); - // Create LocalTsmDb instance - this.localTsmDb = new plugins.smartmongo.LocalTsmDb({ + // Create LocalSmartDb instance + this.localSmartDb = new plugins.smartdb.LocalSmartDb({ folderPath: this.options.storagePath, }); - // Start LocalTsmDb and get connection info - const connectionInfo = await this.localTsmDb.start(); + // Start LocalSmartDb and get connection info + const connectionInfo = await this.localSmartDb.start(); if (this.options.debug) { - logger.log('debug', `LocalTsmDb started with URI: ${connectionInfo.connectionUri}`); + logger.log('debug', `LocalSmartDb started with URI: ${connectionInfo.connectionUri}`); } // Initialize smartdata with the connection URI @@ -109,9 +109,9 @@ export class CacheDb { await this.smartdataDb.close(); } - // Stop LocalTsmDb - if (this.localTsmDb) { - await this.localTsmDb.stop(); + // Stop LocalSmartDb + if (this.localSmartDb) { + await this.localSmartDb.stop(); } this.isStarted = false; diff --git a/ts/classes.dcrouter.ts b/ts/classes.dcrouter.ts index 23cf33c..01e9cbc 100644 --- a/ts/classes.dcrouter.ts +++ b/ts/classes.dcrouter.ts @@ -528,10 +528,36 @@ export class DcRouter { } public async start() { + await this.checkSystemLimits(); logger.log('info', 'Starting DcRouter Services'); await this.serviceManager.start(); this.logStartupSummary(); } + + /** + * Detect OS-level resource limits and warn if they are too low for production use. + * This is detection only — no attempts to raise limits. + */ + private async checkSystemLimits(): Promise { + try { + const fs = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode()); + const limitsContent = await fs.file('/proc/self/limits').encoding('utf8').read() as string; + const nofileLine = limitsContent.split('\n').find((line: string) => line.startsWith('Max open files')); + if (nofileLine) { + const parts = nofileLine.split(/\s{2,}/); + const softLimit = parseInt(parts[1], 10); + const hardLimit = parseInt(parts[2], 10); + if (softLimit < 65536) { + logger.log('warn', `File descriptor soft limit is ${softLimit} (hard: ${hardLimit}). ` + + `For production use, set --ulimit nofile=65536:65536 on the container runtime.`); + } else { + logger.log('info', `File descriptor limits: soft=${softLimit}, hard=${hardLimit}`); + } + } + } catch { + // Non-Linux or /proc not available — silently skip + } + } /** * Log comprehensive startup summary @@ -708,9 +734,28 @@ export class DcRouter { // Track cert entries loaded from cert store so we can populate certificateStatusMap after start const loadedCertEntries: Array<{domain: string; publicKey: string; validUntil?: number; validFrom?: number}> = []; - // Create SmartProxy configuration + // Create SmartProxy configuration with sensible gateway defaults. + // User's smartProxyConfig overrides these defaults via spread. const smartProxyConfig: plugins.smartproxy.ISmartProxyOptions = { + // --- dcrouter gateway defaults --- + maxConnectionsPerIP: 100, + connectionRateLimitPerMinute: 600, + socketTimeout: 120_000, + inactivityTimeout: 120_000, + keepAlive: true, + noDelay: true, + gracefulShutdownTimeout: 30_000, + // --- user overrides --- ...this.options.smartProxyConfig, + // --- deep-merge defaults.security so user can override maxConnections --- + defaults: { + ...this.options.smartProxyConfig?.defaults, + security: { + maxConnections: 50_000, + ...this.options.smartProxyConfig?.defaults?.security, + }, + }, + // --- always set by dcrouter (after spread) --- routes, acme: acmeConfig, certStore: { diff --git a/ts/plugins.ts b/ts/plugins.ts index dec06cb..a7cb298 100644 --- a/ts/plugins.ts +++ b/ts/plugins.ts @@ -47,13 +47,13 @@ import * as qenv from '@push.rocks/qenv'; import * as smartacme from '@push.rocks/smartacme'; import * as smartdata from '@push.rocks/smartdata'; import * as smartdns from '@push.rocks/smartdns'; -import * as smartfile from '@push.rocks/smartfile'; +import * as smartfs from '@push.rocks/smartfs'; import * as smartguard from '@push.rocks/smartguard'; import * as smartjwt from '@push.rocks/smartjwt'; import * as smartlog from '@push.rocks/smartlog'; import * as smartmetrics from '@push.rocks/smartmetrics'; import * as smartmta from '@push.rocks/smartmta'; -import * as smartmongo from '@push.rocks/smartmongo'; +import * as smartdb from '@push.rocks/smartdb'; import * as smartnetwork from '@push.rocks/smartnetwork'; import * as smartpath from '@push.rocks/smartpath'; import * as smartproxy from '@push.rocks/smartproxy'; @@ -64,7 +64,7 @@ import * as smartrx from '@push.rocks/smartrx'; import * as smartunique from '@push.rocks/smartunique'; import * as taskbuffer from '@push.rocks/taskbuffer'; -export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfile, smartguard, smartjwt, smartlog, smartmetrics, smartmongo, smartmta, smartnetwork, smartpath, smartproxy, smartpromise, smartradius, smartrequest, smartrx, smartunique, taskbuffer }; +export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfs, smartguard, smartjwt, smartlog, smartmetrics, smartdb, smartmta, smartnetwork, smartpath, smartproxy, smartpromise, smartradius, smartrequest, smartrx, smartunique, taskbuffer }; // Define SmartLog types for use in error handling export type TLogLevel = 'error' | 'warn' | 'info' | 'success' | 'debug'; @@ -90,7 +90,7 @@ export { uuid, } -// Filesystem utilities (compatibility helpers for smartfile v13+) +// Filesystem utilities export const fsUtils = { /** * Ensure a directory exists, creating it recursively if needed (sync) diff --git a/ts_oci_container/index.ts b/ts_oci_container/index.ts index bfba5b4..b340de0 100644 --- a/ts_oci_container/index.ts +++ b/ts_oci_container/index.ts @@ -96,5 +96,27 @@ export function getOciContainerConfig(): IDcRouterOptions { }; } + // Connection capacity config + const maxConnections = process.env.DCROUTER_MAX_CONNECTIONS; + const maxConnectionsPerIP = process.env.DCROUTER_MAX_CONNECTIONS_PER_IP; + const connectionRateLimit = process.env.DCROUTER_CONNECTION_RATE_LIMIT; + if (maxConnections || maxConnectionsPerIP || connectionRateLimit) { + options.smartProxyConfig = { + ...options.smartProxyConfig, + routes: options.smartProxyConfig?.routes || [], + ...(maxConnectionsPerIP ? { maxConnectionsPerIP: parseInt(maxConnectionsPerIP, 10) } : {}), + ...(connectionRateLimit ? { connectionRateLimitPerMinute: parseInt(connectionRateLimit, 10) } : {}), + ...(maxConnections ? { + defaults: { + ...options.smartProxyConfig?.defaults, + security: { + ...options.smartProxyConfig?.defaults?.security, + maxConnections: parseInt(maxConnections, 10), + }, + }, + } : {}), + }; + } + return options; } diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 89cfdd8..676e1f1 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '11.10.7', + version: '11.11.0', description: 'A multifaceted routing service handling mail and SMS delivery functions.' }