Files
claudetools/.claude/skills/synology/SKILL.md
Howard Enos 5b3dd84fb9 synology: fix SSH backend syno* CLI resolution (full pre-test verification)
Found during a full command-surface recheck: every privileged SSH recipe
(shares/users/groups/acl) was broken — sudo secure_path drops /usr/syno/{bin,sbin}
so synoshare/synouser/synogroup/synoacltool were "command not found" (non-sudo
plain recipes worked because the admin login PATH has them).

- Inject SYNO_PATH into priv()/plain(); run priv via `sh -c` so operators work.
- synouser/synogroup use `--enum local` (not the invalid `--list`).
- acl quotes the share path (handles spaces, e.g. "Sandra Fish").
- services repointed to Web API (no synoservice on DSM 7.2; synosystemctl has no list-all).

Verified live: all Web API reads, all SSH reads (acl returns real Windows ACEs),
write path (share create/delete), and every destructive command correctly gated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 13:28:38 -07:00

12 KiB

name, description
name description
synology Control a Synology NAS via the DSM Web API (auth + API discovery + any method: system, storage, shares, users/groups, packages, services, FileStation) plus an SSH backend for the syno* CLI (ACLs, low-level internals). Reads run free; mutating calls gated behind --confirm. Default device Cascades cascadesDS; --vault for another client's NAS. Triggers: synology, diskstation, DSM, cascadesDS, NAS, synology drive, hyper/active backup, synology package/service/share/user, reboot the nas, synology acl.

synology — Synology DSM control (Web API + SSH)

Full control of a Synology NAS. Two complementary backends:

  1. DSM Web API (scripts/syno_client.py) — the structured surface. Auth → discover the device's own API map → call any API method. This is how you "control all functions": apis shows everything the device exposes, call <API> <method> reaches any of it.
  2. SSH syno* CLI (scripts/syno-ssh.sh) — the gaps the Web API doesn't cover: filesystem ACLs (synoacltool), share/user/group CLI internals, package CLI. We already use this for the Cascades share-permission inventory (docs/migration/synology-permission-inventory.md).

Default target = Cascades cascadesDS (192.168.0.120, DSM on :5000, admin) — credential vaulted at clients/cascades-tucson/synology-cascadesds.sops.yaml (host/port/ credentials.username/credentials.password). ext4 filesystem (NOT Btrfs → no Active Backup for Business). Shares: homes, Public, SalesDept, Server, Management. On a private LAN — every command needs the Cascades site VPN up (a connect failure almost always = VPN down). Point at any other NAS with --vault clients/<x>/synology-...sops.yaml (same field layout).

Web API commands

PY="$CLAUDETOOLS_ROOT/.claude/scripts/py.sh"; S=".claude/skills/synology/scripts/syno_client.py"
# verify + discover
bash "$PY" "$S" test                  # login, print model/serial/firmware/RAM/uptime
bash "$PY" "$S" apis [filter]         # the device's full API map (e.g. `apis backup`, `apis drive`)
# reads
bash "$PY" "$S" sysinfo               # SYNO.Core.System — model/serial/RAM/temp/uptime
bash "$PY" "$S" util                  # live CPU/mem/disk/net
bash "$PY" "$S" storage               # volumes/disks/RAID/usage (SYNO.Storage.CGI.Storage)
bash "$PY" "$S" shares | users | groups | packages | services | connections
bash "$PY" "$S" ls [folder_path]      # FileStation: no path = shares; else list a folder
# generic power tool — ANY API method the device exposes
bash "$PY" "$S" call <API> <method> [k=v ...] [k:=json] [--version N] [--post]
bash "$PY" "$S" call SYNO.Core.System.Status get
bash "$PY" "$S" call SYNO.Backup.Task list           # Hyper Backup tasks (if installed)
# writes — ALL gated --confirm
bash "$PY" "$S" pkg-start <id> --confirm | pkg-stop <id> --confirm
bash "$PY" "$S" reboot --confirm | shutdown --confirm
bash "$PY" "$S" call <API> <set-method> k=v --confirm   # any mutating method needs --confirm

call params: k=v for strings, k:=json for typed/array values (e.g. additional:='["size","owner"]'). A method whose name starts with a mutating verb (set/create/delete/start/stop/reboot/…) is auto-detected and refuses to run without --confirm.

SSH commands (the syno* CLI surface)

