12d532da7b
Closes #1, #4, #6, #7. Container lifecycle (#1, #7): - start_container, stop_container, restart_container, pause_container, unpause_container — all via SYNO.Docker.Container with JSON-encoded name parameter, routed through _resolve_container_name for hash- prefix resolution. stop is live-verified; the other four are implemented by symmetry on the same API surface. inspect_image (#4): - Returns full image detail (layers, env, ports, entrypoint/cmd, labels) via SYNO.Docker.Image/get. Accepts name:tag, registry- prefixed names, and bare hashes. Defensive response parsing handles both wrapped (details.*) and flat envelopes. system_overview (#6): - Aggregates CPU %, RAM, network and block I/O across all running containers plus running/stopped counts. No new DSM endpoint — composed from list + stats, reusing the container_stats CPU formula. Per-source errors are non-fatal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
372 lines
17 KiB
Markdown
372 lines
17 KiB
Markdown
# Changelog
|
|
|
|
All notable changes to this project will be documented in this file.
|
|
|
|
## [0.4.0] - 2026-05-18
|
|
|
|
### Added
|
|
|
|
**Container lifecycle (#1, #7)** — five new tools fill the gap between
|
|
"project" and "exec" operations. Until now the only way to stop or
|
|
restart a single container was to stop the whole project. All five
|
|
use `SYNO.Docker.Container` (version=1) with the same JSON-encoded
|
|
`name` parameter pattern as `delete_container`, and route through
|
|
`_resolve_container_name` so a clean name like `jenkins` resolves
|
|
to DSM's hash-prefixed `f93cb8b504f7_jenkins`:
|
|
|
|
- `start_container` — start a stopped container (no confirmation).
|
|
- `stop_container` — stop a running container (confirmation required).
|
|
- `restart_container` — stop + start in one call (confirmation required).
|
|
- `pause_container` — SIGSTOP the container's processes (confirmation
|
|
required).
|
|
- `unpause_container` — SIGCONT the container's processes (no
|
|
confirmation; reversing a pause is non-destructive).
|
|
|
|
`stop` is live-verified against DSM; `start`, `restart`, `pause`, and
|
|
`unpause` are implemented by symmetry on the same API surface. If any
|
|
of them turns out to need a different parameter shape in production,
|
|
it will be reverse-engineered via DevTools capture in a follow-up.
|
|
|
|
**Image inspection (#4)** — `inspect_image` returns the full image
|
|
detail blob (layers + sizes, environment variables, exposed ports,
|
|
entrypoint/cmd, working directory, labels) via
|
|
`SYNO.Docker.Image/get`. Accepts the same identifier forms as
|
|
`delete_image` — `name:tag`, registry-prefixed `ghcr.io/foo/bar:v1`,
|
|
and bare hash — and resolves the user input against `list_images`
|
|
first so typos produce a clean "not found" rather than an opaque DSM
|
|
error. The response parser is defensive about wrapper shape
|
|
(`details.<docker fields>` vs. flat) so the tool keeps working if DSM
|
|
varies the envelope between firmware versions.
|
|
|
|
**System overview (#6)** — `system_overview` aggregates CPU %, RAM
|
|
used/limit, network I/O, and block I/O across all running containers
|
|
plus running/stopped counts. No new DSM endpoint: composed from
|
|
`SYNO.Docker.Container/list` + `SYNO.Docker.Container/stats`,
|
|
re-using the same Docker-formula CPU calculation as
|
|
`container_stats`. Errors from either upstream call are non-fatal —
|
|
the available section is rendered and the failure is surfaced under a
|
|
`Warnings:` block.
|
|
|
|
## [0.3.3] - 2026-05-18
|
|
|
|
### Fixed
|
|
|
|
- `delete_container`: `SYNO.Docker.Container/delete` requires three
|
|
parameters — `name` (JSON-encoded), `force=false`, and
|
|
`preserve_profile=false`. Previously only `name` was sent (without
|
|
JSON-encoding), causing DSM to reject the call with error 114.
|
|
- `delete_project`: DSM does **not** reject `Project/delete` on a
|
|
running project — it silently removes the registration and leaves
|
|
the containers running as orphans. The connector now blocks the call
|
|
itself when the project status is `RUNNING`, before issuing any DSM
|
|
request, and tells the user to `stop_project` first. The previous
|
|
implementation relied on a DSM-level rejection that never occurs in
|
|
practice; the corresponding unit test was a false positive (mocked
|
|
an error that real DSM never returns) and has been replaced with a
|
|
test that asserts `Project/delete` is never called for a `RUNNING`
|
|
project.
|
|
|
|
|
|
|
|
### Added
|
|
|
|
- `delete_project` — remove a Container Manager project's
|
|
registration via `SYNO.Docker.Project/delete` with the UUID
|
|
JSON-encoded as the `id` parameter (per DSM convention).
|
|
Mirrors the "Delete project" action in Container Manager: only the
|
|
registration is removed; the project folder and compose file remain
|
|
on the NAS. The success message explicitly states the folder was
|
|
preserved so the user is not surprised. Closes the project
|
|
lifecycle (create → start/stop/redeploy → delete).
|
|
Safety:
|
|
- Project-name validation runs before any I/O.
|
|
- A `_find_project` pre-flight returns "not found" with a clear
|
|
message rather than letting DSM reject an unknown UUID.
|
|
- The tool deliberately does NOT auto-stop a running project. If
|
|
DSM rejects the delete on a `RUNNING` project, the response tells
|
|
the user to `stop_project` first rather than silently halting
|
|
containers under the guise of a "delete" call.
|
|
- Requires `confirmed=True`; the preview shows name, UUID, status,
|
|
full path, and share path so the user can verify before deleting.
|
|
|
|
|
|
|
|
### Added
|
|
|
|
- `create_project` — register a new Container Manager project from a
|
|
compose YAML string. Three-step flow:
|
|
1. Create the target folder via `SYNO.FileStation.CreateFolder` with
|
|
`force_parent=true` (idempotent — does not fail if the folder
|
|
already exists, and creates missing intermediate directories).
|
|
Without this step, `SYNO.Docker.Project/create` fails with DSM
|
|
error code 2100.
|
|
2. `SYNO.Docker.Project/create` (form-encoded POST, JSON-encoded
|
|
string parameters per DSM convention) returns the new project's
|
|
UUID.
|
|
3. `trigger_build_stream` + `_wait_for_project_running` — reuses the
|
|
existing image-pull / start / poll machinery (including the
|
|
`BUILD_FAILED` early-exit from welle 2).
|
|
Defaults: `share_path` is derived from `compose_base_path` (e.g.
|
|
`/volume1/docker` + `myapp` → `/docker/myapp`). The compose content
|
|
is validated as YAML before any side effects. A pre-flight
|
|
`list_projects` check rejects duplicate names with a clear message
|
|
rather than leaving an orphaned folder on the NAS. Requires
|
|
`confirmed=True`; the preview shows the resolved share path and the
|
|
service count parsed from the compose content.
|
|
|
|
|
|
|
|
### Fixed
|
|
|
|
- `DsmClient.trigger_build_stream`: broaden transport-error handling
|
|
(M-4). `httpx.ReadTimeout` is still treated as a success signal (the
|
|
request was sent; DSM is processing it server-side), but all other
|
|
`httpx.HTTPError` subclasses (`ConnectError`, `ConnectTimeout`,
|
|
`WriteError`, `RemoteProtocolError`, …) are now converted into a
|
|
`SynologyError` with a clear message. Previously these propagated as
|
|
raw httpx exceptions and left callers with a stack trace.
|
|
- `redeploy_project`: when build_stream (or the polling step) fails
|
|
after the project has already been stopped, the response now
|
|
explicitly tells the user that the project is in `STOPPED` state and
|
|
must be recovered with `start_project` or another `redeploy_project`
|
|
call. Previously the response suggested "use stop + start
|
|
separately", which was misleading because stop had already happened.
|
|
- `_wait_for_project_running`: exit the polling loop early on
|
|
`BUILD_FAILED` / `ERROR` (M-5). DSM signals these statuses within
|
|
seconds of a failed image pull; the old polling loop kept waiting up
|
|
to 5 minutes for `RUNNING`. `redeploy_project` surfaces the terminal
|
|
status with a hint to `update_image_tag` and retry when the cause is
|
|
`BUILD_FAILED`.
|
|
- `system_prune` preview: count unused user-created networks alongside
|
|
dangling images and stopped containers (M-6). Built-in networks
|
|
(`bridge`, `host`, `none`) are excluded because Docker never prunes
|
|
them. Previously the preview noted "Unused networks: (not counted)",
|
|
even though the underlying `SYNO.Docker.Utils/prune` call deletes
|
|
them — users could lose networks they had not been warned about.
|
|
|
|
### Changed
|
|
|
|
- Minor version bump because `redeploy_project` and `system_prune`
|
|
return different strings (and `redeploy_project` returns earlier on
|
|
`BUILD_FAILED`). No tool signatures changed.
|
|
|
|
## [0.2.9] - 2026-05-18
|
|
|
|
### Fixed
|
|
|
|
- `__version__` is now derived from package metadata via
|
|
`importlib.metadata.version()`, eliminating the drift between
|
|
`pyproject.toml` and `src/mcp_synology_container/__init__.py` (was stuck
|
|
at `0.1.0` since the initial release).
|
|
- `compose.py`: reject project names that contain path separators or other
|
|
unsafe characters before they reach `_find_compose_path`. Previously a
|
|
name like `"../../etc"` could traverse out of `compose_base_path` when
|
|
the project was not yet registered with Container Manager. The new
|
|
`_validate_project_name` helper enforces `^[a-zA-Z0-9_-]+$` and is
|
|
applied to `read_compose`, `update_compose`, `update_image_tag`, and
|
|
`update_env_var`. Addresses M-3 from the v0.2.8 review.
|
|
|
|
### Docs
|
|
|
|
- `CHANGELOG.md` backfilled for releases 0.2.7 and 0.2.8 (entries had been
|
|
missed during those releases).
|
|
|
|
## [0.2.8] - 2026-04-21
|
|
|
|
### Added
|
|
|
|
- `tests/test_dsm_client.py` — comprehensive offline test suite for
|
|
`DsmClient`:
|
|
- `_scrub_url` and `_error_message` pure helpers.
|
|
- `request()` happy-path, API-not-cached, `_sid` scrubbing in
|
|
`HTTPStatusError`, sensitive-param log masking.
|
|
- Session re-auth retry: single-retry semantics, auth-manager-absent
|
|
path, re-auth failure path, thundering-herd (login called once under
|
|
concurrent 106 responses).
|
|
- `trigger_build_stream`: SSE fire-and-forget, JSON error detection,
|
|
`ReadTimeout` swallowing, HTTP-error scrubbing.
|
|
- `upload_text` and `download_text` happy-path + error-response branches.
|
|
- `_ensure_initialized` double-checked locking and negative-cache
|
|
cooldown behavior.
|
|
|
|
### Fixed
|
|
|
|
- `DsmClient._ensure_initialized`: cache failed init outcomes for 60 s so
|
|
that repeated tool calls during a credential outage (wrong password,
|
|
IP-blocked 407, DNS failure) do not keep hammering DSM. Each caller
|
|
receives the cached exception until the cooldown window expires, after
|
|
which a fresh attempt is made. Adds `INIT_ERROR_COOLDOWN` module
|
|
constant and `_init_error` / `_init_error_until` state. Addresses M4
|
|
from the 0.2.7 review.
|
|
|
|
## [0.2.7] - 2026-04-21
|
|
|
|
### Fixed
|
|
|
|
- `DsmClient`: scrub `_sid` query-parameter values from URLs embedded in
|
|
`httpx.HTTPStatusError` messages so the raw DSM session ID never reaches
|
|
log output or MCP tool responses (C1).
|
|
- `DsmClient`: re-auth lock now snapshots `_sid` before entering the lock
|
|
and skips the redundant login if another task has already refreshed the
|
|
session, eliminating duplicate logins on concurrent 106/107/119
|
|
responses (M3).
|
|
- `DsmClient.trigger_build_stream`: re-instates immediate JSON-error
|
|
detection (regression from 0.2.6). Inspects the `Content-Type` header
|
|
and reads a small capped prefix of the body for `application/json`
|
|
responses to surface DSM error codes without forcing the caller into a
|
|
multi-minute polling timeout. SSE responses remain fire-and-forget
|
|
(C2/M8).
|
|
- `compose.update_env_var`: parenthesise the apply-branch match condition
|
|
so `(isinstance AND startswith) OR (entry == var_name)` no longer
|
|
evaluates the equality branch for non-string entries — aligns the apply
|
|
side with the preview-side detection logic (M1).
|
|
|
|
### Changed
|
|
|
|
- All 23 `@mcp.tool()` functions: strip `-> str` return annotations and
|
|
trim docstrings to a single line (≤100 chars). FastMCP generates an
|
|
`outputSchema` entry for every annotated tool, which roughly doubles
|
|
the `tools/list` payload size; multi-line docstrings with
|
|
`Args:`/`Returns:` sections add further bulk that Claude Desktop must
|
|
parse on every connection.
|
|
|
|
### Chore
|
|
|
|
- `uv.lock` resynced (was stale at `0.2.2`).
|
|
- `.gitignore`: exclude `.claude/` per-user Claude Code settings.
|
|
- Mechanical `ruff check --fix` + `ruff format` cleanup (import sorting,
|
|
unused-import removal). No functional change.
|
|
|
|
## [0.2.6] - 2026-04-21
|
|
|
|
### Fixed
|
|
|
|
- `DsmClient.trigger_build_stream`: Claude Desktop aborts tool calls after
|
|
~4 minutes. The previous implementation read the first SSE chunk before
|
|
returning, which could block for the entire duration of an image pull.
|
|
Fixed by making the call truly fire-and-forget: the HTTP request is sent,
|
|
response headers are received (HTTP status check only), then the connection
|
|
is closed immediately without reading any SSE events. DSM continues the
|
|
build server-side regardless. The `_json` import added in 0.2.5 is removed.
|
|
|
|
## [0.2.5] - 2026-04-21
|
|
|
|
### Changed
|
|
|
|
- `redeploy_project`: Replaced the delete-before-start image workaround with
|
|
`SYNO.Docker.Project/build_stream` — the programmatic equivalent of the DSM
|
|
"Erstellen" (Build) button, confirmed via browser DevTools capture.
|
|
New 3-step flow for all project states:
|
|
1. Stop (skipped for STOPPED; error-suppressed for BUILD_FAILED)
|
|
2. `build_stream` — DSM pulls updated images and starts the project via SSE
|
|
3. Poll for RUNNING (timeout raised from 30 s to 5 min to accommodate image pulls)
|
|
`build_stream` errors are now fatal (abort the redeploy with a clear message).
|
|
|
|
### Added
|
|
|
|
- `DsmClient.trigger_build_stream(project_id)` — fires a streaming GET to
|
|
`SYNO.Docker.Project/build_stream`, reads the first SSE chunk to confirm DSM
|
|
accepted the request, then closes the connection. The build continues
|
|
server-side. Handles immediate JSON error responses; swallows `ReadTimeout`
|
|
(stream still open = build running).
|
|
|
|
## [0.2.4] - 2026-04-21
|
|
|
|
### Changed
|
|
|
|
- `redeploy_project`: Replaced broken `SYNO.Docker.Image/pull` with a
|
|
delete-before-start workaround. The tool now reads image tags from the
|
|
project's compose file via FileStation, deletes each cached image before
|
|
calling `start` (so DSM auto-pulls the latest version), then polls for
|
|
`RUNNING`. Image deletion is non-fatal — if it fails the project still starts.
|
|
Unified 4-step flow for all project states (RUNNING, STOPPED, BUILD_FAILED).
|
|
|
|
### Added
|
|
|
|
- `update_image_tag`: Auto-updates environment variables whose value equals
|
|
the numeric version prefix of the old tag when the new tag shares the same
|
|
`<digits>-<suffix>` pattern. For example, changing `2.558-jdk21` →
|
|
`2.560-jdk21` automatically updates `JENKINS_VERSION=2.558` to
|
|
`JENKINS_VERSION=2.560`. The preview (unconfirmed call) now lists which env
|
|
vars will be updated. Only triggers when the variable exists and the pattern
|
|
matches; no change for plain tags like `latest`.
|
|
|
|
## [0.2.3] - 2026-04-21
|
|
|
|
### Changed
|
|
|
|
- `CLAUDE.md` rewritten: removed all operator-specific infrastructure details
|
|
(hostnames, container names, image tags, personal notes). Kept DSM API quirks,
|
|
implementation rules, and tool inventory.
|
|
|
|
## [0.2.2] - 2026-04-21
|
|
|
|
### Fixed
|
|
|
|
- `redeploy_project` (BUILD_FAILED): Pull errors are no longer silently suppressed.
|
|
If the image pull fails (e.g. the tag in compose.yaml does not exist on the registry),
|
|
redeploy aborts immediately with a clear message pointing to `update_image_tag`.
|
|
|
|
## [0.2.1] - 2026-04-21
|
|
|
|
### Fixed
|
|
|
|
- `redeploy_project`: After issuing `start`, the tool now polls the project status every 2 seconds
|
|
for up to 30 seconds until the project reaches `RUNNING`. Previously DSM returned immediately
|
|
while containers were still starting, causing the project to appear as `exited` when checked
|
|
right after redeploy. On timeout a warning is returned instead of an error.
|
|
- `delete_image`: Now distinguishes between running and stopped container references.
|
|
A stopped container holding the image produces a clear hint to use `delete_container`
|
|
or `system_prune` instead of a generic "in use" error.
|
|
- `redeploy_project` (BUILD_FAILED path): Added explicit image pull step before restart
|
|
(`stop → pull → start`). Previously the old cached image could be reused.
|
|
|
|
### Added
|
|
|
|
- `delete_container` — delete a stopped container by name; refuses if container is still running;
|
|
requires `confirmed=True`.
|
|
|
|
## [0.2.0] - 2026-04-14
|
|
|
|
### Added
|
|
|
|
**Images**
|
|
- `list_images` — list local images sorted by size; marks images in use and with available updates
|
|
- `delete_image` — delete image by `name:tag` or hash; refuses if image is used by a container
|
|
|
|
**Container**
|
|
- `container_stats` — live CPU %, RAM used/limit, network I/O, block I/O
|
|
|
|
**System**
|
|
- `system_df` — Docker disk usage: image count/size, running/stopped containers, reclaimable space
|
|
- `system_prune` — remove dangling images, stopped containers, and unused networks
|
|
|
|
**Networks**
|
|
- `list_networks` — list all Docker networks with driver, subnet, gateway, attached containers
|
|
- `create_network` — create a new bridge (or other driver) network
|
|
- `delete_network` — delete a network; refuses if any container is still attached
|
|
|
|
### Fixed
|
|
|
|
- `get_container_status` now reads the correct DSM response fields (`details.State` for status/running, `profile.image` for image name) and displays IP addresses, port bindings, and mounts
|
|
- `redeploy_project` is now status-aware: RUNNING → stop + start; STOPPED → start directly; BUILD_FAILED → force-stop + start; unknown status returns a clear error with workaround hint
|
|
- Container names with hash prefix (e.g. `f93cb8b504f7_jenkins`) are now transparently stripped in `list_containers`, `container_stats`, `get_container_status`, `get_container_logs`, and `exec_in_container`
|
|
|
|
### Changed
|
|
|
|
- `DsmClient` gained a `post_request()` method for form-encoded POST operations required by image delete, network create/delete
|
|
|
|
## [0.1.0] - 2026-04-13
|
|
|
|
### Added
|
|
|
|
- Initial implementation
|
|
- **Projects**: `list_projects`, `get_project_status`, `start_project`, `stop_project`, `redeploy_project`
|
|
- **Containers**: `list_containers`, `get_container_status`, `get_container_logs`, `exec_in_container`
|
|
- **Compose**: `read_compose`, `update_compose`, `update_image_tag`, `update_env_var`
|
|
- **Images**: `check_image_updates`
|
|
- DSM session management with auto re-authentication on session timeout
|
|
- OS keyring integration for secure credential storage
|
|
- 2FA / device token support
|
|
- Interactive `setup` wizard and `check` connectivity command
|