Releases: luadch-ng/luadch
Release list
v3.1.14
Luadch v3.1.14
Maintenance patch on the release/3.1.x line. One fix: a Windows-only hub-crash once the hub holds ~64 concurrent sockets. No breaking changes; no cfg / lang-file changes; drop-in upgrade from v3.1.13. Linux operators are unaffected (no functional change).
⚠️ Before upgrading
Back up your cfg/, scripts/lang/, scripts/data/, scripts/cfg/, certs/, and secrets/ directories before any upgrade, on principle. This release has no required cfg or lang-file changes - the upgrade is a pure binary / script tree swap, but the backup discipline is worth keeping.
tar -czf "luadch-backup-$(date +%F).tar.gz" cfg scripts certs secretsWhy upgrade
Windows operators should upgrade, especially busier hubs. Before this fix, a Windows hub crashed with bad argument #1 to 'socket_select' (too many sockets) and dropped every user once it held about 64 concurrent sockets - a ceiling a moderately busy hub reaches organically (it can also be pushed there by a distributed connect flood; a single IP stays capped by ratelimit_perip_max_conns). Reported on Windows Server 2008 R2 running 3.1.13, but it affects any Windows version.
Linux is unaffected - its glibc select ceiling is already 1024, so this release makes no functional change on Linux beyond one informational boot-log line.
Bugfixes
#416 (Sopor) - Windows hub-crash at ~64 concurrent sockets
The symptom in the log, immediately followed by the hub dropping every user:
/core/server.lua:...: bad argument #1 to 'socket_select' (too many sockets)
The hub event loop (core/server.lua's tick()) calls socket.select over every connected socket at once. On Windows, luasocket's select.c hard-caps that at FD_SETSIZE:
#ifdef _WIN32
if (n >= FD_SETSIZE)
luaL_argerror(L, tab, "too many sockets");The bundled Windows build never defined FD_SETSIZE, so it inherited the Winsock default of 64 (luasocket/CMakeLists.txt set only WINVER). Once the hub held ~64 sockets (logged-in users + v4/v6 listeners + HBRI + HTTP), the unguarded select raised and the main loop died.
Fix: define FD_SETSIZE=1024 for the Windows luasocket build (socket module + luasocket_static) - parity with the Linux glibc fd_set. On Windows fd_set is a SOCKET array sized by this macro, so raising it genuinely enlarges the set. This is Windows-only: on Linux, glibc's fd_set is a fixed 1024-bit buffer that redefining FD_SETSIZE would overflow.
Watching more than ~1024 concurrent sockets needs replacing select() with poll() on both platforms, tracked in #310 on the 3.2.x line. max_users (default 3000) counts logged-in users, not sockets, and is not reached on either platform before this ~1024 socket ceiling.
The hub now logs its compile-time select capacity (socket._SETSIZE) once at boot (hub.loop, to event.log) so you can confirm the raised cap. The identical fix is on the 3.2.x line (master, PR #417) with a smoke regression that provably fails pre-fix on the Windows CI leg.
Build / runtime
No toolchain changes. Same Lua 5.4.8, same LuaSec 1.3.2, same LuaSocket 3.1.0, same build toolchain as v3.1.13. The Windows build simply compiles luasocket with -DFD_SETSIZE=1024.
The linux-aarch64 artifact continues with the Bullseye-container pipeline (glibc 2.31 baseline, works on Pi OS Bullseye / Bookworm / DietPi v9.x).
Upgrade
# Windows (the platform this fix targets)
# Download luadch-v3.1.14-windows-x86_64.zip, extract, copy cfg+data over, restart.
# Linux x86_64 / aarch64 (no functional change; upgrade at leisure)
wget https://github.com/luadch-ng/luadch/releases/download/v3.1.14/luadch-v3.1.14-linux-x86_64.tar.gz
tar xzf luadch-v3.1.14-linux-x86_64.tar.gz
# move your cfg/, scripts/data/, etc into the new tree, restart hub3.2.x is the active development line on master; security backports continue to land on release/3.1.x per CLAUDE.md §8.
v3.1.13
Luadch v3.1.13
Security maintenance patch on the release/3.1.x line. One fix: a remote, unauthenticated hub-crash. No breaking changes; no cfg / lang-file changes; drop-in upgrade from v3.1.12.
⚠️ Before upgrading
Back up your cfg/, scripts/lang/, scripts/data/, scripts/cfg/, certs/, and secrets/ directories before any upgrade, on principle. This release has no required cfg or lang-file changes - the upgrade is a pure binary / script tree swap, but the backup discipline is worth keeping.
tar -czf "luadch-backup-$(date +%F).tar.gz" cfg scripts certs secretsWhy upgrade
Every operator should upgrade. This is a remote, unauthenticated denial of service that takes the whole hub down - all users dropped, no reconnections possible until you manually restart the process. It is trivially triggerable (a single TCP connect followed by an immediate reset) and reproducible against any hub regardless of configuration.
Bugfixes
#401 (Sopor) - remote hub-crash on a peer reset right after accept
The symptom in the error log, immediately followed by the hub dropping every user:
././core/ratelimit.lua:176: attempt to concatenate a nil value (local 'ip')
*** TLS error: Connection closed
core/server.lua's accept path reads the connecting client's IP via client:getpeername() and hands it to the per-IP rate-limit guard. When a peer resets the TCP connection in the split second between accept() and getpeername() (connect + immediate RST - anyone can do it), getpeername() returns nil. That nil reached ratelimit.accept_ip, which builds its token-bucket key as "ip:" .. ip - raising attempt to concatenate a nil value (local 'ip'). The error was uncaught inside the listener's accept loop, so the hub stopped accepting connections and dropped everyone online.
ratelimit.release_ip already guarded a nil IP; accept_ip did not - the asymmetry that made this reachable.
Fix (defence in depth):
-- core/server.lua, accept path
local clientip, clientport = client:getpeername( )
+if not clientip then
+ -- peer reset between accept() and getpeername(); socket is dead,
+ -- and we cannot rate-limit an unknown IP. Drop it before the guard.
+ client:close( )
+ return false
+end-- core/ratelimit.lua, accept_ip
if not _activate then return true end
+if not ip then return true end -- mirror release_ip's nil guardcore/server.lua now closes a just-accepted socket whose peer address cannot be read before the rate-limit guard runs, and accept_ip guards nil directly.
The 3.2.x line (master) additionally guards core/blocklist.lua, which does not exist on 3.1.x, and carries a regression unit test (tests/unit/ratelimit_test.lua) that reproduces the exact crash and provably fails pre-fix. The 3.1.x fix is the same server + ratelimit guard, reviewer-verified, and validated by running that same test against this line's ratelimit.lua.
Build / runtime
No changes. Same Lua 5.4, same LuaSec 1.3.2, same LuaSocket 3.1.0, same build toolchain as v3.1.12.
The linux-aarch64 artifact continues with the Bullseye-container pipeline (glibc 2.31 baseline, works on Pi OS Bullseye / Bookworm / DietPi v9.x).
Upgrade
# Linux x86_64 / aarch64
wget https://github.com/luadch-ng/luadch/releases/download/v3.1.13/luadch-v3.1.13-linux-x86_64.tar.gz
tar xzf luadch-v3.1.13-linux-x86_64.tar.gz
# move your cfg/, scripts/data/, etc into the new tree, restart hub
# Windows
# Download luadch-v3.1.13-windows-x86_64.zip, extract, copy cfg+data over, restart.3.2.x is the active development line on master; security backports continue to land on release/3.1.x per CLAUDE.md §8.
v3.1.12
Luadch v3.1.12
Maintenance patch release on the release/3.1.x line. One bugfix for a backport slip introduced in v3.1.10. No breaking changes; no cfg / lang-file changes; drop-in upgrade from v3.1.11.
⚠️ Before upgrading
Back up your cfg/, scripts/lang/, scripts/data/, scripts/cfg/, certs/, and secrets/ directories before any upgrade, on principle. This release has no required cfg or lang-file changes - the upgrade is a pure binary / script tree swap, but the backup discipline is worth keeping.
tar -czf "luadch-backup-$(date +%F).tar.gz" cfg scripts certs secretsWhy upgrade
Only deployments that explicitly set kill_wrong_ips = false are affected (the 3.1.x default is true). Operators run that opt-out specifically to admit NAT / CGNAT users whose client advertises a different IP than the one they connect from - and this bug defeats exactly that opt-out: instead of stamping the real IP and letting the user in, the hub raised a Lua error in incoming and the affected user could not complete login. If you set kill_wrong_ips = false (or plan to), upgrade. If you run the default, you are not affected and can upgrade at leisure.
Bugfixes
#393 (Sopor) - kill_wrong_ips = false path errored with attempt to read undeclared var: 'userfam'
The symptom, once an operator set kill_wrong_ips = false and a mismatched-IP user connected:
hub.lua: function 'incoming': lua error: ././core/hub_dispatch.lua:356: attempt to read undeclared var: 'userfam'
The #214 Gap 2 fix (shipped to 3.1.x in v3.1.10) stamps the authenticated TCP-source IP over a mismatched INF claim so the wrong IP is never broadcast. The v3.1.10 cherry-pick wrote that stamp as adccmd:setnp( userfam, userip ) - but userfam is the master branch's variable name. The 3.1.x incoming function calls its address-family variable ipver (declared at the top of the function, used correctly by the first setnp on the no-IP path ~15 lines above). Under luadch's restricted core environment, reading the undeclared userfam raises an error - so for every user whose claimed INF IP differed from their real TCP-source IP while kill_wrong_ips = false was set, the stamp branch threw and the user (typically the very NAT/CGNAT client the opt-out exists to admit) could not log in.
Fix: use the in-scope ipver at that call site, matching the sibling setnp:
- adccmd:setnp( userfam, userip )
+ adccmd:setnp( ipver, userip )Default kill_wrong_ips = true deployments are unaffected - they take the kill branch and never reach the stamp branch. Master is unaffected; it uses userfam consistently throughout its renamed incoming. This was a partial-rename slip in the v3.1.10 backport only, so the fix is applied directly on release/3.1.x (nothing to cherry-pick from master).
The kill_wrong_ips = false stamp path had no smoke coverage, which is how the slip shipped in v3.1.10; a behavioural regression test needs a kill_wrong_ips = false hub config and is tracked as a follow-up.
Build / runtime
No changes. Same Lua 5.4, same LuaSec 1.3.2, same LuaSocket 3.1.0, same build toolchain as v3.1.11.
The linux-aarch64 artifact continues with the Bullseye-container pipeline (glibc 2.31 baseline, works on Pi OS Bullseye / Bookworm / DietPi v9.x).
Upgrade
# Linux x86_64 / aarch64
wget https://github.com/luadch-ng/luadch/releases/download/v3.1.12/luadch-v3.1.12-linux-x86_64.tar.gz
tar xzf luadch-v3.1.12-linux-x86_64.tar.gz
# move your cfg/, scripts/data/, etc into the new tree, restart hub
# Windows
# Download luadch-v3.1.12-windows-x86_64.zip, extract, copy cfg+data over, restart.3.2.x is the active development line on master; security backports continue to land on release/3.1.x per CLAUDE.md §8.
v3.1.11
Luadch v3.1.11
Maintenance patch release on the release/3.1.x line. One privilege-escalation bugfix cherry-picked from master. No breaking changes; no cfg / lang-file changes; drop-in upgrade from v3.1.10.
⚠️ Before upgrading
Back up your cfg/, scripts/lang/, scripts/data/, scripts/cfg/, certs/, and secrets/ directories before any upgrade, on principle. This release has no required cfg or lang-file changes - the upgrade is a pure binary / script tree swap, but the backup discipline is worth keeping.
tar -czf "luadch-backup-$(date +%F).tar.gz" cfg scripts certs secretsWhy upgrade
If your hub has more than one operator-level account and only one hubowner, the bug fixed here is exploitable: any account with +ban permission (typically op-level and above) could ban the hubowner while the hubowner was offline, locking them out on the next login attempt. With a single hubowner this becomes denial-of-administration on the hub - the hubowner cannot remove their own ban because they cannot log in. With multiple hubowner-level accounts the impact is reduced to a removable nuisance.
Default-config hubs are affected. The fix applies to every operator from level 60 upward (default cmd_ban_permission ladder).
Bugfixes
#320 (Kcchouette) - cmd_ban offline-by-nick path silently bypassed the hierarchy check
scripts/cmd_ban.lua had two divergent target-resolution paths converging on addban():
- Online branch resolves
targetto a user OBJECT (has:level()method), falls through to the existing check at line 594:if permission[level] < target:level() then ... msg_god - Offline-by-nick branch resolves
targetto a profile TABLE fromregnicks[](has.levelfield, no:level()method), then returns ataddban()two lines BEFORE the check could run
Effect: a level-60 operator could send +ban nick hubowner_offline 60 reason, the offline branch ran addban() directly without checking that the operator outranked the target, and the hubowner was banned. On their next login attempt the ban hit, kicking them with ISTA 232 ... TL<reconnect-seconds>.
Fix: explicit hierarchy guard inside the offline-by-nick branch using target.level, mirroring the online-path semantics with the table-shape accessor:
elseif by == "nick" then
local _, regnicks, _ = hub.getregusers()
target = regnicks[ id ]
if not target then
user:reply( msg_off, hub.getbot() )
return PROCESSED
end
if permission[ level ] < ( target.level or 0 ) then -- new
user:reply( msg_god, hub.getbot() )
return PROCESSED
end
endcid / ip offline branches keep no profile lookup and stay unchecked by design (no hierarchy info available - the hub doesn't know whose IP a given IP is).
Plugin v0.36 → v0.37. Cherry-picked from master (PR #322, commit 30c8809); the new HTTP-API-based regression smoke test stays master-only because the HTTP API ships on 3.2.x only - the fix logic is identical and reviewer-verified.
A pattern sweep over the related command-and-profile plugins (cmd_gag, cmd_setpass, cmd_upgrade, cmd_delreg, cmd_nickchange, cmd_redirect, cmd_disconnect, etc_trafficmanager, etc_msgmanager) for the same online-vs-offline-hierarchy-check divergence came back clean - either properly guarded on both paths or online-only by design. cmd_ban was the only affected plugin in the family.
Build / runtime
No changes. Same Lua 5.4.7, same LuaSec 1.3.2, same LuaSocket 3.1.0, same build toolchain as v3.1.10.
The linux-aarch64 artifact continues with the Bullseye-container pipeline (glibc 2.31 baseline, works on Pi OS Bullseye / Bookworm / DietPi v9.x).
Upgrade
# Linux x86_64 / aarch64
wget https://github.com/luadch-ng/luadch/releases/download/v3.1.11/luadch-v3.1.11-linux-x86_64.tar.gz
tar xzf luadch-v3.1.11-linux-x86_64.tar.gz
# move your cfg/, scripts/data/, etc into the new tree, restart hub
# Windows
# Download luadch-v3.1.11-windows-x86_64.zip, extract, copy cfg+data over, restart.3.2.x is the active development line on master; security backports continue to land on release/3.1.x per CLAUDE.md §8.
v3.1.10
Luadch v3.1.10
Maintenance patch release on the release/3.1.x line. Two security / UX bugfixes cherry-picked from master. No breaking changes; no cfg / lang-file changes; drop-in upgrade from v3.1.9.
⚠️ Before upgrading
Back up your cfg/, scripts/lang/, scripts/data/, scripts/cfg/, certs/, and secrets/ directories before any upgrade, on principle. This release has no required cfg or lang-file changes - the upgrade is a pure binary / script tree swap, but the backup discipline is worth keeping.
tar -czf "luadch-backup-$(date +%F).tar.gz" cfg scripts certs secretsWhy upgrade
If operators have set kill_wrong_ips = false (NAT-weird deployments) - their hub was forwarding unverified primary-IP claims to other clients, opening the historical DC++ DDoS-amplification vector. This release closes that path.
If operators see FAILED-AUTH spam with reason "User sent offending flag in INF: I4" (HadesDCH reported this) - legitimate DC++ clients refreshing INF after NAT events were being killed in a loop. This release stops the kill and silent-strips the field instead.
Default-config hubs (kill_wrong_ips = true, no INF-refresh storms) are not actively affected by either bug, but the upgrade is harmless and recommended.
Bugfixes
#214 Gap 2 - DDoS-amplification on kill_wrong_ips = false opt-out
core/hub_dispatch.lua elseif infip_match ~= userip branch: when kill_wrong_ips = false AND the BINF claim doesn't match the TCP-source IP, the wrong claim STAYED in adccmd and was broadcast to other clients - they would then direct CTM / RCM frames at the spoofed address (Maksis-confirmed DC++ DDoS-amp vector).
Fix: the opt-out path now stamps the authenticated userip over the lie via adccmd:setnp(userfam, userip). Opt-out intent (don't kill the user) preserved. Default kill_wrong_ips = true deployments unaffected. Side benefit: legitimate NAT-deployments now broadcast a routable IP, so other clients' CTMs succeed (pre-fix they targeted the wrong IP and failed).
The companion Gap 1 (secondary-family unverified broadcast) is the upcoming HBRI implementation's responsibility - tracked on master.
#222 (HadesDCH) - post-login INF with I4 / I6 killed legitimate users
scripts/hub_inf_manager.lua forbidden.flags_on_inf contained I4 / I6 (originally added in #97 to prevent post-login IP-spoofing). Real DC++ clients refresh INF including I4 on routine triggers (NAT rebind, ISP-IP change, plain refresh); pre-fix those legitimate refreshes triggered ISTA 240 + TL300 reconnect-block, producing FAILED-AUTH log spam and bouncing users in a loop.
Fix: flags_on_inf split into _kill (PD / ID - identity spoofing, real attack signal = kill) and _strip (I4 / I6 - IP mutation attempt OR routine refresh = silent-strip). The strip path removes I4 / I6 from cmd via cmd:deletenp() before applying remaining fields. Anti-spoofing intent of #97 preserved: stored _inf IP is never mutated, broadcast doesn't carry the new claim. Other INF fields in the same update (DE, SS, etc.) still get applied normally. Plugin v0.06 → v0.07.
Build / runtime
No changes. Same Lua 5.4.7, same LuaSec 1.3.2, same LuaSocket 3.1.0, same build toolchain as v3.1.9.
The linux-aarch64 artifact is built with the Bullseye-container pipeline introduced as the v3.1.9 in-place asset swap (glibc 2.31 baseline, works on Pi OS Bullseye / Bookworm / DietPi v9.x).
Upgrade
# Linux x86_64 / aarch64
wget https://github.com/luadch-ng/luadch/releases/download/v3.1.10/luadch-v3.1.10-linux-x86_64.tar.gz
tar xzf luadch-v3.1.10-linux-x86_64.tar.gz
# move your cfg/, scripts/data/, etc into the new tree, restart hub
# Windows
# Download luadch-v3.1.10-windows-x86_64.zip, extract, copy cfg+data over, restart.3.2.x is the active development line on master; security backports continue to land on release/3.1.x per CLAUDE.md §8.
v3.1.9
Luadch v3.1.9
Maintenance patch release on the release/3.1.x line. Three bug fixes (two restoring spec-compliant hublist visibility, one defense-in-depth) plus a new pre-compiled linux-aarch64 release artifact for Raspberry Pi. No breaking changes; no cfg / lang-file changes; drop-in upgrade from v3.1.8.
⚠️ Before upgrading
Back up your cfg/, scripts/lang/, scripts/data/, scripts/cfg/, certs/, and secrets/ directories before any upgrade, on principle. This release has no required cfg or lang-file changes - the upgrade is a pure binary / script tree swap, but the backup discipline is worth keeping.
tar -czf "luadch-backup-$(date +%F).tar.gz" cfg scripts certs secretsWhy upgrade
Public hubs on v3.1.8 are effectively invisible to ADC hublist pingers. The PING HSUP handler errored out silently (#162) and even when it didn't, the BINF validation rejected pinger clients that legitimately omitted I4 / I6 (#161). Both fixes restore hublist visibility on public hubs.
Reg-only (private) hubs are unaffected by #161 / #162 in operator-visible ways but still benefit from the #160 defense-in-depth and the core/server.lua latent-bug closure. Upgrade is recommended for all operators regardless of hub mode.
Bugfixes
#161 - BINF without I4 / I6 was rejected
Per ADC 4.3.x the I4 / I6 fields are conditionally required (only when the client advertises TCP4 / UDP4 / TCP6 / UDP6 in SU). Hublist pingers and any IP-agnostic probe legitimately omit them. The hub now treats a missing I4 / I6 like the spec-defined 0.0.0.0 placeholder - fills in the TCP-source IP under the connection's address family, no special-case "no-IP user" shape downstream. kill_wrong_ips spoof-detection is unchanged for actually-mismatched claims.
Mirrors upstream luadch/luadch#176.
#162 - PING HSUP handler crashed silently on public hubs
A T1.3 regression introduced in v3.1.8 by #147: the new SS / SF aggregator loop called pairs( _normalstatesids ) but pairs was not imported into the core/hub_dispatch.lua sandbox locals. Every ADC PING handshake against a reg_only = false hub hit the sandbox guard, the dispatcher errored out per-connection (caught by the hub's pcall, logged to error.log), and the pinger saw zero frames.
Reg-only hubs were unaffected because the _cfg_reg_only short-circuit prevented the aggregator from running.
#160 (Sopor) - etc_trafficmanager defense-in-depth
The onSearch listener already swallows searches in both directions for blocked users, so a blocked user normally has no search to reply to. The new onSearchResult listener catches the protocol-violating edge case where a blocked user sends an unsolicited DRES / FRES (or a DRES targets a blocked user). Plugin bumped to v2.2.
Latent crash in core/server.lua changesettings()
tonumber() called seven times without local tonumber = use "tonumber" import. Function is currently dead code (no caller in hub or plugins) so no production impact; surfaced by the #162 sandbox-locals audit. Fix is a one-line use declaration.
Features
#159 (Sopor) - pre-compiled linux-aarch64 Raspberry Pi binary
New release artifact luadch-v3.1.9-linux-aarch64.tar.gz alongside the existing linux-x86_64 and windows-x86_64 builds. Native arm64 build on GitHub's ubuntu-24.04-arm runner - no cross-compile.
Covers Raspberry Pi 3+ / 4 / 5 / Zero 2W with a 64-bit OS (>95% of the active Pi installed base in 2026). 32-bit ARM (Pi 1 / Zero v1 / Pi 2 32-bit) still requires the source build per docs/BUILDING.md.
Notes
- No breaking changes, no cfg / lang-file edits required. Drop-in upgrade from v3.1.8.
- Pre-merge review pattern. All three bugfixes were caught by a two-pass review (independent agent + self-spot-check) that was codified during this cycle. The review also surfaced the
core/server.luatonumberlatent bug as a sibling-module audit finding. - 3.1.x line still on security-fixes-only. v3.1.9 is a maintenance release; new feature work continues on the 3.2.x line on
masterperCLAUDE.md§8.
Downloads
| File | Platform |
|---|---|
luadch-v3.1.9-linux-x86_64.tar.gz |
Linux glibc x86_64 |
luadch-v3.1.9-linux-aarch64.tar.gz |
Linux glibc aarch64 (Raspberry Pi 3+ / 4 / 5 / Zero 2W, 64-bit OS) |
luadch-v3.1.9-windows-x86_64.zip |
Windows x86_64 (MinGW UCRT64) |
ghcr.io/luadch-ng/luadch:v3.1.9 |
Container, linux/amd64 + linux/arm64 |
Migration from v3.1.8
Drop the new install tree in place of the old one (or git pull && cmake --build build && cmake --install build from source). Container users get the bundled *.lua sync on the next docker compose up -d after pull.
No cfg/cfg.tbl migration is needed.
Build from source
git clone --branch v3.1.9 https://github.com/luadch-ng/luadch.git
cd luadch
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
cmake --install buildv3.1.8
Luadch v3.1.8
Modernisation-complete patch release. Closes the #80 ratelimit v2 and #147 ADC protocol coverage tracks. After this release the 3.1.x line is stable, security-fixes-only; new feature work moves to the 3.2.x line on master. See CLAUDE.md §8 for the full release-line policy.
⚠️ Before upgrading
Back up your cfg/, scripts/lang/, scripts/data/, scripts/cfg/, certs/, and secrets/ directories. This release adds new keys to several lang files (additive, see below) and introduces several new optional cfg keys for the ratelimit / RDEX / PING extensions.
tar -czf "luadch-backup-$(date +%F).tar.gz" cfg scripts certs secretsLang file changes
Operators with stock bundled lang files get the new keys automatically via the Docker autosync from #118 or cmake --install build for source builds. Operators with custom translations have a small one-time additive merge:
| File | New key |
|---|---|
cmd_shutdown.lang.{en,de} |
msg_hub_disabled |
cmd_restart.lang.{en,de} |
msg_hub_disabled |
Missing keys fall back to the hardcoded English string ("Hub is shutting down." / "Hub is restarting.") so the hub stays functional.
Cfg additions
All new keys are additive with defaults that preserve v3.1.7 behaviour. No cfg/cfg.tbl migration is required.
Ratelimit v2 (#80)
-- Per-bucket rate / burst, each independently tunable
ratelimit_user_pm_rate = 5, ratelimit_user_pm_burst = 10,
ratelimit_user_inf_rate = 2, ratelimit_user_inf_burst = 20,
ratelimit_user_ctm_rate = 2, ratelimit_user_ctm_burst = 30,
-- Per-userlevel tier overlay - any subset of fields per tier,
-- missing fields fall back to the global scalars above
ratelimit_tiers = { },
ratelimit_tier_for_level = { },Worked example for the tier overlay is in docs/SCRIPTS.md. At defaults (empty tier tables) behaviour is identical to v3.1.7.
ADC protocol coverage (#147)
-- ADC-EXT RDEX rich redirect
hub_redirect_protocols = 3, -- bitmask (ADC=1, ADCS=2, NEODC=4)
hub_redirect_alternatives = { }, -- list of alternative URLs as RX fields
hub_redirect_permanent = false, -- PT1 flag on redirects
-- ADC-EXT PING min-hubs federation policy (also from #146)
min_user_hubs = 0, -- min OTHER hubs (federation requirement)
min_reg_hubs = 0,
min_op_hubs = 0,Highlights
Ratelimit v2 (#80, 4 PRs + 2 review followups)
The hub's per-user rate-limit machinery split BMSG / DMSG / EMSG / BINF / DCTM / DRCM into five independent buckets. Each bucket has its own rate + burst cfg keys, and all five become optionally tier-mappable per user level via the new ratelimit_tiers + ratelimit_tier_for_level overlay. Op-level bypass preserved. Worked tier-overlay example in docs/SCRIPTS.md.
A strict-positive validator rejects rate = 0 / burst = -1 / NaN at cfg-load time, with a clear out_error log entry; the previous silent-mute failure mode under operator typo is gone.
ADC protocol coverage (#147, 8 PRs)
Eight protocol-completeness items shipped as small, additive PRs:
- NATT relay (DNAT / DRNT, ADC-EXT 3.9) - hub-relay-only NAT-traversal for passive-passive transfers.
- RDEX rich redirect -
IINF.RPadvertisement +IQUI.RX/PTNPs on every kick / redirect. - PING completeness -
SS/SFaggregate share + file count,HEemail,MU/MR/MOmin-hubs federation policy now emitted in the ADPING reply. Hublist scrapers see the full spec-defined data. - STA emission codes -
cmd_shutdown/cmd_restartemitISTA 212("Hub disabled") before close so clients distinguish a graceful shutdown from a network glitch.cmd_banswitches from incorrect 230 / 231 to spec-correctISTA 232for finite-TL temporary bans. - FRES routing - feature-filtered search-result delivery (F-class) now dispatched.
- HQUI from client honored - client-initiated quit triggers a clean close in any state instead of
ISTA 125unknown-command. - ECTM / ERCM dispatch - modern E-class CTM / RCM variants accepted (was: silently dropped).
- Passthrough extensions documented -
TYPE/ONID/DFAV/FEEDand friends are explicitly transparent passthrough (no SUP advertisement needed, hub relays the commands without inspection).
Per the audit the hub is now at ~90% ADC + ADC-EXT coverage for hub-relevant features. Remaining 10% is BLOM (Tier 2, demand-driven) and HBRI / ZLIF (Tier 3, deferred to 3.2.x).
Operator documentation
- New
docs/SCRIPTS.mdlists every bundled plugin (commands + cfg keys) with a full rate-limit configuration guide. - README cleaned up - dropped the "what's different in this fork" section now that the modernisation programme is done; added a release-line status table near the top.
CLAUDE.md§8 documents the post-3.1.8 maintenance-branch model.
Bugfixes
user.sendstatypo (#151) -pairs(nil)crash on callers that omitted the optional flags arg, present since the API was added.user.redirectMS<quitmsg>was emitted raw; multi-word reasons produced malformed ADC. Now escaped.- Smoke battery's PM / CTM / RCM / NATT burst tests previously short-circuited at the target-lookup before reaching the rate-limit gate; they now self-target and exercise the actual code paths.
Notes
- No breaking changes at defaults. Every new cfg key has a default that preserves v3.1.7 behaviour. Operators upgrading without touching cfg get the new features behind unchanged knobs.
- ERES no longer accepted by the parser. ADC 5.3.6 defines only D and F classes for RES; the parser context tightened to
[FD]as part of FRES routing. No known client emits ERES; exotic NMDC bridges that did would now see parse-time rejection instead of forward-as-E-class. - 3.1.x maintenance from this release on. New feature work goes to
master(3.2.x line); security fixes for 3.1.x go to arelease/3.1.xbranch created from this tag. SeeCLAUDE.md§8.
Downloads
| File | Platform |
|---|---|
luadch-v3.1.8-linux-x86_64.tar.gz |
Linux glibc x86_64 |
luadch-v3.1.8-windows-x86_64.zip |
Windows x86_64 (MinGW UCRT64) |
ghcr.io/luadch-ng/luadch:v3.1.8 |
Container, linux/amd64 + linux/arm64 |
Migration from v3.1.7
Drop the new install tree in place of the old one (or git pull && cmake --build build && cmake --install build from source). Container users get both the bundled *.lua sync and the lang add-only sync on the next docker compose up -d after pull.
No cfg/cfg.tbl migration is needed - all new keys are additive with defaults. To opt into the tier-overlay rate-limit, see the worked example in docs/SCRIPTS.md Rate-limit configuration.
Build from source
git clone --branch v3.1.8 https://github.com/luadch-ng/luadch.git
cd luadch
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
cmake --install buildv3.1.7
Luadch v3.1.7
Plugin data-integrity patch release. util.savearray / util.savetable are now atomic-by-default across the bundled scripts tree, defensive or {} was swept onto util.loadtable consumers, the cross-month accounting bug in usr_uptime is fixed, and cmd_gag gains a shadowmute mode plus duration syntax.
⚠️ Before upgrading
Back up your cfg/, scripts/lang/, scripts/data/, scripts/cfg/, certs/, and secrets/ directories. This release modifies scripts/lang/cmd_gag.lang.{en,de} (5 new keys + 4 rewritten strings for shadowmute / duration support) and adds encrypt_usertbl to examples/cfg/cfg.tbl. If you have customised cmd_gag translations, see the Lang file changes section below before letting the autosync run.
tar -czf "luadch-backup-$(date +%F).tar.gz" cfg scripts certs secretsLang file changes
Operators with stock bundled lang files get the new keys automatically via the Docker autosync from #118, or via cmake --install build for source builds. Operators with custom cmd_gag.lang.* files have a small one-time merge:
| File | What changed |
|---|---|
cmd_gag.lang.{en,de} |
New keys: msg_invalid_duration, msg_add_user_with_duration, msg_expired, ucmd_duration, ucmd_menu_ct1b. Rewritten: msg_usage, help_usage, help_desc (now reference shadowmute + <DURATION>), msg_show_users (adds the "Shadowmuted users" block). Missing keys fall back to the hardcoded English defaults; behaviour stays correct, the chat output just sits in mixed languages until the merge happens. |
Features
cmd_gagshadowmute mode + duration (#85 / #132) - the gag bot gets a 4th mode where the target sees their own messages echo back as if everything works, but nobody else on the hub receives them. Combined with this, all three restriction modes (mute, kennylize, shadowmute) now take an optional duration:30s,10m,2h,1d,1wand combinations like1h30m. Empty duration = permanent. Restrictions auto-expire as the gag table is walked every 60s. Offline ungag works throughhub.getregusers()so admins can lift restrictions even after the user disconnects. Right-click menu gets a "Shadowmute User" entry alongside the existing mute / kennylize ones.encrypt_usertblopt-out toggle (#128 / #129) - the Phase-7f AES-256-GCM at-rest encryption ofcfg/user.tblis now optional. Defaulttruepreserves the v3.1.3+ behaviour. Settingencrypt_usertbl = falsewrites plaintext Lua for single-user / home-hub deployments where disk-level confidentiality isn't part of the threat model and operator tooling needs direct read access to the file. Auto-detected on read via the LDC1 magic prefix, so migration is transparent in both directions;master.keyis loaded if present (legacy decrypt) but only auto-generated when encryption is on.
Bugfixes
usr_uptimecross-month accounting (#127 / #131) - sessions that span month boundaries no longer accumulate as "years" of uptime. The pre-fix code bracketed sessions onloginand credited the whole span to the login month onlogout, so a session starting on the 31st and ending on the 1st saw the end-month-minus-start-month delta arithmetic wrap into multi-year totals. The fix walks the user list on a 60-second timer and credits each tick to the calendar month it falls into, so cross-month sessions land in both months correctly.- Plugin save crash-safety - F-PLG-1 (#133 / #134) -
util.savearrayandutil.savetablepreviously opened the target file in"w+"(truncate) and wrote serialised content directly, so a hub crash mid-write left the.tblpartial. Both helpers now route through a new publicutil.atomic_write(path, content)helper that does tmp + rename (Windows fallback: remove then rename). 21 plugin save sites across the bundled tree get crash-safe writes with zero call-site changes;core/cfg_users.luakeeps itschmod 600foruser.tblseparately viautil.chmod_secret. - Plugin load nil-handling - F-PLG-2 (#133 / #135) -
util.loadtablereturnsnilon missing / unreadable / parse-fail. Defensiveor {}added at 22 consumer sites across 12 bundled plugins (bot_session_chat,cmd_accinfo,cmd_delreg,cmd_nickchange,cmd_reg,cmd_usercleaner,etc_msgmanager,etc_trafficmanager,usr_hide_share, plus field-read defence incmd_hubinfo,cmd_uptime,hub_runtime). Sites with the existing init-pattern (check_hcistyle: type-check + savetable + opchat warning) intentionally left alone - they handle nil correctly and auto-create the file. - F-PLG-3 audit (#133) - silent no-op on incomplete
+cmdaudited across 24 bundled scripts and 53utf.match-on-parameters sites. No actionable bugs found. Every command handler already has either an explicit pre-check guard or a finalmsg_usagefallback. Audit closeout on the tracker.
Notes
- New public helpers in
core/util.lua:util.atomic_write(path, content)andutil.tabletostring(tbl, name). Plugins that roll their own save logic can route through them for crash-safe writes. The companionluadch-ng/scriptsrepo already uses them inptx_poll_bot,ptx_freshstuff,etc_requests, andetc_mainecho(min hub version: v3.1.7). - Smoke harness: 31/31 PASS on Linux + Windows. No test count change in this release; the existing tests already cover the new behaviour (atomic-write path validated by
test_usertbl_bak_atomic_refreshfrom v3.1.5). - Companion plugin updates landed in
luadch-ng/scripts#24(closed) with two PRs adding atomic save + nil-handling to the curated plugin tree - operators running those plugins should also pull the latest from that repo.
Downloads
| File | Platform |
|---|---|
luadch-v3.1.7-linux-x86_64.tar.gz |
Linux glibc x86_64 |
luadch-v3.1.7-windows-x86_64.zip |
Windows x86_64 (MinGW UCRT64) |
ghcr.io/luadch-ng/luadch:v3.1.7 |
Container, linux/amd64 + linux/arm64 |
Migration from v3.1.6
Drop the new install tree in place of the old one (or git pull && cmake --build build && cmake --install build from source). Container users get both the bundled *.lua sync and the lang add-only sync on the next docker compose up -d after pull.
No cfg.tbl migration is needed. The new encrypt_usertbl key in examples/cfg/cfg.tbl is purely additive - existing cfg/cfg.tbl files without the key default to encrypted, which matches v3.1.6 behaviour. To opt out, add encrypt_usertbl = false and +reload (or restart the container).
Build from source
git clone --branch v3.1.7 https://github.com/luadch-ng/luadch.git
cd luadch
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
cmake --install buildv3.1.6
Luadch v3.1.6
Security-themed patch release. Hub now defaults to TLS-only with an auto-generated self-signed cert on first boot, password leakage in admin reply paths is closed for +setpass / +accinfo / +usersearch, and Docker deployments pick up new bundled language files automatically.
⚠️ Before upgrading
Back up your cfg/, scripts/lang/, scripts/data/, scripts/cfg/, certs/, and secrets/ directories. This release touches lang files in particular - if you have customised translations for cmd_accinfo, cmd_setpass, or cmd_usersearch, see the Lang file changes section below before letting the autosync run.
tar -czf "luadch-backup-$(date +%F).tar.gz" cfg scripts certs secretsLang file changes
This release adds new lang keys and one renamed value. Operators with stock bundled lang files (scripts/lang/<plugin>.lang.{en,de}) get all of this automatically via the Docker autosync introduced in #118, or via cmake --install build for source builds. Operators with custom lang files have a small one-time merge:
| Plugin | What changed |
|---|---|
cmd_accinfo.lang.{en,de} |
New key: msg_redacted = "<REDACTED>". Falls back to the hardcoded English default if missing. |
cmd_usersearch.lang.{en,de} |
Same new key: msg_redacted = "<REDACTED>". |
cmd_setpass.lang.{en,de} |
Existing msg_ok rewritten: "Password was changed to: " -> "Password was changed." (no longer concatenates the password value). Custom translations that read like a sentence-prefix will sit next to a missing value cosmetically; behaviour is correct regardless. |
usr_nick_length.lang.{en,de} |
New file - previously the script had no lang infrastructure. Two keys: msg_failedauth_reason, msg_invalid_length. |
Breaking
- TLS-only default (#77 / #113) - the bundled
cfg/cfg.tblnow ships TLS-only on both stacks: IPv4 (tcp_ports = { },ssl_ports = { 5001 }) and IPv6 (tcp_ports_ipv6 = { },ssl_ports_ipv6 = { 5003 }), withuse_ssl = true. Existingcfg/cfg.tblfiles are not migrated - operators upgrading keep their plain-port settings on both stacks until they choose to flip. Fresh installs and Docker first-boot are TLS-only by default.
Features
- Auto-generated self-signed cert on first boot (#113) - if
certs/servercert.pem/serverkey.pemare missing, the hub generates a P-256 ECDSA pair via adclib's OpenSSL bindings and writes them to disk before the TLS listener binds. Keyprint logged to stdout in the boot banner sodocker compose logs/ the launching terminal shows theadcs://host:port/?kp=SHA256/<base32>URL to share with users.make_cert.{sh,bat}stay around for manual rotation; the entrypoint no longer runs them. - Slaxml XML parser bundled (#112) -
lib/slaxml/slaxml.luais now part of the install tree. Plugins canuse "slaxml"for RSS / XML feed parsing without a separate dep install. - Docker autosync extended to lang files (#118) - new bundled
scripts/lang/*.lang.*files land on operator mounts automatically. Strictly add-only, existing translations are never overwritten. The sameLUADCH_AUTOSYNC_SCRIPTS=0opt-out covers both the*.luaoverwrite-on-diff sync and the new lang add-only sync.
Bugfixes
- Password redaction in admin reply paths (#95 partial / #119):
+setpassdrops the password from the caller's reply. The target user still receives the new password via PM (admin-sets-target case) - they need it to log in.+accinfoand+usersearchshow<REDACTED>in the password column instead of the cleartext value.+regauto-generated password delivery is intentionally unchanged - target needs the value to log in. Thecmd_regredesign is Phase-8+ scope and depends on either an alternate delivery channel (#100 SMTP) or a token-based first-login flow.
usr_nick_lengthlocalisation (#48 i18n half / #117) - operator-facingonFailedAuthreason and user-facingISTA 221kill message now route through the newscripts/lang/usr_nick_length.lang.{en,de}.- Plugin-header grammar fix (#114 / #116) -
'an user'->'a user'across nine bundled plugin headers. Comment-only.
Notes
- Bug-report and feature-request issue templates added under
.github/ISSUE_TEMPLATE/. - Smoke harness: 14/14 PASS on Linux + Windows (no test count change in this release; existing tests cover the new behaviour).
Downloads
| File | Platform |
|---|---|
luadch-v3.1.6-linux-x86_64.tar.gz |
Linux glibc x86_64 |
luadch-v3.1.6-windows-x86_64.zip |
Windows x86_64 (MinGW UCRT64) |
ghcr.io/luadch-ng/luadch:v3.1.6 |
Container, linux/amd64 + linux/arm64 |
Migration from v3.1.5
Drop the new install tree in place of the old one (or git pull && cmake --build build && cmake --install build from source). Container users get both the bundled *.lua sync and the new lang add-only sync on the next docker compose up -d after pull.
If you want to flip to TLS-only on an existing deployment, edit cfg/cfg.tbl. luadch separates IPv4 and IPv6 listeners into distinct port arrays - flip both stacks to mirror the new bundled defaults:
tcp_ports = { },
ssl_ports = { 5001 },
tcp_ports_ipv6 = { },
ssl_ports_ipv6 = { 5003 },
use_ssl = true,If you only run on one stack, leave the other one as-is. Then +reload (or restart the container). The hub's auto-cert-gen path picks up the missing certs/serverkey.pem / servercert.pem and writes a fresh self-signed pair before binding the listener.
Build from source
git clone --branch v3.1.6 https://github.com/luadch-ng/luadch.git
cd luadch
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
cmake --install buildv3.1.5
Luadch v3.1.5
Patch release. Closes upstream luadch/luadch#189 (registered users disappearing) and adds image-side auto-sync of bundled plugin code on docker compose pull.
⚠️ Before upgrading
Back up your cfg/, scripts/lang/, scripts/data/, scripts/cfg/, certs/, and secrets/ directories. Bundled scripts/*.lua are auto-synced from the image; everything else is operator-owned and never touched by an upgrade, but a clean snapshot is the safety net for any production hub.
tar -czf "luadch-backup-$(date +%F).tar.gz" cfg scripts certs secretsBugfixes
- #108 / upstream luadch#189 - registered users no longer disappear from
user.tbl. Stale file-scope cache incmd_nickchange.lua+ non-atomicsaveuserswere the two root causes; both fixed in #109.
Features
- Docker entrypoint auto-syncs bundled
scripts/*.luafrom the image to the mountedscripts/directory on every container start (#110). Operator-owned state untouched. Opt-out:LUADCH_AUTOSYNC_SCRIPTS=0. Seedocs/DOCKER.md.
Notes
user.tbl.baknow refreshed on every successful save (was: only at+reload). Operators relying on.bakas a stale-rollback should adjust workflows.- Smoke harness: 12 -> 13 tests.
Downloads
| File | Platform |
|---|---|
luadch-v3.1.5-linux-x86_64.tar.gz |
Linux glibc x86_64 |
luadch-v3.1.5-windows-x86_64.zip |
Windows x86_64 (MinGW UCRT64) |
ghcr.io/luadch-ng/luadch:v3.1.5 |
Container, linux/amd64 + linux/arm64 |
Migration from v3.1.4
None required. Drop the new install tree in place of the old one (or git pull && cmake --build build && cmake --install build from source). Container users get the auto-sync of bundled scripts/*.lua on the next docker compose up -d after pull.
Build from source
git clone --branch v3.1.5 https://github.com/luadch-ng/luadch.git
cd luadch
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
cmake --install build