-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6108 from Attackwave/Truenas-Deploy-Websocket
Truenas deploy websocket
- Loading branch information
Showing
1 changed file
with
294 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,294 @@ | ||
#!/usr/bin/env sh | ||
|
||
# TrueNAS deploy script for SCALE/CORE using websocket | ||
# It is recommend to use a wildcard certificate | ||
# | ||
# Websocket Documentation: https://www.truenas.com/docs/api/scale_websocket_api.html | ||
# | ||
# Tested with TrueNAS Scale - Electric Eel 24.10 | ||
# Changes certificate in the following services: | ||
# - Web UI | ||
# - FTP | ||
# - iX Apps | ||
# | ||
# The following environment variables must be set: | ||
# ------------------------------------------------ | ||
# | ||
# # API KEY | ||
# # Use the folowing URL to create a new API token: <TRUENAS_HOSTNAME OR IP>/ui/apikeys | ||
# export DEPLOY_TRUENAS_APIKEY="<API_KEY_GENERATED_IN_THE_WEB_UI" | ||
# | ||
|
||
### Private functions | ||
|
||
# Call websocket method | ||
# Usage: | ||
# _ws_response=$(_ws_call "math.dummycalc" "'{"x": 4, "y": 5}'") | ||
# _info "$_ws_response" | ||
# | ||
# Output: | ||
# {"z": 9} | ||
# | ||
# Arguments: | ||
# $@ - midclt arguments for call | ||
# | ||
# Returns: | ||
# JSON/JOBID | ||
_ws_call() { | ||
_debug "_ws_call arg1" "$1" | ||
_debug "_ws_call arg2" "$2" | ||
_debug "_ws_call arg3" "$3" | ||
if [ $# -eq 3 ]; then | ||
_ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2" "$3") | ||
fi | ||
if [ $# -eq 2 ]; then | ||
_ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2") | ||
fi | ||
if [ $# -eq 1 ]; then | ||
_ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1") | ||
fi | ||
_debug "_ws_response" "$_ws_response" | ||
printf "%s" "$_ws_response" | ||
return 0 | ||
} | ||
|
||
# Check argument is a number | ||
# Usage: | ||
# | ||
# Output: | ||
# n/a | ||
# | ||
# Arguments: | ||
# $1 - Anything | ||
# | ||
# Returns: | ||
# 0: true | ||
# 1: false | ||
_ws_check_jobid() { | ||
case "$1" in | ||
[0-9]*) | ||
return 0 | ||
;; | ||
esac | ||
return 1 | ||
} | ||
|
||
# Wait for job to finish and return result as JSON | ||
# Usage: | ||
# _ws_result=$(_ws_get_job_result "$_ws_jobid") | ||
# _new_certid=$(printf "%s" "$_ws_result" | jq -r '."id"') | ||
# | ||
# Output: | ||
# JSON result of the job | ||
# | ||
# Arguments: | ||
# $1 - JobID | ||
# | ||
# Returns: | ||
# n/a | ||
_ws_get_job_result() { | ||
while true; do | ||
sleep 2 | ||
_ws_response=$(_ws_call "core.get_jobs" "[[\"id\", \"=\", $1]]") | ||
if [ "$(printf "%s" "$_ws_response" | jq -r '.[]."state"')" != "RUNNING" ]; then | ||
_ws_result="$(printf "%s" "$_ws_response" | jq '.[]."result"')" | ||
_debug "_ws_result" "$_ws_result" | ||
printf "%s" "$_ws_result" | ||
_ws_error="$(printf "%s" "$_ws_response" | jq '.[]."error"')" | ||
if [ "$_ws_error" != "null" ]; then | ||
_err "Job $1 failed:" | ||
_err "$_ws_error" | ||
return 7 | ||
fi | ||
break | ||
fi | ||
done | ||
return 0 | ||
} | ||
|
||
######################## | ||
### Public functions ### | ||
######################## | ||
|
||
# truenas_ws_deploy | ||
# | ||
# Deploy new certificate to TrueNAS services | ||
# | ||
# Arguments | ||
# 1: Domain | ||
# 2: Key-File | ||
# 3: Certificate-File | ||
# 4: CA-File | ||
# 5: FullChain-File | ||
# Returns: | ||
# 0: Success | ||
# 1: Missing API Key | ||
# 2: TrueNAS not ready | ||
# 3: Not a JobID | ||
# 4: FTP cert error | ||
# 5: WebUI cert error | ||
# 6: Job error | ||
# 7: WS call error | ||
# 10: No CORE or SCALE detected | ||
# | ||
truenas_ws_deploy() { | ||
_domain="$1" | ||
_file_key="$2" | ||
_file_cert="$3" | ||
_file_ca="$4" | ||
_file_fullchain="$5" | ||
_debug _domain "$_domain" | ||
_debug _file_key "$_file_key" | ||
_debug _file_cert "$_file_cert" | ||
_debug _file_ca "$_file_ca" | ||
_debug _file_fullchain "$_file_fullchain" | ||
|
||
########## Environment check | ||
|
||
_info "Checking environment variables..." | ||
_getdeployconf DEPLOY_TRUENAS_APIKEY | ||
# Check API Key | ||
if [ -z "$DEPLOY_TRUENAS_APIKEY" ]; then | ||
_err "TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable." | ||
return 1 | ||
fi | ||
_secure_debug2 DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY" | ||
_info "Environment variables: OK" | ||
|
||
########## Health check | ||
|
||
_info "Checking TrueNAS health..." | ||
_ws_response=$(_ws_call "system.ready" | tr '[:lower:]' '[:upper:]') | ||
_ws_ret=$? | ||
if [ $_ws_ret -gt 0 ]; then | ||
_err "Error calling system.ready:" | ||
_err "$_ws_response" | ||
return $_ws_ret | ||
fi | ||
|
||
if [ "$_ws_response" != "TRUE" ]; then | ||
_err "TrueNAS is not ready." | ||
_err "Please check environment variables DEPLOY_TRUENAS_APIKEY, DEPLOY_TRUENAS_HOSTNAME and DEPLOY_TRUENAS_PROTOCOL." | ||
_err "Verify API key." | ||
return 2 | ||
fi | ||
_savedeployconf DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY" | ||
_info "TrueNAS health: OK" | ||
|
||
########## System info | ||
|
||
_info "Gather system info..." | ||
_ws_response=$(_ws_call "system.info") | ||
_truenas_system=$(printf "%s" "$_ws_response" | jq -r '."version"' | cut -d '-' -f 2 | tr '[:lower:]' '[:upper:]') | ||
_truenas_version=$(printf "%s" "$_ws_response" | jq -r '."version"' | cut -d '-' -f 3) | ||
_info "TrueNAS system: $_truenas_system" | ||
_info "TrueNAS version: $_truenas_version" | ||
if [ "$_truenas_system" != "SCALE" ] && [ "$_truenas_system" != "CORE" ]; then | ||
_err "Cannot gather TrueNAS system. Nor CORE oder SCALE detected." | ||
return 10 | ||
fi | ||
|
||
########## Gather current certificate | ||
|
||
_info "Gather current WebUI certificate..." | ||
_ws_response="$(_ws_call "system.general.config")" | ||
_ui_certificate_id=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."id"') | ||
_ui_certificate_name=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."name"') | ||
_info "Current WebUI certificate ID: $_ui_certificate_id" | ||
_info "Current WebUI certificate name: $_ui_certificate_name" | ||
|
||
########## Upload new certificate | ||
|
||
_info "Upload new certificate..." | ||
_certname="acme_$(_utc_date | tr -d '\-\:' | tr ' ' '_')" | ||
_info "New WebUI certificate name: $_certname" | ||
_debug _certname "$_certname" | ||
_ws_jobid=$(_ws_call "certificate.create" "{\"name\": \"${_certname}\", \"create_type\": \"CERTIFICATE_CREATE_IMPORTED\", \"certificate\": \"$(_json_encode <"$_file_fullchain")\", \"privatekey\": \"$(_json_encode <"$_file_key")\", \"passphrase\": \"\"}") | ||
_debug "_ws_jobid" "$_ws_jobid" | ||
if ! _ws_check_jobid "$_ws_jobid"; then | ||
_err "No JobID returned from websocket method." | ||
return 3 | ||
fi | ||
_ws_result=$(_ws_get_job_result "$_ws_jobid") | ||
_ws_ret=$? | ||
if [ $_ws_ret -gt 0 ]; then | ||
return $_ws_ret | ||
fi | ||
_debug "_ws_result" "$_ws_result" | ||
_new_certid=$(printf "%s" "$_ws_result" | jq -r '."id"') | ||
_info "New certificate ID: $_new_certid" | ||
|
||
########## FTP | ||
|
||
_info "Replace FTP certificate..." | ||
_ws_response=$(_ws_call "ftp.update" "{\"ssltls_certificate\": $_new_certid}") | ||
_ftp_certid=$(printf "%s" "$_ws_response" | jq -r '."ssltls_certificate"') | ||
if [ "$_ftp_certid" != "$_new_certid" ]; then | ||
_err "Cannot set FTP certificate." | ||
_debug "_ws_response" "$_ws_response" | ||
return 4 | ||
fi | ||
|
||
########## ix Apps (SCALE only) | ||
|
||
if [ "$_truenas_system" = "SCALE" ]; then | ||
_info "Replace app certificates..." | ||
_ws_response=$(_ws_call "app.query") | ||
for _app_name in $(printf "%s" "$_ws_response" | jq -r '.[]."name"'); do | ||
_info "Checking app $_app_name..." | ||
_ws_response=$(_ws_call "app.config" "$_app_name") | ||
if [ "$(printf "%s" "$_ws_response" | jq -r '."network" | has("certificate_id")')" = "true" ]; then | ||
_info "App has certificate option, setup new certificate..." | ||
_info "App will be redeployed after updating the certificate." | ||
_ws_jobid=$(_ws_call "app.update" "$_app_name" "{\"values\": {\"network\": {\"certificate_id\": $_new_certid}}}") | ||
_debug "_ws_jobid" "$_ws_jobid" | ||
if ! _ws_check_jobid "$_ws_jobid"; then | ||
_err "No JobID returned from websocket method." | ||
return 3 | ||
fi | ||
_ws_result=$(_ws_get_job_result "$_ws_jobid") | ||
_ws_ret=$? | ||
if [ $_ws_ret -gt 0 ]; then | ||
return $_ws_ret | ||
fi | ||
_debug "_ws_result" "$_ws_result" | ||
_info "App certificate replaced." | ||
else | ||
_info "App has no certificate option, skipping..." | ||
fi | ||
done | ||
fi | ||
|
||
########## WebUI | ||
|
||
_info "Replace WebUI certificate..." | ||
_ws_response=$(_ws_call "system.general.update" "{\"ui_certificate\": $_new_certid}") | ||
_changed_certid=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."id"') | ||
if [ "$_changed_certid" != "$_new_certid" ]; then | ||
_err "WebUI certificate change error.." | ||
return 5 | ||
else | ||
_info "WebUI certificate replaced." | ||
fi | ||
_info "Restarting WebUI..." | ||
_ws_response=$(_ws_call "system.general.ui_restart") | ||
_info "Waiting for UI restart..." | ||
sleep 6 | ||
|
||
########## Certificates | ||
|
||
_info "Deleting old certificate..." | ||
_ws_jobid=$(_ws_call "certificate.delete" "$_ui_certificate_id") | ||
if ! _ws_check_jobid "$_ws_jobid"; then | ||
_err "No JobID returned from websocket method." | ||
return 3 | ||
fi | ||
_ws_result=$(_ws_get_job_result "$_ws_jobid") | ||
_ws_ret=$? | ||
if [ $_ws_ret -gt 0 ]; then | ||
return $_ws_ret | ||
fi | ||
|
||
_info "Have a nice day...bye!" | ||
|
||
} |