- Settings page now uses a left-rail + content-pane layout. Each section is URL-addressable (/settings/inspector, /settings/notifications, ...). The rail shows a live subtitle under each item so state is readable without clicking in. - Gotify and Pushover grouped under a single Notifications section with one Save per channel. - Containers moved to a dedicated top-level route: /containers lists tiles; /containers/<name> is the edit form. Containers tab added to the main nav. - Saved banner is a prominent green flash with a success check.
28 KiB
Executable File
Changelog
All notable changes to Inspector are documented here.
Format follows Keep a Changelog. Version numbers follow Semantic Versioning.
[0.7.1] — 2026-10-27
Changed
- Settings page redesigned to a classic SaaS layout (left rail + content pane):
- Left rail groups sections under Core / Data / Scheduling with a live subtitle under each item (e.g. "60s · dry-run on", "gotify on · pushover off", "on · :8080") so you can read state without clicking in.
- Each section is URL-addressable:
/settings/inspector,/settings/notifications,/settings/dashboard,/settings/database,/settings/health_summary,/settings/maintenance,/settings/notification_mute. - Gotify and Pushover now share a single Notifications section with one Save button per channel, so the fan-out model is visible at a glance.
- Save flash is a prominent green banner with a success check.
- Containers moved to a dedicated top-level route (
/containers): a grid of tiles showing status, enabled, max errors, cooldown, and restart_on at a glance. Click a tile for the per-container edit form at/containers/<name>. Keeps the Settings page small when buddies have many containers. - Top navigation adds a Containers tab alongside Status / Events / Settings.
Internal
POST /settings/gotifyandPOST /settings/pushovernow redirect back to/settings/notifications(the grouped page).POST /containers/<name>added as a parallel entry point to the existing settings backend for container edits.
[0.7.0] — 2026-10-26
Added
- Public packaging under
euronvault/inspectoron Gitea with a dedicated image atgitea.euronvault.com/euronvault/inspector:0.7.0(plus:latestand:sha-<short>tags). Dev continues oneugene/inspector. scripts/sync_public.shwith an explicit allowlist (scripts/public_allowlist.txt) and grep-based denylist (scripts/public_denylist_patterns.txt) that refuses to publish any file matching a personal-string pattern (lab IPs, lab container names, lab env var names, agent-workflow scaffolding paths).- Sanitized
public/staging tree: generic README, INSTALL guide, MIT LICENSE (Copyright (c) 2026 Europium 63),config.example.ymlwith two neutral example containers, and acompose.ymlthat pulls fromgitea.euronvault.com/euronvault/inspector. - Web settings page at
/settingsfor editingconfig.ymlwithout SSH: per-section forms for Inspector loop, Gotify, Pushover, Dashboard, Database, Health summary, Maintenance, Notification mute, and per-container tuning (thresholds, cooldowns,restart_on, color). - Saves round-trip through ruamel.yaml so comments and key order in
config.ymlare preserved. Each save keeps a.bakof the prior file. (In the Docker container.baklives in the overlay fs becauseconfig.ymlis a single-file bind mount; the live file is still updated cleanly.) - In-process hot reload via a new
ConfigRef: edits to container-level settings andinspector.dry_run/heartbeat/interval_secondstake effect on the next loop iteration without a container restart. Fields requiring restart are explicitly labeled in the UI. - CSRF token on all cookie-authenticated POSTs (Settings forms, maintenance toggle, mute toggle). Basic Auth clients bypass the check because they prove identity on every request.
- Secret fields (Gotify token, Pushover token/user_key, dashboard password)
render masked and treat the
__UNCHANGED__sentinel as "keep existing"; real secret values are never echoed into HTML.
Changed
main.run_loop()now takes aConfigRef, re-reads config each iteration, and rebuildsenabled_configs/check_orderso edits apply without a restart.DashboardServerconstructor simplified: now takesConfigRef,states,maintenance,notification_mute(per-section configs are resolved live viaConfigRef.get()).- Added
ruamel.yaml==0.18.6torequirements.txt.
[0.6.0] — 2026-10-26
Added
- Pushover notifications as a second independent channel alongside Gotify.
Enable either, both, or neither under
notifications.pushover; alerts fan out to every enabled channel. Supports optionaldeviceandsoundoverrides. - Priority mapping from Inspector's Gotify-style integer scale to Pushover's
-2..2range:<=2 → -1,3..4 → 0,5..6 → 1,>=7 → 2. Emergency tier (2) ships defaultretry/expirevalues and triggers the usual Pushover acknowledgement prompt. MultiNotifierinternally fans eachnotify()call out to every enabled channel; a crash in one channel never prevents the others from running.
[0.5.5] — 2026-06-18
Changed
- Timestamp display format shortened: year and timezone abbreviation (PST/PDT) removed.
Timestamps now render as
MM-DD HH:MM AM/PM(e.g.06-18 09:30 AM).
[0.5.4] — 2026-06-17
Added
- Dashboard cards now surface scheduled maintenance for containers opted into the recurring
daily window (
maintenance_window: true):- while idle, a meta-row
🔧 Next maintenance: in <countdown> (<start> – <end>)shows when the next window begins, with the window times in the dashboard's local format; - while the window is active, the phase row shows
🔧 In maintenance — ends in <countdown>so it's clear when normal monitoring resumes.
- while idle, a meta-row
- All countdowns reuse the existing 1-second client-side ticker (no page reload needed).
/api/statusnow returnsmaintenance_scheduled(bool) andmaintenance_window_start/maintenance_window_end(ISO UTC or null) per container.
[0.5.3] — 2026-06-15
Added
- Dashboard status cards now show a phase row below the error bar, indicating what
Inspector is currently doing for each container:
⏱ Cooldown — Xs remaining— post-restart cooldown is active (countdown to expiry)⏱ Grace period — Xs remaining— container is booting post-restart; failures not counted⚠ Restart limit — resets in Xs— hourly restart cap hit; countdown to when it clears⚠ Failing X/Y — next check in Zs— error count is accumulating toward the restart threshold👁 Observe only—restart_on: []is set; Inspector tracks status but never acts🔧 In maintenance— container is in a maintenance windowNext check in Zs— container is healthy; soft reminder of the monitoring cadence
- All countdown timers update every second in the browser without a page reload (JS
setInterval); the existing 30 s auto-refresh keeps data fresh. /api/statusnow returnsphase,phase_until(ISO UTC or null),last_check_at(ISO UTC or null), andinterval_secondsper container entry.
[0.5.2] — 2026-06-15
Added
- Portainer environments now support
api_token: "ptr_..."to embed the API token directly inconfig.yml, as an alternative toapi_token_env(env var lookup).api_tokentakes precedence when both are set. Useful when injecting env vars into the Inspector container is inconvenient. - Improved token-not-found error message: now mentions both
api_tokenandapi_token_envoptions so the fix is self-evident in the logs. _find_stack()now logs a debug line with how many stacks were scanned and whichendpoint_idwas used, aiding multi-environment Portainer diagnosis.
[0.5.1] — 2026-06-15
Fixed
health_summarydigest crash:KeyError: 'portainer_redeploy_failed'on the first periodic digest when event history contained Portainer redeploy events.health_summary._LABELSwas missing the three Portainer event types (portainer_redeploy,portainer_redeploy_failed,portainer_redeploy_skipped) that were added toSUMMARY_EVENT_TYPESin v0.4.0.
[0.5.0] — 2026-06-16
Added
- New per-container
recovery_action: restart_then_redeploy— tiered recovery for VPN-routed containers. Inspector first tries a plaindocker restart(which fixes the common case where the VPN sidecar was merely restarted, keeping the same container id) and only escalates to a Portainer stack redeploy when the restart errors (e.g. the stale-namespace "No such container" case after the sidecar is recreated) or the container is still failing on the next recovery cycle. Escalation is non-blocking — it happens on the following cycle, not via an inline wait. Requiresportainer_stack_namelikeportainer_stack_redeploy, and honors the samedepends_ongating.
[0.4.2] — 2026-06-15
Added
- Result notifications for recovery actions: a successful
docker restart/startnow sends a✅ … completednotification, and a successful Portainer stack redeploy sends✅ … redeployed. A failed post-redeploy HTTP recheck sends⚠️ … recheck failed. - "Testing the Portainer API" guide in the README (uses
python3+requests, since the container image has nocurl).
Changed
- Portainer API errors now include the HTTP status code and response body in the
failure message/log (e.g.
HTTP 401: {"message":"Invalid JWT token"}), making misconfigured tokens/endpoints far easier to diagnose. Added intermediate debug logging for stack lookup and a warning when a stack name isn't found.
[0.4.1] — 2026-06-15
Fixed
- Per-container
restart_on: [](empty list) now correctly disables all restart triggers for that container. Previously, an empty list was treated as "not set" and silently fell back to the default[UNHEALTHY, NOT RUNNING], so a container couldn't be configured as observe-only (e.g. fordepends_onstatus tracking without Inspector restarting it itself). Omittingrestart_onentirely still defaults to[UNHEALTHY, NOT RUNNING].
[0.4.0] — 2026-06-15
Added
- New per-container
recovery_action:restart_container(default, unchanged behavior),start_container(callsdocker startinstead ofdocker restart), andportainer_stack_redeploy. portainer_stack_redeployrecovers containers whosenetwork_mode: container:<vpn>namespace goes stale after the VPN sidecar (e.g. Gluetun) is recreated — the"joining network namespace of container: No such container"failure a plain restart can't fix. It redeploys the owning Portainer stack via the Portainer API. Newintegrations.portainerconfig: named environments (token loaded fromapi_token_env, never logged or stored),default_environment,stack_redeploy_cooldown_seconds,post_redeploy_wait_seconds. New per-containerportainer_environment/portainer_stack_name.- New per-container
depends_on/vpn_routed: containers are checked (and recovered) in dependency order each cycle, so a VPN sidecar is recovered before its dependents. Recovery is deferred — without consuming the container's cooldown — while itsdepends_oncontainer isn't healthy/starting;vpn_routed: truelogs why. - Dry-run extended to cover the new actions, including the Portainer
redeploy flow (
DRY RUN: would resolve/redeploy/wait Portainer ...). - New event types:
portainer_redeploy,portainer_redeploy_failed,portainer_redeploy_skipped. - See Portainer stack redeploy recovery in the README.
- New per-container notification mute:
notification_muteconfig (enabled,dir,default_hours, default 12h auto-expiring), dashboard "Mute notifications"/"Unmute notifications" button + "Muted" badge,POST /notifications/<name>/mute|unmute. Muting silences outbound Gotify notifications only — monitoring, failure counting, restarts/recovery, cooldowns, event history, and the health-summary digest continue unaffected. New event typesnotification_mute_enter/exit. See Muting notifications in the README.
All new fields are optional and backward-compatible; existing configs are
unaffected. recovery_action: recreate_container is not implemented and
is rejected at config load.
[0.3.1] — 2026-06-14
Changed
http_checknow classifies outcomes into three explicit tiers: the configuredexpected_status= up, a connection refused / timeout / DNS failure = unreachable (reported as the synthetic status code0), and any other code = "responding but unexpected". Connection-level failures were already treated as failed checks (→UNREACHABLE); this makes the distinction explicit in the result (HttpCheckResult.status_code) and in the log / notification messages. Matching remains exact — the README now documents how to find a container's actual status code.
[0.3.0] — 2026-06-14
Added
- Opt-in "unreachable" detection per container: even when Docker reports a
container as healthy/running, Inspector can independently verify it's
actually reachable via an HTTP/HTML health check (
http_check) and/or a shared Uptime Kuma status page (integrations.uptime_kuma+ per-containeruptime_kuma.monitor_name). A failing check on an otherwise-healthy container surfaces as a newUNREACHABLEstatus (red badge on the dashboard); opt it into a container'srestart_onto have it count toward failures/restarts like any other status. Both checks default to disabled and never crash Inspector. - Events page (
/events) redesigned as a daily view: shows one calendar day (12:00 AM-11:59 PM US Pacific) at a time, defaulting to today, with "Prev day" / "Next day" navigation (?date=YYYY-MM-DD, also supported by/api/events). The timeline now always renders all 24 hourly markers (12 AM..11 PM) so you can scroll directly to a time of day, plus a trailing "11 PM (yesterday)" section for the 1-hour lookback overlap from the previous day.
Changed
- All dashboard and Gotify notification timestamps now render in US
Pacific time (
PDT/PST, DST-aware) instead of UTC, e.g.2026-06-14 03:45 PM PDT. JSON API endpoints are unaffected and continue to return raw UTC ISO 8601 timestamps.
[0.2.6] — 2026-06-14
Changed
- Gotify notifications reformatted as Markdown (
extras.client::display.contentType: text/markdown) with richer context per message: error counts and thresholds, previous/current status, Docker error details for failed restarts, restart/cooldown info, and a formatted timestamp. The health-history digest is now a Markdown bulleted list with a header. - Dashboard timestamps (container "Last restart" / "Cooldown until", and
the events timeline) now render as 12-hour time with AM/PM (e.g.
2026-06-14 03:45 PM UTC) instead of raw ISO 8601.
Fixed
- "Last restart" no longer reverts to "Never" after Inspector itself
restarts: on startup, with
database.enabled: true, each container's most recent recorded restart (and any still-active cooldown) is restored from the event history database.
[0.2.5] — 2026-06-14
Added
- Per-container identity colors on the dashboard: each container card gets
a soft background tint and colored name from a 16-color auto-assigned
palette, and the same color carries over to that container's chip on the
events timeline. Override the auto-assigned color with an optional
color: "#rrggbb"field per container inconfig.yml.
[0.2.4] — 2026-06-14
Fixed
- Sign out: removed the redirect-if-authenticated on
GET /loginso sign out always reaches the login form, even with a cached browser Basic Auth session. - Mobile header: version/refresh text now hides on narrow screens, and sign-out renders as a small outlined button beside the brand.
- Status page error bar: shows 100% green when a container has zero errors (previously rendered as an invisible 0-width bar) and a proportional red fill once errors are detected.
- Container cards: hide the cooldown row when no cooldown is active, and show "Never" instead of "—" when a container has no restart history.
- Events page: the "event history not enabled" message now includes the
database: enabled: trueconfig snippet needed to turn it on.
[0.2.3] — 2026-06-14
Changed
- Replaced the browser-native Basic Auth prompt with a styled dark-theme login page backed by a session cookie (24h expiry); HTTP Basic Auth still works for API clients. Added a "Sign out" link, and made the dashboard header, stats grid, and card grid responsive on mobile.
[0.2.2] — 2026-06-14
Added
- Dashboard pages now set a full favicon/app-icon set (favicon.ico,
favicon.svg, favicon-96x96.png, apple-touch-icon.png, web app manifest
icons, site.webmanifest) generated from the Inspector icon. Served from
new
GET /static/<file>endpoints alongside the existing icon route (Basic Auth required, same as every other dashboard route).
[0.2.1] — 2026-06-14
Changed
- Web dashboard redesigned: dark "card grid" theme. Each container gets its
own card (status badge, error-count progress bar, cooldown/last-restart
details, maintenance badge and toggle button) instead of a plain table;
the events page renders a colour-coded vertical timeline instead of a
table. The header now shows the Inspector icon plus an "Inspector" /
"Monitor. Restart. Report." wordmark set in
Google Sans Flex
(loaded from Google Fonts; falls back to the system sans-serif if
unavailable). New
GET /static/icon.pngendpoint (Basic Auth required, same as every other dashboard route) serves the icon from a newsrc/inspector/assets/package directory.
[0.2.0] — 2026-06-14
Added
- Optional embedded web dashboard (
dashboard.enabled, defaultfalse), protected by HTTP Basic Auth (dashboard.username/dashboard.password). If enabled with an empty password, Inspector logs an error and refuses to start the dashboard (fails closed). Runs in the same process as the monitoring loop — no extra container, zero new dependencies (stdlibhttp.server).- Status page (
/, JSON at/api/status) — live per-container table: current status, error count vs. threshold, cooldown expiry, last restart time, and whether it's in a maintenance window. Auto-refreshes every 30 seconds. - Events page (
/events, JSON at/api/events) — last 100 rows from event history, ifdatabase.enabled: true. - Maintenance toggle — when
maintenance.enabled: true, per-container "Enter/Exit maintenance" buttonsPOST /maintenance/<name>/enter|exit, touching/removing the same marker files described under "Ad-hoc marker files". Returns409if maintenance windows are disabled,404for an unknown container. New modulesrc/inspector/dashboard.py, new config sectiondashboard:inconfig.example.yml.
- Status page (
MaintenanceTracker.peek(name)— read-only maintenance check (no transition logging, no eventstore writes) used by the dashboard.tests/test_dashboard.py— unit tests covering the disabled no-op, the empty-password refusal, Basic Auth enforcement, the status page and API, the maintenance toggle (enter/exit, disabled-config 409, unknown-container 404), and the events page/API with and without event history enabled.
[0.1.11] — 2026-06-14
Added
-
Optional maintenance windows (
maintenance.enabled, defaultfalse). Two complementary mechanisms, both gated by this one flag:- Recurring daily window — containers with
maintenance_window: trueare "in maintenance" every day betweenmaintenance.startandmaintenance.end(default00:00–06:00, local time viaTZ, may wrap past midnight). - Ad-hoc marker files —
touch/rm<maintenance.dir>/<container>(default/app/data/maintenance/<container>) for unplanned maintenance. A marker older thanmaintenance.max_hours(default8) auto-expires.
While either mechanism is active for a container, Inspector is fully silent for it: no failure counting, restarts, notifications, status-change logging, or event recording. Only one-time "entering/exiting maintenance window" log lines and new
maintenance_enter/maintenance_exitevents (excluded from the health-history digest) mark the transitions. State (error counts, cooldowns, restart history) carries over unchanged across maintenance. New modulesrc/inspector/maintenance.py, new config sectionmaintenance:inconfig.example.yml, new per-containermaintenance_windowfield. - Recurring daily window — containers with
-
tests/test_maintenance.py— unit tests covering the disabled no-op, marker-file activation and auto-expiry, the recurring window (including midnight wraparound), enter/exit transition logging and event recording, and safe handling of marker-check errors.
[0.1.10] — 2026-06-14
Added
- Optional periodic health-history digest (
health_summary.enabled, defaultfalse). When enabled, Inspector logs a per-container summary of notable activity (failures, restarts, restart-limit hits, recoveries, not-found events — status changes are excluded) since the last digest, everyhealth_summary.interval_hours(default24). The first digest fires immediately on startup, covering the lastinterval_hours. The digest is always logged; it is pushed to Gotify (athealth_summary.priority, default3) only if any container had activity in the window. Depends ondatabase.enabled: truefor real data — ifhealth_summary.enabledistruebut event history isn't enabled, Inspector logs a one-time startup warning and digests report "no issues" for every container. New modulesrc/inspector/health_summary.py, new config sectionhealth_summary:inconfig.example.yml. EventStore.summarize(since)— per-container counts of notable event types at/after a timestamp, andEventStore.enabledproperty.tests/test_health_summary.py— unit tests covering the disabled no-op, the database-disabled warning, immediate first report with a capped window, interval gating, message formatting, and Gotify activity gating.
[0.1.9] — 2026-06-13
Added
- Optional SQLite event history (
database.enabled, defaultfalse). When enabled, Inspector persists notable per-container events (status changes, recoveries, failures, restarts, restart-limit hits, container-not-found) to a local SQLite database atdatabase.path(default/app/data/events.db), surviving container restarts. Old events are pruned according todatabase.retention_days(default 30) on startup and once per 24h thereafter. The database is opened in WAL mode for future concurrent read access (e.g. a read-only dashboard). A database error is logged but never crashes Inspector. New modulesrc/inspector/eventstore.py, new config sectiondatabase:inconfig.example.yml, new./datavolume mount incompose.yml/compose.prod.yml, new/app/datadirectory inDockerfile. tests/test_eventstore.py— unit tests covering the disabled no-op, row insertion, WAL mode, and retention pruning.
Changed
_check_one()signature gains aneventstore: EventStoreparameter (always passed; no-ops internally whendatabase.enabledisfalse), mirroring the existingnotifier: GotifyNotifierpattern.- README "What Inspector does not do" updated: live
ContainerStateis still in-memory and resets on restart, but event history can now persist via the new opt-in SQLite database.
[0.1.8] — 2026-06-13
Changed
- Log format overhauled for readability: container names are now
left-padded into a fixed-width column, the redundant logger-name field
(
inspector:) was dropped from both the file and console formatters, and a blank line now brackets each failure → restart → recovery "incident" so it stands out as a block. Message wording adjusted accordingly (e.g.gotify Restarting due to 3 consecutive UNHEALTHY checksinstead ofRestarting gotify due to 3 consecutive UNHEALTHY checks).
[0.1.7] — 2026-06-13
Fixed
- Docker socket permission errors at startup no longer crash with a raw
Python traceback.
run_loop()now catches the connection failure and logs an actionable message pointing atDOCKER_GID(stat -c '%g' /var/run/docker.sock).
[0.1.6] — 2026-06-13
Added
tests/test_config.py— 4 unit tests covering config loading, defaults, and validation (non-mapping containers rejected, unknownrestart_onstatuses rejected).tests/test_health.py— 8 unit tests covering all Docker status classifications (HEALTHY, UNHEALTHY, STARTING, NO HEALTH CHECK, NOT RUNNING, UNKNOWN, missing state, ERROR constant).tests/test_state.py— 10 unit tests covering failure counting, restart decision logic, and the full per-container state machine (dry-run, grace period, cooldown, hourly restart limit, recovery).
Changed
- CI workflow now runs the full unit test suite
(
python3 -m unittest discover -s tests -v) before building the image.
[0.1.5] — 2026-06-13
Changed
- CI and release workflows reverted to
runs-on: ubuntu-latest(the self-hosted runner approach introduced in v0.1.3 was abandoned — the Gitea-hosted runner handles builds reliably). - Gitea runner setup guide (
docs/gitea_runner_setup.md,ops/gitea-runner/) updated with label-mismatch troubleshooting and clearer registration instructions.
Added
- README documents the verified release baseline (v0.1.5) and the step-by-step release flow.
[0.1.4] — 2026-06-13
Added
- Inspector logo and wordmark assets:
logo/inspector_icon.{png,svg},logo/inspector_wordmark.{png,svg},logo/inspector_icon_chip.{png,svg}. - README header now displays the Inspector wordmark image.
Changed
- Previous logo files moved to
logo/archive/. .gitignore— addedact_data/(localactrunner data directory).
[0.1.3] — 2026-06-13
Changed
- CI and release workflows switched to
runs-on: self-hosted. - CI simplified to use
docker builddirectly (removeddocker/setup-buildx-actiondependency).
[0.1.2] — 2026-06-13
Changed
- Release workflow rewritten to use native
docker login/docker pushshell commands, replacing thedocker/build-push-actionthird-party action.
Added
workflow_dispatchtrigger on the release workflow — enables manual image builds without a tag push (useful for testing the CI publish path).
[0.1.1] — 2026-06-13
Fixed
- Release workflow secret names corrected:
GITEA_REGISTRY_USER→REGISTRY_USER,GITEA_REGISTRY_TOKEN→REGISTRY_TOKEN. The v0.1.0 release workflow referenced the wrong secret names, causing the publish step to fail.
[0.1.0] — 2026-06-13
Added
- Initial Inspector v1: self-hosted Python Docker container health watcher.
- Multi-container support via a single
config.yml— replaces the old pattern of one script + one systemd service per watched container. - Per-container state machine with independent failure counters.
- Health status classification: HEALTHY, UNHEALTHY, STARTING, NO HEALTH CHECK, NOT RUNNING, CONTAINER NOT FOUND, ERROR, UNKNOWN.
- Restart logic: fires after
max_error_countconsecutive failures. - Cooldown: post-restart window that blocks further restarts.
- Grace period: post-restart window that suppresses failure counting while a container boots.
- Hourly restart cap (
max_restarts_per_hour) to break endless loops; fires a critical Gotify alert when hit. - Optional Gotify notifications on state transitions (not per-loop).
- Dry-run mode: logs decisions without executing restarts.
- Rotating log file (
TimedRotatingFileHandler, midnight, 30-day retention) with colored console output and plain-text file output. Dockerfile—python:3.12-slimbase, non-rootinspectoruser (UID/GID 1000).compose.yml— dev compose: builds from source, mounts Docker socket.compose.prod.yml— prod compose: pulls published image from Gitea registry, no local build.requirements.txt— pinned:docker==7.1.0,PyYAML==6.0.2,requests==2.32.3,colorlog==6.9.0.Makefilewithbuild,smoke,compose-config,tag,push-local,release,clean,version,helptargets.- Gitea Actions CI (
ci.yml): byte-compile + Docker build on every push. - Gitea Actions release (
release.yml): build and publish four image tags onv*.*.*tag push. scripts/bump_version.sh— updates__version__insrc/inspector/__init__.py.scripts/smoke_test.sh— end-to-end dry-run test against the live Docker socket.- README with full feature documentation, configuration reference, Portainer deployment guide, and troubleshooting table.