036429e9bf
DSM emits a readable plaintext build log over the build_stream HTTP body (one short status line per step) and closes the connection when the build is done. The 0.2.5 implementation sent the request and dropped the body unread, leaving users with nothing more than a BUILD_FAILED polling status and no actionable diagnostic. DsmClient.trigger_build_stream now consumes the body line-by-line and returns the collected log as a string. Wall-clock budget of 210 s (under the Claude Desktop ~4 min ceiling); on timeout the partial log is returned with a "[build_stream: timeout — stream still open server-side]" marker so callers know the build continues server-side. Per-chunk ReadTimeout is treated the same way. JSON error envelope, transport-error mapping (M-4), and SID-scrubbed HTTP-error formatting are unchanged. redeploy_project and create_project now parse the returned log via _parse_build_stream_log (any line containing "Error response from daemon:" or ending in " Error" counts as a failure). On a failed log the tools abort immediately, surface the daemon line(s) in the result (e.g. "Error response from daemon: manifest for nginx:9.9.9 not found: manifest unknown"), and skip the polling step. The BUILD_FAILED polling guard (M-5) stays as a second safety net for late failures where the stream was clean but the container exited after start. No new MCP tool: the build log is a live stream and cannot be re-fetched after the build ends, so it is surfaced during redeploy_project / create_project rather than exposed as a standalone get_project_build_log call. Minor version bump because redeploy_project and create_project return materially different strings on a failed build and exit earlier in the failure path. Signatures unchanged. Tests: streamed-log collection, daemon-error log, header ReadTimeout marker, per-chunk ReadTimeout partial log, wall-clock budget truncation, _parse_build_stream_log unit tests, redeploy/create end- to-end behavior with a failing log. Closes #2 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
520 lines
24 KiB
Markdown
520 lines
24 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to this project will be documented in this file.
|
||
|
||
## [0.6.0] - 2026-05-18
|
||
|
||
### Changed
|
||
|
||
**`build_stream` is no longer fire-and-forget (#2).** A live stream
|
||
capture confirmed that DSM emits a readable plaintext build log over
|
||
the HTTP body — one short status line per step — and closes the
|
||
connection when the build is done. The 0.2.5 implementation sent the
|
||
request and dropped the body unread, which meant a failed image pull
|
||
left users with nothing more than a `BUILD_FAILED` polling status and
|
||
no actionable diagnostic.
|
||
|
||
- `DsmClient.trigger_build_stream` now consumes the body line by line
|
||
and returns the collected log as a string instead of `None`. A wall-
|
||
clock budget of 210 s (`BUILD_STREAM_BUDGET`, kept under the Claude
|
||
Desktop ~4 min tool-call ceiling) caps the read; on timeout the
|
||
partial log is returned with a `[build_stream: timeout — stream
|
||
still open server-side]` marker appended so the caller knows the
|
||
build is still going server-side. Per-chunk `ReadTimeout` is treated
|
||
the same way — return what we have plus the marker.
|
||
- `redeploy_project` and `create_project` now parse the returned log
|
||
via `_parse_build_stream_log`, which classifies any line containing
|
||
`Error response from daemon:` or ending in ` Error` as a failure.
|
||
When the log contains errors the tools abort immediately, surface
|
||
the daemon line(s) in the result (e.g. `Error response from daemon:
|
||
manifest for nginx:9.9.9-nonexistent not found: manifest unknown`),
|
||
and skip the polling step entirely — DSM has already told us the
|
||
build is dead. The existing `BUILD_FAILED` / `ERROR` polling guard
|
||
(M-5) stays as a second safety net for late failures where the
|
||
stream log was clean but the container exited after start.
|
||
- No new MCP tool: the build log is a live stream and cannot be
|
||
re-fetched after the build ends, so it is surfaced during
|
||
`redeploy_project` / `create_project` rather than exposed as a
|
||
standalone `get_project_build_log` call.
|
||
|
||
JSON error envelope handling, transport-error mapping (M-4), and the
|
||
SID-scrubbed HTTP-error formatting are unchanged. Closes #2.
|
||
|
||
Minor version bump because `redeploy_project` and `create_project`
|
||
return materially different strings on a failed build and exit
|
||
earlier in the failure path; signatures unchanged.
|
||
|
||
## [0.5.1] - 2026-05-18
|
||
|
||
### Fixed
|
||
|
||
- `pull_image` (#3): the 0.5.0 implementation called
|
||
`SYNO.Docker.Registry/pull_start`, which DSM rejects with
|
||
"Method does not exist". A live DSM API capture confirmed that
|
||
`pull_start` actually lives on `SYNO.Docker.Image`, not
|
||
`SYNO.Docker.Registry`. The Registry API only exposes the
|
||
synchronous read-only methods (`search`, `tags`, `get/set/create/
|
||
delete`, `using`). Parameters are unchanged — both `repository`
|
||
and `tag` are still JSON-encoded — and the `Image/list` polling
|
||
for completion detection works as before. Note: #3 is already
|
||
closed; this is the follow-up fix.
|
||
|
||
## [0.5.0] - 2026-05-18
|
||
|
||
### Added
|
||
|
||
**Welle B Teil 1 — Registry tools (#3, #5).** Three new `SYNO.Docker.Registry`
|
||
tools, reverse-engineered from a live DSM API capture (the n4s4 reference
|
||
disagrees on parameter names and methods; the live capture wins):
|
||
|
||
- `search_registry` (#5) — search the active Docker registry by query string.
|
||
Uses `SYNO.Docker.Registry/search` (version 1) with the query JSON-encoded
|
||
as `q`, plus `offset`, `limit`, and `page_size`. Renders stars, downloads,
|
||
the official-image flag, and a truncated description per hit, and shows
|
||
the total match count so the caller knows when to raise `limit`. No
|
||
confirmation gate (read-only).
|
||
- `list_image_tags` — list available tags for a repository (bonus tool).
|
||
Uses `SYNO.Docker.Registry/tags` (version 1) with the repository
|
||
JSON-encoded as `repo` — note the parameter name diverges from the n4s4
|
||
reference which uses `name`. The response shape is unusual: DSM returns
|
||
the tag list as the envelope's `data` field directly (not wrapped in a
|
||
sub-key), so the tool accepts both shapes defensively. Output is capped
|
||
by `limit` (default 50) because popular images like `alpine` ship 200+
|
||
tags. No confirmation gate (read-only).
|
||
- `pull_image` (#3) — pull an image into the local cache via
|
||
`SYNO.Docker.Registry/pull_start` (version 1) with both `repository` and
|
||
`tag` JSON-encoded. Requires `confirmed=True`. DSM exposes no confirmed
|
||
`pull_status` method, so completion is detected by polling
|
||
`SYNO.Docker.Image/list` for the new `repository:tag` pair with a 2–10 s
|
||
backoff schedule and a 240 s overall budget (kept under the Claude
|
||
Desktop ~4 min tool-call ceiling). A timeout returns a non-fatal "still
|
||
running" hint pointing at `list_images` instead of raising — DSM keeps
|
||
pulling server-side regardless. Short-circuits when the image is already
|
||
present locally so a repeated call is cheap. Closes #3 and #5.
|
||
|
||
Tool count rises from 31 to 34.
|
||
|
||
### Fixed
|
||
|
||
- `inspect_image` rendering polish (follow-up to #4 after the 0.4.2
|
||
parameter-contract fix). Three live-NAS observations that the
|
||
initial implementation got wrong:
|
||
- **Header showed `?:?`** — DSM `SYNO.Docker.Image/get` returns
|
||
`image=""` and `tag=""` when the lookup is by name:tag, so the
|
||
response fields are unreliable. The header now echoes the user-
|
||
supplied `image_id` (e.g. `gitea/gitea:1.26.1`), falling back to
|
||
the sha256 `id` only if `image_id` itself were empty.
|
||
- **Ports rendered as raw Python dicts** like
|
||
`{'port': '22', 'protocol': 'tcp'}`. The `ports` array is actually
|
||
a list of `{"port", "protocol"}` objects; each entry is now
|
||
formatted as `22/tcp`.
|
||
- **Environment rendered as raw Python dicts** like
|
||
`{'key': 'PATH', 'value': '...'}`. The `env` array is actually a
|
||
list of `{"key", "value"}` objects; each entry is now formatted
|
||
as `PATH=/usr/local/...`.
|
||
`cmd`, `entrypoint`, and `volumes` (plain string arrays) were
|
||
already correct and are unchanged. Referencing #4 (already closed).
|
||
|
||
## [0.4.2] - 2026-05-18
|
||
|
||
### Fixed
|
||
|
||
- `inspect_image` (#4): the 0.4.0 implementation called
|
||
`SYNO.Docker.Image/get` with `name` + `tag` + `id` parameters, which
|
||
DSM rejected with error 114 ("invalid parameter"). Reverse-
|
||
engineering the live API capture revealed the correct contract:
|
||
one JSON-encoded parameter named `identity` that accepts either form:
|
||
- `name:tag` (e.g. `"gitea/gitea:1.26.1"`)
|
||
- `sha256:<hash>` (e.g. `"sha256:cd21e54e..."`)
|
||
The pre-resolution lookup against `list_images` is no longer needed —
|
||
the user input is JSON-encoded and passed straight through.
|
||
- `inspect_image` response parsing: the endpoint does NOT return
|
||
`details.Config.*` / `RootFS.Layers` (the Docker-engine inspect shape
|
||
the previous code assumed). The actual top-level fields are `image`,
|
||
`tag`, `id`, `digest`, `size`, `virtual_size`, `author`,
|
||
`docker_version`, `cmd`, `entrypoint`, `env`, `ports`, `volumes`.
|
||
Layer rendering is removed (the endpoint does not surface layers);
|
||
digest, author, docker version, and volumes are now displayed.
|
||
|
||
## [0.4.1] - 2026-05-18
|
||
|
||
### Removed
|
||
|
||
- `pause_container` and `unpause_container` (added in 0.4.0) — live
|
||
test against DSM revealed that Container Manager on this firmware
|
||
does not expose pause/resume at all. The GUI action menu only offers
|
||
Start / Stop / Force-Stop / Restart / Reset, and direct calls to
|
||
`SYNO.Docker.Container/pause` and `/unpause` return "Method does not
|
||
exist". Both tools have been removed rather than left as a broken
|
||
surface. The remaining three lifecycle tools (`start_container`,
|
||
`stop_container`, `restart_container`) are unaffected. Tool count
|
||
drops from 33 to 31. Closes #7 (won't fix — DSM-side limitation).
|
||
|
||
## [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
|