Skip to content

Instantly share code, notes, and snippets.

@rezarajan
Created October 13, 2025 09:34
Show Gist options
  • Select an option

  • Save rezarajan/ebc40becaca69f805233243062dc775c to your computer and use it in GitHub Desktop.

Select an option

Save rezarajan/ebc40becaca69f805233243062dc775c to your computer and use it in GitHub Desktop.
A curl-based synology-cli tool for listing, locking and unlocking shares. Works with DSM 7.2.2 + and 2FA.
#!/usr/bin/env bash
# synology-cli.sh
# Works with DSM 7.2.2 and 2FA
set -euo pipefail
NAS_URL=""
while getopts ":u:" opt; do
case $opt in
u) NAS_URL="$OPTARG" ;;
*) echo "Usage: $0 [-u https://your-nas:5001]"; exit 1 ;;
esac
done
: "${NAS_URL:="https://your-nas:5001"}"
has_jq() { command -v jq >/dev/null 2>&1; }
pretty() { if has_jq; then jq -C . 2>/dev/null || cat; else cat; fi; }
# Secure cleanup
SID=""
cleanup() {
if [[ -n "${SID:-}" ]]; then
printf '' | curl -k -s --data-binary @- \
"${NAS_URL}/webapi/auth.cgi?api=SYNO.API.Auth&version=6&method=logout&session=FileStation&_sid=${SID}" >/dev/null 2>&1 || true
fi
unset SID DSM_PASS OTP_CODE
}
trap cleanup EXIT
api_post() { local endpoint="$1"; curl -k -s --data-binary @- "${NAS_URL}/webapi/${endpoint}"; }
urlencode() {
local string="$1" encoded=""
local length="${#string}"
for (( i = 0; i < length; i++ )); do
c="${string:i:1}"
case "$c" in
[a-zA-Z0-9.~_-]) encoded+="$c" ;;
*) printf -v hex '%%%02X' "'$c"; encoded+="$hex" ;;
esac
done
echo "$encoded"
}
login() {
read -rp "DSM username: " DSM_USER
printf "DSM password: "
stty -echo; read DSM_PASS; stty echo; echo
LOGIN_RESP=$(printf 'api=SYNO.API.Auth&version=6&method=login&account=%s&passwd=%s&session=FileStation&format=sid' \
"$DSM_USER" "$DSM_PASS" | api_post "auth.cgi")
OTP_TOKEN=$(echo "$LOGIN_RESP" | jq -r '.error.errors.token // empty')
if [[ -n "$OTP_TOKEN" ]]; then
echo "๐Ÿ” 2FA detected. Please enter OTP."
read -rp "OTP code: " OTP_CODE
LOGIN_RESP=$(printf 'api=SYNO.API.Auth&version=6&method=login&account=%s&passwd=%s&otp_code=%s&session=FileStation&format=sid' \
"$DSM_USER" "$DSM_PASS" "$OTP_CODE" | api_post "auth.cgi")
fi
SID=$(echo "$LOGIN_RESP" | jq -r '.data.sid // empty')
if [[ -z "$SID" ]]; then
echo "โŒ Login failed. Check credentials or OTP."
exit 2
fi
echo "โœ… Logged in. SID: $SID"
DSM_PASS=""; unset DSM_PASS OTP_CODE
}
list_shares() {
echo "๐Ÿ“‚ Listing shares (with encryption info)..."
ALL_SHARES=$(curl -k -s -G "${NAS_URL}/webapi/entry.cgi" \
--data-urlencode "api=SYNO.Core.Share" \
--data-urlencode "version=1" \
--data-urlencode "method=list" \
--data-urlencode "additional=[\"hidden\",\"encryption\"]" \
--data-urlencode "shareType=\"all\"" \
--data-urlencode "_sid=${SID}" || true)
if [[ -z "$ALL_SHARES" ]]; then
echo "โš ๏ธ Failed to fetch shares."
return 1
fi
# Print name + encryption status
echo "$ALL_SHARES" | jq -r '.data.shares[] | "\(.name) | Encrypted: \(.encryption != 0)"'
}
unlock_share() {
SHARES=$(curl -k -s -G "${NAS_URL}/webapi/entry.cgi" \
--data-urlencode "api=SYNO.Core.Share" \
--data-urlencode "version=1" \
--data-urlencode "method=list" \
--data-urlencode "additional=[\"encryption\"]" \
--data-urlencode "shareType=\"all\"" \
--data-urlencode "_sid=${SID}" \
| jq -r '.data.shares[] | select(.encryption==1) | .name')
if [[ -z "$SHARES" ]]; then
echo "No encrypted (locked) shares available to unlock."
return
fi
echo "Encrypted (locked) shares:"
echo "$SHARES"
read -rp "Share name to unlock: " share_name
[[ -z "$share_name" ]] && { echo "Share name required"; return; }
printf "Password for '%s': " "$share_name"
stty -echo; read SHARE_PASS; stty echo; echo
SHARE_NAME_ESC=$(urlencode "$share_name")
SHARE_PASS_ESC=$(urlencode "$SHARE_PASS")
RESPONSE=$(printf 'api=SYNO.Core.Share.Crypto&version=1&method=decrypt&name=%s&password=%s&_sid=%s' \
"$SHARE_NAME_ESC" "$SHARE_PASS_ESC" "$SID" | api_post "entry.cgi")
pretty <<<"$RESPONSE"
SHARE_PASS=""; unset SHARE_PASS
}
lock_share() {
SHARES=$(curl -k -s -G "${NAS_URL}/webapi/entry.cgi" \
--data-urlencode "api=SYNO.Core.Share" \
--data-urlencode "version=1" \
--data-urlencode "method=list" \
--data-urlencode "additional=[\"encryption\"]" \
--data-urlencode "shareType=\"all\"" \
--data-urlencode "_sid=${SID}" \
| jq -r '.data.shares[] | select(.encryption==2) | .name')
if [[ -z "$SHARES" ]]; then
echo "No decrypted (unlocked) shares available to lock."
return
fi
echo "Decrypted (unlocked) shares:"
echo "$SHARES"
read -rp "Share name to lock: " share_name
[[ -z "$share_name" ]] && { echo "Share name required"; return; }
SHARE_NAME_ESC=$(urlencode "$share_name")
RESPONSE=$(printf 'api=SYNO.Core.Share.Crypto&version=1&method=encrypt&name=%s&_sid=%s' \
"$SHARE_NAME_ESC" "$SID" | api_post "entry.cgi")
pretty <<<"$RESPONSE"
}
logout() {
echo "Logging out..."
SID="" # Will be cleaned up by trap
echo "โœ… Logged out."
}
main_menu() {
while true; do
echo
echo "Select an action:"
echo " 1) List shares"
echo " 2) Unlock (decrypt) encrypted share"
echo " 3) Lock (encrypt) decrypted share"
echo " 4) Logout and exit"
read -rp "Choice [1-4]: " choice
case "$choice" in
1) list_shares ;;
2) unlock_share || echo "โš ๏ธ Failed to unlock share" ;;
3) lock_share || echo "โš ๏ธ Failed to lock share" ;;
4) logout; break ;;
*) echo "Invalid choice." ;;
esac
done
}
login
main_menu
# SID cleanup handled by trap
@rezarajan
Copy link
Author

Usage

The use-case for this script is very simple: list, unlock and lock shares.

The script prompts for login, asking for an OTP if required. All passwords are hidden from stdout. Once logged in, a menu will be presented which will guide you from there.

# Ensure executable permissions
chmod +x syno-cli.sh
# Login to Synology NAS
./syno-cli.sh -u <url_to_synology>:<port>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment