Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Releases: fran-olivares/usulnet

v26.5.2 — privacy and reliability for self-hosted Docker

21 May 22:07

Choose a tag to compare

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 every v* 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 the linux-elf-suspicious ruleset.
  • 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 usulnet CLI (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 of N × T for 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.2

v26.5.1 — 2026-05-15

15 May 12:35

Choose a tag to compare

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 cleanupisEditionAvailable / 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 startStandalone split into
    phased init_*.go files; scripts/verify-migrations.sh strengthened.
  • Opt-in local-services TLSUSULNET_TLS_LOCAL_SERVICES=true wires
    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

AGPL-3.0-or-later. Self-hosted use is free in perpetuity.

v26.5.0 — 2026-05-13

13 May 18:55

Choose a tag to compare

[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
    no enterprise fork, 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 is make build against 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

23 Feb 22:48

Choose a tag to compare

usulnet v26.2.7 — 2026-02-23

Fixed

  • Issue #16 — Container file editing crash: WriteContainerFile passed entire base64-encoded content as a single shell argument via echo '…' | base64 -d > file, hitting shell argument length limits on files larger than ~100 KB. Replaced with Docker's CopyToContainer tar-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: DeleteOldMetrics query referenced non-existent column created_at — changed to recorded_at.

  • Container stats cleanup error: DeleteOldStats query referenced non-existent column collected_at — changed to recorded_at.

  • System backup "unsupported type": Scheduled automatic database backup job used BackupTypeSystem which had no handler. Added createSystemBackup that finds the database container and backs up its volumes.

  • /quotas endpoint 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} -c failed silently for system users with /usr/sbin/nologin shell (www-data, nginx, nobody). Changed to su -s /bin/sh to force a usable shell.

  • File Browser showing only symlinks (host + non-BusyBox containers): Both parseHostLS and parseContainerLS required len(fields) < 8 to accept an ls -la line, 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 to len(fields) < 7. BusyBox containers were unaffected because BusyBox ls doesn't support --time-style and 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 -la mode prefix is l not d for symlinks, so is_dir was always false. Fixed by adding symlink type resolution — host browser uses stat -L -c '%F %n' (GNU coreutils), container browser uses POSIX test -d (works in BusyBox/Alpine). Also replaced stat -L -c '%F' with test -d in ReadContainerFile for 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 MB editor.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-show transition) — automaticLayout: true causes a ResizeObserver layout-thrashing loop, and automaticLayout: false still 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 /editor page, 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() called viewFile() which reset editingFile=false, then initMonaco(false) created a read-only editor, and the .then() callback that set editingFile=true ran too late. Refactored viewFile() to accept an edit parameter and editFile() to call viewFile(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.js x-init expressions, but Alpine 3.x doesn't support try/catch in inline expressions (throws Uncaught SyntaxError). Also used the x-collapse directive without the Collapse plugin installed. Moved try/catch logic into a global _sidebarSectionOpen() helper function and replaced x-collapse with the built-in x-transition.

  • DNS Service Discovery startup failure: Creating the containers.local zone for DNS service discovery failed with NOT NULL constraint violation on "forwarders". The Forwarders field was Go's nil slice, which PostgreSQL receives as NULL. Initialized to empty slice []string{}.

  • Agent Node Metrics Showing 0s and Docker Version "dev" (critical — agent nodes appear empty): The gateway's handleHeartbeat() discarded the QuickStats payload (CPU%, memory, container counts) from every agent heartbeat, and handleInventory() never persisted SystemInfo (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 the host_metrics table and inventory system info to the hosts table. 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) used nsenter via 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": WSContainerLogs and WSContainerStats performed a type assertion to *docker.Client, which fails for AgentProxyClient (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 via ContainerLogsSince every 5 seconds, stats poll via ContainerStatsOnce every 3 seconds.

  • Stack Deployment on Agent Node Appears Successful but Never Installs (critical — silent failure): The stack service's execCompose() ran docker compose locally with a DOCKER_HOST env 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. Added handleStackDeploy and handleStackRemove command handlers to the agent executor, added GatewayCommandSender interface to the stack service, and the Deploy() method now detects agent hosts and routes deployments through the NATS gateway. The agent writes compose files to /data/stacks/{name}/ and runs docker compose up -d locally. Gateway sender wired at application startup.

  • Security Scanning Fails for Agent Node Containers: securityAdapter.Scan() called ContainerInspectRaw() which returns ErrNotSupportedRemote on AgentProxyClient. The agent executor already supported CmdSecurityScan (collecting inspect data and sending it to the master for analysis), but it was never triggered from the web UI path. Added isAgentHost() detection and scanViaGateway() 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...
Read more

v26.2.6

21 Feb 21:13

Choose a tag to compare

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.go used BackupTypeVolume instead of BackupTypeContainer, causing the backup creator to treat the container ID as a Docker volume name. Every update attempt failed with VOLUME_NOT_FOUND and was aborted by failUpdate(). Fixed the backup type to BackupTypeContainer so the correct createContainerBackup() path is used. Also added ContainerName propagation 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, and NotificationsPartialTempl concatenated user-controlled values (search queries, container names, event messages) directly into HTML without escaping. Added html.EscapeString() to all dynamic values and url.QueryEscape() for URL parameters. Refactored search partial to use strings.Builder instead of string concatenation.

  • Silent Error Suppression in Dashboard and Updates Handlers: DashboardTempl and UpdatesTempl discarded all service errors with blank identifiers (_), rendering empty data without any indication of failure. Added proper error logging via slog.Error() so operators can diagnose service failures. NotificationsPartialTempl conflated 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. Added DriftSetBaselineAPI handler, registered POST /drift/{id}/set-baseline route, 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: ContainerSettingsUpdate POST handler used 302 Found for all redirects instead of 303 See Other (correct for POST-to-GET redirects per HTTP spec). ContainerSettingsSummary returned 200 OK when a container was not found instead of 404 Not Found. Fixed all redirect status codes and error responses.

  • Misleading "Stubs" Comment in Profile Handler: handler_profile.go contained 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.go used GetUserInfo(ctx) which looks up a user_info context key that was never populated by any middleware. The auth middleware stores the authenticated user under the user context key via GetUserFromContext(ctx). Since GetUserInfo always returned nil, the profile handler always redirected to /login — even for authenticated users. Fixed all 10 occurrences to use GetUserFromContext(ctx). Also added a clickable link to /profile in 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): stackDeploySSE used r.Context() for the DeployStream goroutine, so closing the browser tab or navigating away canceled the context and killed docker-compose mid-deploy, leaving containers in an inconsistent state. Switched to context.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, and UpdateBatch all used r.Context() synchronously, meaning any browser navigation or tab close during these long-running operations would cancel them mid-execution. Converted all four handlers to the context.Background() + goroutine pattern already established in BackupCreate. Operations now redirect immediately with a progress message and continue in the background.

  • SSE Parser Bug in Stack Deploy Template: stacks/new.templ used literal multiline newlines inside single-quoted JavaScript strings for the SSE parser (buf.split('...')), which is invalid JavaScript syntax. Fixed to use proper \n\n and \n escape sequences, matching the working implementation in catalog_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), and changes.templ (load diff) all had fetch() calls without .catch() error handlers. Network errors or server failures were silently ignored. Added .catch() handlers with user-visible toast notifications via window.usulnet.toast().

  • Stack Catalog Deploys Killed by Browser Navigation (critical — broken catalog deploys): StackCatalogDeploySubmit used r.Context() for both SSE streaming and synchronous deploy paths. The logs show signal: killed and context canceled errors when users navigate away during catalog app deployment (e.g., deploying Vaultwarden). Applied the same background context + client disconnect handling pattern as StackDeploy.

  • Container Updates Run Synchronously for 60+ Seconds (critical — updates fragile): UpdateApplyTempl, UpdateManual, and UpdateRollbackTempl all ran updatesSvc.Apply() synchronously using r.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 with context.Background().

  • Vulnerability Scan Blocks for 77+ Seconds: VulnScan handler ran secSvc.ScanAll() plus CVE import synchronously using r.Context(). Converted to background goroutine — the scan and CVE import now complete independently of browser state.

  • Stale Template Codegen for Nvim Editor: nvim_templ.go was missing SnippetID and Content fields that existed in the .templ source, causing compilation errors. Regenerated with templ generate.

