Releases: Kitsune-Den/KitsuneCommand
v2.8.2 — KitsuneCommand
Full notes
· Patch release — three stability fixes from a live Windows prod box,
plus the timezone field on the Server Restart panel grows up into a
host-resolvable dropdown. No new features, no schema changes.
Fixed
- No more stack overflow from
LogCallbackEventre-entrancy during
shutdown.EventBroadcastersubscribed toLogCallbackEventand
broadcast each fired log message over the WebSocket. When KC was
mid-shutdown and the WebSocket manager had already stopped, the
innerBroadcastthrew ("The current state of the manager is not
Start.") and the catch block calledLog.Warning(...)— which fired
anotherLogCallbackEvent, re-entered the same subscriber, threw
again, logged again, and recursed until the stack overflowed and KC
crashed. Observed live on a Windows prod box: a botched
GracefulRestartleft the WS manager stopped while the event bus
was still alive, and KC logged "Error in event handler for
LogCallbackEvent: The requested operation caused a stack overflow."
dozens of times before the service died. Fix: a[ThreadStatic]
re-entrancy guard scoped to theLogCallbackEventpath only (other
events don't loop back into the logger), and the failure path on
that path writes toConsole.Errorinstead ofLog.Warningso the
recursion can't restart. - Restart Server no longer probes
systemctlon Windows. The
POST /api/server/restartendpoint always tried
sudo -n systemctl restart 7daystodie.servicefirst and waited up to
5 seconds before falling back to in-game shutdown. On Windows there
is nosystemctl, so every restart click wasted ~5s and logged a
misleadingsystemctl restart failed or not available...warning.
On a live Windows prod box this code path was the trigger for a
cascading crash — the fallback in-game shutdown ran concurrent with
a stuck main-thread operation and snowballed into a stack overflow.
Fix: newCore/OsRestartStrategy.cspicks the path per OS. Windows
goes straight to in-game shutdown and relies on the service
supervisor (NSSMAppExit Restart, which is its default) to bounce
7DTD on game exit; an INFO-level log line announces the chosen path
instead of the warning. Linux behavior is unchanged — systemctl
first, fall back to in-game shutdown forRestart=always. Console
commandkrestartalready uses the OS-agnostic
GracefulRestartFeature.TriggerNow(no shell-out) so it didn't need
to change. - Graceful restart timezone is now host-resolvable. The
GracefulRestart feature called
TimeZoneInfo.FindSystemTimeZoneByIdwith whatever string was in
the persisted config. On .NET Framework 4.8 / Windows, IANA IDs
likeAmerica/Los_Angelesaren't recognized, so the tick threw,
the catch block logged "bad schedule config" every 30 seconds, and
the daily restart never fired. Observed in the wild with a value
of"UTC-5"(not even an IANA ID) where nobody realized the
feature was a no-op. Two-part fix: (1)LoadPersistedSettings
now probes the configured timezone, and if it doesn't resolve,
falls back toTimeZoneInfo.Local(orUtcif Local is unset),
logs one INFO line, and persists the corrected value back to the
DB so the next boot is clean. (2) NewGET /api/server/timezones
endpoint returns the runtime-resolvable timezone list sorted by
UTC offset; the Settings → Server Restart panel now renders a
PrimeVueSelectpopulated from that endpoint instead of a
free-text input that admins would fill with strings the host
couldn't parse. Savesid(whatever the runtime accepts:
Windows registry IDs on .NET Framework, IANA on .NET Core),
showsdisplayNameto the user.
v2.8.1 — KitsuneCommand
Full notes
· VIP tiers, join diagnostics, and the PackRelay Launcher land here.
Supersedes v2.8.0, which shipped without the Windows SkiaSharp native
(broken map rendering) and self-reported the wrong version.
Added
- VIP tiers. Per-player tiers with a first-login welcome pack and
recurring tier gifts. New012_vip_tiersmigration; set a player's
tier straight from the Players panel. - Join diagnostics. A companion
KitsuneJoinDiagmod feeds a new
"Join Attempts" panel - see who tried to connect, when, and why a
join failed. - PackRelay Launcher v0.1. Scaffold that hooks the main menu
(XUiC_MainMenu.OnOpen) - groundwork for launching straight into a
PackRelay-published modpack.
Fixed
- Windows map rendering no longer breaks on a clean install. The
release build sourcedlibSkiaSharp.dllonly from version-pinned
NuGet-cache paths and skipped it with a warning when they weren't
there, so the v2.8.0 zip shipped without the Windows SkiaSharp native
andMapTileRendererthrew "Unable to load library 'libSkiaSharp'".
Both Skia natives now come from the committedsrc/.../x64/copies
(same assqlite3.dll), and a missing native is a hard build error
instead of a silent skip. - Mod version matches the release again.
ModInfo.xmlwas stuck at
2.7.4, so the in-game mod list disagreed with the release tag. Bumped
to track the release.
v2.7.3 — KitsuneCommand
Full notes
· Patch release — mods page gets a "Check for Updates" button that
cross-references installed mods against Nexus, and the mod uploader
now streams large files to disk instead of buffering in RAM (no more
OOM on big modpacks).
Added
- Mods → Check for Updates — new button on the Mods page that
cross-references every installed mod against Nexus by exact-name
match and surfaces a per-row badge (update available/version differs) linking to the matching Nexus page. Stateless, on-demand —
nomod_originstable or persisted match cache. Skips
IsProtected(KitsuneCommand itself). New
Services/ModUpdateService.cs+POST /api/mods/check-updates+
i18n keys across all eight locales (en/de/fr/es translated;
ja/ko/zh-CN/zh-TW carry English placeholders pending translation).
(PR #86)
Fixed
- Mod uploader — large modpacks no longer OOM the mod process.
ModsController.UploadModwas usingMultipartMemoryStreamProvider,
which buffers the entire multipart body in RAM — a 500MB modpack
allocated 500MB+ inside the mod process, enough to OOM-kill prod on
an 8GB box with the game server already resident. Swapped in
MultipartFileStreamProviderso the upload streams to a temp file
in constant memory. Temp dir cleaned up in afinallyblock.
Frontend FileUpload cap bumped 200MB → 1GB to match. (PR #87)
v2.7.2 — KitsuneCommand
Full notes
· Patch release — deploy-script reliability + the first signed
release. From here on every release zip ships with a SHA-256 sum
and a minisign signature attached so downloaders can verify
provenance before extracting.
Added
- Signed releases — every release zip now ships with two
sidecar files:<zip>.sha256(BSD-style sum,sha256sum -c-verifiable
from any platform) and<zip>.minisig(minisign Ed25519 signature).
Verify viasha256sum -c+minisign -Vm <zip> -P <public-key>.
Public key + verify instructions live indocs/RELEASES.md.
Rationale, key-rotation, and the one-time setup walkthrough in
docs/SIGNING.md. Minisign chosen for parity
with the PackRelay launcher's updater (also minisign-based) — one
signing primitive across the product family. (PR #78, kanban #138) - Tag-push release workflow —
.github/workflows/release.yml
turnsgit tag -a vX.Y.Z && git push --tagsinto a draft GitHub
Release with all three signed assets attached and the body
auto-pulled from the matching## [X.Y.Z]CHANGELOG section.
Maintainer reviews the draft + clicks Publish. Pre-release tags
(-rc.N,-beta.N) get marked prerelease automatically. The
workflow gracefully degrades when minisign secrets aren't set —
still produces the zip + .sha256, skips the .minisig with a
warning. (PR #78, kanban #136) - README "signed releases" pill in the existing pill row, linking
to the verify section.
Fixed
tools/deploy.ps1heredoc through ssh — Windows OpenSSH
joins argv with spaces, not newlines, so multi-line scripts
passed asssh $remote $scriptcollapsed to one line on the
remote and bash choked (bash: line 1: set: -: invalid option).
Fix: pipe the heredoc tossh ... bash -svia stdin. Newlines
survive verbatim. Two call sites changed (snapshot + restore
phases). Worked around by hand during the v2.7.0 + v2.7.1
deploys; future v2.7.x deploys can usetools\deploy.ps1
directly from Windows. (PR #77, kanban #145)tools/deploy.shrsync graceful degradation — native
Windows Git Bash doesn't ship rsync; previously the script
died at the sync step. Now detects rsync viacommand -v. If
present, fast path. If absent, falls back to scp + snapshot
pattern (parity withdeploy.ps1). Refactored sync block into
sync_via_rsync+sync_via_scp_snapshothelpers. Behavior on
Linux/macOS unchanged. Real-world tested against prod with rsync
masked — works end-to-end. (PR #77, kanban #172)
v2.7.1 — PackRelay panel readability patch
What's new since v2.7.0
Patch release — PackRelay panel readability fixes that landed right after the v2.7.0 cut. Pure CSS, no backend changes, no migration. Cosmetic but worth shipping standalone instead of letting the rough edges sit on main until the next feature release.
🎨 PackRelay panel: readable on the dark theme
Two contrast bugs that snuck through the v2.7.0 cut, both in frontend/src/views/PackRelayView.vue:
- Card titles + form labels were washing out at ~3:1 contrast on the dark theme. Bumped
.page-title,.p-card-title,.form-label, and.meta-labelto--kc-text-primary(#e8eaed) so they read at WCAG-AA contrast against the dark card surface. (PR #74) - Cards rendered on a pale default surface because KC's
global.cssonly set--p-card-color(text) and never--p-card-background— so PrimeVue's stock pale card bled through and made the just-brightened labels invisible. Scoped:deep(.p-card)override in the view forces the card background to--kc-bg-cardwith a--kc-borderoutline. Same pattern other KC views (e.g. SettingsView) already use locally. (PR #75)
Net result: the PackRelay tab now matches the rest of the panel's dark-card chrome.
Installation
Server (KitsuneCommand)
Download KitsuneCommand-v2.7.1.zip, extract, drop KitsuneCommand/ into your server's Mods/ directory. Same zip works on Windows and Linux.
If you're already on v2.7.0, this is the first patch release where tools/deploy.{sh,ps1} is the recommended upgrade path — no migration this time, just a drop-in replacement:
tools/deploy.sh --build # Linux / WSL / Git Bashtools\deploy.ps1 -Build # Windows nativeClient (optional — KitsuneTraderUnlock)
Unchanged from v2.6.4.
Operators upgrading from v2.7.0
No migration. No config changes. No new env vars. Drop the new zip in place, restart, done.
Coming next
- PackRelay curator epic (kanban #146–#154) — locking pack creation to a curated source whitelist starting with Nexus Mods, hybrid manifest+cache architecture, per-publisher Premium API key model. First feature work for
packrelay.cloudv0.50 series. - In-panel update (kanban #139–#144) — still the goal that retires manual deploys entirely.
🤖 Release notes generated with Claude Code
v2.7.0 — Publish to PackRelay.cloud from the panel
What's new since v2.6.4
Marquee feature: Publish to PackRelay.cloud from the panel, in one click. Plus a sweeping test-suite green-up (32 fails -> 0), a license switch to PolyForm Noncommercial 1.0.0, and the first version of a proper deploy script so you stop accidentally clobbering your appsettings.json.
🎯 Publish to PackRelay
New PackRelay entry in the sidebar (between Mods and Backups). Paste your packrelay.cloud API token + Ed25519 signing key once (encrypted at rest with AES-256-CBC + HMAC-SHA256), curate a modpack via the existing Mods → Modpack flow, then one click publishes a signed, content-addressed pack to packrelay.cloud:
- Walks bundled mod folders, SHA-256s every file streaming
- Idempotent file uploads — re-publishing the same content short-circuits at
/files/exists - Single-shot ≤4 MB / multipart ≥4 MB (Vercel platform limit)
- Canonical-JSON manifest signed locally with Ed25519, verified server-side
- Live progress UI (Walking → Hashing → Uploading → Signing → Posting → Done)
- 409 duplicate_version surfaces as "already published" (soft success for retries)
Five backend stages went in: C# crypto primitives (canonical-JSON + Ed25519 + SHA-256), HTTP client, publish orchestrator with parallel uploads, encrypted settings storage, controller + Vue tab. Backed by 75 new NUnit cases against RFC 8032 + reference vectors + cloud-shape parity.
(PR #66, kanban #129–#133)
📝 License: MIT → PolyForm Noncommercial 1.0.0
KC is now licensed under PolyForm Noncommercial 1.0.0. Reads like MIT (read, fork, modify, redistribute, build on it) with one carve-out: no commercial use. Personal use, research, hobby projects, nonprofits, education, and government use all explicitly permitted. Commercial license available on request.
Not OSI-approved by design. Same change landed in parallel on the companion repos (PackRelay Cloud, Kitsunebi Cloud). (PR #71)
🛠 Better deploy: tools/deploy.{sh,ps1}
For the manual-deploy-until-in-panel-update-lands era, two new scripts wrap the dance:
- Snapshot
Config/appsettings.json+Plugins/*on the remote - Wipe the mod folder, scp/rsync the fresh dist (with
--deletesemantics) - Restore the snapshot,
chown -R ada:ada,systemctl restart - Health check via
systemctl is-active(failed → dump last 20 journalctl lines + exit non-zero)
Bash flavor uses rsync --exclude in one pass. PowerShell flavor uses ssh+scp with snapshot pattern (no rsync needed on Windows native). Config via .deploy.env (gitignored). Documented in docs/DEPLOYING.md. (PR #72)
🧪 Test suite green-up (32 fails → 0 passes)
The test suite had been 32 failed / 30 passed / 3 skipped on main. Two unrelated root causes:
- 31 DB-backed integration tests failing OneTimeSetUp with
DllNotFoundException: 'sqlite3'. The native sqlite3.dll wasn't copied to bin output. Fixed:<None CopyToOutputDirectory>rule inKitsuneCommand.csproj+ a newTestAssemblySetUp.csthat preloadsx64/sqlite3.dllviaLoadLibrary(same shapeModEntry.csuses at production runtime). - 1 stale AuthService test asserting against the buggy
Update(account)shape that the production fix had already replaced withUpdatePassword(id, hash).
After: 177 passed / 0 failed / 3 skipped. The 3 skips are intentional [Ignore] cases for paths that need a live 7DTD process to exercise. (PR #66)
📚 CHANGELOG.md + docs/RELEASES.md
Backfilled keep-a-changelog 1.1.0 entries for every release from v2.0.0 to v2.7.0. Documents the MAJOR.MINOR.PATCH convention (semver-ish with KC-shaped edges) and today's manual release process. (PR #66, kanban #137)
✨ UX + chrome polish
- PackRelay panel styling — switched from stock Tailwind utility classes to KC's
--kc-text-secondary/--kc-bg-card/--kc-borderCSS variables. Labels and hints had been washing out at ~3:1 contrast on the dark theme; now ~5.4:1 (WCAG AA). Cards get visible 1.5rem gaps + internal dividers. (PR #68) - PackRelay sidebar entry between Mods and Backups (was missing from #66 — route + view shipped but
navItemsarray wiring slipped). (PR #67) - README pill row — added a Codecov pill alongside CI / release / license / PRs; canonicalized all badge URLs to
Kitsune-Den/KitsuneCommand. (PRs #70, #71)
🔧 CI hygiene
- Coverage workflow refresh: concurrency cancel-in-progress (~50% CI minute savings on rapid pushes), npm cache, codecov-action v4 → v5,
fail_ci_if_error: true,flags: frontend(for the day backend coverage lands). (PR #69)
Installation
Server (KitsuneCommand)
Download KitsuneCommand-v2.7.0.zip, extract, drop KitsuneCommand/ into your server's Mods/ directory. The same zip works on both Windows and Linux — the mod auto-detects OS and loads the correct native libraries.
Client (optional — KitsuneTraderUnlock)
Unchanged from v2.6.4. Only needed for V2's locked-down client if you want to interact with trader-area blocks when an admin opens them up via ktrader off.
Operators upgrading from v2.6.4
First time using the new deploy script
If you're upgrading from a v2.6.x panel, this is a good moment to start using tools/deploy.{sh,ps1} so future updates don't accidentally clobber your Config/appsettings.json. Copy .deploy.env.example to .deploy.env, fill in your KC_DEPLOY_HOST, then:
tools/deploy.sh --build # Linux / WSL / Git Bashtools\deploy.ps1 -Build # Windows native (uses ssh + scp)See docs/DEPLOYING.md for the full walkthrough.
Manual upgrade (legacy)
Drop the new zip in place and restart. Migration 011_packrelay.sql applies on first boot (creates the encrypted-settings table for the publish flow). No other migration steps needed.
If you customized Config/appsettings.json (changed WebUrl, port numbers, CORS), back it up first — manual scp without the new deploy script will overwrite it.
Coming next
- In-panel update — kanban #139–#144 tracks an SSH-less version where the admin clicks Update in the panel and KC swaps itself in via the systemd pre-start hook. Will replace the manual deploy entirely.
- Automated release workflow — kanban #136 — tag-push → GHA → draft GH release with built zip. This is the last fully-manual release before that lands.
🤖 Release notes generated with Claude Code
v2.6.4 — Modpack feature
What's new since v2.6.2
A marquee feature + a slate of UX, ops, and i18n improvements. v2.6.3 was bumped on main but never tagged — this release folds it into v2.6.4 so the public history stays clean.
🎯 Modpack — Bundle installed mods for player download
Like Minecraft modpacks. Admin picks installed mods, names + versions a "pack," builds a zip, publishes. Once published, a top-right download CTA appears on the login page so players can grab matching client-side mods without needing a panel account.
- Mods → new Modpack tab with mod picker, name + version + description
- Three-state workflow: draft → published → archived
- Anonymous metadata + download endpoints (no auth required)
- Atomic temp-file zip build so public downloaders can't grab a half-written archive mid-rebuild
- Bundle target is
<game-root>/KitsuneModpacks/(parallel to KitsuneBackups)
(PR #64)
🛠 Graceful Restart — Daily restarts with player warnings
Schedule daily restarts with a player-friendly in-game countdown.
- Configurable warning ladder (default: 10 min / 5 min / 1 min / 0 min, escalating colors)
- IANA-timezone schedule (DST-aware) — e.g.
04:00 America/Los_Angelesfollows the actual wall clock through PDT/PST transitions krestart [minutes]console command for on-demand restarts- Manual "Restart Now" button in the Settings → Server Restart tab
- Re-entrancy guard prevents overlapping countdowns
Replaces the systemd-timer-plus-shell-script stopgap from v2.6.1.
🐛 Backups silent write loss
BackupService had a redundant conn.Open() call on every method — six call sites total. The custom System.Data.SQLite build that ships with KC silently dropped INSERT/UPDATE statements after a double-Open (no exception, just no row written). Reads worked, so the bug went unnoticed until the first hands-on test of the backup feature, where ZIPs appeared on disk but the audit table stayed empty.
(PR #56)
🌍 German, French, Spanish locales
Polite-form translations across ~250 keys / 30 namespaces in each. Browser auto-detect now picks the right one for de-* / fr-* / es-* visitors. Tooltip on the language switcher notes that in-game broadcast messages don't auto-translate.
8 supported locales total: English, German, French, Spanish, Chinese Simplified, Chinese Traditional, Japanese, Korean.
✨ Favicon refresh
Regenerated all four favicon variants (favicon.svg, favicon-16x16.png, favicon-32x32.png, favicon.ico) from kitsune-command-logo-transparent.png so the browser tab matches the panel's sidebar logo. Tooling: tools/regen-favicons.py (Pillow only, single source-of-truth).
(PR #60)
📝 README updates
Features list now reflects Vote Rewards (was missed in v2.6.2's README pass), Graceful Restart, Trader Zone Toggle, and Modpack.
Installation
Server (KitsuneCommand)
Download KitsuneCommand-v2.6.4.zip, extract, drop KitsuneCommand/ into your server's Mods/ directory.
Client (optional — KitsuneTraderUnlock)
Unchanged from v2.6.1. Only needed for V2's locked-down client if you want to interact with trader-area blocks when an admin opens them up via ktrader off.
Operators upgrading from v2.6.2
Drop the new ZIP in place and restart. No migration steps needed beyond letting the new 010_modpack.sql migration apply on first boot. If you were using the systemd timer + shell script stopgap for graceful restarts (set up earlier this cycle), disable it before deploying — KC's GracefulRestart feature now handles that natively and the two would fire at the same time otherwise.
🤖 Release notes generated with Claude Code
v2.6.2 — Vote-for-Rewards integration
What's new since v2.6.1
A meaty release. The marquee feature is Vote-for-Rewards, plus four supporting changes from the same cycle: an in-game /help discoverability command, a panel-screenshot tour in the README, a troubleshooting entry covering the V 2.x EOS migration's downstream effects, and a bundle of UI polish from real-world testing.
🎯 Vote-for-Rewards integration
Self-hosted 7D2D admins routinely list their server on sites like 7daystodie-servers.com. Players who vote bump the listing's ranking, and in exchange the server hands them an in-game reward. KC already had every piece of the reward delivery (Points, VIP Gifts, audit log) — this release is the glue that pulls votes in and dispatches them.
v1 ships with one provider — 7daystodie-servers.com — but the abstraction is built pluggable from day one. New listing sites slot in as a single IVoteSiteProvider class. The shape costs nothing now and saves a real refactor when admins start asking for gtop100 and similar.
Two trigger paths share the same idempotent grant primitive:
- Background sweep — a 60-second timer ticks; per-provider
PollIntervalMinutes(default 5) gates the actual API calls. Fire-and-forget on aTask.Runso the timer thread never blocks on network I/O. Re-entrancy guarded viaInterlocked.CompareExchangeso a stalled API call past 60s doesn't pile up overlapping sweeps. /votechat command — for the "I voted from outside the game" case. Off the game thread, replies viapmonce the round-trip completes. Cooldown gated.
Idempotency is insert-first. A vote_grants table (new in this release) is unique on (provider, steam_id, vote_date). Both the sweep and /vote insert the audit row before granting the reward; if the unique constraint fires (because another sweep beat us to it, or /vote raced the sweep), we skip without granting. Two sweeps running concurrently can't double-grant. A sweep racing a /vote can't double-grant. The listing site's "mark claimed" call is the third step, never the first.
Three reward types are wired:
- Points — credits the player's existing wallet via
IPointsRepository.AdjustPoints. Works. - VIP Gift — clones a pre-built template (admin creates a
vip_giftrow with player_id_template_via the regular VIP Gifts admin tab; the clone happens at grant time, mirroring items + commands junctions). Voter claims with/vipon next login. Works. - CD Key — reserved with a clear
NotImplementedException. The path is in place; the template wiring is the next iteration.
Failure mode handled: if dispatch throws (e.g. an admin typo'd the VIP-gift template name), the audit row is rolled back via IVoteGrantRepository.DeleteByKey so the next sweep can retry once the misconfiguration is fixed. Without this, a typo would silently lock out every voter on that provider — the HasGrantForDate short-circuit would skip every future attempt.
Player ID resolution went through a learning curve. 7D2D V 2.x identifies players via EOS even on Steam, so a player's CrossplatformId is EOS_<32hex> and that's what KC's tables key on. A naive Steam_<76digit> produces orphan rows that don't merge with the player's real account. The dispatch now resolves the actual player_id via LivePlayerManager.GetAllOnline() matched on PlatformId — common case (vote arrives while the player is online or recently was) lands on the correct row. Offline-at-grant-time fall-through is logged with a warning; tightening that path to skip-and-retry-when-online is queued.
Settings UI (admin-only Vote Rewards tab):
- Master enable toggle + per-provider config (API key as password input, server ID, poll interval, reward type, broadcast template with
{player}and{reward}tokens) - Recent grants audit table beside the config in a 2-col layout
- Reward type Select switches the value field below it (points amount / VIP-gift template name / CD-key template id)
- Self-heals from any duplicate-provider state poisoning (a Newtonsoft-append quirk that was caught and fixed mid-cycle)
(PRs #46, #47, #48, #49, #50, #51, #53, #54)
/help chat command
Players can now type /help, /commands, or /? in chat to see every enabled command on the server, grouped by feature (Home, Teleport, Points, Store, VIP, Tickets, Blood Moon, Vote Rewards). Only enabled groups appear — running /help on a server with the store turned off won't tell players to try /buy. The command is gated by the same prefix as the rest (default /, configurable). (PR #44)
Panel screenshots in the README
Eight click-to-zoom panels covering Server Control, Server Update, Config Editor, Mods Manager, Backups, Item Database, Discord Settings, Account Settings — so prospective admins can see what the panel looks like before pulling the mod. Click opens full-size in a new tab. (PRs #42, #43)
Server-browser N/A troubleshooting note
V 2.x moved server-browser discovery from Steam Query to EOS, and a subset of servers' EOS listings render as "N/A" ping with a blank description in the in-game browser even when the server is healthy. Direct-connect still works; it's a presentation-layer issue downstream of EOS, not a server-side bug. New docs/troubleshooting.md entry walks through the symptom-cause-fix and a verification grep on boot logs. (PR #45)
Installation
Server (KitsuneCommand)
Download KitsuneCommand-v2.6.2.zip, extract, drop KitsuneCommand/ into your server's Mods/ directory. Restart the dedicated server.
Vote Rewards setup (if you want to use the marquee feature)
- Get an API key from your server's management page on https://7daystodie-servers.com
- Open the panel → Settings → Vote Rewards
- Master toggle ON, enable the
7daystodie-servers.comprovider, paste API key + your numeric server ID - Pick a reward type (Points or VIP Gift) and configure
- Save. Vote at the listing site to verify — sweep grants within ~5 min, or
/votein-game claims instantly
Operators upgrading from v2.6.1
Drop-in replacement. Migration 009_vote_grants.sql runs on first boot to create the audit table — no-op idempotent if you've already booted a v2.6.2 build.
If you'd been testing pre-release builds (#46 → ~#49) and have polluted VoteRewards settings state with duplicate provider entries, you'll see a one-time line in the boot log: VoteRewards: deduped 1 duplicate provider entry/entries on load. That's normal; the dedup logic self-heals on first boot.
🤖 Release notes generated with Claude Code
v2.6.1 — Discord race fix, WebSocket host-header, SkiaSharp Linux, kcresetpw
What's new since v2.6.0
A patch-release rollup of seven prod-quality fixes and one new admin recovery command. If you were running v2.6.0 behind a Cloudflare Tunnel, on a Linux server, or with the Discord bot enabled — at least one of these is for you.
Discord "Server Online" notification now reliably fires
The notification was racing the bot's own gateway connection on every boot — GameStartDoneEvent fired ~1.3 seconds before the websocket actually finished its handshake, so SendEmbedAsync silently no-op'd. Two-flag tracking (_gameStarted + _botReady) emits exactly when both are true, idempotently across reconnects. (PR #40)
Live WebSocket Live badge actually flips green on prod
WebSocketSharp's server does strict Host-header validation: it rejects any request whose Host doesn't match the authority it was bound to. Cloudflared was forwarding the public hostname (panel.example.com) → 400 from origin → "Finished 0 kB" in the browser. Most of the surface area is documentation: docs/cloudflared-tunnel.example.yml ships a proven config (incl. the gotcha that httpHostHeader must be host:port form, not bare host), and docs/troubleshooting.md indexes this and six other prod-only failure modes in symptom → cause → fix shape. The WebSocket endpoint also moved from /ws to /kctunnel along the way; harmless but visible if you're poking at headers. (PRs #38, #39)
Map view renders on Linux
SkiaSharp's LibraryLoader does [DllImport("dl")], and 7D2D's bundled Mono config has no dllmap for the short dl name. KC now ships SkiaSharp.dll.config next to the DLL with <dllmap dll="dl" target="libdl.so.2" os="!windows" />, mirroring the existing System.Data.SQLite.dll.config. Bonus: build scripts now copy libSkiaSharp.so to x64/ (where SkiaSharp's GetLibraryPath actually looks) instead of linux-x64/. (PR #34)
kcresetpw console command — admin password recovery
New ConsoleCmdAbstract for the "I'm locked out of the panel" case. Run kcresetpw <username> <newpassword> from the 7D2D game console, telnet (nc localhost 8081), or KC's own Console view. Min 8 chars, admin-only (DefaultPermissionLevel = 0), hashes via the existing PasswordHasher and writes via IUserAccountRepository.UpdatePassword. The plaintext is not logged. (PR #37)
Settings → Account "Change Password" actually saves
Was calling a generic Update that didn't persist password_hash, so the new password appeared to save but never wrote. Fixed by calling IUserAccountRepository.UpdatePassword directly. (PR #36)
Login + sidebar polish
- Logo above the KitsuneCommand brand mark in the sidebar (centered, 72×72)
- Logo on the login page (110×110, above the title)
- Password input now stretches the full form width
- Language selector moved from absolute-top-right into the login footer (next to "MANAGEMENT")
- Fox-head favicon (orange brand silhouette, SVG-first with PNG/ICO/apple-touch-icon fallbacks generated from the same source)
Installation
Server (KitsuneCommand)
Download KitsuneCommand-v2.6.1.zip, extract, drop KitsuneCommand/ into your server's Mods/ directory.
Client (optional — KitsuneTraderUnlock)
Unchanged from v2.6.0 (same v1.0.0, bit-identical asset). Only needed for V2's locked-down client if you want to interact with trader-area blocks when an admin opens them up via ktrader off. No-ops on dedicated servers.
Operators upgrading from v2.6.0
Most of you can drop the new ZIP in place and restart. Two flags to check if you're behind a Cloudflare Tunnel:
- Your tunnel ingress for the WS endpoint should target
localhost:8002(or whatever yourWebSocketPortresolves to) withoriginRequest.httpHostHeader: "localhost:<port>". Barelocalhostwon't work — must behost:portform. - The
/wspath is now/kctunnel. Update your tunnel rule (or the dashboard public hostname) to match. The frontend bundles the new path automatically.
Both gotchas are documented in docs/troubleshooting.md and the example tunnel config at docs/cloudflared-tunnel.example.yml.
🤖 Release notes generated with Claude Code
v2.6.0 — Smart Mod Upload + Config Editor Polish
What's new since v2.5.0
Smart mod upload
The Mods Manager now handles real-world zip shapes that previously broke metadata detection:
- Walks the extracted tree recursively (case-insensitive) for
ModInfo.xml— nested structures, wrapper folders, mod packs all detected correctly - Fixes Windows-zipped backslash paths — some Nexus packagers write
ModName\ModInfo.xmlinstead ofModName/ModInfo.xml; we now normalize both separators so Linux extraction actually unfolds into a tree - Supports mod packs (multiple
ModInfo.xmlin one zip → multiple installs) - Strips Nexus-style
-<modId>-<version>-<timestamp>suffixes from folder names - Auto-generates a minimal
ModInfo.xmlfor zips that ship without one so 7D2D actually loads the mod - Success + warning toasts on upload surface exactly what happened
Config Editor polish
- Rewritten help text for every field — warm, specific, vanilla-reference numbers ("100% is vanilla", "125% is a gentle boost")
- Day/Night Cycle visual widget with slider + warm/cool split bar + compact day/night minute spinners
- Group reshape: World / Player / Gameplay / Block Damage — Gameplay and Player split apart, Block Damage demoted to 4th
- Humanized labels via
formatFieldLabelfallback (camelCase → human)
Discord restart-required banner
First-time Discord bot setup silently needed a server restart to take effect. Now a yellow banner appears after save with an inline "Restart now" button.
Reverse-proxy URL compat
KC now works when served through HTTPS proxies (Cloudflare Tunnel, Caddy, nginx). Login fetch uses a relative path; WebSocket connects on same origin unless the page is at :8890 directly.
Dynamic version display
ModInfo.xml is now the single source of truth for the mod version. Read once at startup into ModEntry.ModVersion, exposed via /api/server/info, consumed by the sidebar badge and the Dashboard info row. Bumping the version = editing one line.
Dashboard layout fix
The KitsuneCommand row is back on the Dashboard info card (middle slot, not last), so Local IP and Public IP align on the final row again.
Installation
Server (KitsuneCommand)
Download KitsuneCommand-v2.6.0.zip, extract, drop KitsuneCommand/ into your server's Mods/ directory.
Client (optional — KitsuneTraderUnlock)
Download KitsuneTraderUnlock-v1.0.0.zip, extract, drop into your client's Mods/ directory. Only needed for V2's locked-down client if you want to interact with trader-area blocks when an admin opens them up. No-ops on dedicated servers.
🤖 Release notes generated with Claude Code