]>
Commit | Line | Data |
---|---|---|
623d615c MA |
1 | #!/bin/bash |
2 | ||
3 | ################################################################################ | |
4 | # ACME.sh 3rd party deploy plugin for Synology DSM | |
5 | ################################################################################ | |
6 | # Authors: Brian Hartvigsen (creator), https://github.com/tresni | |
7 | # Martin Arndt (contributor), https://troublezone.net/ | |
8 | # Updated: 2023-05-28 | |
9 | # Issues: https://github.com/acmesh-official/acme.sh/issues/2727 | |
10 | ################################################################################ | |
11 | # Usage: | |
12 | # 1. export SYNO_Username="adminUser" | |
13 | # 2. export SYNO_Password="adminPassword" | |
14 | # Optional exports (shown values are the defaults): | |
15 | # - export SYNO_Certificate="" to replace a specific certificate via description | |
16 | # - export SYNO_Scheme="http" | |
17 | # - export SYNO_Hostname="localhost" | |
18 | # - export SYNO_Port="5000" | |
19 | # - export SYNO_Device_Name="CertRenewal" - required for skipping 2FA-OTP | |
20 | # - export SYNO_Device_ID="" - required for skipping 2FA-OTP | |
21 | # 3. acme.sh --deploy --deploy-hook synology_dsm -d example.com | |
22 | ################################################################################ | |
4635dacf | 23 | # Dependencies: |
623d615c MA |
24 | # - jq & curl |
25 | ################################################################################ | |
26 | # Return value: | |
27 | # 0 means success, otherwise error. | |
28 | ################################################################################ | |
555e0de9 | 29 | |
623d615c | 30 | ########## Public functions #################################################### |
555e0de9 BH |
31 | #domain keyfile certfile cafile fullchain |
32 | synology_dsm_deploy() { | |
555e0de9 BH |
33 | _cdomain="$1" |
34 | _ckey="$2" | |
35 | _ccert="$3" | |
36 | _cca="$4" | |
37 | ||
38 | _debug _cdomain "$_cdomain" | |
39 | ||
623d615c | 40 | # Get username & password, but don't save until we authenticated successfully |
12593410 BH |
41 | _getdeployconf SYNO_Username |
42 | _getdeployconf SYNO_Password | |
43 | _getdeployconf SYNO_Create | |
623d615c MA |
44 | _getdeployconf SYNO_Device_Name |
45 | _getdeployconf SYNO_Device_ID | |
3a7c7fe4 | 46 | if [ -z "${SYNO_Username:-}" ] || [ -z "${SYNO_Password:-}" ]; then |
555e0de9 BH |
47 | _err "SYNO_Username & SYNO_Password must be set" |
48 | return 1 | |
49 | fi | |
623d615c MA |
50 | if [ -n "${SYNO_Device_Name:-}" ] && [ -z "${SYNO_Device_ID:-}" ]; then |
51 | _err "SYNO_Device_Name set, but SYNO_Device_ID is empty" | |
52 | return 1 | |
53 | fi | |
555e0de9 BH |
54 | _debug2 SYNO_Username "$SYNO_Username" |
55 | _secure_debug2 SYNO_Password "$SYNO_Password" | |
623d615c MA |
56 | _debug2 SYNO_Create "$SYNO_Create" |
57 | _debug2 SYNO_Device_Name "$SYNO_Device_Name" | |
58 | _secure_debug2 SYNO_Device_ID "$SYNO_Device_ID" | |
555e0de9 | 59 | |
623d615c | 60 | # Optional scheme, hostname & port for Synology DSM |
12593410 BH |
61 | _getdeployconf SYNO_Scheme |
62 | _getdeployconf SYNO_Hostname | |
63 | _getdeployconf SYNO_Port | |
555e0de9 | 64 | |
623d615c MA |
65 | # Default values for scheme, hostname & port |
66 | # Defaulting to localhost & http, because it's localhost… | |
555e0de9 BH |
67 | [ -n "${SYNO_Scheme}" ] || SYNO_Scheme="http" |
68 | [ -n "${SYNO_Hostname}" ] || SYNO_Hostname="localhost" | |
69 | [ -n "${SYNO_Port}" ] || SYNO_Port="5000" | |
12593410 BH |
70 | _savedeployconf SYNO_Scheme "$SYNO_Scheme" |
71 | _savedeployconf SYNO_Hostname "$SYNO_Hostname" | |
72 | _savedeployconf SYNO_Port "$SYNO_Port" | |
555e0de9 BH |
73 | _debug2 SYNO_Scheme "$SYNO_Scheme" |
74 | _debug2 SYNO_Hostname "$SYNO_Hostname" | |
75 | _debug2 SYNO_Port "$SYNO_Port" | |
76 | ||
77 | # Get the certificate description, but don't save it until we verfiy it's real | |
78 | _getdeployconf SYNO_Certificate | |
694194be | 79 | _debug SYNO_Certificate "${SYNO_Certificate:-}" |
555e0de9 | 80 | |
dcb51683 | 81 | # shellcheck disable=SC1003 # We are not trying to escape a single quote |
74a4a788 BH |
82 | if printf "%s" "$SYNO_Certificate" | grep '\\'; then |
83 | _err "Do not use a backslash (\) in your certificate description" | |
84 | return 1 | |
85 | fi | |
86 | ||
555e0de9 BH |
87 | _base_url="$SYNO_Scheme://$SYNO_Hostname:$SYNO_Port" |
88 | _debug _base_url "$_base_url" | |
89 | ||
cc692854 T |
90 | _debug "Getting API version" |
91 | response=$(_get "$_base_url/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query&query=SYNO.API.Auth") | |
92 | api_version=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"maxVersion" *: *\([0-9]*\).*/\1/p') | |
93 | _debug3 response "$response" | |
94 | _debug3 api_version "$api_version" | |
95 | ||
623d615c | 96 | # Login, get the session ID & SynoToken from JSON |
52a168b9 | 97 | _info "Logging into $SYNO_Hostname:$SYNO_Port" |
52b81608 BH |
98 | encoded_username="$(printf "%s" "$SYNO_Username" | _url_encode)" |
99 | encoded_password="$(printf "%s" "$SYNO_Password" | _url_encode)" | |
cc692854 | 100 | |
623d615c MA |
101 | # Get device ID if still empty first, otherwise log in right away |
102 | if [ -z "${SYNO_Device_ID:-}" ]; then | |
103 | printf "Enter OTP code for user '%s': " "$SYNO_Username" | |
104 | read -r otp_code | |
105 | if [ -z "${SYNO_Device_Name:-}" ]; then | |
106 | printf "Enter device name or leave empty for default (CertRenewal): " | |
107 | read -r SYNO_Device_Name | |
108 | [ -n "${SYNO_Device_Name}" ] || SYNO_Device_Name="CertRenewal" | |
5ae3a020 | 109 | fi |
4635dacf | 110 | |
623d615c MA |
111 | response=$(_get "$_base_url/webapi/entry.cgi?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&otp_code=$otp_code&enable_syno_token=yes&enable_device_token=yes&device_name=$SYNO_Device_Name") |
112 | _debug3 response "$response" | |
113 | SYNO_Device_ID=$(echo "$response" | grep "device_id" | sed -n 's/.*"device_id" *: *"\([^"]*\).*/\1/p') | |
114 | _secure_debug2 SYNO_Device_ID "$SYNO_Device_ID" | |
115 | else | |
116 | response=$(_get "$_base_url/webapi/entry.cgi?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&device_name=$SYNO_Device_Name&device_id=$SYNO_Device_ID") | |
117 | _debug3 response "$response" | |
cc692854 T |
118 | fi |
119 | ||
623d615c | 120 | sid=$(echo "$response" | grep "sid" | sed -n 's/.*"sid" *: *"\([^"]*\).*/\1/p') |
cc692854 | 121 | token=$(echo "$response" | grep "synotoken" | sed -n 's/.*"synotoken" *: *"\([^"]*\).*/\1/p') |
0548ad2f | 122 | _debug "Session ID" "$sid" |
623d615c MA |
123 | _debug SynoToken "$token" |
124 | if [ -z "$SYNO_Device_ID" ] || [ -z "$sid" ] || [ -z "$token" ]; then | |
125 | _err "Unable to authenticate to $_base_url - check your username & password." | |
126 | _err "If two-factor authentication is enabled for the user, set SYNO_Device_ID." | |
555e0de9 BH |
127 | return 1 |
128 | fi | |
129 | ||
cc692854 | 130 | _H1="X-SYNO-TOKEN: $token" |
52a168b9 | 131 | export _H1 |
5d3bc95a | 132 | _debug2 H1 "${_H1}" |
52a168b9 | 133 | |
623d615c | 134 | # Now that we know the username & password are good, save them |
52a168b9 BH |
135 | _savedeployconf SYNO_Username "$SYNO_Username" |
136 | _savedeployconf SYNO_Password "$SYNO_Password" | |
623d615c MA |
137 | _savedeployconf SYNO_Device_Name "$SYNO_Device_Name" |
138 | _savedeployconf SYNO_Device_ID "$SYNO_Device_ID" | |
555e0de9 | 139 | |
52a168b9 | 140 | _info "Getting certificates in Synology DSM" |
cc692854 | 141 | response=$(_post "api=SYNO.Core.Certificate.CRT&method=list&version=1&_sid=$sid" "$_base_url/webapi/entry.cgi") |
555e0de9 | 142 | _debug3 response "$response" |
74a4a788 BH |
143 | escaped_certificate="$(printf "%s" "$SYNO_Certificate" | sed 's/\([].*^$[]\)/\\\1/g;s/"/\\\\"/g')" |
144 | _debug escaped_certificate "$escaped_certificate" | |
145 | id=$(echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\"id\":\"\([^\"]*\).*/\1/p") | |
52a168b9 | 146 | _debug2 id "$id" |
555e0de9 | 147 | |
3a7c7fe4 | 148 | if [ -z "$id" ] && [ -z "${SYNO_Create:-}" ]; then |
623d615c | 149 | _err "Unable to find certificate: $SYNO_Certificate & \$SYNO_Create is not set" |
555e0de9 BH |
150 | return 1 |
151 | fi | |
152 | ||
153 | # we've verified this certificate description is a thing, so save it | |
74a4a788 | 154 | _savedeployconf SYNO_Certificate "$SYNO_Certificate" "base64" |
555e0de9 | 155 | |
52a168b9 | 156 | _info "Generate form POST request" |
0deea539 | 157 | nl="\0015\0012" |
79637097 | 158 | delim="--------------------------$(_utc_date | tr -d -- '-: ')" |
0deea539 | 159 | content="--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")\0012" |
160 | content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_ccert")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ccert")\0012" | |
161 | content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"inter_cert\"; filename=\"$(basename "$_cca")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cca")\0012" | |
52a168b9 BH |
162 | content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"id\"${nl}${nl}$id" |
163 | content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_Certificate}" | |
74a4a788 | 164 | if echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then |
5ab9ca1c BH |
165 | _debug2 default "this is the default certificate" |
166 | content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"as_default\"${nl}${nl}true" | |
167 | else | |
168 | _debug2 default "this is NOT the default certificate" | |
169 | fi | |
52a168b9 | 170 | content="$content${nl}--$delim--${nl}" |
95769de4 BH |
171 | content="$(printf "%b_" "$content")" |
172 | content="${content%_}" # protect trailing \n | |
52a168b9 BH |
173 | |
174 | _info "Upload certificate to the Synology DSM" | |
cc692854 | 175 | response=$(_post "$content" "$_base_url/webapi/entry.cgi?api=SYNO.Core.Certificate&method=import&version=1&SynoToken=$token&_sid=$sid" "" "POST" "multipart/form-data; boundary=${delim}") |
555e0de9 | 176 | _debug3 response "$response" |
555e0de9 | 177 | |
1b475cf9 BH |
178 | if ! echo "$response" | grep '"error":' >/dev/null; then |
179 | if echo "$response" | grep '"restart_httpd":true' >/dev/null; then | |
623d615c | 180 | _info "Restarting HTTP services succeeded" |
555e0de9 | 181 | else |
623d615c | 182 | _info "Restarting HTTP services failed" |
555e0de9 | 183 | fi |
623d615c MA |
184 | |
185 | _logout | |
6459ccb1 | 186 | return 0 |
555e0de9 | 187 | else |
52a168b9 | 188 | _err "Unable to update certificate, error code $response" |
623d615c | 189 | _logout |
555e0de9 BH |
190 | return 1 |
191 | fi | |
192 | } | |
623d615c MA |
193 | |
194 | #################### Private functions below ################################## | |
195 | _logout() { | |
196 | # Logout to not occupy a permanent session, e. g. in DSM's "Connected Users" widget | |
197 | response=$(_get "$_base_url/webapi/entry.cgi?api=SYNO.API.Auth&version=$api_version&method=logout") | |
198 | _debug3 response "$response" | |
199 | } |