Added

  • Docker Daemon Configuration Management: New /config/docker page 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.json in /etc/docker/backups/ before writing. Backups tab lists all snapshots with one-click restore.
    • Raw JSON View: Read-only tab showing the current daemon.json content for reference.
    • Reload & Restart Controls: "Reload Config" button sends SIGHUP to 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.json that would stop Docker from starting.
    • Unknown Field Preservation: Custom JSON marshaling preserves any daemon.json fields 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 RefreshButton templ 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...
Read more

v26.2.5

21 Feb 13:04

Choose a tag to compare

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 explicit USULNET_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 with openssl rand -hex 32, set USULNET_ENCRYPTION_KEY, or configure security.config_encryption_key in 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 to Error level 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-backend internal 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 via NATS_PORT environment variable. Install script updated to display NATS connection URL.

Full Changelog: v26.2.4...v26.2.5

v26.2.4

21 Feb 02:35

Choose a tag to compare

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 from redis:// to rediss://. New PKI method EnsureRedisServerCert() generates and signs Redis server certificates via the internal CA. Full mTLS support available via tls_cert_file and tls_key_file config 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 RegistryService with 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, migration 040_drift_detection, and DriftDetection/DriftDiff models.

  • 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, migration 039_change_events, and ChangeEvent/ChangeEventStats models. 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, migration 041_resource_optimization, ResourceRecommendation/ResourceUsageSample models, and /costopt page.

  • 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, migration 042_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, migration 044_calendar, CalendarEvent/CalendarTask models, 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 /tools and 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.yml via 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 DashboardService aggregates 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 with backend.go and caddy_backend.go interfaces.

  • Compliance PDF Report Generator: The compliance service gains evaluator.go (CIS Docker Benchmark scoring engine) and pdf_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 RunbookApproval model, migration 043_runbook_approvals, and runbook_execute.go scheduler worker.

  • Automatic Deployment Worker: New auto_deploy.go scheduler 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.go worker 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.go worker handles reliable asynchronous delivery of outbound webhooks with retry logic and delivery confirmation tracking.

  • TOTP Replay Attack Protection: Redis-backed TOTPReplayStore rejects 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.go module validates the Origin header 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.go files added across the entire codebase. Shared testutil/testutil.go helper 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 with httptest mocking.

  • 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, and go mod tidy checks.

  • 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, and init_context.go — plus bootstrap.go, adapters.go, and commands.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 require host:update; listing and viewing require only host:view.

  • Auth Rate Limiting: Login and TOTP POST endpoints are now wrapped with WebAuthRateLimit() middleware, limiting brute-force attempts per IP.

  • **Request Body Li...

