Skip to content

Commit ffe0ca5

Browse files
committed
feat: enable image-updates & auto-heal from Telegram via /manage Features (v0.14.0)
1 parent 4c3b0cc commit ffe0ca5

19 files changed

Lines changed: 847 additions & 31 deletions

.claude/structure/bot.yaml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ files:
4242
- from: src.bot.ignore_command
4343
items: [ignore_command, ignores_command, IgnoreSelectionState, ignore_similar_callback, ignore_toggle_callback, ignore_all_callback, ignore_done_callback, ignore_cancel_callback]
4444
- from: src.bot.manage_command
45-
items: [manage_command, manage_back_callback, manage_status_callback, manage_resources_callback, manage_server_callback, manage_disks_callback, manage_ignores_callback, manage_ignores_container_callback, manage_mutes_callback, manage_delete_ignore_callback, manage_delete_mute_callback]
45+
items: [manage_command, manage_back_callback, manage_status_callback, manage_resources_callback, manage_server_callback, manage_disks_callback, manage_ignores_callback, manage_ignores_container_callback, manage_mutes_callback, manage_delete_ignore_callback, manage_delete_mute_callback, manage_features_callback, feat_image_toggle_callback, feat_heal_open_callback, feat_heal_toggle_callback, feat_heal_save_callback, AutoHealSelectionState]
4646
- from: src.services.container_control
4747
items: [ContainerController]
4848
- from: src.services.diagnostic
@@ -342,6 +342,29 @@ files:
342342
kind: function
343343
signature: (mute_manager: MuteManager, server_mute_manager: ServerMuteManager | None, array_mute_manager: ArrayMuteManager | None) -> Callable[[CallbackQuery], Awaitable[None]]
344344
description: Factory for mdm:* callback (unmutes, re-renders list)
345+
- name: AutoHealSelectionState
346+
kind: class
347+
description: Per-user in-memory container selections while the auto-heal picker is open
348+
- name: manage_features_callback
349+
kind: function
350+
signature: (image_update_monitor: Any = None, auto_heal_config: Any = None) -> Callable[[CallbackQuery], Awaitable[None]]
351+
description: Factory for manage:features panel (explains + toggles image updates and auto-heal)
352+
- name: feat_image_toggle_callback
353+
kind: function
354+
signature: (image_updates_config: Any, restart_cb: Callable[[], Awaitable[None]] | None = None) -> Callable[[CallbackQuery], Awaitable[None]]
355+
description: Factory for feat:img:on|off (persists image-update flag, restarts to apply)
356+
- name: feat_heal_open_callback
357+
kind: function
358+
signature: (state: ContainerStateManager, auto_heal_config: Any, selection_state: AutoHealSelectionState, protected_containers: list[str] | None = None) -> Callable[[CallbackQuery], Awaitable[None]]
359+
description: Factory for feat:heal (opens auto-heal container picker, seeds selection from config)
360+
- name: feat_heal_toggle_callback
361+
kind: function
362+
signature: (state: ContainerStateManager, selection_state: AutoHealSelectionState, protected_containers: list[str] | None = None) -> Callable[[CallbackQuery], Awaitable[None]]
363+
description: Factory for fh_tog:<container> (toggles a container in the picker)
364+
- name: feat_heal_save_callback
365+
kind: function
366+
signature: (auto_heal_config: Any, selection_state: AutoHealSelectionState, image_update_monitor: Any = None) -> Callable[[CallbackQuery], Awaitable[None]]
367+
description: Factory for fh_save (live-applies auto-heal container list, re-renders features)
345368
imports:
346369
- from: src.state
347370
items: [ContainerStateManager]

.claude/structure/root.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,23 @@ files:
2323
items: [create_bot, create_dispatcher, register_setup_wizard]
2424
- from: src.background
2525
items: [_BackgroundTasks]
26+
- from: src.restart
27+
items: [restart_bot]
2628
- from: src.startup
2729
items: [start_monitoring]
2830

31+
restart.py:
32+
description: Graceful in-place process restart via os.execv (applies config changes that need a fresh startup).
33+
exports:
34+
- name: restart_bot
35+
kind: function
36+
description: Stops polling, optionally broadcasts a notice, then re-execs `python -m src.main`.
37+
params: [{name: bot, type: Bot}, {name: dp, type: Dispatcher}, {name: chat_id_store, type: ChatIdStore}, {name: notice, type: "str | None"}]
38+
returns: Coroutine
39+
imports:
40+
- from: src.alerts.manager
41+
items: [ChatIdStore]
42+
2943
alert_proxy.py:
3044
description: AlertManagerProxy for multi-user alert delivery with queuing.
3145
exports:

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
All notable changes to UnraidMonitor will be documented in this file.
44

5+
## [0.14.0] - 2026-06-05
6+
7+
### Added
8+
- **Features panel in `/manage`** — a new ⚙️ Features section explains image-update detection and auto-heal, and lets you enable them from Telegram instead of editing `config.yaml`
9+
- **Enable image updates from Telegram** — one tap persists `image_updates.enabled` and restarts the bot to apply it
10+
- **Auto-heal container picker** — choose which containers get auto-restarted when unhealthy via a tappable picker (protected containers excluded); applies live with no restart, since the running `AutoHealer` shares the config object
11+
- **Discoverability hint** — the startup card and `/health` now point to `/manage` → ⚙️ Features whenever image updates or auto-heal is off
12+
13+
### Changed
14+
- `ImageUpdatesConfig` and `AutoHealConfig` gained runtime persistence (`set_enabled` / `set_containers`) mirroring the existing `ResourceConfig`/`UnraidConfig` pattern
15+
- Extracted a reusable `restart_bot()` helper (`src/restart.py`); the setup-wizard restart paths now use it
16+
517
## [0.13.0] - 2026-06-04
618

