41 KiB
Executable File
Changelog
All notable changes to Inspector are documented here.
Format follows Keep a Changelog. Version numbers follow Semantic Versioning.
[0.9.4] — 2026-10-28
Fixed
-
Containers without a Docker health check now "recover" properly. Many images don't define a health check, so Docker reports them as
NO HEALTH CHECKeven when they work. Inspector only treatedHEALTHYas back to normal, so for these containers:- the error count never reset, and one-off failures days apart added up to a restart;
- no "recovered" alert was sent;
- with Restart, then redeploy, the next unrelated problem skipped the restart and redeployed the whole stack.
Now
NO HEALTH CHECKcounts as working (unless you made it a restart trigger): the count resets, the "recovered" alert is sent ("back to running (no health check)"), and the next problem starts with a plain restart. Containers with a health check behave exactly as before.
[0.9.3] — 2026-10-28
Changed
- Every time follows the
TZof the Inspector stack. Before, the dashboard, Events page and alerts were always in US Pacific time while the maintenance window and logs usedTZ, so anyone outside Pacific saw times hours off (e.g. a 02:00 Berlin window shown as 5:00 PM). Now the dashboard, the Events page (including where each day starts), alert times, cooldown times and logs all useTZ. - Alert times include the zone abbreviation (
10-28 09:27 AM CET), and "Cooldown until" in alerts, event messages and logs is a local time instead of a UTC timestamp. - The active time zone is shown at the bottom of every Settings page, on the Events page and on the Maintenance page.
- If you never set
TZ, times are now shown in UTC instead of Pacific. SetTZin the stack (e.g.America/New_York). An unrecognized name logs a warning and falls back to UTC.
Docs
- INSTALL:
TZis now marked as needed, with a "Why the time zone matters" section (maintenance window, alerts, dashboard, logs), how to find your zone name, and a troubleshooting row. Guide: a Time zone section and a first-login check. Compose and README updated.
[0.9.2] — 2026-10-28
Fixes from a code and security review. None of them change how containers are monitored or recovered.
Fixed
- A bad value saved from Settings can no longer stop Inspector from starting.
The forms now reject invalid times (maintenance start/end), unknown log levels and
out-of-range numbers (for example
interval_seconds: 0, which made the loop spin). Ifconfig.ymlalready has such a value, Inspector starts with the default and logs a warning instead of crashing in a restart loop. - The dashboard password can no longer be cleared. Saving an empty password (or username) is refused, and an empty password never signs anyone in. Before, clearing it left the running dashboard open to anyone.
- Passwords with non-ASCII characters (é, ü, £ …) now work. Before, every login attempt with one crashed.
- The Events page no longer errors for a moment after a container is removed.
- The HTTP check hint no longer suggests
302as an expected status; redirects are followed, so the final page's code is what counts.
Security
- Sign-in throttling: after 5 failed sign-ins (form or Basic Auth) from one address within 5 minutes, that address is refused for the rest of the window.
- Changing the dashboard username or password signs out existing sessions. Expired sessions are cleaned up.
- Request bodies are limited to 1 MB; an invalid
Content-Lengthgets a 400. - The Gotify token is sent in the
X-Gotify-Keyheader instead of the URL, so it no longer appears in Gotify or reverse-proxy access logs. - Portainer token environment variables must be named
PORTAINER_…(a name already in yourconfig.ymlkeeps working), so Test connection can't be used to send other container secrets to an arbitrary URL. - Single quotes are escaped in HTML, and the delete confirmations build their text safely.
- Two settings saves at the same moment can no longer overwrite each other.
requestsupdated to 2.32.5 (CVE-2024-47081).
[0.9.1] — 2026-10-28
Fixed
- A dependency on a disabled container no longer blocks recovery forever. If
depends_onnamed a container that is disabled in Inspector, the dependent app was never restarted. Inspector now only waits for dependencies it is watching, and the container page warns when the selected dependency is disabled. PORTAINER_API_TOKENnow reaches the container. The publiccompose.ymldidn't pass it in, so setting it as a Portainer stack variable had no effect. Existing stacks need the line- PORTAINER_API_TOKEN=${PORTAINER_API_TOKEN:-}added underenvironment:(Stacks → inspector → Editor).- The "depends_on … is disabled or not found" warning was logged on every check (since 0.8.0). It is now logged once, and again only when the container settings change.
Changed
- Guide: new sections 6. Apps behind a VPN container (why depends_on matters, how to spot a VPN-routed app, setup, what Inspector does) and 7. Portainer stack redeploy (why it's needed, what a redeploy does to the whole stack, requirements, where to find every value, adding the token, the other settings, Test connection errors).
- Clearer form hints:
vpn_routedonly affects logging;depends_onworks for any dependency; where to find the endpoint ID.
[0.9.0] — 2026-10-28
Added
- Every container option is now in the dashboard. The container page has three
new groups:
- Reachability: HTTP check (URL, timeout, expected status) and Uptime Kuma monitor.
- Dependencies:
depends_on(pick from your containers) andvpn_routed. - Recovery:
recovery_actionwith plain-language descriptions, plus the Portainer environment and stack name for stack redeploys.
- Integrations settings. New Uptime Kuma and Portainer pages under
Settings. Portainer environments can be added, edited, renamed (containers
that use them are updated) and deleted (refused while a container uses one).
The API token can be an environment variable name (recommended) or a masked
value saved in
config.yml. - Test connection buttons for Uptime Kuma (lists monitors) and each Portainer environment (lists stacks on the endpoint), using the values in the form even before saving.
- The All containers grid shows each container's extras (HTTP check, Uptime Kuma, dependency, recovery action).
- Guide: new sections on reachability checks and apps behind a VPN container.
Changed
- Uptime Kuma and Portainer changes take effect on the next check; Inspector rebuilds those clients when their settings change instead of needing a restart.
- Saving a container form unchanged now leaves
config.ymlbyte-identical. Blank optional fields are removed instead of written as empty values (color: '',maintenance_window: false), and an unchangedrestart_onlist keeps its order and the comments after it. An explicitrecovery_action: restart_containeris kept.
[0.8.0] — 2026-10-27
Added
- Install from Portainer with no command line. Mount one folder per purpose
(
/app/config,/app/logs,/app/data) and deploy. If there's no config yet, Inspector writes a starterconfig.yml(dashboard on, event history on, no containers). Dashboard login comes fromINSPECTOR_USERNAME/INSPECTOR_PASSWORD; without a password, one is generated and printed once in the container logs. - Docker socket access is automatic. A new entrypoint starts as root only long
enough to read the socket's group and fix ownership of the mounted folders, then
runs Inspector as
PUID:PGID(default1000:1000) with that group added.DOCKER_GID/group_addare no longer needed. - In-app guide at
/guide/with screenshots, linked from a new Help tab in the header. Also in the repo atsrc/inspector/assets/guide/guide.html. maintenance_windowswitch on the container form, so containers can opt in to the daily maintenance window without editing YAML.
Fixed
- Containers added from the UI are now watched on the next check. Before, the loop only logged that a restart was needed, despite what the UI said. Containers removed or disabled from the UI are dropped the same way.
- Status page could fail to render while a container was being added or removed (it iterated the live state dict and assumed every entry was still in config).
Changed
- Config path:
--config, then$INSPECTOR_CONFIG, then/app/config.ymlif present (existing single-file setups keep working unchanged), then/app/config/config.yml. - Public
compose.yml,INSTALL.md, andREADME.mdrewritten for Portainer stacks; README gains the logo and screenshots.
Upgrade notes
- Existing deployments need no changes. To adopt the simpler layout, move
config.ymlinto the new config folder and switch to the new compose file.
[0.7.4] — 2026-10-27
Changed
- Settings rail container list is compact. Per-container entries are indented under "All containers" and fit on one line: colored dot, name, then enabled / disabled in small muted text. The max-error stat is gone. Long names truncate with an ellipsis, and the status stays visible.
[0.7.3] — 2026-10-27
Changed
- Containers moved into Settings. The top-nav Containers tab is gone; the
settings rail now has an "All containers" entry (the tile grid at
/containers) plus one entry per container (/containers/<name>). Both views render inside the same rail layout, and the Settings tab stays highlighted while you browse them. - Per-channel priority reference. The shared priority block is replaced by one reference per platform: Gotify (0 silent · 1–3 low · 4–7 normal · 8–10 high) and Pushover (-2 Lowest · -1 Low · 0 Normal · 1 High · 2 Emergency), with the Inspector → Pushover mapping noted under the Pushover one.
- restart_on redesign. The pill row is replaced by a grid of toggleable cards, each with a color accent and a plain-language description of the status, plus "Recommended" (UNHEALTHY + NOT RUNNING) and "Observe only" quick buttons.
Fixed
- Unchecking every
restart_onstatus now savesrestart_on: [](observe only). Previously an all-unchecked submit silently kept the old list.
[0.7.2] — 2026-10-27
Added
- Container CRUD from the UI.
/containers/newadds a new container toconfig.yml; a delete button on/containers/<name>removes one. The config change is picked up on the next polling cycle without a restart. - Test-notification buttons on the Gotify and Pushover sub-sections. The
button posts the current (unsaved) form values to
POST /api/test-notify/<channel>and shows the result inline. Unlike the regular notifier it does not swallow errors — HTTP failures are reported back to the UI. - Priority reference block on the Notifications section, including the
Inspector → Pushover
-2..2mapping. - Config diagnostics strip at the bottom of every settings page: shows the
live
config.ymlpath, whether it's writable, and the Inspector version. If the bind mount is read-only (a common deployment misconfiguration — a:romount means saves fail silently) a redREAD-ONLYbadge appears with a hint. - Containers listed in the settings left rail under their own group, plus a "+ Add container" link, so you can jump between sections and containers from the same navigation.
- Color input upgrade on container edit / new forms: ten preset color swatches, a native color picker, and the existing hex text field, all bound together.
Changed
- Toggle switches replace the stock checkbox + "requires restart" layout for
all
enabled-style fields (notifications, dashboard, database, health summary, maintenance, notification mute, container enabled, Inspectordry_run/heartbeat). Reads much cleaner. - Status page no longer prints "auto-refreshes every 30s" (the
<meta refresh>still ticks every 30s).
Internal
_apply_container_create()+_apply_container_delete()helpers alongside the existing_apply_settings_update(); container names are validated against^[a-zA-Z0-9][a-zA-Z0-9_-]{0,62}$and rejected if duplicate._render_diag_strip()usesos.access(path, os.W_OK)to detect read-only mounts.- Test-notification bypasses
GotifyNotifier.notify()/PushoverNotifier.notify()so HTTP failures are surfaced (the real notifier intentionally swallows them to avoid breaking the inspector loop).
[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.