X=".claude/skills/synology/scripts/syno-ssh.sh"
bash "$X" info | df | shares | users | groups | packages | services   # reads
bash "$X" acl <share>                     # synoacltool -get /volume1/<share> (e.g. acl Server)
bash "$X" reboot --confirm                # synoshutdown -r (use when the Web-API reboot 103s)
bash "$X" shutdown --confirm              # synoshutdown -s
bash "$X" run "<command>" --confirm       # arbitrary command (gated); privileged auto-sudo -S

Requires SSH enabled on the NAS (DSM → Terminal & SNMP). Privileged recipes feed the vaulted admin password to sudo -S. Auth: sshpass if present, else OpenSSH SSH_ASKPASS (on Windows the fallback needs MSYS/Git-bash ssh on PATH — system OpenSSH can't exec a shell askpass).

Auth model (DSM Web API)

SYNO.API.Auth login (session=DSM, enable_syno_token=yes) → _sid (session cookie) + a synotoken (CSRF). Both are attached to every subsequent call (the synotoken as X-SYNO-TOKEN header + SynoToken param) so writes pass CSRF. 2FA: pass --otp <code> or SYNO_OTP. The client always logs out at the end. Env overrides for ad-hoc use: SYNO_HOST/SYNO_PORT/SYNO_USER/SYNO_PASS, SYNO_HTTPS=1 (→ :5001, cert verify off).

Full API reference

references/dsm-api.md is the consolidated, sourced map (auth, system/power, storage/shares, users/groups/security, packages, Log Center, Drive, backup, network/services + the SSH syno* surface) with per-row confidence and the 5 "golden rules". Read it before any write — the key ones: discover versions via apis (don't hardcode), CSRF is mandatory for writes (handled), setters are read-modify-write (get → mutate → push back; never construct from scratch), and when a setter's param keys are uncertain run it on-box via synowebapi --exec (validates params).

Why both surfaces (don't reach for SSH first)

The Web API is the default and covers system/storage/shares/users/packages/services/FileStation/ backup/connections. Use SSH only for what the API genuinely lacks — chiefly filesystem ACLs (synoacltool): the Web API tells you a share is in Windows-ACL mode but NOT the per-file ACE list. That ACE data is exactly what the Cascades cutover needs (docs/migration/ synology-permission-analysis-2026-04-22.md).

Open Cascades work this skill serves (verified call recipes)

  • Confirm model/RAM/DSM version (wiki TODO) — test answers it in one call.
  • Log Center syslog collector (docs/network/network-logging-plan.md) — the NAS-as-syslog-SERVER is SYNO.LogCenter.* (needs the Log Center package): apis logcenter to confirm it's installed, then call SYNO.LogCenter.RecvRule list (receive rules), call SYNO.LogCenter.Log list (received logs). NOTE SYNO.Core.SyslogClient.* is the forward-own-logs-out side, not the collector. Schedule the snapshotter as a root task via SYNO.Core.TaskScheduler.Root create.
  • Synology Drive Team Folder migration (PROJECT_STATE pending) — apis synologydrive, then call SYNO.SynologyDrive.TeamFolders list / ... Tasks list / ... Connection list to inspect before the CS-SERVER cutover. (SYNO.C2FS.* is C2 cloud — not this.)
  • Hyper Backup offsite (phase4 §6.4) — call SYNO.Backup.Task list / ... Repository list once configured. (NOTE: Active Backup is installed + running on this ext4 box — the Btrfs requirement is only for ABB dedup/self-healing features, not the package. call SYNO.ActiveBackup.Task list.)

Error logging (mandatory)

On a GENUINE functional failure (DSM login fail, API error, SSH connect/auth rc=255) both scripts log via .claude/scripts/log-skill-error.sh before surfacing. Handled conditions (missing cred, VPN-down connect error surfaced to the user, a method refused for lack of --confirm) are NOT logged.

Status / verification

  • [VERIFIED 2026-06-25 — HOWARD-HOME, Cascades VPN up] Full read-surface exercise against the device. Device is DS718+, DSM 7.2.1-69057 Update 11, 6 GB RAM, serial 1920PEN537202; ext4, 2x WD10EZEX 1 TB (volume1 913G, 43% used). 10 shares (incl. hidden pacs, Activities, chat, Sandra Fish); 30 packages running incl. Active Backup for Business 3.1.0, Synology Drive Server 3.5.0, Chat, VPN Server, Hybrid Share. 41 users, 4 groups (administrators, http, MainOffice, users).
  • Command-by-command results (Web API): test apis sysinfo util storage shares users groups packages services connections ls (share roots) callall OK. Auth (sid + synotoken CSRF), API discovery, version-by-maxVersion routing, and the generic call power-tool all confirmed live.
  • SSH backend (syno-ssh.sh): info df run confirmed OK (incl. privileged sudo -S and on-box synowebapi --exec at /usr/syno/bin/synowebapi). This is the path for real file browsing.
  • ls <folder> (FileStation list) is DENIED for the admin account on this box — returns 407 on-box (SYSTEM_ADMIN) / 400 via Web API even for valid subfolders; the built-in admin lacks FileStation file privileges. ls (no path / share roots via list_share) works fine. For actual file/folder browsing use the SSH backend: syno-ssh.sh run "ls -la /volume1/<share>/<subpath>" --confirm. (ls <folder> now catches this and prints the SSH hint.) Windows note: a bare ls /Public arg is also rewritten by MSYS path-conversion — prefer MSYS_NO_PATHCONV=1 or the call ... folder_path=/x form, but the 407 denial blocks it regardless.
  • WRITE/SETTER PATH VERIFIED 2026-06-25. Live round-trip on cascadesDS: SYNO.Core.Share create (hidden throwaway share) -> confirmed in shares -> SYNO.Core.Share delete -> confirmed gone. Both mutating calls passed CSRF (synotoken) end-to-end and returned success; device left pristine. The mutating-verb gate correctly blocks without --confirm and allows with it. (Session-expiry re-login 106/119 still not forced live, but the auth/CSRF/setter plumbing is now proven.) Share create signature confirmed: name=<n> 'shareinfo:={"name":..,"vol_path":"/volume1",..}'.
  • Built originally from a 5-agent scan of the DSM 7 help tree + authoritative API sources (kwent _full.json, N4S4/synology-api, Synology Web API guides) — see references/dsm-api.md.
  • Live fixes from this run (2026-06-25): services used SYNO.Core.Service.list which 103s on DSM 7.2.1 — corrected to get (returns {"service":[...]}). apis | head raised a BrokenPipeError traceback — now caught and exits cleanly. ls <folder> now degrades to an SSH hint on the FileStation 400/407 denial. --confirm/--vault after the subcommand were rejected by argparse (every documented gated-write example, e.g. call X set k=v --confirm, would have failed) — moved to a shared parent parser so both flags now work before AND after the subcommand.
  • SSH backend fully verified + fixed (2026-06-25): the priv (sudo) recipes — shares, users, groups, acl — were ALL broken: sudo's secure_path drops /usr/syno/{bin,sbin}, so synoshare/synouser/synogroup/synoacltool returned "command not found" (the non-sudo plain recipes worked because the admin login PATH has those dirs). Fixed by injecting SYNO_PATH and running priv via sh -c (so shell operators survive). Also: synouser/ synogroup use --enum local (not --list); acl quotes the share path (handles "Sandra Fish"); services repointed to the Web API (synoservice doesn't exist on DSM 7.2, and synosystemctl has no list-all). Verified live: info df shares users(41) groups(4) packages acl Server(real Windows ACEs) acl Public(Linux-mode) all OK. acl on a Windows-ACL share is the SSH backend's unique value (the per-file ACE list the Web API can't give).
  • Code-review hardening (2026-06-25, /code-review high): SynoError now carries the DSM code
    • a handled flag; call() no longer logs eagerly — the top-level handler logs only genuine unhandled failures, so the handled FileStation denial (and VPN-down connect errors) no longer pollute errorlog.md. FileStation-denial detection is numeric (code in (400,407)), not a substring match, and the SSH hint now also fires on the generic call path (not just ls). services falls back get->list on 103 for older DSM builds. BrokenPipe flush moved inside the try so small piped output can't leak a shutdown traceback.
  • DSM 7.2.x reboot/shutdown 103: the Web-API reboot/shutdown can return error 103 on some builds. Fallback is the SSH reboot|shutdown --confirm recipe (synoshutdown -r|-s).
  • Param-schema caveat: method names + versions in the reference are device-authoritative, but many setter param keys are community/inferred. For a production write where keys are uncertain, capture the matching get and round-trip it, or run on-box via synowebapi --exec.