719
### Added

CLAUDE.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ src/main.py - Entry point, signal handling, wizard setup (delegates to startup.p
1717
src/startup.py - start_monitoring() orchestrator, provider/monitor initialization
1818
src/alert_proxy.py - AlertManagerProxy for multi-user alert delivery with queuing
1919
src/background.py - _BackgroundTasks tracker for graceful shutdown of monitors
20+
src/restart.py - restart_bot() graceful in-place process restart via os.execv (applies config that needs a fresh start)
2021
src/monitor_callbacks.py - Factory functions for monitor alert/mute callbacks
2122
src/constants.py - Named constants for all configuration defaults and thresholds
2223
src/config.py - Settings, configuration loading, YAML parsing, ConfigWriter
@@ -91,7 +92,7 @@ src/utils/version_store.py - read/write data/announced_version.json for startup
9192

9293
## Project Overview
9394

94-
Unraid Server Monitor Bot (v0.13.0) - A Docker-based Telegram bot for monitoring Unraid servers. Monitors Docker containers (events, logs, resources) and Unraid server health (CPU, memory, disks, array, UPS). Uses multi-provider LLM support (Anthropic, OpenAI, Ollama) for AI-powered diagnostics and natural language interaction. Sends alerts via Telegram with quick-action buttons.
95+
Unraid Server Monitor Bot (v0.14.0) - A Docker-based Telegram bot for monitoring Unraid servers. Monitors Docker containers (events, logs, resources) and Unraid server health (CPU, memory, disks, array, UPS). Uses multi-provider LLM support (Anthropic, OpenAI, Ollama) for AI-powered diagnostics and natural language interaction. Sends alerts via Telegram with quick-action buttons.
9596

9697
**Version string lives in TWO places**`pyproject.toml` (`version`) and `src/__init__.py` (`__version__`). Both must be bumped together on release; `tests/test_version.py` guards against drift. `BOT_VERSION` (in `src/bot/health_command.py`) resolves from installed package metadata when available, else falls back to `src.__version__` — the package is not installed in the Docker image, so the `__version__` fallback is what runs in production.
9798

@@ -187,7 +188,9 @@ Inline keyboard buttons use `prefix:data` format, parsed with `split(":", 1)` to
187188
- `res_set:container_name:metric:value` (apply threshold, 0 = reset to default)
188189
- `mem_kill:container_name`, `mem_restart_yes:container_name`, `mem_restart_no:container_name`, `mem_cancel_kill:container_name`
189190
- `nl_confirm:action:container` (e.g., `nl_confirm:restart:plex`), `nl_cancel`
190-
- `manage:section` (e.g., `manage:status`, `manage:resources`, `manage:server`, `manage:disks`, `manage:ignores`, `manage:mutes`, `manage:back`)
191+
- `manage:section` (e.g., `manage:status`, `manage:resources`, `manage:server`, `manage:disks`, `manage:ignores`, `manage:mutes`, `manage:features`, `manage:back`)
192+
- `feat:img:on` / `feat:img:off` (enable/disable image updates from /manage → Features; persists + restarts), `feat:heal` (open auto-heal container picker)
193+
- `fh_tog:container_name` (toggle a container in the auto-heal picker), `fh_save` (live-apply auto-heal container list, no restart)
191194
- `arr_mute:minutes` (mute array alerts, e.g., `arr_mute:60`)
192195
- `arr_thresh:metric:current` (show array threshold options, e.g., `arr_thresh:capacity:85`)
193196
- `arr_set:metric:value` (apply array threshold, e.g., `arr_set:array_usage:90`, 0 = reset to default)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "unraid-monitor-bot"
3-
version = "0.13.0"
3+
version = "0.14.0"
44
requires-python = ">=3.11"
55
dependencies = [
66
"docker>=7.0.0,<8.0.0",

src/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.13.0"
1+
__version__ = "0.14.0"

src/bot/health_command.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,15 @@ def build_status_lines(
7575
lines.append(f" Memory: {'✅ Running' if memory_monitor.is_running else '🔴 Stopped'}")
7676
else:
7777
lines.append(" Memory: ⚪ Disabled")
78+
image_updates_off = not image_update_monitor
7879
if image_update_monitor:
7980
lines.append(f" Image updates: {'✅ Running' if image_update_monitor.is_running else '🔴 Stopped'}")
8081
else:
8182
lines.append(" Image updates: ⚪ Disabled")
82-
if auto_heal_config is not None and auto_heal_config.enabled and auto_heal_config.containers:
83+
auto_heal_off = not (
84+
auto_heal_config is not None and auto_heal_config.enabled and auto_heal_config.containers
85+
)
86+
if not auto_heal_off:
8387
lines.append(f" Auto-heal: ✅ {len(auto_heal_config.containers)} container(s)")
8488
else:
8589
lines.append(" Auto-heal: ⚪ Disabled")
@@ -91,6 +95,10 @@ def build_status_lines(
9195
lines.append(f" Array: {'✅' if unraid_array_monitor.is_running else '🔴'}")
9296
else:
9397
lines.append(" Unraid: ⚪ Not configured")
98+
99+
if image_updates_off or auto_heal_off:
100+
lines.append("")
101+
lines.append("💡 Turn these on in /manage → ⚙️ Features")
94102
return lines
95103

96104

0 commit comments

Comments
 (0)