Read more

v26.2.3 - Beta

16 Feb 22:59
8fd466d

Choose a tag to compare

Changelog

usulnet v26.2.3 — Beta — 2026-02-16

Added

  • Collapsible Sidebar with Per-User Preferences: New migration 035 adds sidebar_prefs JSONB column to user_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 via PUT /profile/sidebar-prefs for 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 GetMetrics to 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: RegistryAuth field added to CommandParams, allowing agents to pull from private registries using credentials forwarded from the master
  • Agent Build Cache Prune: New BuildCachePrune command via Docker SDK, allowing remote cache cleanup from the master UI
  • Settings API Endpoints: New GET/PUT /api/v1/settings for 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/ldap for LDAP configuration management and POST /api/v1/settings/ldap/test for connection testing. Feature-gated behind FeatureLDAP (Business+ license)
  • License Management API: New GET/POST/DELETE /api/v1/license for 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, completed jobs, and container_logs. RetentionWorker now 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/FUNCTIONDROP coverage, full rollback sequence simulation with orphan table detection, dependency order and gap detection. CI-ready verification script at scripts/verify-migrations.sh
  • JWT Secret Automatic Rotation: KeyRotationService with 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, latest tag usage, privileged ports, missing healthchecks, and resource limits
  • Remote Agent Security Scanning: New CmdSecurityScan and CmdSecurityScanImage command handlers on agents, collecting container inspect data and forwarding to master for analysis via NATS
  • Proxy Redirections: Full CRUD for HTTP redirections with static_response handlers, 301/302 support, preserve-path option, and atomic Caddy config sync
  • Proxy TCP/UDP Streams: TCP and UDP forwarding via Caddy layer4 app 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-all modes
  • 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 ProfilePage handler with sessions, preferences, and 2FA section linked to existing TOTP setup (replaces the previous ProfileTempl stub)
  • 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 ProgressBar component 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-hidden on decorative icons, aria-label on buttons, aria-current=page on active nav items, role=alert on flash messages, proper label/for associations 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 BackupService with 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.yml with PostgreSQL, Redis, NATS on isolated ports (15432, 16379, 14222). E2E suite with apiClient, health/auth/container/security tests. Build-tag gated (go:build e2e)
  • Coverage Thresholds: scripts/check-coverage.sh with 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.yml with 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. GracefulDegradation state machine for expired licenses (CE fallback). GET /api/v1/license/status endpoint with degradation state and days remaining
  • License Tier Documentation: docs/licensing.md with 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() and LimitUsagePercent() helpers. LimitProximityChecker with configurable threshold (default 80%) for resource proximity alerts. Enforcement verified in host.Service.Create() (MaxNodes) and user.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). ObservabilityConfig with TracingConfig in app config
  • Business Metrics & Dashboards: BusinessMetrics collector 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: SystemAlertChecker with 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 from LoginSubmit (success + failure), Logout, TOTPVerifySubmit (success + failure), and OAuthCallbackHandler (success + failure). ActiveSessions stat computed from sessions marked a...
