Conversation
When accessing the dashboard via NPM domain (sen.lucknet.uk), the HTTP Host header was used for NPM ForwardHost matching. Since NPM stores IPs not domains, no proxy hosts matched and every port fell back to sen.lucknet.uk:<port>. For local containers, use Lookup() (matches against the resolver's configured sentinelHost IP) instead of LookupForHost() with the request domain. Cluster containers still use LookupForHost() with the remote host's IP.
NPM proxy hosts with wildcard domains like *.s3.garage.example.com produced broken URLs. The resolver now picks the first non-wildcard domain from the list, falling back to skipping the entry entirely if all domains are wildcards.
The filter bar on history, logs, and images pages was missing the bottom border that the dashboard had. Added explicit border-bottom to .filter-bar.
… queue entries Portainer settings now take effect immediately without a container restart, using the same factory pattern as the NPM connector. The connection test always recreates the provider from current DB settings so token changes are picked up. Portainer endpoints that point at the same Docker socket Sentinel monitors no longer produce duplicate queue entries (container IDs are compared against the local scan). Updated help text and integration descriptions for accuracy.
The dashboard stat card used a local-only pending count that excluded Portainer queue items, while the nav badge used the full queue length. Both now use the queue length directly so they always match. Removed the checkmark icon from the zero-state as it added no value.
The detail page handler only knew about local and cluster containers. Portainer containers (host=portainer:N) fell through to the cluster lookup which returned "not found". Added a Portainer branch that extracts the endpoint ID, fetches containers from the Portainer API, and builds the detail view with policy, version, and queue info.
Portainer and cluster container detail pages looked up history and snapshots using the bare container name, but records are stored under scoped keys (e.g. "portainer:3::name"). Now uses hostFilter::name when a host filter is present.
Covers data model, local socket detection, connector UI, dashboard integration, engine changes, and migration path.
13 tasks across 7 chunks: store CRUD, migration, engine multi-instance scanning, local socket detection, web API, dashboard host groups, and frontend connector cards.
Adds MigratePortainerSettings() which converts the flat portainer_url/ portainer_token/portainer_enabled settings keys into a PortainerInstance record (id "p1", name "Portainer") and clears the old keys. Safe to call multiple times: skips if any instances already exist. Also adds DeleteSetting() to bolt.go.
Replace single-instance Portainer settings (flat portainer_enabled/url/token
keys) with a full instance CRUD API backed by PortainerInstanceStore. The
PortainerProvider interface now takes instanceID parameters on all methods.
New routes: GET/POST /api/portainer/instances, PUT/DELETE instances/{id},
POST instances/{id}/test, GET instances/{id}/endpoints,
PUT instances/{id}/endpoints/{epid}.
Existing container detail handler updated to parse the new
"portainer:instanceID:epID" host filter format while remaining backwards
compatible with the legacy "portainer:epID" format.
Note: cmd/sentinel/ adapters will not compile until Task 7 updates them.
Three bugs found during live testing on the test cluster: 1. Portainer instances added via the API had no live scanner (only boot-time instances worked). Added ConnectInstance/DisconnectInstance to PortainerProvider, called from create/update/delete handlers. 2. Portainer self-signed certs caused TLS verification failures. Added InsecureSkipVerify to the Portainer HTTP client (standard for homelab and private network setups). 3. csrfToken is a function reference (window.csrfToken = getCSRFToken) but connectors.html passed it as a value. Changed all 11 occurrences to csrfToken() calls.
IsLocalSocket() was defined but never called. Now applied in two places: 1. Scanner.Endpoints() filters out local socket endpoints so the engine never scans them (defence in depth). 2. Test Connection handler auto-marks new local socket endpoints as blocked with reason "local Docker socket (duplicates direct monitoring)" so users see why the endpoint is disabled. Updated scanner tests to use TCP URLs for mock endpoints (empty URL + EndpointDocker type now correctly triggers IsLocalSocket).
Only auto-block unix:// endpoints when the Portainer instance runs on the same host as Sentinel. Previously all unix:// endpoints were blocked regardless of host, which incorrectly disabled remote Portainer instances. - Add isLocalPortainerInstance() to compare Portainer URL against local IPs - Remove over-aggressive IsLocalSocket filter from Scanner.Endpoints() - Wire engine into multiPortainerAdapter so runtime-added instances are scanned without restart - Reconnect engine after endpoint config changes (test, update) - Add unit tests for local detection helpers
Use the official portainer/portainer-updater container to safely update Portainer instances without crashing the API mid-request. The updater mounts the Docker socket directly, bypasses the Portainer API, and survives the stop/recreate cycle. - Add UpdatePortainerSelf to scanner with pull, create, start, recovery - Add IsPortainerImage detection (CE, EE, GHCR, registry prefixes) - Route Portainer images through self-updater in apiUpdate, apiUpdateToVersion - Add approvePortainerUpdate for queue approval path (was missing) - Add PullImage, CreateContainer, StartContainer, WaitContainer, WaitForRecovery and RemoveContainer to Portainer client - Add smart local socket blocking (isLocalPortainerInstance) - Live tested: Portainer 2.19.0 -> 2.39.0 on .61 test cluster
…hadowing Replace single SENTINEL_HOST string match with a local address set built from net.InterfaceAddrs(), hostname, host.docker.internal DNS, and the SENTINEL_HOST env var (additive override). Fixes port 8080 on .64 shadowing port 8080 on .57 when SENTINEL_HOST was unset. Also consolidates unreleased changelog entries under [Unreleased].
Scan summary rows were rendered as regular container rows, causing truncated text in the Container column and broken /container/history-0 URLs when clicked. Now they render with colspan spanning Container + Version columns, no click handler, a "Summary" badge, and proper text wrapping at all viewport widths.
colspan shifts nth-child indices by one in scan summary rows, so the Outcome and Duration cells inherit the wrong column styles. Explicit text-align: center on nth-child(3) and nth-child(4) within .scan-summary-row corrects the alignment.
When an approved update failed (e.g. Portainer agent disconnected), only a log line was written and the update vanished from the queue with no history record. Now writes a "failed" UpdateRecord with the error message and elapsed duration so failures are visible on the history page.
Grey badge-muted blended into the dark background. Switched to badge-error (red) for better visibility.
Size is now right-aligned so numbers line up, Status and Actions are centred so badges and buttons sit under their headers.
DetectLocalAddrs was including container-internal addresses (172.17.x.x, localhost, hostname) which never match NPM ForwardHost values. This caused Lookup() to silently filter out all proxies when SENTINEL_HOST was not set, making port chips fall back to raw IP:port links. Now only includes routable addresses: explicit SENTINEL_HOST values and Docker host IP via host.docker.internal. Returns an empty set when neither is available, which disables filtering (safe fallback matching all proxies).
…t shadowing When hostAddr from the HTTP request is a valid IP (direct IP access or SENTINEL_HOST), use LookupForHost to match only NPM proxies forwarding to that specific host. Falls back to Lookup() when accessed via domain. Fixes regression from 1c18dec where empty localAddrs disabled all filtering, allowing port 8080 on host A to shadow port 8080 on host B.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
What changed
portainer_instanceswith CRUD + migration from legacy single-instance settingsIsLocalSocket) to prevent scanning the host Docker twiceTest plan
🤖 Generated with Claude Code