Backend:
- SSH console: close the session on browser disconnect (Wait() blocked
forever, leaking the 50-slot console cap); add ping/read deadline
- VNC console dials PVE with the connection's pinned TLS fingerprint;
test-connection honors the pin too
- Claim a console slot before consuming the single-use session
- Exempt /ai/chat and /events from the 30s router timeout so Stop cancels
the AI tool loop and the event stream isn't cut every 30s
- Lift server Read/WriteTimeout for ISO uploads and file-restore downloads
- Shutdown: webhook dispatcher waits for its goroutines; cancel request
contexts via BaseContext; force Close on Shutdown timeout
- Admin endpoint DELETE /ssh/known-hosts to clear a pinned host key
- Stamp version/commit/date into Docker images; fix rebind comment
Frontend:
- Dashboard: stop background refetch from reverting unsaved drags; fix
save-failure check comparing activeId with itself
- AI assistant: fix reversed-index tool-result matching; scope Stop,
progress bar and auto-scroll to the streaming conversation; persist
partial answers after unmount; per-user chat history storage
- Hosts file saves with the digest it was loaded with
- Guest dialog: don't clobber config/DNS edits on refetch; key state by
connection + guest
- PBS GC settled state survives tab remount; cards keyed per connection
- Cluster panels reset on connection switch; encode vnet names
- Shell console UTF-8 split across frames; live rates reset per guest;
per-instance chart gradient ids; McpIntegrationCard uses useCopiedFlag
Fixes regressions from the previous audit-fixes commit (SSH host-key TOFU
race/silent-swallow, webhook durability, guest-switch exec race, dashboard
save dirty-flag misattribution) plus new findings across both frontend and
backend: AI Assistant streaming into the wrong conversation on switch,
NodeSystemPanel forms getting clobbered by background refetch, stale
ClusterPage vnet/connection selection, NaN-producing numeric form fields,
PBS oversized-response parity with the PVE client, gauge/sparkline gradient
ID collisions, PBS GC progress surviving tab switches, digest fleet-average
skew from fully-offline connections, unbounded console/SSH sessions, and
Postgres "?" rebinding corrupting literal "?" in string literals.
Also fixes three UI bugs reported directly: the dashboard meter glow being
clipped on one side, the "Available widgets" sticky label showing scrolled
content through its padding gap, and ClusterActivityWidget's generic error
message hiding the real upstream failure reason.
Separately: fixes the Release workflow's docker job hanging for GitHub's
360-minute hard cap — the web/go build stages were running under QEMU
emulation for the linux/arm64 target instead of natively, which is known to
hang Node/npm outright. Pinned both stages to --platform=$BUILDPLATFORM
(Go cross-compiles without needing to execute target-arch code) and added
a 30-minute job timeout so a real hang fails fast instead of burning hours.
- Add 48 new MCP/AI-assistant tools covering guest lifecycle, node
operations, firewall/security, and backup/replication/HA/storage/SDN
management. Every mutating tool is admin-gated the same way
guest_power_action already is; migrate/resize/move-disk, node
reboot/shutdown, disk wipe, cert revocation, and cluster-node removal are
deliberately left out as being as destructive as a delete.
- ai_chat.go: when a provider (chiefly Needle, a pure tool-router with no
narrative output of its own) finishes calling tools but returns nothing to
say, render the tool results themselves as the answer instead of the
misleading "ran out of tool calls" message.
- needle.go: serialize every request against the shared Needle subprocess
(it handles one request at a time) to stop concurrent callers from racing
it, and surface the subprocess's captured output when a request fails
because it died mid-response, instead of a bare network error.
- Dockerfile: switch the final stage from distroless "static" to "base" —
the bundled Needle CLI is a dynamically-linked glibc binary and cannot run
in an image with no libc at all.
- .github/workflows/release.yml: build and push a multi-arch (amd64/arm64)
Docker image to ghcr.io on every version tag, tagged with the version and
"latest".
- Dockerfile/README: add OCI image labels and document the published GHCR
image as the primary Docker install path.
scripts/build.sh, get.sh, and scripts/linux/*.sh had lost their executable
bit in git (core.fileMode=false on this Windows checkout meant a local
chmod +x never made it into the index) — release.yml invokes
scripts/build.sh directly rather than via `bash`, so the CI job failed
immediately with exit code 126 (permission denied). Also invoke it via
`bash` explicitly as a second line of defense, and fix the zip-install
step's `||`/`&&` operator precedence (it always ran apt-get regardless of
whether zip was already present, harmless but not what it looked like).
Verified locally end-to-end: scripts/build.sh all now builds and packages
all 6 platform targets and every checksum verifies.
- .gitignore/.dockerignore: cover Go build artifacts, env files, logs,
editor/OS cruft, and local runtime data (sqlite db/secret, config.yaml).
- README: add a Screenshots section (captured against a mock Proxmox
cluster) and a Deploying a release build section.
- LICENSE: MIT.
Deployable binaries:
- cmd/ferrum: -version flag with build-time version/commit/date via
-ldflags; -log-file flag (Windows services don't capture stdout/stderr
the way systemd does); native Windows Service Control Manager support
(service_windows.go) so ferrum.exe manages its own start/stop lifecycle
under a Windows service, same graceful-shutdown path SIGTERM already used
on Linux.
- packaging/systemd/ferrum.service: hardened systemd unit.
- scripts/build.sh, build.ps1: cross-compile linux/windows/darwin x
amd64/arm64, package as .tar.gz/.zip with an install script and
checksums.txt.
- scripts/linux/install.sh, uninstall.sh: create a dedicated system user,
install the binary, seed /etc/ferrum/config.yaml, enable + start the
systemd service.
- scripts/get.sh: one-line curl-pipeable installer (get.docker.com style)
that resolves the latest release, verifies its checksum, and hands off
to install.sh.
- scripts/windows/install-service.ps1, uninstall-service.ps1: register/
remove the self-managing Windows service.
- .github/workflows/release.yml: publish all platform archives + checksums
as GitHub Release assets on a vX.Y.Z tag push.
- .github/workflows/ci.yml: go vet/build/test, frontend lint/test/build,
and shell-script syntax checks on push/PR.