Read more

v26.2.2 - Beta

13 Feb 21:19

Choose a tag to compare

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.socket config option, USULNET_DOCKER_SOCKET / DOCKER_SOCKET env vars, and automatic socket detection for rootless Docker setups. When not explicitly configured, usulnet auto-detects the socket by checking DOCKER_HOST, /var/run/docker.sock, $XDG_RUNTIME_DIR/docker.sock, /run/user/<UID>/docker.sock, and docker context inspect — eliminating the hard-coded /var/run/docker.sock dependency that broke rootless Docker deployments
  • guacd Core Service: Apache Guacamole daemon now ships as a default service alongside PostgreSQL, Redis, and NATS — no --profile rdp flag 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-display container used display: block in windowed mode but display: flex only in fullscreen, causing the Guacamole canvas element to be obscured by the parent's background. Fixed by applying display: flex; align-items: center; justify-content: center in both modes, adding z-index: 1 to .guac-display, and using x-cloak on 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.Infodocker.DockerInfomodels.HostDockerInfoDockerInfoView → 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 the latest tag 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.Digest now sourced from Docker's ImageID field, enabling accurate digest-based comparison for latest-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 typed ContextKey, causing the CSRF token to always be empty; replaced with h.getCSRFToken(r)
  • Container Bulk Action Form Missing CSRF Token: Added csrf_token hidden 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.js was the CJS (Node.js) build ending with module.exports=Guacamole; — browsers have no module global, causing ReferenceError. Stripped the CJS export since the file already sets Guacamole as a global via IIFE
  • RDP WebSocket Rejected by Browser (missing subprotocol): guacamole-common-js opens WebSocket with "guacamole" subprotocol but the Go upgrader didn't negotiate it, causing the browser to drop the connection immediately. Added dedicated rdpUpgrader with Subprotocols: ["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 ready response 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_0 protocol version parameter caused guacd to fall back to a legacy protocol incompatible with the 1.5.0 JS client; (2) http.Server.WriteTimeout deadline was inherited by the hijacked WebSocket net.Conn, killing the connection after the server's 30s write timeout. Fixed by adding the version parameter to the handshake and clearing deadlines with SetDeadline(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 the X-CSRF-Token header and csrf_token form field, and improved error handling to check resp.ok before calling resp.json()
  • Backup Creation Fails with NOT NULL Constraint: Migration 006 defined name VARCHAR(255) NOT NULL and target_type VARCHAR(50) NOT NULL on the backups table, but the Go model and repository layer use target_name and type columns 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) from backups and (name, target_type, storage_type, retention_count) from backup_schedules. Also fixed the backup creation form to send target_name (human-readable name) instead of only the raw container/volume/stack ID
  • getUserID() Always Returned Nil: The helper used r.Context().Value("user_id") with a plain string key, but the auth middleware stores the user under the typed ContextKey("user") as *UserContext; replaced with GetUserFromContext() — fixes Git connection creation, GitOps sync, and enterprise handlers all receiving zero UUID for created_by
  • unknown 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.css and favicon.ico into the runtime stage; web/static/js/ (containing guacamole-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: Handles latest=latest with 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.js instead 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 typed ContextKey via GetUserFromContext(), preventing user identity bypass that caused created_by foreign key violations
  • Monaco editor doCommit() and saveSnippet() 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

12 Feb 19:38

Choose a tag to compare

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.Encoder for 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 BUILDPLATFORM default 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 -race detection

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