Docker-watchdog with per-container restart rules, Gotify/Pushover notifications, and an embedded dashboard + web settings page. See README.md for an overview and INSTALL.md for the Docker quickstart.
607 lines
26 KiB
Markdown
Executable File
607 lines
26 KiB
Markdown
Executable File
# Changelog
|
||
|
||
All notable changes to Inspector are documented here.
|
||
|
||
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||
Version numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||
|
||
---
|
||
|
||
## [0.7.0] — 2026-10-26
|
||
|
||
### Added
|
||
|
||
- **Web settings page** at `/settings` for editing `config.yml` without 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.yml` are preserved. Each save keeps a `.bak` of the prior file.
|
||
(In the Docker container `.bak` lives in the overlay fs because
|
||
`config.yml` is 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 and `inspector.dry_run`/`heartbeat`/`interval_seconds` take
|
||
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 a `ConfigRef`, re-reads config each
|
||
iteration, and rebuilds `enabled_configs`/`check_order` so edits apply
|
||
without a restart.
|
||
- `DashboardServer` constructor simplified: now takes `ConfigRef`,
|
||
`states`, `maintenance`, `notification_mute` (per-section configs are
|
||
resolved live via `ConfigRef.get()`).
|
||
- Added `ruamel.yaml==0.18.6` to `requirements.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 optional `device` and `sound`
|
||
overrides.
|
||
- Priority mapping from Inspector's Gotify-style integer scale to Pushover's
|
||
`-2..2` range: `<=2 → -1`, `3..4 → 0`, `5..6 → 1`, `>=7 → 2`. Emergency
|
||
tier (`2`) ships default `retry`/`expire` values and triggers the usual
|
||
Pushover acknowledgement prompt.
|
||
- `MultiNotifier` internally fans each `notify()` 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.
|
||
- All countdowns reuse the existing 1-second client-side ticker (no page reload needed).
|
||
- `/api/status` now returns `maintenance_scheduled` (bool) and `maintenance_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 window
|
||
- `Next 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/status` now returns `phase`, `phase_until` (ISO UTC or null), `last_check_at`
|
||
(ISO UTC or null), and `interval_seconds` per container entry.
|
||
|
||
---
|
||
|
||
## [0.5.2] — 2026-06-15
|
||
|
||
### Added
|
||
|
||
- Portainer environments now support `api_token: "ptr_..."` to embed the API token
|
||
directly in `config.yml`, as an alternative to `api_token_env` (env var lookup).
|
||
`api_token` takes 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_token` and
|
||
`api_token_env` options so the fix is self-evident in the logs.
|
||
- `_find_stack()` now logs a debug line with how many stacks were scanned and which
|
||
`endpoint_id` was used, aiding multi-environment Portainer diagnosis.
|
||
|
||
---
|
||
|
||
## [0.5.1] — 2026-06-15
|
||
|
||
### Fixed
|
||
|
||
- `health_summary` digest crash: `KeyError: 'portainer_redeploy_failed'` on the
|
||
first periodic digest when event history contained Portainer redeploy events.
|
||
`health_summary._LABELS` was missing the three Portainer event types
|
||
(`portainer_redeploy`, `portainer_redeploy_failed`, `portainer_redeploy_skipped`)
|
||
that were added to `SUMMARY_EVENT_TYPES` in 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 plain `docker 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. Requires
|
||
`portainer_stack_name` like `portainer_stack_redeploy`, and honors the same
|
||
`depends_on` gating.
|
||
|
||
## [0.4.2] — 2026-06-15
|
||
|
||
### Added
|
||
- Result notifications for recovery actions: a successful `docker restart` /
|
||
`start` now sends a `✅ … completed` notification, 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 no `curl`).
|
||
|
||
### 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. for `depends_on` status tracking without Inspector
|
||
restarting it itself). Omitting `restart_on` entirely 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` (calls `docker start` instead of
|
||
`docker restart`), and `portainer_stack_redeploy`.
|
||
- `portainer_stack_redeploy` recovers containers whose
|
||
`network_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. New `integrations.portainer`
|
||
config: named environments (token loaded from `api_token_env`, never
|
||
logged or stored), `default_environment`, `stack_redeploy_cooldown_seconds`,
|
||
`post_redeploy_wait_seconds`. New per-container `portainer_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 its `depends_on` container isn't
|
||
healthy/starting; `vpn_routed: true` logs 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](README.md#portainer-stack-redeploy-recovery)
|
||
in the README.
|
||
- New per-container notification mute: `notification_mute` config
|
||
(`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 types `notification_mute_enter`/`exit`. See
|
||
[Muting notifications](README.md#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_check` now classifies outcomes into three explicit tiers: the
|
||
configured `expected_status` = up, a connection refused / timeout / DNS
|
||
failure = unreachable (reported as the synthetic status code `0`), 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](https://github.com/louislam/uptime-kuma) status page
|
||
(`integrations.uptime_kuma` + per-container `uptime_kuma.monitor_name`).
|
||
A failing check on an otherwise-healthy container surfaces as a new
|
||
`UNREACHABLE` status (red badge on the dashboard); opt it into a
|
||
container's `restart_on` to 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 in `config.yml`.
|
||
|
||
---
|
||
|
||
## [0.2.4] — 2026-06-14
|
||
|
||
### Fixed
|
||
- Sign out: removed the redirect-if-authenticated on `GET /login` so 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: true` config 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](https://fonts.google.com/specimen/Google+Sans+Flex)
|
||
(loaded from Google Fonts; falls back to the system sans-serif if
|
||
unavailable). New `GET /static/icon.png` endpoint (Basic Auth required,
|
||
same as every other dashboard route) serves the icon from a new
|
||
`src/inspector/assets/` package directory.
|
||
|
||
---
|
||
|
||
## [0.2.0] — 2026-06-14
|
||
|
||
### Added
|
||
- Optional embedded web dashboard (`dashboard.enabled`, default `false`),
|
||
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 (stdlib
|
||
`http.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, if `database.enabled: true`.
|
||
- **Maintenance toggle** — when `maintenance.enabled: true`, per-container
|
||
"Enter/Exit maintenance" buttons `POST /maintenance/<name>/enter|exit`,
|
||
touching/removing the same marker files described under "Ad-hoc marker
|
||
files". Returns `409` if maintenance windows are disabled, `404` for an
|
||
unknown container.
|
||
New module `src/inspector/dashboard.py`, new config section `dashboard:`
|
||
in `config.example.yml`.
|
||
- `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`, default `false`).
|
||
Two complementary mechanisms, both gated by this one flag:
|
||
- **Recurring daily window** — containers with `maintenance_window: true`
|
||
are "in maintenance" every day between `maintenance.start` and
|
||
`maintenance.end` (default `00:00`–`06:00`, local time via `TZ`, may
|
||
wrap past midnight).
|
||
- **Ad-hoc marker files** — `touch`/`rm`
|
||
`<maintenance.dir>/<container>` (default
|
||
`/app/data/maintenance/<container>`) for unplanned maintenance. A
|
||
marker older than `maintenance.max_hours` (default `8`) 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_exit` events (excluded from the
|
||
health-history digest) mark the transitions. State (error counts,
|
||
cooldowns, restart history) carries over unchanged across maintenance.
|
||
New module `src/inspector/maintenance.py`, new config section
|
||
`maintenance:` in `config.example.yml`, new per-container
|
||
`maintenance_window` field.
|
||
- `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`,
|
||
default `false`). 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,
|
||
every `health_summary.interval_hours` (default `24`). The first digest
|
||
fires immediately on startup, covering the last `interval_hours`. The
|
||
digest is always logged; it is pushed to Gotify (at
|
||
`health_summary.priority`, default `3`) only if any container had
|
||
activity in the window. Depends on `database.enabled: true` for real
|
||
data — if `health_summary.enabled` is `true` but event history isn't
|
||
enabled, Inspector logs a one-time startup warning and digests report
|
||
"no issues" for every container. New module
|
||
`src/inspector/health_summary.py`, new config section
|
||
`health_summary:` in `config.example.yml`.
|
||
- `EventStore.summarize(since)` — per-container counts of notable event
|
||
types at/after a timestamp, and `EventStore.enabled` property.
|
||
- `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`, default `false`).
|
||
When enabled, Inspector persists notable per-container events (status
|
||
changes, recoveries, failures, restarts, restart-limit hits,
|
||
container-not-found) to a local SQLite database at `database.path`
|
||
(default `/app/data/events.db`), surviving container restarts. Old
|
||
events are pruned according to `database.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 module
|
||
`src/inspector/eventstore.py`, new config section `database:` in
|
||
`config.example.yml`, new `./data` volume mount in `compose.yml` /
|
||
`compose.prod.yml`, new `/app/data` directory in `Dockerfile`.
|
||
- `tests/test_eventstore.py` — unit tests covering the disabled no-op,
|
||
row insertion, WAL mode, and retention pruning.
|
||
|
||
### Changed
|
||
- `_check_one()` signature gains an `eventstore: EventStore` parameter
|
||
(always passed; no-ops internally when `database.enabled` is `false`),
|
||
mirroring the existing `notifier: GotifyNotifier` pattern.
|
||
- README "What Inspector does not do" updated: live `ContainerState` is
|
||
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 checks` instead of
|
||
`Restarting 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 at `DOCKER_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, unknown `restart_on`
|
||
statuses 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` — added `act_data/` (local `act` runner data directory).
|
||
|
||
---
|
||
|
||
## [0.1.3] — 2026-06-13
|
||
|
||
### Changed
|
||
- CI and release workflows switched to `runs-on: self-hosted`.
|
||
- CI simplified to use `docker build` directly (removed
|
||
`docker/setup-buildx-action` dependency).
|
||
|
||
---
|
||
|
||
## [0.1.2] — 2026-06-13
|
||
|
||
### Changed
|
||
- Release workflow rewritten to use native `docker login` / `docker push`
|
||
shell commands, replacing the `docker/build-push-action` third-party
|
||
action.
|
||
|
||
### Added
|
||
- `workflow_dispatch` trigger 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_count` consecutive 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-slim` base, non-root `inspector` user
|
||
(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`.
|
||
- `Makefile` with `build`, `smoke`, `compose-config`, `tag`, `push-local`,
|
||
`release`, `clean`, `version`, `help` targets.
|
||
- Gitea Actions CI (`ci.yml`): byte-compile + Docker build on every push.
|
||
- Gitea Actions release (`release.yml`): build and publish four image tags
|
||
on `v*.*.*` tag push.
|
||
- `scripts/bump_version.sh` — updates `__version__` in `src/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.
|