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>
This commit is contained in:
@@ -140,6 +140,16 @@ VPN-down connect error surfaced to the user, a method refused for lack of --conf
|
||||
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
|
||||
|
||||
@@ -46,21 +46,28 @@ else
|
||||
run_ssh() { local rc; SYNO_SSH_PW="$P" SSH_ASKPASS="$ASKPASS" SSH_ASKPASS_REQUIRE=force DISPLAY="${DISPLAY:-:0}" ssh "${SSH_OPTS[@]}" "$@" || { rc=$?; [ "$rc" = 255 ] && logerr "syno SSH connect/auth failed (rc=255)" "host=$H vp=$VP"; return $rc; }; }
|
||||
fi
|
||||
|
||||
# privileged remote command: feed the admin password to `sudo -S`
|
||||
priv() { run_ssh "$U@$H" "echo '$P' | sudo -S -p '' $1" 2>&1 | grep -v '^Password:' ; }
|
||||
plain() { run_ssh "$U@$H" "$1" 2>&1 | grep -viE 'Permanently added'; }
|
||||
# The syno* CLI lives in /usr/syno/{bin,sbin}. The admin's interactive login PATH has
|
||||
# these (so `plain` worked), but `sudo`'s secure_path drops them -> `priv` got
|
||||
# "command not found" for synoshare/synouser/synoacltool/... Inject the dirs explicitly.
|
||||
SYNO_PATH='/usr/syno/bin:/usr/syno/sbin:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin'
|
||||
# privileged remote command: feed the admin password to `sudo -S`, with the syno bin dirs on
|
||||
# PATH. Run via `sh -c` so shell operators (|, ||, ;) in the recipe all execute as root+PATH.
|
||||
priv() { run_ssh "$U@$H" "echo '$P' | sudo -S -p '' env PATH='$SYNO_PATH' sh -c '$1'" 2>&1 | grep -v '^Password:' ; }
|
||||
plain() { run_ssh "$U@$H" "export PATH='$SYNO_PATH':\$PATH; $1" 2>&1 | grep -viE 'Permanently added'; }
|
||||
|
||||
case "$RECIPE" in
|
||||
info) plain 'uname -a; echo; cat /etc/synoinfo.conf 2>/dev/null | grep -iE "^(productversion|buildnumber|unique|upnpmodelname)" ; echo; cat /proc/meminfo | head -1';;
|
||||
df) plain 'df -h | grep -E "Filesystem|/volume"';;
|
||||
shares) priv 'synoshare --enum ALL';;
|
||||
users) priv 'synouser --list local || synouser --enum local';;
|
||||
groups) priv 'synogroup --list || synogroup --enum local';;
|
||||
users) priv 'synouser --enum local';;
|
||||
groups) priv 'synogroup --enum local';;
|
||||
packages) plain 'synopkg list 2>/dev/null || ls /var/packages';;
|
||||
services) priv 'synoservice --list 2>/dev/null | head -80';;
|
||||
services) # DSM 7.2 has no `synoservice`/synosystemctl list-all -> service enumeration is Web-API only.
|
||||
echo "[INFO] service enumeration is Web-API only on DSM 7.2 (no synoservice CLI)."
|
||||
echo " use: bash .claude/scripts/py.sh .claude/skills/synology/scripts/syno_client.py services";;
|
||||
acl)
|
||||
SHARE="${POS[1]:?acl needs a share name, e.g. acl Server}"
|
||||
priv "synoacltool -get /volume1/$SHARE";;
|
||||
priv "synoacltool -get \"/volume1/$SHARE\"";;
|
||||
reboot)
|
||||
[ "$CONFIRM" = "1" ] || { echo "[BLOCKED] reboot the NAS — re-run with --confirm"; exit 2; }
|
||||
echo "[INFO] rebooting $H via synoshutdown -r (Web-API 103 fallback)"; priv 'synoshutdown -r';;
|
||||
|
||||
Reference in New Issue
Block a user