Releases: fran-olivares/usulnet
v26.5.2 — privacy and reliability for self-hosted Docker
usulnet v26.5.2 lands the 14-session development plan that earns the
"ciberseguridad + privacidad + self-host" tagline beyond container management.
Zero breaking changes against v26.5.1; no new external port, no new bind
mount, no new container capability, no call-home.
Highlights
Reliability gates
- Smoke E2E in CI boots the actual compose stack against a freshly built
image, logs in as admin, walks the sidebar, fails the build on any 5xx. - govulncheck with empirically-pinned allowlist — every allowlist entry
is enforced by a CI script that fails the build if a future commit silently
invalidates its "not exploitable" justification. - Coverage threshold bumped 15% → 16% with new tests on the panic-prone
routers and the recon wiring. - Tag-driven release workflow publishes multi-arch images
(linux/amd64 + linux/arm64) to GHCR and Docker Hub on everyv*tag.
Privacy and security tier
- Shodan recon connector (BYO key) joins HIBP as the second external
integration. Full-cycle secrecy test pins that the key never leaks to logs
or errors. - L7 egress filter — in-process forward proxy with per-host allow/deny
policies, default-deny, audit log of denials. No TLS interception. - YARA scanner — one-shot scans against host files and container paths
via the recon-toolkit sandbox. Ships thelinux-elf-suspiciousruleset. - Container forensics snapshot — one-click memory dump, process tree,
open FDs, network connections, packaged as a verifiable tarball. - Marketplace honeypots — Cowrie (SSH/Telnet), Dionaea (multi-protocol
malware-catcher), Endlessh (SSH tarpit), one-click deployable. - Tor SOCKS5 proxy marketplace app for routing individual workloads
through Tor.
Operator UX
- Host-side
usulnetCLI (contexts,login,containers ls,
stack deploy,recon scan, ...) shipped as a static binary alongside
the server. - Sidebar regrouped from 9 to 7 sections (Compute / Operations /
Security / Privacy / Platform / Admin / Help). - First-run onboarding wizard for password change + host attach.
- Uniform empty-states across 12 modules — the bare "NPM Not Connected"
card from v26.5.1 is gone. - a11y landmarks on header, sidebar, modal, and flash regions.
- Shell tab-completion install script and Makefile target, baked into
both production Docker images.
Recon sandbox
- recon-toolkit rebased on Arch (
mat2,exiftool,yara,
holehe,h8mail,oletools,pdfid). Weekly rebuild via cron so the
toolset stays current. amd64-only.
Performance pass
- Route-scoped frontend gzip (~70% off vendor JS/CSS bundles); the
authenticated route group stays uncompressed to close the BREACH-class
risk on CSRF-bearing pages. - Host summary fan-out across a goroutine pool capped at 16:
max(T)latency instead ofN × Tfor N-host installs. - Container reconciliation fan-out across a goroutine pool capped at 8.
- Shared WebSocket JSON encoder pool — ~36% less wall time, ~99% less
garbage per message on the editor and SSH terminal hot paths.
Upgrade
docker pull usulnet/usulnet:v26.5.2
docker pull usulnet/usulnet-agent:v26.5.2
# or via GHCR
docker pull ghcr.io/fran-olivares/usulnet:v26.5.2
docker pull ghcr.io/fran-olivares/usulnet-agent:v26.5.2
**Full Changelog**: https://github.com/fran-olivares/usulnet/compare/v26.5.1...v26.5.2v26.5.1 — 2026-05-15
v26.5.1 — 2026-05-15 — 11 ported modules, one AGPL build, no biz gating
One AGPL build, all features.
v26.5.1 finishes what v26.5.0 started: every module previously gated behind
the Business edition now ships in the standard self-hosted binary. Eleven
ported modules, zero edition checks, zero call-home, zero runtime caps.
Highlights
- Firewall, crontab, backup verification, automated rollback,
ssl observatory, docker engine config, wireguard, image builder,
dns providers, calendar, marketplace — all in the AGPL build, no
biz gating, no license-tier features removed at runtime. - Proxy extended — access lists, dead hosts, locations, redirections,
and streams now live in usulnet's PostgreSQL as authoritative state and
apply to either nginx or Caddy with an explicit feature-support matrix. - Edition cleanup —
isEditionAvailable/navItemLocked/
requireFeature/RequirePaid/RequireEnterprise/RequireLimit
are gone.license.CELimits()→license.OpenLimits()(all unlimited).
Commercial JWTs still parse for the support-tier display tag. - Bootstrap restructure — the 2,700-line
startStandalonesplit into
phasedinit_*.gofiles;scripts/verify-migrations.shstrengthened. - Opt-in local-services TLS —
USULNET_TLS_LOCAL_SERVICES=truewires
self-signed ECDSA P-256 certs onto in-cluster Postgres / Redis / NATS.
Defaults unchanged. - Security hardening — dependency bumps for govulncheck (Go 1.25.7
→ 1.25.10, pgx, NATS, go-redis, go-chi, jwt, go-oidc, go-ldap, docker);
CI workflow now posts the report as a sticky PR comment.
Upgrade
Migration-additive only. Pull and restart:
docker compose pull
docker compose up -d
11 new migrations (046–056) join the schema. The v26.5.0 recon_* tables
(044/045) are untouched. No application config key removed; no API
endpoint removed; no permission key removed.
Rollback
Drop the new migrations in reverse order before downgrading:
usulnet migrate down 11
Per-step SQL listed in
docs/v26.5/release-notes-v26.5.1.md.
Capability requirements
| Module | Needed |
|---|---|
| firewall | NET_ADMIN on the agent process; ufw/nft/iptables on host |
| wireguard | Wireguard kernel module + wg/wg-quick on host; NET_ADMIN; USULNET_ENCRYPTION_KEY |
| docker engine config | /etc/docker bind-mounted as :rw into the usulnet container |
| image builder | Docker socket (already required by the platform) |
| dns providers | Egress to the provider's API + USULNET_ENCRYPTION_KEY |
All other modules (crontab, ssl-observatory, backup verification,
rollback, calendar, marketplace, proxy-extended) need nothing beyond the
default compose stack.
No breaking changes
No removed API endpoint, config key, CLI subcommand, or permission. No
destructive schema change.
Full notes
- CHANGELOG:
CHANGELOG.md→[v26.5.1] — 2026-05-15 - Release notes:
docs/v26.5/release-notes-v26.5.1.md - Security review:
docs/v26.5/security-review-v26.5.1.md - Ported modules status:
docs/v26.5/v26.5.1-ported-modules.md
AGPL-3.0-or-later. Self-hosted use is free in perpetuity.
v26.5.0 — 2026-05-13
[v26.5.0] — 2026-05-13
Open and unlimited
Added:
- One AGPL build, all features. v26.5.0 makes it explicit: the
recon and metadata modules, like every previous feature in this
repository, are part of the standard self-hosted install. There is
noenterprisefork, no closed-source extension point, and no
feature gated by a paid license tier. What lives in
github.com/fr4nsys/usulnet
is the entire product surface — Docker control plane, multi-node
orchestration, security scanning, backups, reverse proxy
automation, monitoring, alerting, OSINT recon, metadata hygiene. - No runtime caps. The binary does not enforce node-count,
container-count, user-count, scan-count, or retention-window
quotas. It does not check a license at start-up to decide what to
expose. - No call-home. The binary issues no outbound request to any
usulnet-controlled endpoint at runtime, at start-up, or during the
optional commercial-license check (which is purely local
cryptographic verification of a self-contained token). - AGPL-3.0-or-later covers the entire tree. Including the
SpiderFoot integration, the metadata-hygiene toolkit container,
the gofpdf report generator, and the recon retention worker. The
commercial licenses documented in
docs/licensing.md(Business, Enterprise)
grant additional rights to the same code base — redistribution
outside AGPL, formal support contracts, custom IdP integrations —
and do not unlock additional runtime features. - Reproducible build, no proprietary toolchain. The full build
chain ismake buildagainst the sources in this repository: Go
1.25.7, the Templ generator, and the Tailwind standalone CLI. No
vendored binary blobs, no closed-source code generators, no
privileged registry access required.
Full Changelog: v26.2.7...v26.5.0
v26.2.7
usulnet v26.2.7 — 2026-02-23
Fixed
-
Issue #16 — Container file editing crash:
WriteContainerFilepassed entire base64-encoded content as a single shell argument viaecho '…' | base64 -d > file, hitting shell argument length limits on files larger than ~100 KB. Replaced with Docker'sCopyToContainertar-based API (no shell limits). Falls back to chunked 48 KB shell writes for remote agent-proxied hosts. Also added a 10 MB request body limit in the handler. -
Host metrics cleanup error:
DeleteOldMetricsquery referenced non-existent columncreated_at— changed torecorded_at. -
Container stats cleanup error:
DeleteOldStatsquery referenced non-existent columncollected_at— changed torecorded_at. -
System backup "unsupported type": Scheduled automatic database backup job used
BackupTypeSystemwhich had no handler. AddedcreateSystemBackupthat finds the database container and backs up its volumes. -
/quotasendpoint 500: Docker API calls for container stats had no timeout, causing the page to hang/crash when Docker was slow. Added 8-second overall timeout and 2-second per-container stats timeout. -
Host File Browser user switch:
su - {user} -cfailed silently for system users with/usr/sbin/nologinshell (www-data, nginx, nobody). Changed tosu -s /bin/shto force a usable shell. -
File Browser showing only symlinks (host + non-BusyBox containers): Both
parseHostLSandparseContainerLSrequiredlen(fields) < 8to accept anls -laline, but with the ISO time format (--time-style=+%Y-%m-%dT%H:%M:%S) regular entries have only 7 fields — so every directory, file, and device node was silently dropped. Only symlinks (9+ fields due to-> target) passed the filter, causing the host and Debian/GNU-based container file browsers to show only a handful of symlink entries. Changed threshold tolen(fields) < 7. BusyBox containers were unaffected because BusyBoxlsdoesn't support--time-styleand falls back to the traditional 9-field format. -
File Browser symlinks to directories: Clicking a symlink pointing to a directory (e.g.
/bin → /usr/bin) tried to open it as a file instead of navigating into it. Root cause:ls -lamode prefix islnotdfor symlinks, sois_dirwas always false. Fixed by adding symlink type resolution — host browser usesstat -L -c '%F %n'(GNU coreutils), container browser uses POSIXtest -d(works in BusyBox/Alpine). Also replacedstat -L -c '%F'withtest -dinReadContainerFilefor BusyBox compatibility, and directory errors now return 400 instead of 500. -
File browser freezes browser tab when opening files: Clicking any file in the host or container file browser froze the entire browser tab for 16-68+ seconds. Root cause:
monaco.editor.create()(3.7 MBeditor.main.js) blocks the main thread during initialization, and was called for every file open — even just to view. Monaco is also fundamentally incompatible with the file browser's modal context (flex layout +backdrop-blur+x-showtransition) —automaticLayout: truecauses a ResizeObserver layout-thrashing loop, andautomaticLayout: falsestill freezes because Monaco's DOM creation (~3000+ nodes) triggers a reflow cascade against the flex+blur compositing layers. Fixed by removing Monaco Editor entirely from both file browsers. File viewing now uses a native<pre><code>block (instant rendering, zero JS). File editing in the container file browser uses a styled<textarea>with monospace font, dark theme, and Ctrl+S/Cmd+S save shortcut. The full Monaco editing experience remains available on the dedicated/editorpage, which uses a static full-page layout without modal/flex conflicts. -
File editing broken in container file browser: Clicking "Edit" on a file opened a read-only editor due to a race condition:
editFile()calledviewFile()which reseteditingFile=false, theninitMonaco(false)created a read-only editor, and the.then()callback that seteditingFile=trueran too late. RefactoredviewFile()to accept aneditparameter andeditFile()to callviewFile(file, true), ensuring the edit state is set correctly from the start. -
Alpine.js sidebar errors on every page load: The sidebar section template used
try { ... } catch(e) {}in Alpine.jsx-initexpressions, but Alpine 3.x doesn't supporttry/catchin inline expressions (throwsUncaught SyntaxError). Also used thex-collapsedirective without the Collapse plugin installed. Movedtry/catchlogic into a global_sidebarSectionOpen()helper function and replacedx-collapsewith the built-inx-transition. -
DNS Service Discovery startup failure: Creating the
containers.localzone for DNS service discovery failed withNOT NULL constraint violation on "forwarders". TheForwardersfield was Go'snilslice, which PostgreSQL receives asNULL. Initialized to empty slice[]string{}. -
Agent Node Metrics Showing 0s and Docker Version "dev" (critical — agent nodes appear empty): The gateway's
handleHeartbeat()discarded theQuickStatspayload (CPU%, memory, container counts) from every agent heartbeat, andhandleInventory()never persistedSystemInfo(Docker version, CPUs, memory) to the hosts table. Agent nodes always showed 0 running containers, 0 images, 0 CPUs, 0 memory, and Docker version "dev" in the UI. Fixed by storing heartbeat metrics to thehost_metricstable and inventory system info to thehoststable.ListSummaries()now enriches agent host data from the latest DB metrics instead of only from the local client pool. -
Terminal on Agent Node Opens Master's Shell Instead (critical — security issue): The host terminal WebSocket handler (
WSHostExec) usednsentervia PID 1 to enter the host namespace, but never checked whether the requested host was local or remote. Navigating to/nodes/{agent-id}and opening a terminal would silently give a shell on the master node, not the agent. Added endpoint type validation in both the page handler and WebSocket handler to reject non-local hosts with a clear error message. Terminal and Files buttons are now hidden in the UI for remote/agent nodes. -
Log Streaming "Not Available for Remote Hosts":
WSContainerLogsandWSContainerStatsperformed a type assertion to*docker.Client, which fails forAgentProxyClient(agent hosts connected via NATS). Any attempt to view logs or stats for a container on an agent node returned an error. Replaced with a dual-path approach: direct streaming for local hosts (existing behavior), and buffered polling fallback for agent hosts — logs poll viaContainerLogsSinceevery 5 seconds, stats poll viaContainerStatsOnceevery 3 seconds. -
Stack Deployment on Agent Node Appears Successful but Never Installs (critical — silent failure): The stack service's
execCompose()randocker composelocally with aDOCKER_HOSTenv var pointing to the remote host's endpoint URL. Agent hosts have no TCP endpoint (they connect via NATS), so the compose command silently failed while the UI showed the stack as deployed. AddedhandleStackDeployandhandleStackRemovecommand handlers to the agent executor, addedGatewayCommandSenderinterface to the stack service, and theDeploy()method now detects agent hosts and routes deployments through the NATS gateway. The agent writes compose files to/data/stacks/{name}/and runsdocker compose up -dlocally. Gateway sender wired at application startup. -
Security Scanning Fails for Agent Node Containers:
securityAdapter.Scan()calledContainerInspectRaw()which returnsErrNotSupportedRemoteonAgentProxyClient. The agent executor already supportedCmdSecurityScan(collecting inspect data and sending it to the master for analysis), but it was never triggered from the web UI path. AddedisAgentHost()detection andscanViaGateway()routing to the security adapter. Agent scans now: agent collects container inspect data → sends via NATS → master runs all security analyzers + Trivy on the received data → persists results. Both single-container and scan-all operations work for agent nodes.
Changed
- guacd updated to 1.6.0: Apache Guacamole daemon updated from 1.5.5 to 1.6.0 in all Docker Compose files, documentation, and public website.
Added
-
WireGuard VPN: Native WireGuard VPN management with auto-generated Curve25519 keys, multi-interface support, peer management with QR config generation, transfer statistics, and persistent keepalive. Features include:
- Create and manage multiple WireGuard interfaces per host
- Add peers with auto-generated key pairs and preshared keys
- Client configuration generation for easy setup
- Transfer statistics tracking (rx/tx) per interface and peer
- Post-up/post-down script support for routing
- Database: migration
053_wireguard_vpn - Backend:
internal/services/wireguard/,internal/repository/postgres/wireguard.go
-
Container Image Builder: Build Docker images from Dockerfiles in the UI with multi-stage build support, build arguments, platform targeting, and reusable Dockerfile templates.
-
Automated Rollback: Automatic stack rollback on deploy failure or health check failure, with configurable rollback policies, retry limits, cooldown periods, and full execution history.
-
Firewall Manager: Visual iptables/nftables management directly from the web UI — no CLI commands needed. Create, edit, delete, apply, and sync firewall rules across hosts. Features include:
- Chain Support: INPUT, OUTPUT, FORWARD, and DOCKER-USER chains.
- Protocol & Action Coverage: TCP, UDP, ICMP, and all protocols with ACCEPT, DROP, REJECT, and LOG actions.
- Full Audit Log: Every rule change is recorded with user, action, timestamp, and rule details for compliance and troubleshooti...
v26.2.6
usulnet v26.2.6 — 2026-02-21
Fixed
-
Container Updates Always Fail at Backup Step (critical — updates completely broken): The update-to-backup adapter in
update_adapters.gousedBackupTypeVolumeinstead ofBackupTypeContainer, causing the backup creator to treat the container ID as a Docker volume name. Every update attempt failed withVOLUME_NOT_FOUNDand was aborted byfailUpdate(). Fixed the backup type toBackupTypeContainerso the correctcreateContainerBackup()path is used. Also addedContainerNamepropagation so backup records show human-readable names instead of hex container IDs, and made pre-update backup failure non-fatal so containers without volumes (e.g., guacd) can still be updated. -
XSS Vulnerabilities in Dashboard Partial Handlers (security):
SearchPartialTempl,ContainersPartialTempl,EventsPartialTempl, andNotificationsPartialTemplconcatenated user-controlled values (search queries, container names, event messages) directly into HTML without escaping. Addedhtml.EscapeString()to all dynamic values andurl.QueryEscape()for URL parameters. Refactored search partial to usestrings.Builderinstead of string concatenation. -
Silent Error Suppression in Dashboard and Updates Handlers:
DashboardTemplandUpdatesTempldiscarded all service errors with blank identifiers (_), rendering empty data without any indication of failure. Added proper error logging viaslog.Error()so operators can diagnose service failures.NotificationsPartialTemplconflated error state with empty state — now shows distinct "Failed to load" message when the alert service returns an error, and "No new notifications" only when the list is genuinely empty. -
Drift Detection Missing "Set Baseline" Action: Backend
drift.Service.SetBaseline()existed but was not exposed in the frontend. AddedDriftSetBaselineAPIhandler, registeredPOST /drift/{id}/set-baselineroute, and added a baseline button (bookmark icon) to each open drift row. Clicking it sets the current snapshot as the new baseline, completing the drift management workflow (accept, remediate, or set baseline). -
HTTP Status Code Violations in Container Settings:
ContainerSettingsUpdatePOST handler used302 Foundfor all redirects instead of303 See Other(correct for POST-to-GET redirects per HTTP spec).ContainerSettingsSummaryreturned200 OKwhen a container was not found instead of404 Not Found. Fixed all redirect status codes and error responses. -
Misleading "Stubs" Comment in Profile Handler:
handler_profile.gocontained an outdated comment block (Helper Methods (stubs — implement with your Handler struct fields)) describing fields that already existed and methods that were fully implemented. Removed the misleading documentation. -
Profile Page Always Redirects to Login (critical — page completely broken):
handler_profile.gousedGetUserInfo(ctx)which looks up auser_infocontext key that was never populated by any middleware. The auth middleware stores the authenticated user under theusercontext key viaGetUserFromContext(ctx). SinceGetUserInfoalways returned nil, the profile handler always redirected to/login— even for authenticated users. Fixed all 10 occurrences to useGetUserFromContext(ctx). Also added a clickable link to/profilein the sidebar user section (avatar + username) so users can actually navigate there without manually typing the URL. -
Stack Deploys Break When Browser Window Closes (critical — broken deploys):
stackDeploySSEusedr.Context()for theDeployStreamgoroutine, so closing the browser tab or navigating away canceled the context and killeddocker-composemid-deploy, leaving containers in an inconsistent state. Switched tocontext.Background()with active host propagation. The deploy goroutine now drains the log channel silently if the client disconnects, ensuring the deploy completes regardless of browser state. Non-streaming fallback path also converted to background execution. -
Image Pull, Security Scan, and Batch Update Canceled by Browser Navigation (critical — broken operations):
ImagePullSubmit,SecurityScan,SecurityScanContainer, andUpdateBatchall usedr.Context()synchronously, meaning any browser navigation or tab close during these long-running operations would cancel them mid-execution. Converted all four handlers to thecontext.Background()+ goroutine pattern already established inBackupCreate. Operations now redirect immediately with a progress message and continue in the background. -
SSE Parser Bug in Stack Deploy Template:
stacks/new.templused literal multiline newlines inside single-quoted JavaScript strings for the SSE parser (buf.split('...')), which is invalid JavaScript syntax. Fixed to use proper\n\nand\nescape sequences, matching the working implementation incatalog_deploy.templ. -
Silent Failures on fetch() Calls in Connection and Changes Pages:
shortcuts.templ(delete shortcut),ldap.templ(toggle write mode),database.templ(toggle write mode), andchanges.templ(load diff) all hadfetch()calls without.catch()error handlers. Network errors or server failures were silently ignored. Added.catch()handlers with user-visible toast notifications viawindow.usulnet.toast(). -
Stack Catalog Deploys Killed by Browser Navigation (critical — broken catalog deploys):
StackCatalogDeploySubmitusedr.Context()for both SSE streaming and synchronous deploy paths. The logs showsignal: killedandcontext cancelederrors when users navigate away during catalog app deployment (e.g., deploying Vaultwarden). Applied the same background context + client disconnect handling pattern asStackDeploy. -
Container Updates Run Synchronously for 60+ Seconds (critical — updates fragile):
UpdateApplyTempl,UpdateManual, andUpdateRollbackTemplall ranupdatesSvc.Apply()synchronously usingr.Context(), blocking the HTTP handler for 60-70 seconds per update. Any browser timeout, navigation, or tab close would cancel the update mid-execution, potentially leaving containers in an inconsistent state. All three handlers converted to background goroutines withcontext.Background(). -
Vulnerability Scan Blocks for 77+ Seconds:
VulnScanhandler ransecSvc.ScanAll()plus CVE import synchronously usingr.Context(). Converted to background goroutine — the scan and CVE import now complete independently of browser state. -
Stale Template Codegen for Nvim Editor:
nvim_templ.gowas missingSnippetIDandContentfields that existed in the.templsource, causing compilation errors. Regenerated withtempl generate.
Added
-
Docker Daemon Configuration Management: New
/config/dockerpage for managing Docker engine settings (daemon.json) directly from the web UI — no CLI commands needed. Features include:- 6 Category Tabs: Network (address pools, bridge IP, CIDR, DNS, MTU, ICC, IPv6), Logging (driver, level, format, options), Registry (mirrors, insecure registries), Runtime (default runtime, storage driver, data root, cgroup namespace, live restore, iptables), Proxy (HTTP/HTTPS proxy, no-proxy), and Security (ulimits, no-new-privileges, SELinux, seccomp, userns-remap, auth plugins).
- General Settings: Debug, labels, shutdown timeout, concurrent downloads/uploads, experimental, metrics address — always visible above tabs.
- Risk Color Coding: Each setting displays a risk badge — green (Safe), yellow (Moderate), or red (Dangerous with confirmation dialog) — so operators understand the impact before changing.
- Apply Mode Indicators: Blue "Live Reload" badge for settings that take effect via
SIGHUP, orange "Restart Required" badge for settings that need a full Docker restart. - Automatic Backup: Every change creates a timestamped backup of
daemon.jsonin/etc/docker/backups/before writing. Backups tab lists all snapshots with one-click restore. - Raw JSON View: Read-only tab showing the current
daemon.jsoncontent for reference. - Reload & Restart Controls: "Reload Config" button sends
SIGHUPto dockerd for live-reloadable changes. "Apply & Restart" button with confirmation modal warns about container interruption and potential self-kill if running inside Docker. - Validation: CIDR, IP address, absolute path, log driver, storage driver, proxy URL, and ulimit validation before applying — prevents invalid
daemon.jsonthat would stop Docker from starting. - Unknown Field Preservation: Custom JSON marshaling preserves any
daemon.jsonfields not modeled in the UI, ensuring no settings are silently dropped. - 50+ configurable daemon settings across all categories. Daemon info summary cards show version, storage driver, log driver, runtime, CPUs, memory, OS, and architecture.
-
Refresh Button & Auto-Refresh for Docker Resource Pages: Added a refresh button (with spinning animation) and auto-refresh toggle (30-second interval) to Containers, Images, Networks, Volumes, Stacks, and Updates pages. Auto-refresh only fires when the browser tab is visible. Users can toggle auto-refresh on/off via a small indicator dot next to the refresh button. Implemented as a reusable
RefreshButtontempl component. -
Complete Swarm Cluster Management: Full professional Swarm management from the UI, eliminating the need for CLI commands. New features include:
- Node Management: Promote workers to managers, demote managers to workers, drain nodes for maintenance, pause/activate nodes — all from the cluster page with one-click actions.
- Service Detail Page: Dedicated page for each service showing overview cards (image, mode, replicas, tasks), scale controls, image update form, published ports, task list with per-task state/node/error, live service log...
v26.2.5
usulnet v26.2.5 — 2026-02-21
Fixed
-
Encryption Key Lost on Every Restart (critical — data loss):
config.go:Validate()auto-generated a new random encryption key each time the application started without an explicitUSULNET_ENCRYPTION_KEY. This meant all previously encrypted data — SSH credentials, TOTP secrets, NPM registry tokens, and config variables — became permanently unreadable after every restart. Removed the random key generation; the application now deterministically derives the encryption key from the JWT secret via SHA-256, producing a stable key that survives restarts as long as the JWT secret remains unchanged. Reported by community in #14. -
SSH "Not Configured" Error Page Unhelpful: The SSH service error page displayed a vague message (
Enable SSH by setting the encryption key in your configuration) with no actionable guidance. Replaced with a structured HTML card explaining that the key auto-derives from the JWT secret, plus numbered setup steps: generate a key withopenssl rand -hex 32, setUSULNET_ENCRYPTION_KEY, or configuresecurity.config_encryption_keyin config.yaml. -
Encryption Failure Logged as Warning Instead of Error: When the AES encryptor failed to initialize (disabling SSH, TOTP, NPM, and Config services), the application only logged a
Warn-level message with no diagnostic context. Elevated toErrorlevel with structured fields (key_length,hint) so operators can immediately identify and fix misconfigured encryption keys. -
NATS Port Not Exposed for Multi-Node Deployments: NATS port 4222 was only reachable inside the
usulnet-backendinternal Docker network, preventing remote agents on other hosts from connecting to the master. Exposed port 4222 to the host in all production Docker Compose files (docker-compose.yml,deploy/docker-compose.prod.yml). Configurable viaNATS_PORTenvironment variable. Install script updated to display NATS connection URL.
Full Changelog: v26.2.4...v26.2.5
v26.2.4
usulnet v26.2.4 — 2026-02-21
Added
-
Redis TLS Encryption by Default: All Redis connections now use TLS with ECDSA P-256 self-signed certificates. The Redis container generates certificates at startup (or uses custom certs mounted at
/certs-src/). Connection URL scheme changed fromredis://torediss://. New PKI methodEnsureRedisServerCert()generates and signs Redis server certificates via the internal CA. Full mTLS support available viatls_cert_fileandtls_key_fileconfig options. -
Container Registry Browser: Full UI for browsing private and public container registries. List repositories, explore tags with digest, platform, and size details, and inspect image manifests (config, layers, OS/architecture). Supports Docker Hub, GitHub Container Registry, and any OCI-compliant private registry. New
RegistryServicewith authentication caching,GET /registries/{id}/browse,/{id}/tags/{repo}, and/{id}/manifest/{repo}/{ref}routes. Three new template pages: browse, tags, manifest. -
Drift Detection: Automatically detects configuration drift between the desired and actual state of running containers. Compares expected image tag, environment variables, volume mounts, port bindings, and resource limits against what is actually running. Results presented in a dedicated Drift page with per-container diff view. Backed by
DriftService, migration040_drift_detection, andDriftDetection/DriftDiffmodels. -
Change Events Feed: Chronological audit trail of infrastructure changes — container start/stop/restart, image pulls, stack deploys, configuration edits, and user actions — displayed in a filterable timeline. Backed by
ChangesService, migration039_change_events, andChangeEvent/ChangeEventStatsmodels. Accessible at/changes. -
Resource Cost Optimization: Analyzes container CPU and memory usage patterns and surfaces rightsizing recommendations (over-provisioned and under-provisioned containers). Calculates estimated cost impact based on resource waste. New
CostOptService, migration041_resource_optimization,ResourceRecommendation/ResourceUsageSamplemodels, and/costoptpage. -
Session Recording & Replay: Terminal sessions (SSH, host exec, container exec) can now be recorded and replayed in-browser with full timing accuracy. Recordings are stored server-side. New
RecordingService, migration042_session_recording, session replay routes at/session-replay/{id}and/session-replay/{id}/data. -
Calendar: Integrated operations calendar for scheduling maintenance windows, tracking SLA deadlines, and coordinating team tasks. Supports events, tasks with checklists, and notes. New
CalendarService, migration044_calendar,CalendarEvent/CalendarTaskmodels, and a full calendar grid UI at/calendar. -
Scheduled Jobs Management UI: Visual management page for all recurring background jobs (backup, retention, metrics collection, security scans, drift checks, SLA evaluation). View status, next run time, last result, and manually trigger jobs. Accessible at
/jobs/scheduled. -
Developer Tools Suite: 15 built-in browser-based developer utilities, accessible at
/toolsand individually:- Encoders: Base64 encode/decode, URL encode/decode, HTML entities, hex
- Formatters: JSON pretty-print/minify, YAML↔JSON conversion
- Generators: UUID v4/v7, random strings, secure passwords, Lorem Ipsum
- Hash: MD5, SHA-1, SHA-256, SHA-512, bcrypt calculator
- Network: CIDR/subnet calculator, IP geolocation lookup, DNS resolver
- Regex: Live regex tester with match highlighting and group extraction
- Text Diff: Side-by-side and unified diff viewer for text comparison
- JWT: JWT decoder with header/payload/claims display and expiry validation
- Crypto: RSA/ECDSA key pair generation, certificate info viewer
- Token: API key generator, TOTP secret generator
- All tools run client-side with no server round-trips
-
Enterprise Feature Previews: Eight preview pages for upcoming Enterprise tier features — OPA Policy Engine, Compliance Frameworks (SOC2/ISO27001/CIS), Image Signing & Verification, Runtime Security (eBPF-based), Custom Dashboards, Ephemeral Environments, Manifest Builder, and Git Sync — each with feature descriptions and early-access prompts.
-
About Page: Application version, build info, license edition, active feature flags, and runtime statistics accessible at
/about. -
App Catalog: 14 New One-Click Deploy Apps: The stack catalog grows from 6 to 20 apps, now organized into 7 categories. New additions:
- Storage: Nextcloud (file sync, calendar, contacts, 300+ apps)
- Networking: Traefik v3 (Docker service discovery, Let's Encrypt), WireGuard Easy (VPN server with web UI)
- Communication: Mattermost (Slack-compatible team messaging)
- Security (new category): Passbolt (E2E-encrypted team password manager, LDAP sync), Vaultwarden (Bitwarden-compatible, works with all Bitwarden clients), Authentik (enterprise SSO with OAuth2, SAML, LDAP proxy, SCIM)
- Monitoring (new category): Uptime Kuma (uptime monitoring with status pages, 90+ notification channels), Grafana + Prometheus (metrics stack with embedded
prometheus.ymlvia Docker Compose configs, includes node-exporter) - Development: Woodpecker CI (pipeline-as-code CI/CD with native Gitea OAuth2 integration)
- Database (new category): PostgreSQL 16 + pgAdmin 4 (shared database server with web management UI)
-
Dashboard Service: New
DashboardServiceaggregates container stats, host health, recent events, storage usage, and security scan summaries into a single optimized query for the main dashboard. Replaces multiple individual queries. -
Nginx Reverse Proxy Backend: New
services/proxy/nginx/sub-package adds a complete Nginx proxy backend alongside the existing Caddy adapter. Includes ACME certificate management, upstream builder, and API client. The proxy service abstraction layer is refactored withbackend.goandcaddy_backend.gointerfaces. -
Compliance PDF Report Generator: The compliance service gains
evaluator.go(CIS Docker Benchmark scoring engine) andpdf_report.go(generates downloadable audit-ready PDF reports with findings, risk ratings, and remediation steps). -
Runbook Approvals: Multi-step runbook execution now supports approval gates. Designated approvers must sign off before the next runbook step executes. New
RunbookApprovalmodel, migration043_runbook_approvals, andrunbook_execute.goscheduler worker. -
Automatic Deployment Worker: New
auto_deploy.goscheduler worker continuously watches for GitOps sync triggers and stack update webhooks, automatically redeploying stacks when new image tags are pushed or repository changes are detected. -
SLA Breach Worker: New
sla_breach.goworker evaluates SLA thresholds against live metrics on each scheduler tick, firing notifications and creating calendar events when breach conditions are detected. -
Webhook Dispatch Worker: New
webhook_dispatch.goworker handles reliable asynchronous delivery of outbound webhooks with retry logic and delivery confirmation tracking. -
TOTP Replay Attack Protection: Redis-backed
TOTPReplayStorerejects one-time codes that have already been used within their validity window, preventing replay attacks even when the server clock is slightly ahead of the client. -
Database Migrations 036–044: Nine new schema migrations covering agent events table (
036), git sync repo full-name field (037), vulnerability remediation tracking (038), change events (039), drift detection state (040), resource usage samples and optimization recommendations (041), session recording storage (042), runbook approval gates (043), and calendar events/tasks (044). -
WebSocket Origin Validation: Dedicated
ws_origin.gomodule validates theOriginheader on all WebSocket upgrade requests in both the web and API layers, rejecting cross-origin connections from unauthorized domains. -
Comprehensive Test Suite: ~140 new
_test.gofiles added across the entire codebase. Sharedtestutil/testutil.gohelper package. Coverage now spans agent sub-packages (executor, connection, inventory), Docker SDK wrappers, all seven service layers, PostgreSQL and Redis repositories (including integration tests), API auth handlers, and web WebSocket origin logic. Integration tests for Gitea and NPM clients added withhttptestmocking. -
GitHub Actions CI Pipeline: Lint, test, security scan, build, and Docker image push workflow.
-
GoReleaser Configuration: Multi-platform release builds for Linux (amd64, arm64).
-
Pre-commit Hook:
govulncheck, migration verification, andgo mod tidychecks. -
Prometheus Scrape Configuration: Templates for usulnet and agent metrics.
Changed
-
Application Initialization Refactored: Monolithic
app.go(2,554 lines) decomposed into eight domain-specific initialization files —init_docker.go,init_api.go,init_auth.go,init_scheduler.go,init_server.go,init_services.go,init_web.go, andinit_context.go— plusbootstrap.go,adapters.go, andcommands.go. Each file has a single clear responsibility, improving readability and testability. -
Route Permissions Hardened: SSH, RDP, and SFTP connection routes are now split into separate permission groups. Read-only operations (
host:view) and mutation operations (host:update) are independently gated. SFTP write operations and SSH tunnel create/toggle/delete now requirehost:update; listing and viewing require onlyhost:view. -
Auth Rate Limiting: Login and TOTP POST endpoints are now wrapped with
WebAuthRateLimit()middleware, limiting brute-force attempts per IP. -
**Request Body Li...
v26.2.3 - Beta
Changelog
usulnet v26.2.3 — Beta — 2026-02-16
Added
- Collapsible Sidebar with Per-User Preferences: New migration 035 adds
sidebar_prefsJSONB column touser_preferences. Sidebar sections are now collapsible via Alpine.js with per-item visibility filtering. State is persisted client-side in localStorage for instant responsiveness and debounced to the backend viaPUT /profile/sidebar-prefsfor DB persistence. Sidebar configuration UI moved to the Settings page with grouped toggle switches and auto-save - Node Storage & Resources Card: Node detail page now displays a Storage & Resources card showing disk usage, memory usage, CPU percent, and network I/O from host metrics. Added
GetMetricsto HostService interface and host adapter - Agent Container & Image Listing: Remote agents can now list containers and images via NATS using Docker SDK
ContainerList/ImageList, enabling multi-host inventory from the master node - Agent Streaming Logs: Large container logs are streamed in 64KB chunks with a 10MB maximum, preventing memory exhaustion on the master when tailing verbose containers
- Agent Image Pull Progress: Image pull operations now report per-layer progress tracking through NATS, enabling real-time pull status in the UI
- Agent Registry Auth:
RegistryAuthfield added toCommandParams, allowing agents to pull from private registries using credentials forwarded from the master - Agent Build Cache Prune: New
BuildCachePrunecommand via Docker SDK, allowing remote cache cleanup from the master UI - Settings API Endpoints: New
GET/PUT /api/v1/settingsfor general, security, and UI settings. Persists as global config variables via atomic upsert with audit logging for all changes - LDAP Settings API: New
GET/PUT /api/v1/settings/ldapfor LDAP configuration management andPOST /api/v1/settings/ldap/testfor connection testing. Feature-gated behindFeatureLDAP(Business+ license) - License Management API: New
GET/POST/DELETE /api/v1/licensefor activation, deactivation, and status queries. Returns edition, features, limits, and instance info - Config Sync API: Enabled previously commented-out sync routes —
POST /sync,/sync/preview,/sync/bulk,GET /outdated,/stats - Multi-Host Inventory Optimization: Migration 031 adds composite indices for container/image/volume/network multi-host queries, trigram indices for search, and a materialized view for dashboard summary aggregation. Cursor-based (keyset) pagination for container listing. Redis caching layer for inventory dashboard queries
- Automatic Data Cleanup: Migration 032 adds retention functions for
security_scans, completedjobs, andcontainer_logs.RetentionWorkernow covers 11 tables (was 8). Scheduler auto-registers daily retention job at 03:00 UTC - Migration Rollback Validation: Static analysis tests verify up/down migration pairs,
CREATE TABLE/INDEX/FUNCTION→DROPcoverage, full rollback sequence simulation with orphan table detection, dependency order and gap detection. CI-ready verification script atscripts/verify-migrations.sh - JWT Secret Automatic Rotation:
KeyRotationServicewith multi-key validation support in auth middleware and DB persistence via migration 033. Old tokens remain valid during key transition - Password Policies: Migration 034 adds password history tracking. Configurable expiration support, complexity requirements (lowercase, max length), and warning notifications when passwords approach expiry
- Security Analyzer Checks: Miscellaneous security analyzer now checks for namespace sharing, Docker socket mounts,
latesttag usage, privileged ports, missing healthchecks, and resource limits - Remote Agent Security Scanning: New
CmdSecurityScanandCmdSecurityScanImagecommand handlers on agents, collecting container inspect data and forwarding to master for analysis via NATS - Proxy Redirections: Full CRUD for HTTP redirections with
static_responsehandlers, 301/302 support, preserve-path option, and atomic Caddy config sync - Proxy TCP/UDP Streams: TCP and UDP forwarding via Caddy
layer4app with port validation, conflict detection, and protocol selection - Proxy Access Lists: IP/CIDR restrictions and HTTP basic auth with bcrypt password hashing,
satisfy-any/satisfy-allmodes - Proxy Dead Hosts: 410 Gone static responses for blocked domains with HTML error pages via Caddy routes
- GitHub/GitLab Branch & Tag Management: Create and delete branches and tags in GitHub (via Git refs API) and GitLab (via Tags API) directly from the integrations UI
- Gateway Event Notifications: Gateway events (agent connect/disconnect, command results) now route through the notification service with severity-based priority mapping
- Profile Page Overhaul: Full tabbed
ProfilePagehandler with sessions, preferences, and 2FA section linked to existing TOTP setup (replaces the previousProfileTemplstub) - Enhanced Settings Page: New security section (session timeout, max login attempts, require 2FA), LDAP section conditioned by license tier, client-side validation with Alpine.js, and visual feedback on save
- Enhanced License Page: Deactivation confirmation modal, days-until-expiration countdown, improved feature enabled/disabled indicators, and edition comparison table
- Real-Time Job Progress Bar: Reusable
ProgressBarcomponent with WebSocket connection to/ws/jobs/{id}, real-time progress updates, animated transitions, and fallback spinner when no progress data is available - Accessibility Improvements: Skip navigation link, ARIA landmarks (
role=navigation,role=banner,role=main,role=dialog),aria-hiddenon decorative icons,aria-labelon buttons,aria-current=pageon active nav items,role=alerton flash messages, properlabel/forassociations on form inputs, focus ring styles for keyboard navigation - CI/CD Pipeline: GitHub Actions workflow with lint, test (PostgreSQL/Redis/NATS services), multi-arch Docker build, security scan (gosec + Trivy), and image push to GHCR
- Automated Releases: GitHub Actions workflow triggered on
v*tags — builds binaries for linux/darwin x amd64/arm64, creates GitHub Release with changelog and checksums, pushes versioned Docker images to GHCR - Automatic Database Backup Job: Scheduled job auto-registered at startup (daily 02:00 UTC), uses existing
BackupServicewith all storage providers, 7-day retention, encryption enabled - API Handler Integration Tests: Test framework with
httptest, JWT token generation, assertion helpers. Tests for system handlers (health, liveness, readiness, version, info), router (public routes, auth, RBAC, 404/405), and base handler (JSON, pagination, sorting, auth) - E2E Test Infrastructure:
docker-compose.test.ymlwith PostgreSQL, Redis, NATS on isolated ports (15432, 16379, 14222). E2E suite withapiClient, health/auth/container/security tests. Build-tag gated (go:build e2e) - Coverage Thresholds:
scripts/check-coverage.shwith configurable threshold (default 40%). New Makefile targets:test-check-coverage,quality - Benchmarks & Load Tests: 9 Go benchmarks (health, version, auth, JWT, JSON, pagination) and k6 load test script with staged ramp-up and SLA thresholds (p95 <500ms, health <100ms, errors <5%)
- Linting Configuration:
.golangci.ymlwith 16 linters (gosec, staticcheck, errcheck, gocritic, revive, gofmt, goimports, prealloc, nilerr, errorlint, misspell, unconvert, whitespace). Pre-commit hook with fmt, vet, test, lint checks - License Expiration Checker: Configurable thresholds (30, 15, 7, 3, 1 days) with 24h dedup cooldown for notifications.
GracefulDegradationstate machine for expired licenses (CE fallback).GET /api/v1/license/statusendpoint with degradation state and days remaining - License Tier Documentation:
docs/licensing.mdwith complete feature matrix (22 features across CE/Business/Enterprise), resource limits, JWT structure, security model, lifecycle, API reference, and middleware enforcement - MaxNodes/MaxUsers Limit Enforcement:
IsWithinLimit()andLimitUsagePercent()helpers.LimitProximityCheckerwith configurable threshold (default 80%) for resource proximity alerts. Enforcement verified inhost.Service.Create()(MaxNodes) anduser.Service.Create()(MaxUsers) with HTTP 402 responses - OpenTelemetry Instrumentation: Database tracing helpers (
StartDBSpan,RecordDBError,RecordDBRowsAffected), Docker Engine tracing helpers (StartDockerSpan,RecordDockerResult), NATS messaging tracing helpers (StartNATSSpan).ObservabilityConfigwithTracingConfigin app config - Business Metrics & Dashboards:
BusinessMetricscollector with domain-specific gauges, counters, and summaries. Pre-built Grafana dashboard (deploy/grafana/usulnet-dashboard.json) and Prometheus/Alertmanager alert rules (deploy/prometheus/alerts.yml) - Structured Logging Enhancements: Log sanitizer with 22 sensitive field patterns. Per-component log level configuration
- Proactive System Alerts:
SystemAlertCheckerwith periodic health probes and debouncing. Built-in probes for PostgreSQL, Redis, NATS, disk, agents, TLS certificates, and license status - WebSocket Reconnection & Keepalive: Container, host, SSH, and multi-tab terminals auto-reconnect after 3s on unexpected close (not on graceful 1000/1001). Packet capture and job progress streams also reconnect. Backend ping/pong (30s interval, 60s read deadline) on all WebSocket handlers prevents dead client goroutine leaks
- Access Audit Event Recording:
RecordAccessEvent()now called fromLoginSubmit(success + failure),Logout,TOTPVerifySubmit(success + failure), andOAuthCallbackHandler(success + failure).ActiveSessionsstat computed from sessions marked a...
v26.2.2 - Beta
Changelog
usulnet v26.2.2 — Beta — 2026-02-13
Added
- RDP Web Sessions: Full web-based RDP client via Apache Guacamole daemon (guacd), with WebSocket proxy in Go backend, guacamole-common-js canvas rendering, fullscreen mode, Ctrl+Alt+Del injection, clipboard sync, and auto-resize
- Configurable Docker Socket Path: New
docker.socketconfig option,USULNET_DOCKER_SOCKET/DOCKER_SOCKETenv vars, and automatic socket detection for rootless Docker setups. When not explicitly configured, usulnet auto-detects the socket by checkingDOCKER_HOST,/var/run/docker.sock,$XDG_RUNTIME_DIR/docker.sock,/run/user/<UID>/docker.sock, anddocker context inspect— eliminating the hard-coded/var/run/docker.sockdependency that broke rootless Docker deployments - guacd Core Service: Apache Guacamole daemon now ships as a default service alongside PostgreSQL, Redis, and NATS — no
--profile rdpflag required
Fixed
- RDP Session Drops After ~7 Seconds (close 1005): Two causes — (1) The guacd→WebSocket relay forwarded raw TCP chunks that could end mid-instruction; the Guacamole JS parser calls
close("Incomplete instruction.")on any partial data, killing the tunnel immediately. Fixed by buffering reads and only sending complete instructions (up to the last;boundary). (2) The Guacamole JS tunnel sends internal ping instructions (0.,4.ping,…) every 500ms to keep its 15-second receive-timeout alive; these were forwarded to guacd (which ignores them) and never echoed back. Fixed by intercepting empty-opcode messages and echoing them to the browser - RDP Canvas Hidden Behind Background in Non-Fullscreen Mode: The
#rdp-displaycontainer useddisplay: blockin windowed mode butdisplay: flexonly in fullscreen, causing the Guacamole canvas element to be obscured by the parent's background. Fixed by applyingdisplay: flex; align-items: center; justify-content: centerin both modes, addingz-index: 1to.guac-display, and usingx-cloakon overlays to prevent a flash of loading/error UI before Alpine.js initializes - Node Detail Page Missing Docker Engine Fields: Version, Storage Driver, Logging Driver, Cgroup Driver, Default Runtime, OS Type, and Architecture showed "—" because these fields were never mapped through the chain: Docker SDK
system.Info→docker.DockerInfo→models.HostDockerInfo→DockerInfoView→ template. Added field mapping across all four layers and fallback to live Docker info when database values are empty - False Update Alerts for
latest-Tagged Containers: Registry clients (Docker Hub, GHCR, Generic) now return the digest of thelatesttag itself instead of resolving it to the highest semver tag, eliminating false positives where "latest" vs "1.27.0" always triggered an update notification - Container Digest Not Populated in Batch Checks:
ContainerInfo.Digestnow sourced from Docker'sImageIDfield, enabling accurate digest-based comparison forlatest-tagged images - CSRF Validation on Git Integration Forms: The Gitea list and repo detail handlers used
ctx.Value("csrf_token")with a plain string key instead of the typedContextKey, causing the CSRF token to always be empty; replaced withh.getCSRFToken(r) - Container Bulk Action Form Missing CSRF Token: Added
csrf_tokenhidden input to the bulk-action form for defense in depth (was relying solely on HTMX global header injection) - Guacamole JS "module is not defined" in Browser: The bundled
guacamole-common.min.jswas the CJS (Node.js) build ending withmodule.exports=Guacamole;— browsers have nomoduleglobal, causingReferenceError. Stripped the CJS export since the file already setsGuacamoleas a global via IIFE - RDP WebSocket Rejected by Browser (missing subprotocol):
guacamole-common-jsopens WebSocket with"guacamole"subprotocol but the Go upgrader didn't negotiate it, causing the browser to drop the connection immediately. Added dedicatedrdpUpgraderwithSubprotocols: ["guacamole"] - RDP Session: guacd Response Not Forwarded to Browser: After completing the Guacamole handshake (select→args→connect), the Go handler started the relay loop but never forwarded guacd's initial
readyresponse to the JS client, so the client never transitioned to CONNECTED state - RDP Session Dies After ~5 Seconds: Two causes — (1) Missing
VERSION_1_5_0protocol version parameter caused guacd to fall back to a legacy protocol incompatible with the 1.5.0 JS client; (2)http.Server.WriteTimeoutdeadline was inherited by the hijacked WebSocketnet.Conn, killing the connection after the server's 30s write timeout. Fixed by adding the version parameter to the handshake and clearing deadlines withSetDeadline(time.Time{})after upgrade - Monaco Editor "Save & Commit" CSRF Failure: The
doCommit()fetch POST in the Monaco editor did not include a CSRF token, causing the server to return a 403 plain-text response which the JS then failed to parse as JSON (Unexpected token 'C'). Added CSRF token from<meta name="csrf-token">to both theX-CSRF-Tokenheader andcsrf_tokenform field, and improved error handling to checkresp.okbefore callingresp.json() - Backup Creation Fails with NOT NULL Constraint: Migration 006 defined
name VARCHAR(255) NOT NULLandtarget_type VARCHAR(50) NOT NULLon thebackupstable, but the Go model and repository layer usetarget_nameandtypecolumns instead — the legacy columns were never populated, causing every INSERT to fail. Added migration 030 to drop orphaned columns (name,target_type,storage_path,storage_type) frombackupsand (name,target_type,storage_type,retention_count) frombackup_schedules. Also fixed the backup creation form to sendtarget_name(human-readable name) instead of only the raw container/volume/stack ID getUserID()Always Returned Nil: The helper usedr.Context().Value("user_id")with a plain string key, but the auth middleware stores the user under the typedContextKey("user")as*UserContext; replaced withGetUserFromContext()— fixes Git connection creation, GitOps sync, and enterprise handlers all receiving zero UUID forcreated_byunknown provider type: Gitea:NewProvider()factory matched provider types case-sensitively; any casing variation (e.g."Gitea"vs"gitea") fell through to the error default — now normalizes to lowercase before matching- Guacamole JS Not Copied into Docker Image: Dockerfile only copied
style.cssandfavicon.icointo the runtime stage;web/static/js/(containingguacamole-common.min.js) was never included, causing a 404 in production
Changed
- HOST_TERMINAL_ENABLED defaults to
true: Host terminal and host files browser are now enabled by default without requiring manual environment variable configuration - GUACD_ENABLED defaults to
true: RDP web access is enabled by default across all deployment modes (docker-compose.yml, docker-compose.dev.yml, docker-compose.prod.yml) NeedsUpdate()Logic: Handleslatest=latestwith no digests as "no update needed" instead of always reporting an update- Guacamole JS Bundled Locally:
[email protected]now served from/static/js/guacamole-common.min.jsinstead of an external CDN, eliminating external dependency for RDP web sessions
Security
- CSRF token extraction in Git integration handlers now uses the proper typed context key, preventing token bypass on connection creation, sync, test, and delete operations
getUserID()now uses the typedContextKeyviaGetUserFromContext(), preventing user identity bypass that causedcreated_byforeign key violations- Monaco editor
doCommit()andsaveSnippet()now include CSRF tokens, preventing form submission bypass from the code editor - Database migration 030 removes legacy NOT NULL columns that were never populated, eliminating a denial-of-service vector on backup creation
Full Changelog: v26.2.1...v26.2.2
Full Changelog: v26.2.1...v26.2.2
v26.2.1 - Beta
Changelog
usulnet v26.2.1 — Beta — 2026-02-12
Added
- RDP Connection Management: Full CRUD with PostgreSQL persistence, password encryption (AES-256-GCM), TCP connectivity testing, and per-user connection isolation
- Runbook Condition Steps: Container status checks (
running/stopped/exited) and string comparison operators (eq/neq/contains) - Runbook API Call Steps: HTTP client with configurable method, URL, body, auth headers, timeout caps (30s), and response validation
- Container Rename: End-to-end rename via Docker API through the service layer
- OAuth/OIDC SSO Login: Complete OAuth authorization flow with CSRF state validation and auto-provisioning
- Profile Management Routes: Preferences, theme toggle, session management, and GDPR data export
- SFTP Rename: Frontend now performs real rename API calls (was placeholder alert)
- License System Tests: ~160 tests covering validation, enforcement, expiry, and edition gating
Fixed
- SSH Connection Test JSON Injection: Replaced unsafe string interpolation with
json.Encoderfor safe serialization - Silent Error Handling (20+ handlers): Stack operations, volume/network prune, security scans, update checks, and connection CRUD now display flash error/success messages instead of silently discarding errors
- CSRF Token Generation: Token now generated on login and TOTP verification (was empty)
- NPM Proxy Settings Lost on Update: All 14+ NPM settings now preserved through create/update flows
- User Role Dropdown Not Pre-Selected: Role matched against roles table for edit form
- Labels Nil Map Panic: Added nil check before accessing container labels in stack builder
- json.Unmarshal Errors Ignored (7 locations): Container settings and runbook steps now validate JSON input
- Dockerfile BuildKit Compatibility: Added
BUILDPLATFORMdefault for environments without buildx - Git Provider Detection: Fixed hardcoded "gitea" provider in editor hub connections
- sendBulkResultToast Panic: Added bounds check before string slicing
Improved
- Input Validation: Name/host/URL required checks added across webhooks, registries, runbooks, database and LDAP connections
- Checkbox Consistency: All form toggles now check both
"true"and"on"values - Session Error Logging: Logout, host switch, and flash message session saves now log warnings on failure
- Dashboard System Info: Populated from Docker host info (was TODO stub)
- Stacks API User Context: User ID extracted from JWT for version create/restore operations
Tests
- 503 unit tests across crypto, models, HTTP utils, and validation packages
- ~160 license system tests (validation, middleware enforcement, fingerprinting)
- All tests run with
-racedetection
Security
- Database migration for RDP connections with proper constraints and unique indexes
- RDP passwords encrypted at rest via AES-256-GCM
- SFTP rename includes CSRF token validation
- SSH delete responses use safe JSON encoding
- Runbook API call step: 30s timeout cap, 4KB response body limit
Full Changelog: v26.2.0...v26.2.1