You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

294 lines
8.3 KiB

  1. #!/usr/bin/env sh
  2. # TrueNAS deploy script for SCALE/CORE using websocket
  3. # It is recommend to use a wildcard certificate
  4. #
  5. # Websocket Documentation: https://www.truenas.com/docs/api/scale_websocket_api.html
  6. #
  7. # Tested with TrueNAS Scale - Electric Eel 24.10
  8. # Changes certificate in the following services:
  9. # - Web UI
  10. # - FTP
  11. # - iX Apps
  12. #
  13. # The following environment variables must be set:
  14. # ------------------------------------------------
  15. #
  16. # # API KEY
  17. # # Use the folowing URL to create a new API token: <TRUENAS_HOSTNAME OR IP>/ui/apikeys
  18. # export DEPLOY_TRUENAS_APIKEY="<API_KEY_GENERATED_IN_THE_WEB_UI"
  19. #
  20. ### Private functions
  21. # Call websocket method
  22. # Usage:
  23. # _ws_response=$(_ws_call "math.dummycalc" "'{"x": 4, "y": 5}'")
  24. # _info "$_ws_response"
  25. #
  26. # Output:
  27. # {"z": 9}
  28. #
  29. # Arguments:
  30. # $@ - midclt arguments for call
  31. #
  32. # Returns:
  33. # JSON/JOBID
  34. _ws_call() {
  35. _debug "_ws_call arg1" "$1"
  36. _debug "_ws_call arg2" "$2"
  37. _debug "_ws_call arg3" "$3"
  38. if [ $# -eq 3 ]; then
  39. _ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2" "$3")
  40. fi
  41. if [ $# -eq 2 ]; then
  42. _ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2")
  43. fi
  44. if [ $# -eq 1 ]; then
  45. _ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1")
  46. fi
  47. _debug "_ws_response" "$_ws_response"
  48. printf "%s" "$_ws_response"
  49. return 0
  50. }
  51. # Check argument is a number
  52. # Usage:
  53. #
  54. # Output:
  55. # n/a
  56. #
  57. # Arguments:
  58. # $1 - Anything
  59. #
  60. # Returns:
  61. # 0: true
  62. # 1: false
  63. _ws_check_jobid() {
  64. case "$1" in
  65. [0-9]*)
  66. return 0
  67. ;;
  68. esac
  69. return 1
  70. }
  71. # Wait for job to finish and return result as JSON
  72. # Usage:
  73. # _ws_result=$(_ws_get_job_result "$_ws_jobid")
  74. # _new_certid=$(printf "%s" "$_ws_result" | jq -r '."id"')
  75. #
  76. # Output:
  77. # JSON result of the job
  78. #
  79. # Arguments:
  80. # $1 - JobID
  81. #
  82. # Returns:
  83. # n/a
  84. _ws_get_job_result() {
  85. while true; do
  86. sleep 2
  87. _ws_response=$(_ws_call "core.get_jobs" "[[\"id\", \"=\", $1]]")
  88. if [ "$(printf "%s" "$_ws_response" | jq -r '.[]."state"')" != "RUNNING" ]; then
  89. _ws_result="$(printf "%s" "$_ws_response" | jq '.[]."result"')"
  90. _debug "_ws_result" "$_ws_result"
  91. printf "%s" "$_ws_result"
  92. _ws_error="$(printf "%s" "$_ws_response" | jq '.[]."error"')"
  93. if [ "$_ws_error" != "null" ]; then
  94. _err "Job $1 failed:"
  95. _err "$_ws_error"
  96. return 7
  97. fi
  98. break
  99. fi
  100. done
  101. return 0
  102. }
  103. ########################
  104. ### Public functions ###
  105. ########################
  106. # truenas_ws_deploy
  107. #
  108. # Deploy new certificate to TrueNAS services
  109. #
  110. # Arguments
  111. # 1: Domain
  112. # 2: Key-File
  113. # 3: Certificate-File
  114. # 4: CA-File
  115. # 5: FullChain-File
  116. # Returns:
  117. # 0: Success
  118. # 1: Missing API Key
  119. # 2: TrueNAS not ready
  120. # 3: Not a JobID
  121. # 4: FTP cert error
  122. # 5: WebUI cert error
  123. # 6: Job error
  124. # 7: WS call error
  125. # 10: No CORE or SCALE detected
  126. #
  127. truenas_ws_deploy() {
  128. _domain="$1"
  129. _file_key="$2"
  130. _file_cert="$3"
  131. _file_ca="$4"
  132. _file_fullchain="$5"
  133. _debug _domain "$_domain"
  134. _debug _file_key "$_file_key"
  135. _debug _file_cert "$_file_cert"
  136. _debug _file_ca "$_file_ca"
  137. _debug _file_fullchain "$_file_fullchain"
  138. ########## Environment check
  139. _info "Checking environment variables..."
  140. _getdeployconf DEPLOY_TRUENAS_APIKEY
  141. # Check API Key
  142. if [ -z "$DEPLOY_TRUENAS_APIKEY" ]; then
  143. _err "TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable."
  144. return 1
  145. fi
  146. _secure_debug2 DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY"
  147. _info "Environment variables: OK"
  148. ########## Health check
  149. _info "Checking TrueNAS health..."
  150. _ws_response=$(_ws_call "system.ready" | tr '[:lower:]' '[:upper:]')
  151. _ws_ret=$?
  152. if [ $_ws_ret -gt 0 ]; then
  153. _err "Error calling system.ready:"
  154. _err "$_ws_response"
  155. return $_ws_ret
  156. fi
  157. if [ "$_ws_response" != "TRUE" ]; then
  158. _err "TrueNAS is not ready."
  159. _err "Please check environment variables DEPLOY_TRUENAS_APIKEY, DEPLOY_TRUENAS_HOSTNAME and DEPLOY_TRUENAS_PROTOCOL."
  160. _err "Verify API key."
  161. return 2
  162. fi
  163. _savedeployconf DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY"
  164. _info "TrueNAS health: OK"
  165. ########## System info
  166. _info "Gather system info..."
  167. _ws_response=$(_ws_call "system.info")
  168. _truenas_system=$(printf "%s" "$_ws_response" | jq -r '."version"' | cut -d '-' -f 2 | tr '[:lower:]' '[:upper:]')
  169. _truenas_version=$(printf "%s" "$_ws_response" | jq -r '."version"' | cut -d '-' -f 3)
  170. _info "TrueNAS system: $_truenas_system"
  171. _info "TrueNAS version: $_truenas_version"
  172. if [ "$_truenas_system" != "SCALE" ] && [ "$_truenas_system" != "CORE" ]; then
  173. _err "Cannot gather TrueNAS system. Nor CORE oder SCALE detected."
  174. return 10
  175. fi
  176. ########## Gather current certificate
  177. _info "Gather current WebUI certificate..."
  178. _ws_response="$(_ws_call "system.general.config")"
  179. _ui_certificate_id=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."id"')
  180. _ui_certificate_name=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."name"')
  181. _info "Current WebUI certificate ID: $_ui_certificate_id"
  182. _info "Current WebUI certificate name: $_ui_certificate_name"
  183. ########## Upload new certificate
  184. _info "Upload new certificate..."
  185. _certname="acme_$(_utc_date | tr -d '\-\:' | tr ' ' '_')"
  186. _info "New WebUI certificate name: $_certname"
  187. _debug _certname "$_certname"
  188. _ws_jobid=$(_ws_call "certificate.create" "{\"name\": \"${_certname}\", \"create_type\": \"CERTIFICATE_CREATE_IMPORTED\", \"certificate\": \"$(_json_encode <"$_file_fullchain")\", \"privatekey\": \"$(_json_encode <"$_file_key")\", \"passphrase\": \"\"}")
  189. _debug "_ws_jobid" "$_ws_jobid"
  190. if ! _ws_check_jobid "$_ws_jobid"; then
  191. _err "No JobID returned from websocket method."
  192. return 3
  193. fi
  194. _ws_result=$(_ws_get_job_result "$_ws_jobid")
  195. _ws_ret=$?
  196. if [ $_ws_ret -gt 0 ]; then
  197. return $_ws_ret
  198. fi
  199. _debug "_ws_result" "$_ws_result"
  200. _new_certid=$(printf "%s" "$_ws_result" | jq -r '."id"')
  201. _info "New certificate ID: $_new_certid"
  202. ########## FTP
  203. _info "Replace FTP certificate..."
  204. _ws_response=$(_ws_call "ftp.update" "{\"ssltls_certificate\": $_new_certid}")
  205. _ftp_certid=$(printf "%s" "$_ws_response" | jq -r '."ssltls_certificate"')
  206. if [ "$_ftp_certid" != "$_new_certid" ]; then
  207. _err "Cannot set FTP certificate."
  208. _debug "_ws_response" "$_ws_response"
  209. return 4
  210. fi
  211. ########## ix Apps (SCALE only)
  212. if [ "$_truenas_system" = "SCALE" ]; then
  213. _info "Replace app certificates..."
  214. _ws_response=$(_ws_call "app.query")
  215. for _app_name in $(printf "%s" "$_ws_response" | jq -r '.[]."name"'); do
  216. _info "Checking app $_app_name..."
  217. _ws_response=$(_ws_call "app.config" "$_app_name")
  218. if [ "$(printf "%s" "$_ws_response" | jq -r '."network" | has("certificate_id")')" = "true" ]; then
  219. _info "App has certificate option, setup new certificate..."
  220. _info "App will be redeployed after updating the certificate."
  221. _ws_jobid=$(_ws_call "app.update" "$_app_name" "{\"values\": {\"network\": {\"certificate_id\": $_new_certid}}}")
  222. _debug "_ws_jobid" "$_ws_jobid"
  223. if ! _ws_check_jobid "$_ws_jobid"; then
  224. _err "No JobID returned from websocket method."
  225. return 3
  226. fi
  227. _ws_result=$(_ws_get_job_result "$_ws_jobid")
  228. _ws_ret=$?
  229. if [ $_ws_ret -gt 0 ]; then
  230. return $_ws_ret
  231. fi
  232. _debug "_ws_result" "$_ws_result"
  233. _info "App certificate replaced."
  234. else
  235. _info "App has no certificate option, skipping..."
  236. fi
  237. done
  238. fi
  239. ########## WebUI
  240. _info "Replace WebUI certificate..."
  241. _ws_response=$(_ws_call "system.general.update" "{\"ui_certificate\": $_new_certid}")
  242. _changed_certid=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."id"')
  243. if [ "$_changed_certid" != "$_new_certid" ]; then
  244. _err "WebUI certificate change error.."
  245. return 5
  246. else
  247. _info "WebUI certificate replaced."
  248. fi
  249. _info "Restarting WebUI..."
  250. _ws_response=$(_ws_call "system.general.ui_restart")
  251. _info "Waiting for UI restart..."
  252. sleep 6
  253. ########## Certificates
  254. _info "Deleting old certificate..."
  255. _ws_jobid=$(_ws_call "certificate.delete" "$_ui_certificate_id")
  256. if ! _ws_check_jobid "$_ws_jobid"; then
  257. _err "No JobID returned from websocket method."
  258. return 3
  259. fi
  260. _ws_result=$(_ws_get_job_result "$_ws_jobid")
  261. _ws_ret=$?
  262. if [ $_ws_ret -gt 0 ]; then
  263. return $_ws_ret
  264. fi
  265. _info "Have a nice day...bye!"
  266. }