-
DcRouter v32.6.3
StableRelease / build-and-release (push) Successful in 21m45sreleased this
2026-09-29 05:07:41 +00:00 | 0 commits to main since this release2026-09-29 - 32.6.3
Maintenance
- Release tooling:
@git.zone/tsdeno^1.11.0→^1.12.0,@git.zone/tsdocker^3.8.1→^3.9.0,@git.zone/tswatch^4.0.1→^4.0.2and@git.zone/tsrun^3.0.1→^3.0.2; none needs a migration. tsdeno 1.12.0 reads the module graph withdeno infobefore each compile and fails withUNRESOLVED_DEPENDENCYinstead of writing a binary without an npm package Deno could not resolve, the failure behind the short 32.6.1 binaries; tsdocker 3.9.0 adds--repositorytopush, which dcrouter does not use; tswatch 4.0.2 keeps watching after a failed bundle build. tsrun 3.0.2 no longer loads two modules it never used, which starts everytsrunprocess about a second sooner; tsbuild 5.0.0, tsbundle 3.0.0 and tstest 6.3.2 are current; pnpm 12.5.1 and@types/node26.6.2 stay until pnpm 12.6.0 (2026-09-29 17:08 UTC) and@types/node26.6.3 (2026-10-02) are seven days old. @design.estate/dees-catalog^16.6.0→^16.12.0(locked 16.6.0 → 16.12.0), which brings@design.estate/dees-element3.3.0 → 3.4.0; the tree keeps one copy of each. The Ops UI binds noframelessand uses neither the stepper's removedscrollernorsetScrollStatus(), so no source changes. What looks different comes from the catalog: the login screen (dees-simple-login) drops its card surface and page backdrop, the bootstrap and email-domain steppers show the new progress ribbon with a close control in the step header, anddees-input-listrows take the reworked layout.- The SmartDB upgrade/rollback test gives its four restart phases one shared 50 s budget, inside the suite's 60 s per-file limit, instead of 20 s each, so one phase slowed by host load no longer fails a file that still has time; a timeout names the phase that ran out. It starts the fixture with
tsrundirectly rather than throughpnpm exec, which did not forward SIGTERM, so a timed-out fixture no longer outlives the test and re-creates its work directory after cleanup.
Downloads
- Release tooling:
-
DcRouter v32.6.2
StableRelease / build-and-release (push) Successful in 22m2sreleased this
2026-09-28 02:50:05 +00:00 | 5 commits to main since this release2026-09-28 - 32.6.2
Fixes
-
Move the in-process RemoteIngress hub to
@serve.zone/remoteingress^32.0.3(from^32.0.0, locked at 32.0.0), whose QUIC control stream is read cancel-safely on both sides. Up to 32.0.2 a control message was read as a 5-byte header and then its payload with tworead_exactcalls inside atokio::select!, and written in two writes that could arrive in separate packets; when another branch won after the header was consumed, the next read decoded the payload as a header. A splitCTRL_UDP_SESSION_CLOSEframe made the edge refusecontrol message too large: <N> bytes (max 65536)and tear its tunnel down, dropping every port listener of that edge while the hub loggedcontrol stream error: connection lost- the teardowns seen on the SSH tunnel tocode.foss.global:29419. From 32.0.3 the hub keeps a partially received control message buffered across cancelled reads and writes each message as one buffer, so it no longer loses a frame an edge sent, and the frames it sends no longer leave the header and payload in separate writes. The hub API dcrouter uses (RemoteIngressHub, its events,start,stop,getStatus,updateAllowedEdges, the egress proxy calls andencodeConnectionToken) is unchanged across 32.0.1-32.0.3, and the wire format is unchanged, so no dcrouter code changes. Edges read the hub's control stream with their own reader: an edge is fully protected only once it runs remoteingress 32.0.3 as well (see the readme's RemoteIngress version note). -
The release binaries resolve a first-party release published the same day. Deno 2.9 compiles with a 24-hour minimum dependency age by default, which left
@serve.zone/remoteingress@^32.0.3unresolved indeno compile(the smoke check caught it).deno.jsonnow commits the owner's policy that pnpm already applies: seven days for third-party packages, owner scopes (@api.global,@apiclient.xyz,@design.estate,@git.zone,@idp.global,@lossless.org,@push.rocks,@serve.zone,@tempfix,@tsclass) exempt, so every build host compiles under the same age policy whatever its own pnpm configuration (tsdeno 1.11.0 leaves a committed policy untouched). Deno resolves thepackage.jsonranges against the registry at compile time, notpnpm-lock.yaml. The binaries return to their complete size (816 MiB x64, 798 MiB arm64 from 564 npm packages, the size of the June builds): the 32.6.1 binaries (about 475 MiB) were most likely compiled without owner packages published within 24 hours of the build, which the--versionsmoke check cannot see.
Maintenance
@design.estate/dees-catalog^16.4.0→^16.6.0(locked 16.5.0 → 16.6.0).- Release tooling:
@git.zone/tsdeno1.11.0 and@git.zone/tsbundle3.0.0 (the dashboard bundle shipsdist_serve/bundle.js.third-party-notices.{txt,json}); pnpm 12.6.0 and@types/node26.6.3 wait for the seven-day rule.
Downloads
-
-
DcRouter v32.6.1
StableRelease / build-and-release (push) Successful in 19m53sreleased this
2026-09-27 21:05:57 +00:00 | 10 commits to main since this release2026-09-27 - 32.6.1
Fixes
- The Ops UI's application log (Logs > Application Logs) hands the log each entry once. It counted the entries it had passed on, so once the browser held its cap of 2000 entries no new entry reached the log, returning to the tab showed an empty log until the next entry arrived, and a view that was attached again passed every entry a second time. It now remembers the newest entry the log on screen holds and passes the entries after it; a fetched list that replaces the shown one clears the log first, and a log created anew, on returning to the tab, gets the whole list.
test/test.logs-view.chromium.tscovers streaming, the cap, a replacing fetch and the return to the tab. ts_web/appstate/runtime.tsimports the platform-events state statically. It loaded it through a dynamicimport(), so a bundle evaluated that module lazily and an element that readconfigEventStatePartwhile it was constructed, as the logs view does, could find it undefined; the Chromium test bundle did.
Maintenance
-
Move the Ops UI to
@design.estate/dees-catalog^10.0.0→^16.4.0(16.5.0 in the lockfile),@design.estate/dees-element^3.1.1→^3.3.0and@serve.zone/catalog^4.0.0→^33.0.0, which renders on dees-catalog 16; the three move together, because two copies of the catalog register the samedees-*elements and the page stops withNotSupportedError.pnpm dedupeleaves one copy each of dees-catalog, dees-element, dees-domtools (3.0.1), lit and@serve.zone/catalogin the bundle; it also drops duplicate resolutions of@types/node26.6.1,@push.rocks/smartrust2.0.0,@push.rocks/smartwatch6.4.3,@design.estate/dees-commsandpicomatch. The one source change the catalog needs is in the application log: dees-catalog 14 removeddees-chart-log'sterminalReady, and the log keeps entries it is given before its terminal is up, so the view no longer waits for it (see Fixes). The other catalog breaks between 11 and 16 do not touch the Ops UI: it usesdees-simple-appdashrather thandees-appui, gives everydees-chart-areaits ownyAxisFormatter, reads noseriesStats, binds novintegratedand uses thesz-*elements, whose API did not change from@serve.zone/catalog4 to 33. What looks different comes from the catalog: button glyph size and inset (15.2.0), stats-grid titles that break between words (16.3.0), the log on xterm 6 with the theme's terminal palette (14.0.0), and Enter in the last field of a dialog's form now runs the dialog's last action (16.3.0). -
Bring release tooling current:
@git.zone/tstest^6.3.0→^6.3.2(a Node.js test file that runs past--timeoutnow fails even when it handles SIGTERM and exits cleanly),@git.zone/tsrun^3.0.0→^3.0.1and@git.zone/tsdeno^1.8.0→^1.10.0; tsbuild 5.0.0, tsbundle 2.15.0, tsdocker 3.8.1 and tswatch 4.0.1 were already current, and pnpm 12.5.1 and@types/node26.6.2 stay until pnpm 12.6.0 and@types/node26.6.3 are seven days old. None of the three needs a migration. With tsdeno 1.10.0 the release binaries leave out the native engines built for the other architecture and for macOS (tsdeno 1.9.0's default per-target pruning): on Deno 2.9.7dcrouter-linux-x64is 498,262,576 bytes anddcrouter-linux-arm64489,086,376 bytes, down from 984,393,616 and 975,118,288 bytes in the 32.6.0 release. The compile targets in.smartconfig.jsonnow pin Deno withdenoVersion2.9.7(tsdeno refuses any other version withDENO_VERSION_MISMATCH), fail a binary below a size floor (minSize355 MiB for x64 and 350 MiB for arm64, about three quarters of the measured size, becausedeno compilecan exit 0 after writing an incomplete binary) and run the x64 binary once with--versionin an empty environment, expecting exit code 0 and a version line (the arm64 binary cannot run on the x64 runner and is skipped). The release workflow installs the pinned Deno 2.9.7 from its checksum-verified release archive and puts it first onPATHfor the binary build, because the base image's own Deno shadoweddenoland/setup-deno: 32.6.0 was compiled with the image's Deno 2.9.6 although setup-deno had installed 2.9.7.
Downloads
- The Ops UI's application log (Logs > Application Logs) hands the log each entry once. It counted the entries it had passed on, so once the browser held its cap of 2000 entries no new entry reached the log, returning to the tab showed an empty log until the next entry arrived, and a view that was attached again passed every entry a second time. It now remembers the newest entry the log on screen holds and passes the entries after it; a fetched list that replaces the shown one clears the log first, and a log created anew, on returning to the tab, gets the whole list.
-
DcRouter v32.6.0
StableRelease / build-and-release (push) Successful in 26m16sreleased this
2026-09-26 17:03:29 +00:00 | 14 commits to main since this release2026-09-26 - 32.6.0
Features
- Report stored routes that cannot be served. Route warnings (
IRouteWarning) gain the typeroute-ingress-disabled: a stored route whose only ingress paths are off (SmartVPN or the RemoteIngress hub not running) is left out of the applied routes and listed with this warning, instead of stopping every other route.
Fixes
- Hold one local mirror row per provider DNS record. Releases before 19.1.0 ran provider-zone syncs, gateway route DNS reconciles and mail DNS reconciles concurrently, so two writers could each mirror the same new provider record;
syncDomain()then refuses the zone withLocal mirror has duplicate provider record id …, and every automated DNS write for that zone fails aszone-unavailableuntil the row is removed.DnsRecordDocnow storesproviderRecordKey(<domainId>:<providerRecordId>), derived in one place (ts/dns/provider-record-key.ts) bycreateSavableObject()and by the ACME and testing mirror writers, and cleared with the provider id when a zone migrates to dcrouter. The sparse unique indexprovider_record_key_uniquemakes a racing second mirror insert fail instead of persisting. The new migration stepreconcile-provider-record-mirror-duplicates(32.0.1to32.6.0) keeps the row that carriesmanagedByownership and deletes its unowned synced copies, keeps the oldest row where none is owned, trims stored ids, backfills the key and then creates the index. It deletes local mirror rows only and never calls the provider. When rows with different owners share one provider record, it refuses by name and changes nothing. The error lists the domain, the provider id, the row ids and the owners. - Start dcrouter when neither SmartVPN nor a RemoteIngress hub runs. Since 17.9.0 every route without an explicit ingress policy, including the generated
dns-over-https-dns-queryroute and email routes, was authorized for SmartVPN, and aremoteIngressroute for the edge tunnel, whether or not that runtime was on. The listener therefore gotinboundProxyProtocoloptionalorrequiredwhile SmartProxy had no trusted proxy, and SmartProxy refused the route set:Route 'dns-over-https-dns-query': inboundProxyProtocol mode optional/required needs trustedProxyIPs or global trustedProxyIPs, a critical startup failure. Ingress origins are now authorized only for the paths whose runtime is on:smartVpnwhenvpnConfig.enabled,trustedProxywhen the RemoteIngress hub is enabled. A listener that only direct traffic reaches getsreject. A route created through the API whose only declared paths are off, for example a VPN-only route on a hub without SmartVPN, is now left out of the applied route set on its own. Every other route is still applied, at startup and on every route application. The route is logged atwarnwhen it is first refused and listed among the route warnings ofgetMergedRoutesand the Ops UI routes view asroute-ingress-disabled. A route from dcrouter's own configuration whose only paths are off still fails startup withRoute '<name>' has no enabled ingress path, because it restates the operator's options.
Downloads
- Report stored routes that cannot be served. Route warnings (
-
DcRouter v32.5.0
StableRelease / build-and-release (push) Failing after 13m3sreleased this
2026-09-26 09:13:58 +00:00 | 17 commits to main since this release2026-09-26 - 32.5.0
Features
- Let the ACME settings name another CA's ACME directory.
IAcmeConfig,AcmeConfigDocandupdateAcmeConfigcarry an optionaldirectoryUrl, which dcrouter passes to SmartAcme in place of Let's Encrypt, for example SmartAcme's ownserver.AcmeServeron the loopback of a disposable rehearsal host; absent, dcrouter orders from Let's Encrypt as before, andnullreturns to it.updateAcmeConfigaccepts only SmartAcme's own rule, anhttps:URL or anhttp:URL onlocalhost,127.0.0.1or[::1], without credentials or fragment, and refuses anything else with the rule it breaks, without repeating the URL. A change is treated like an account change: it is refused while certificate jobs or ACME DNS cleanup are unfinished, and issuance waits until SmartAcme is rebuilt on the new directory. A directory stored past that check, by a direct database write, is refused by SmartAcme at startup and now classified as a permanentacme-account-configurationfailure instead of consuming the startup retry budget. The settings dialog under Domains > Certificates gains an ACME directory field and keeps the dialog open with dcrouter's reason when it refuses the settings; the settings tile and the configuration view show the directory, and dcrouter's logs name only its origin. Rows written by earlier builds carry no directory and keep Let's Encrypt, so no migration is needed.test/test.acme-directory.node.tscovers the rule against SmartAcme's own refusal, persistence, reload and clearing, the refusal during unfinished DNS cleanup, a dcrouter that rebuilds SmartAcme from the directory stored throughupdateAcmeConfigonto two loopback CAs in turn, with an account key per directory, and a route certificate and a gateway-client exact certificate issued by the CA of the stored directory.
Maintenance
- Release tooling currency:
@git.zone/tstest^6.2.0→^6.3.0(Chromium test files tear down their browser, servers and timers on every outcome and no longer share one browser in a parallel group; no migration),@types/node26.6.1 → 26.6.2 andpackageManagerpnpm@12.4.2→pnpm@12.5.1. The other@git.zone/*dev tools are already at their current releases. The lockfile keeps@types/node26.6.1 only as the resolution of four@types/*packages'*range.
Downloads
- Let the ACME settings name another CA's ACME directory.
-
DcRouter v32.4.0
StableRelease / build-and-release (push) Successful in 21m9sreleased this
2026-09-25 14:52:19 +00:00 | 20 commits to main since this release2026-09-25 - 32.4.0
Features
- Serve gateway-client hostname records and exact certificates, the
@serve.zone/interfaces32.21.0 contractssyncGatewayClientDnsRecord,getGatewayClientCertificateandreleaseGatewayClientCertificate, and advertise them asdns.clientRecordsandcertificates.exactIssuance. A gateway client can now hold an exact hostname without a gateway route: oneAorAAAArecord at an address inside its newallowedDnsRecordAddressespolicy field, and one certificate for exactly that name, which Cloudly needs for its cluster relay names. Only gateway-client credentials can call them, with thesyncDnsRecords,requestCertificatesandreadCertificatescapabilities, for hostnames inside the client'shostnamePatterns. A hostname has one holder: a testing reservation, an enabled gateway-client route, another client or app, or an address record dcrouter does not own is a conflict, and dcrouter never overwrites or adopts such a record. The route DNS reconciler in turn answersrecord-conflictfor a hostname an ownership holds, also through a certificate alone. Records are managed asgateway-client-hostnameunder the route reconciler's DNS exclusion. Certificates need a zone with verified authority and are issued by SmartAcme's exact-identifier DNS-01 path in a namespace per client and app; a request answerspendingwhile the issuance runs. A request for a hostname nobody holds yet first synchronizes a provider zone and is refused ashostname-conflictwhen the name carries an address record dcrouter does not own, or answersissuance-unavailable(retryable) when the synchronization fails; polls for a held hostname synchronize nothing. Releasing a certificate retires its stored material, and a sweep at start and every minute removes the records, certificates and claims of deleted clients, of hostnames outside the client's patterns and of addresses outside its allowance. The allowance is set throughprovisionGatewayClientCredential,createGatewayClientandupdateGatewayClient, stored canonically, and left out of the policy digest while it is empty, so existing credentials keep matching. The exact-certificate issuance now takes its SmartAcme namespace from the caller, and testing grants keep theirs.@serve.zone/interfacesmoves from 32.0.0 to 32.21.0 in the lockfile (^32.0.0→^32.21.0), which brings@push.rocks/smartlog4.0.0 as its dependency.test/test.gateway-client-hostname.node.tscovers the record lifecycle, the conflicts in both directions, certificate issuance, release and the sweep against a real database and DNS manager, and the handlers' credential, pattern and capability checks. - Show and edit a gateway client's
allowedDnsRecordAddressesin the Ops UI under Access > Gateway Clients: a DNS addresses column, a field in the create dialog and the row action Edit DNS Addresses. Both dialogs judge the comma separated list with@serve.zone/interfaces'validateGatewayAllowedDnsRecordAddresses, which the web bundle now imports, keep the dialog open with the reason when it or dcrouter refuses the value, and close only on success. The create dialog previously closed before the request and ignored a refusal, andupdateGatewayClientActionignored an unsuccessful answer; it now records dcrouter's message as the view's error. Changing the allowance through an admin update raises the client's policy generation like any policy change, so its credentials must be provisioned again.test/test.gateway-clients.chromium.tsrenders the view and drives the edit dialog through a refused value, a refused update and a saved one. - Refuse a testing grant, on request and on approval, for a hostname a gateway-client hostname ownership holds, with
ownership_conflict. The ownership is looked up by hostname under the DNS exclusion its writers take, so one that holds only a certificate, with no DNS record, is refused too.test/test.testing-access.node.tscovers the certificate-only case.
Maintenance
- Make
test/test.email-domain-creation-progress.node.ts"domain observation switches from list discovery to id polling" independent of the wall clock; it failed on every run, also on v32.3.2. The test gaveobserveNewEmailDomaina 100 ms deadline with 1 ms polls and asserted two list and two id reads. tsx, which runs the test, scans its disk cache synchronously once per process (131,641 entries in the shared cache on the build host) and blocked the event loop for about 110 ms during the first 1 ms wait, so the deadline expired after one list read.observeNewEmailDomainnow takes an optionalnowclock for its deadline (defaultDate.now), and the test passes a frozen one, so the reads alone decide the result and tstest's timeout still bounds a hang. The module's behaviour with the default clock is unchanged.
Downloads
- Serve gateway-client hostname records and exact certificates, the
-
DcRouter v32.3.2
StableRelease / build-and-release (push) Successful in 16m51sreleased this
2026-09-24 14:22:04 +00:00 | 22 commits to main since this release2026-09-24 - 32.3.2
Fixes
- Serve the OpsServer through
@api.global/typedserver13.0.2 (from 11.2.1), with@api.global/typedsocket8.5.0 (from 8.0.3) and@push.rocks/smartserve7.2.0 (from 6.2.2), which typedserver 13 and typedsocket 8.5.0 run on. typedserver 11.2.1'sUtilityWebsiteServer.stop()threw aTypeErrorbeforestart(), so the OpsServer tracked whether it had calledstart()and stopped the web server only then; 13.x resolves thatstop(), and the tracking is gone. The OpsServer now injects the live reload client and watchesdist_serveonly under tswatch (TSWATCH=true), as typedserver 13'sUtilityWebsiteServerdefaults; 11.2.1 did both in production as well. TypedServer answers the service worker'sserviceworker_versionInfoitself with the app hash of the served files, and the OpsServer now declares dcrouter's version from its commit info as the app version, which that answer and/appversioncarry (11.2.1 answeredx.x.x, later1.0.0). Without the reload client, production no longer serves typedserver's/typedserver/devtoolsand/typedserver/reloadcheck, and responses of added routes,/mcpincluded, carryCache-Control: no-cacheand the app-hash stamp. Otherwise routes,/typedrequest,/mcp, log streams and their authorization, and static serving with the SPA fallback are unchanged.
Maintenance
- The lockfile carries one typedserver line for runtime and dev tooling (tswatch 4 already brought typedserver 13 and typedsocket 8.5.0), drops typedserver 11.2.1, typedsocket 8.0.3 and 8.1.0, smartserve 6.2.2 and
@push.rocks/smartlog-destination-devtools1.0.12, which only typedserver 11.2.1 used, and resolves@idp.global/sdk's@push.rocks/smartserve^6.2.2to 6.3.1, which meets typedsocket 8.5.0's>=6.2.4peer range.@api.global/typedrequeststays at 8.0.4, which typedserver 13 and typedsocket 8.5.0 accept. - Bundle the web UI with
@git.zone/tsbundle2.15.0 (from 2.14.0), which tstest 6.2.0 and tswatch 4.0.1 resolve to as well, so the lockfile keeps one tsbundle. 2.15.0 changes howincludeFilesentries are placed and matched; dcrouter's one entry,./html/**/*.html, matches onlyhtml/index.html, and a cleandist_servebuilt with 2.15.0 is byte-identical to one built with 2.14.0 (bundle.js, its source map, the worker chunk with its map and artifact manifest, andindex.html).
Downloads
- Serve the OpsServer through
-
DcRouter v32.3.1
StableRelease / build-and-release (push) Successful in 15m46sreleased this
2026-09-24 12:21:48 +00:00 | 25 commits to main since this release2026-09-24 - 32.3.1
Fixes
- Pass the ACME renewal threshold to SmartAcme, and consume
@push.rocks/smartacme9.8.1 (from 9.7.0), which honours it. SmartAcme received no threshold and kept a stored certificate until 10 days before expiry, so SmartProxy's daily renewal sweep, which asks from the configured 30 days on, got the old certificate back every day until the 10-day mark. The configured threshold (default 30 days) now governs both: a certificate is renewed once at most that many days, or a third of its lifetime if that is less, remain, and testing certificates carry the same margin inrenewAfter. A failed renewal keeps the current certificate stored.updateAcmeConfigrefuses a threshold that is not a positive number of days, because SmartAcme refuses it at construction. One already stored, by an earlier build or a direct database write, leaves ACME unavailable: dcrouter builds neither SmartAcme nor SmartProxy's ACME options from it, logs anAcmePermanentFailureError(invalid-renewal-threshold) that points to Domains > Certificates > Settings andupdateAcmeConfig, and certificate issuance reportsacme_unavailable, while SmartProxy and the OpsServer keep running for the correction.test/test.acme-renewal-threshold.node.tsruns dcrouter's provision path against SmartAcme's loopback CA: a stored 90-day certificate with 20 days left is renewed under the default threshold and kept under a configured 10 days. It also starts dcrouter on a stored threshold of 0: SmartProxy runs without SmartAcme, the OpsServer lists the error in its logs and accepts a corrected threshold. smartacme 9.8.1 stores through@lossless.org/client1.5.1, which the tree already carried, so the deprecated@push.rocks/smartdata7.4.0 leaves the lockfile. - Defer certificate provisioning while SmartAcme is not ready, and consume
@push.rocks/smartproxy27.22.0 (from 27.21.0), whosecertProvisionFunctionmay answer'defer'. dcrouter answered SmartProxy's provision callback withacme_unavailablewhile SmartAcme was starting, retrying or out of its startup budget. SmartProxy recorded that as a failure and skipped the domain for its 30-minute cooldown, including in the sweep dcrouter runs once SmartAcme becomes ready, so after every restart the routes that needed a certificate got none until a sweep ran after the cooldown. The callback now defers whenever issuance is not ready, which also covers ACME being disabled and an account change waiting for the rebuild. SmartProxy records nothing for a deferral, and the sweep that follows SmartAcme becoming ready, afterrearm()too, issues the certificate. The log line for an exhausted SmartAcme startup budget, a code comment and the readme said the callback fell back to HTTP-01; certificate provisioning is DNS-01 only, and they now say what the code does.test/test.acme-provision-cooldown.node.tsruns a real SmartProxy with dcrouter's callback and SmartAcme lifecycle against SmartAcme's loopback CA: the startup sweep is deferred without a cooldown and the domain is issued once SmartAcme is ready, and the same holds after a permanent startup failure andrearm(). It also rebuilds SmartProxy on the sameDcRouterafter ACME was disabled and shows that neither SmartAcme nor the provision callback remains. - Let an operator reprovision take effect at once after a failed attempt. A failed provisioning attempt puts the domain on SmartProxy's 30-minute provisioning cooldown, and
reprovisionCertificateDomainonly re-applied the routes, whose sweep skipped the cooling-down domain without a log line, so the reprovision did nothing until the cooldown ended. The handler now goes through the newDcRouter.reprovisionProxyCertificate(), which lifts the domain's cooldown with smartproxy 27.22.0'sinvalidateCertProvisionCooldown()before it re-applies the routes, and the sweep asks for the certificate again. A domain that was not cooling down is reprovisioned as before. The message returned while the proxy still serves an expired certificate said SmartProxy exposed no per-domain invalidation and that toggling the route off and on was the only remedy; it now says that the background provisioning sweep had not replaced the certificate when the check ran, or failed.test/test.acme-provision-cooldown.node.tsfails an attempt on a zone whose delegation is not verified yet, verifies it and reprovisions: the certificate is issued without waiting for the cooldown. - Correct the documented reload semantics of the ACME settings. SmartProxy and SmartAcme receive them,
renewThresholdDaysincluded, when dcrouter builds them, so a change takes effect at the next restart or SmartProxy rebuild. The config manager, theupdateAcmeConfigcontract and the settings dialog said the threshold applied to the next renewal check at once. - Store a renewed certificate's id with its material. SmartAcme now renews every certificate by storing the replacement over the due one, as forced renewals already did, and dcrouter's certificate store kept the old id on an overwrite, so the record, and a certificate exported from it, named a certificate it no longer held.
- Keep an abandoned testing certificate job from ordering a replacement. smartacme 9.8.1 replaces an abandoned exact-identifier attempt with a new key and order on the next request once an hour has passed, where 9.7.0 answered it as pending for good. The grant reservation already kept new jobs out, but a job claimed again after its abandon, because recording the result was interrupted, requested the certificate and relied on that old answer. A job now publishes an attempt it finds abandoned as
abandoned_unverifiedinstead of requesting it. The readme and the job model state that abandonment is final at dcrouter's job layer, not in SmartAcme.test/test.testing-certificates.node.tsinterrupts the recording of an abandon, lets SmartAcme's retry time pass and shows that the job claimed again stays abandoned without a request. - Let a stopped
DcRouter, or one whose start failed, start again.start()afterstop()on the same instance failed withCritical service 'OpsServer' failed to start: a TypedHandler for getAdminBootstrapStatus alredy exists!, because each OpsServer linked its routers into the DcRouter's main typedrouter and never detached them, so the new OpsServer's handlers collided with the stopped one's. OpsServer now links into that router instart()and detaches both links instop(), also when its cleanup fails. A failedstart(), such as one whose port is taken, runs the same cleanup before it rethrows its error, because the service manager never stops a service whose start failed; a failing cleanup is raised together with the start error. Either way the next OpsServer composes into a router that no longer reaches the previous one's handlers. - Log service events again after a
DcRouteris stopped and started, and consume@push.rocks/taskbuffer9.0.4 (from 9.0.3), which carries the root fix. taskbuffer 9.0.3'sServiceManager.stop()unsubscribed the forwarders that feedserviceSubjectandstart()never recreated them, and dcrouter subscribed toserviceSubjectonce in its constructor and ended that subscription instop(), so the ops log received no service event after the first restart.DcRouternow subscribes instart()and unsubscribes instop(), and also whenstart()fails.test/test.opsserver-restart.node.tsshows that the second run'sService 'OpsServer': startedevent reaches the ops log; the test fails with taskbuffer 9.0.3 and passes with 9.0.4.
Maintenance
- Build, test and watch tooling on its current line:
@git.zone/tsbuild5.0.0 (from 4.5.1),@git.zone/tsrun3.0.0 (from 2.0.6),@git.zone/tstest6.2.0 (from 6.1.5) and@git.zone/tswatch4.0.1 (from 3.3.5); tsbundle 2.14.0, tsdocker 3.8.1 and tsdeno 1.8.0 were already current, and pnpm 12.4.2 and@types/node26.6.1 stay until their successors are seven days old. No source folder carries its owntsconfig.jsonortspublish.jsoncompiler options, so tsbuild 5's folder configuration needs no migration. Under tsrun 3 thecli.ts.jsdevelopment entry gives the script Node's standardprocess.argv, whichrunCli()already reads withprocess.argv.slice(2); tsrun 2 passed the user arguments alone, so that slice dropped them, andnode cli.ts.js --versionnow prints the version. Thedcrouter-devwatcher in.smartconfig.jsonkeeps its shape under tswatch 4. tswatch 4 brings@api.global/typedserver13 and@api.global/typedsocket8.5.0 into the dev tooling only; dcrouter's runtime dependencies are unchanged in the lockfile.
Downloads
- Pass the ACME renewal threshold to SmartAcme, and consume
-
DcRouter v32.3.0
StableRelease / build-and-release (push) Successful in 18m12sreleased this
2026-09-23 03:31:42 +00:00 | 34 commits to main since this release2026-09-23 - 32.3.0
Features
@serve.zone/dcrouter-apiclientnow publishes in the samegitzone releaseas@serve.zone/dcrouterand the Docker images, at the same version, so the client package no longer trails the runtime. gitzone 7 publishes the named tspublish module before the root package, records every package on every registry in the release journal, and promotes the images only once both packages are verified; a resumed release publishes only what is still missing. Thepublish:packagesscript and the@git.zone/tspublishdev dependency that existed for it are gone, because the release is now the only publisher and gitzone refuses to release beside a script that runs the standalone one.@serve.zone/dcrouter-apiclienthas no release for 32.2.0 and earlier, so a consumer pinned to one of those versions keeps importing the client from@serve.zone/dcrouter/apiclient.
Maintenance
- Test tooling on its current line:
@git.zone/tstest6.1.5 and@types/node26.6.1; pnpm, tsbuild, tsbundle and tsdocker were already current. tstest 6.1.5 moves itscreateSmartmongo()helper from@lossless.org/nosqldb8.0.0 to 10.5.0; dcrouter's suites do not start that helper, so only the lockfile's single nosqldb copy changes. The lockfile keeps one copy of each tool and drops the older@push.rocks/smartcli4.3.0,@push.rocks/smartconfig6.2.0,@push.rocks/smartpuppeteer2.4.0 and@types/node26.4.1 copies in favour of the versions already beside them.
Downloads
-
DcRouter v32.2.0
StableRelease / build-and-release (push) Successful in 18m43sreleased this
2026-09-23 00:26:48 +00:00 | 37 commits to main since this release2026-09-23 - 32.2.0
Features
- Add
@serve.zone/dcrouter-apiclient, a package that carries the client half of dcrouter, the API client and the shared contracts, without the server. This release defines the package; publishing it is a separate step, described in the last point below.ts_apiclient/tspublish.jsonnow names the package and ownsts_interfacesthroughfolders, exporting the client at.and the contracts at./interfaces;ts_interfaceskeeps its build order and is still compiled first. A consumer that only calls the OpsServer API —gitzone testing, custom CLIs, integration suites, anything that speaks/typedrequest— declares 8 dependencies instead of the runtime package's 48, and resolves a production closure of 184 packages instead of 579. Gone from that closure: the nativeargon2build,mongodb,@lossless.org/client,@design.estate/dees-catalog,@push.rocks/smartdataand@push.rocks/smartbucket.@serve.zone/dcrouterkeeps its./interfacesand./apiclientsubpath exports unchanged, so existing consumers migrate when it suits them.- The two folders ship as one package rather than two, because
@git.zone/tspublishrequires sibling packages to import each other through their published names. That bare specifier would land indist_ts_apiclient/*.d.ts, which the runtime package also ships, and would force@serve.zone/dcrouterto declare a same-version dependency on a package cut from the same release. Owningts_interfacesthroughfolderskeeps the existing relative import valid inside the published tarball and leaves the runtime package's exports self-contained. - What the new package does not shed:
@push.rocks/smartproxy,@push.rocks/smartmtaand@push.rocks/smartnetworkstay runtime dependencies, because contract members are typed against them (IRouteConfig,IUnifiedEmailServerOptions,IIpIntelligenceResult, andIRouteChallengeConfigfrom@push.rocks/smartchallenge; the gateway contracts come from@serve.zone/interfaces). A type-only import still has to resolve for a consumer's type check, so the three Rust payloads — each tarball carrying both the amd64 and the arm64 binary — are installed exactly as before, and so is@push.rocks/smartlog3.2.2, reachable through@api.global/typedsocket,@push.rocks/smartmta,@push.rocks/smartproxyand@serve.zone/interfacesalike. The 1.1 MB figure this package is worth quoting is its own packed payload, not its install footprint. - Because
@push.rocks/smartmtadeclaresos: ["linux"]andcpu: ["x64", "arm64"], the new package installs on Linux x64 and arm64 only, exactly like@serve.zone/dcrouteritself. The readmes say so at the install step. This is not a dcrouter-side choice and no dcrouter change lifts it: it goes away when@push.rocks/smartproxy,@push.rocks/smartmtaand@push.rocks/smartnetworkmove their Rust payloads into per-platform optional packages. pnpm run publish:packagesrunstspublishover the repository after a release and publishes the package at the released version;@git.zone/tspublishis a dev dependency so the descriptor can also be planned and prepared locally.gitzone releasekeeps publishing the runtime package and qualifying the Docker images unchanged — it cannot carry a tspublish module and a Docker target in the same release.
- The two folders ship as one package rather than two, because
Maintenance
- Build, test and release tooling on its current line: pnpm 12.4.2,
@git.zone/tsbuild4.5.1,@git.zone/tsbundle2.14.0,@git.zone/tsdocker3.8.1 and@git.zone/tstest6.1.4. tsbundle 2.14.0 bundles the web UI with dcrouter's own tsconfig instead of the packaged one; the lockfile keeps one copy of each tool. - Git and the Docker build context ignore
.envand.env.*files, so a local environment file cannot reach a commit or an image.
Downloads
- Add