Help with API changes in Goldeye - remote authentification

Hi there, so I came across this handy script from @coolaj86

Summary

#!/bin/sh
set -e
set -u

g_version=“1.0.0”
g_build=“2024-08-26”
g_author=“AJ ONeal aj@therootcompany.com (https://bnna.net)”
g_license=“CC0-1.0”
g_license_url=“Deed - CC0 1.0 Universal - Creative Commons

if test “version” = “${1:-}” || test “–version” = “${1:-}” || test “-V” = “${1:-}”; then
echo “truenas-unlock-volumes v${g_version} (${g_build})”
echo “copyright 2024 ${g_author} (${g_license} license)”
echo “${g_license_url}”
exit 0
fi

if test “help” = “${1:-}” || test “–help” = “${1:-}”; then
{
echo “truenas-unlock-volumes v${g_version} (${g_build})”
echo “copyright 2024 ${g_author} (${g_license} license)”
echo “${g_license_url}”
echo “”
echo “USAGE”
echo " truenas-unlock-volumes"
echo “”
echo “CONFIG”
echo “”
echo " ~/.config/truenas/env:"
echo " # note: no trailing /"
echo " export TRUENAS_BASE_URL=https://truenas.local"
# shellcheck disable=SC2016 # use of literal $ is intentional
echo ’ # from ${TRUENAS_BASE_URL}/ui/apikeys’
echo " export TRUENAS_API_KEY=abc123"
echo “”
} >&2
exit 0
fi

if ! test -s ~/.config/truenas/env; then
mkdir -p ~/.config/truenas/
{
echo ‘# Example’
echo ‘# export TRUENAS_BASE_URL=https://truenas.local
echo ‘# export TRUENAS_API_KEY=abc123 # from https:///ui/apikeys’
} > ~/.config/truenas/env
fi
if ! grep -q -v -E ‘^\s*(#.*)?$’ ~/.config/truenas/env; then
{
echo “”
echo “ERROR”
echo " Missing ~/.config/truenas/env"
echo “”
echo “SOLUTION”
echo " Create and save an API key from https://truenas.local/ui/apikeys"
echo “”
} >&2
exit 1
fi

shellcheck disable=SC1090

. ~/.config/truenas/env
if test -z “${TRUENAS_BASE_URL:-}” || test -z “${TRUENAS_API_KEY:-}”; then
{
echo “”
echo “ERROR”
echo " Missing config from ~/.config/truenas/env"
echo “”
echo “SOLUTION”
echo " Set the config in this format:"
echo " export TRUENAS_BASE_URL=https://truenas.local # no trailing slash"
echo " export TRUENAS_API_KEY=abc123"
echo “”
} >&2
exit 1
fi

if ! test -s ~/.config/truenas/zfs-passphrases.conf; then
{
echo “”
echo “ERROR”
echo " Missing ~/.config/truenas/zfs-passphrases.conf"
echo “”
echo “SOLUTION”
echo " Set the passphrases in this format:"
echo " tank1/Data:foo bar baz"
echo " tankN/VolumeName:pass phrase goes here"
echo “”
} >&2
exit 1
fi

fn_list() { (
b_dataset_url=“${TRUENAS_BASE_URL}/api/v2.0/pool/dataset”
echo " GET ${b_dataset_url} (listing dataset ids)…" >&2
curl --fail-with-body -sS -k “${b_dataset_url}”
-H “Authorization: Bearer ${TRUENAS_API_KEY}” |
jq -r ‘. | select(.encrypted == true) | .id’
); }

fn_unlock() { (
b_dataset_id=“${1}”
b_dataset_phrase=“${2}”

b_unlock_url="${TRUENAS_BASE_URL}/api/v2.0/pool/dataset/unlock"
printf "    POST %s (unlocking %s)..." "${b_unlock_url}" "${b_dataset_id}" >&2
curl --fail-with-body -sS -k "${b_unlock_url}" \
    -H "Authorization: Bearer ${TRUENAS_API_KEY}" \
    -H "Content-Type: application/json" \
    -d '{ "id": "'"${b_dataset_id}"'"
        , "unlock_options": {
            "recursive": true,
            "datasets": [
              { "name": "'"${b_dataset_id}"'"
              , "passphrase": "'"${b_dataset_phrase}"'"}
            ]
          }
        }'
echo " unlocked" >&2

); }

main() { (
b_truenas_zfs_phrases=“$(grep -v -E ‘^\s*(#.*)?$’ ~/.config/truenas/zfs-passphrases.conf)”
echo “Unlocking TrueNAS…” >&2
fn_list | while read -r b_dataset_id; do
b_dataset_phrase=“$(
echo “${b_truenas_zfs_phrases}” |
grep -F “${b_dataset_id}:” |
cut -d’:’ -f2
)”
if test -z “${b_dataset_phrase}”; then
echo " SKIP ‘${b_dataset_id}’: no passphrase" >&2
continue
fi

    fn_unlock "${b_dataset_id}" "${b_dataset_phrase}"
done
echo "Done"

); }

main

Here is the link:

With the help of Claude I translated it to Powershell.

Summary

#!/usr/bin/env pwsh
param(
[string]$Action
)

====== CONFIGURATION SECTION ======

Edit these values directly in the script

$TRUENAS_BASE_URL = “https://10.10.10.30” # Your TrueNAS URL (no trailing slash)
$TRUENAS_API_KEY = “xyz” # API key from https://truenas.local/ui/apikeys

Dataset passphrases in format: “dataset-name” = “passphrase”

$DATASET_PASSPHRASES = @{
“sata” = “secret”
#“tank2/Media” = “another passphrase”
# Add more datasets as needed:
# “pool/dataset” = “passphrase”
}

====== END CONFIGURATION SECTION ======

Set strict mode for better error handling

Set-StrictMode -Version Latest
$ErrorActionPreference = “Stop”

Configure TLS/SSL settings for Windows PowerShell 5.1

if ($PSVersionTable.PSVersion.Major -lt 6) {
# Allow all TLS versions and ignore certificate errors
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls11 -bor [System.Net.SecurityProtocolType]::Tls
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
}

Handle version argument

if ($Action -eq “version” -or $Action -eq “–version” -or $Action -eq “-V”) {
Write-Host “truenas-unlock-volumes v$g_version ($g_build)”
Write-Host “copyright 2024 $g_author ($g_license license)”
Write-Host $g_license_url
exit 0
}

Handle help argument

if ($Action -eq “help” -or $Action -eq “–help”) {
Write-Host “truenas-unlock-volumes v$g_version ($g_build)” -ForegroundColor Yellow
Write-Host “copyright 2024 $g_author ($g_license license)”
Write-Host $g_license_url
Write-Host “”
Write-Host “USAGE” -ForegroundColor Green
Write-Host " truenas-unlock-volumes"
Write-Host “”
Write-Host “CONFIG” -ForegroundColor Green
Write-Host “”
Write-Host " Edit the configuration section directly in this script:"
Write-Host " $TRUENAS_BASE_URL = https://truenas.local`” # no trailing slash"
Write-Host " $TRUENAS_API_KEY = “your-api-key" # from /ui/apikeys" Write-Host " $DATASET_PASSPHRASES = @{”
Write-Host " "tank1/Data" = "your passphrase""
Write-Host " }"
Write-Host “”
exit 0
}

Validate configuration

if (-not $TRUENAS_BASE_URL -or $TRUENAS_BASE_URL -eq “https://truenas.local” -or $TRUENAS_BASE_URL -eq “”) {
Write-Host “”
Write-Host “ERROR” -ForegroundColor Red
Write-Host " TRUENAS_BASE_URL not configured in script"
Write-Host “”
Write-Host “SOLUTION” -ForegroundColor Green
Write-Host " Edit the script and set:"
Write-Host " $TRUENAS_BASE_URL = https://your-truenas-ip-or-hostname`”"
Write-Host “”
exit 1
}

if (-not $TRUENAS_API_KEY -or $TRUENAS_API_KEY -eq “your-api-key-here” -or $TRUENAS_API_KEY -eq “”) {
Write-Host “”
Write-Host “ERROR” -ForegroundColor Red
Write-Host " TRUENAS_API_KEY not configured in script"
Write-Host “”
Write-Host “SOLUTION” -ForegroundColor Green
Write-Host " 1. Go to $TRUENAS_BASE_URL/ui/apikeys"
Write-Host " 2. Create a new API key"
Write-Host " 3. Edit the script and set:"
Write-Host " $TRUENAS_API_KEY = “your-actual-api-key`”"
Write-Host “”
exit 1
}

if (-not $DATASET_PASSPHRASES -or $DATASET_PASSPHRASES.Count -eq 0) {
Write-Host “”
Write-Host “ERROR” -ForegroundColor Red
Write-Host " No dataset passphrases configured in script"
Write-Host “”
Write-Host “SOLUTION” -ForegroundColor Green
Write-Host " Edit the script and add your datasets to $DATASET_PASSPHRASES:" Write-Host " $DATASET_PASSPHRASES = @{"
Write-Host " "tank1/Data" = "your passphrase here""
Write-Host " "pool/dataset" = "another passphrase""
Write-Host " }"
Write-Host “”
exit 1
}

Configure TLS/SSL settings for Windows PowerShell 5.1

if ($PSVersionTable.PSVersion.Major -lt 6) {
# Force TLS 1.2 specifically
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
}

Function to list encrypted datasets

function Get-EncryptedDatasets {
$datasetUrl = “$TRUENAS_BASE_URL/api/v2.0/pool/dataset”
Write-Host " GET $datasetUrl (listing dataset ids)…" -ForegroundColor Cyan

try {
    $headers = @{
        "Authorization" = "Bearer $TRUENAS_API_KEY"
    }
    
    # Skip certificate validation for self-signed certificates
    if ($PSVersionTable.PSVersion.Major -ge 6) {
        $response = Invoke-RestMethod -Uri $datasetUrl -Headers $headers -SkipCertificateCheck
    } else {
        # For Windows PowerShell 5.1 - SSL settings already configured globally
        $response = Invoke-RestMethod -Uri $datasetUrl -Headers $headers
    }
    
    # Filter for encrypted datasets and return their IDs
    return $response | Where-Object { $_.encrypted -eq $true } | Select-Object -ExpandProperty id
}
catch {
    Write-Host "Failed to retrieve datasets: $_" -ForegroundColor Red
    exit 1
}

}

Function to unlock a dataset

function Unlock-Dataset {
param(
[string]$DatasetId,
[string]$DatasetPhrase
)

$unlockUrl = "$TRUENAS_BASE_URL/api/v2.0/pool/dataset/unlock"
Write-Host "    POST $unlockUrl (unlocking $DatasetId)..." -ForegroundColor Cyan -NoNewline

$body = @{
    id = $DatasetId
    unlock_options = @{
        recursive = $true
        datasets = @(
            @{
                name = $DatasetId
                passphrase = $DatasetPhrase
            }
        )
    }
} | ConvertTo-Json -Depth 5

try {
    $headers = @{
        "Authorization" = "Bearer $TRUENAS_API_KEY"
        "Content-Type" = "application/json"
    }
    
    # Skip certificate validation for self-signed certificates
    if ($PSVersionTable.PSVersion.Major -ge 6) {
        $response = Invoke-RestMethod -Uri $unlockUrl -Method Post -Headers $headers -Body $body -SkipCertificateCheck
    } else {
        # For Windows PowerShell 5.1 - SSL settings already configured globally
        $response = Invoke-RestMethod -Uri $unlockUrl -Method Post -Headers $headers -Body $body
    }
    
    Write-Host " unlocked" -ForegroundColor Green
}
catch {
    Write-Host " failed: $_" -ForegroundColor Red
}

}

Main execution

function Main {
Write-Host “Unlocking TrueNAS…” -ForegroundColor Yellow

$datasetIds = Get-EncryptedDatasets

foreach ($datasetId in $datasetIds) {
    # Find matching passphrase in the hashtable
    if ($DATASET_PASSPHRASES.ContainsKey($datasetId)) {
        $datasetPhrase = $DATASET_PASSPHRASES[$datasetId]
        
        if (-not [string]::IsNullOrWhiteSpace($datasetPhrase)) {
            Unlock-Dataset -DatasetId $datasetId -DatasetPhrase $datasetPhrase
        } else {
            Write-Host "    SKIP '$datasetId': empty passphrase" -ForegroundColor Yellow
        }
    } else {
        Write-Host "    SKIP '$datasetId': no passphrase configured in script" -ForegroundColor Yellow
    }
}

Write-Host "Done" -ForegroundColor Green

}

Execute main function

Main

Now after upgrading to Goldeye it tells me unlock_options is not expected.
Also tried on Linux there it gives me a jq parsing error.

What to do?

That script appears to be using the deprecated REST API. I don’t know when it’s going away completely, but it was noted as deprecated in 25.04. A rewrite to use the Websocket API will likely be needed sooner rather than later.

1 Like

With the help of clause I was able to change it to websockets. Claude first gave somethign with websocat, and then a python script- this works now. Bit stuck on Powershell though, there it tells me it cant connect to serhver Net-Test is positive though… Not sure though this uses tls?

#!/bin/sh
set -e
set -u

============================================================================

CONFIGURATION - Edit these values

============================================================================

TRUENAS_BASE_URL=“https://truenas.local” # no trailing slash
TRUENAS_USERNAME=“truenas_admin”
TRUENAS_PASSWORD=“abc555”

ZFS Dataset Passphrases (format: “dataset_name:passphrase”)

TRUENAS_ZFS_PASSPHRASES=’
sata:vantage wise probable proofing gimmick slider
tank2/VolumeName:pass phrase goes here

============================================================================

g_version=“1.0.0-ws-python”
g_build=“2025-10-30”
g_author=“AJ ONeal aj@therootcompany.com (https://bnna.net)”
g_license=“CC0-1.0”
g_license_url=“https://creativecommons.org/publicdomain/zero/1.0/”

if test “version” = “${1:-}” || test “–version” = “${1:-}” || test “-V” = “${1:-}”; then
echo “truenas-unlock-volumes v${g_version} (${g_build})”
echo “copyright 2024 ${g_author} (${g_license} license)”
echo “${g_license_url}”
exit 0
fi

if test “help” = “${1:-}” || test “–help” = “${1:-}”; then
{
echo “truenas-unlock-volumes v${g_version} (${g_build})”
echo “copyright 2024 ${g_author} (${g_license} license)”
echo “${g_license_url}”
echo “”
echo “USAGE”
echo " truenas-unlock-volumes"
echo “”
echo “CONFIG”
echo " Edit the configuration variables at the top of this script"
echo “”
} >&2
exit 0
fi

Validate configuration

if test -z “${TRUENAS_BASE_URL:-}” || test -z “${TRUENAS_USERNAME:-}” || test -z “${TRUENAS_PASSWORD:-}”; then
{
echo “ERROR: Missing required configuration in script”
} >&2
exit 1
fi

if test -z “${TRUENAS_ZFS_PASSPHRASES:-}” || ! echo “${TRUENAS_ZFS_PASSPHRASES}” | grep -q -v -E ‘^\s*$’; then
{
echo “ERROR: Missing ZFS passphrases configuration in script”
} >&2
exit 1
fi

Check for Python

if ! command -v python3 >/dev/null 2>&1; then
echo “ERROR: python3 is not installed” >&2
exit 1
fi

Check if websocket-client is installed

python3 -c “import websocket” 2>/dev/null || {
{
echo “”
echo “ERROR”
echo " Python ‘websocket-client’ library is not installed"
echo “”
echo “SOLUTION”
echo " Install it with:"
echo " pip3 install websocket-client"
echo “”
} >&2
exit 1
}

Create temporary Python script file

TEMP_PYTHON_SCRIPT=“/tmp/truenas_unlock_$$.py”

cat > “${TEMP_PYTHON_SCRIPT}” <<‘PYTHON_EOF’
import json
import ssl
import sys
import websocket

class TrueNASClient:
def init(self, base_url, username, password):
self.base_url = base_url
self.username = username
self.password = password
self.ws_url = base_url.replace(‘https://’, ‘wss://’).replace(‘http://’, ‘ws://’) + ‘/websocket’
self.ws = None
self.msg_id = 0

def connect(self):
    """Connect to TrueNAS WebSocket"""
    print("    Connecting to TrueNAS WebSocket...", file=sys.stderr)
    sslopt = {"cert_reqs": ssl.CERT_NONE}
    self.ws = websocket.create_connection(self.ws_url, sslopt=sslopt, timeout=10)

    # Send connect message
    connect_msg = {
        "msg": "connect",
        "version": "1",
        "support": ["1"]
    }
    self.ws.send(json.dumps(connect_msg))

    # Wait for connected response
    response = json.loads(self.ws.recv())
    if response.get("msg") != "connected":
        raise Exception(f"Failed to connect: {response}")

    # Authenticate
    print("    Authenticating...", file=sys.stderr)
    self.call("auth.login", [self.username, self.password])

def call(self, method, params=None):
    """Make a method call"""
    if params is None:
        params = []

    self.msg_id += 1
    msg_id = str(self.msg_id)

    call_msg = {
        "msg": "method",
        "method": method,
        "params": params,
        "id": msg_id
    }

    self.ws.send(json.dumps(call_msg))

    # Wait for response with matching ID
    while True:
        response_str = self.ws.recv()
        response = json.loads(response_str)

        # Skip non-result messages
        if response.get("msg") not in ["result", "error"]:
            continue

        # Check if this is our response
        if response.get("id") == msg_id:
            if response.get("msg") == "error":
                raise Exception(f"API Error: {response.get('error')}")
            return response.get("result")

def close(self):
    """Close WebSocket connection"""
    if self.ws:
        self.ws.close()

def main():
base_url = sys.argv[1]
username = sys.argv[2]
password = sys.argv[3]
passphrases_file = sys.argv[4]

# Read passphrases from file
passphrases = {}
with open(passphrases_file, 'r') as f:
    for line in f:
        line = line.strip()
        if line and ':' in line:
            dataset, phrase = line.split(':', 1)
            passphrases[dataset] = phrase

client = TrueNASClient(base_url, username, password)

try:
    client.connect()

    print("Unlocking TrueNAS...", file=sys.stderr)

    # List encrypted datasets
    print("    WS pool.dataset.query (listing dataset ids)...", file=sys.stderr)
    datasets = client.call("pool.dataset.query", [[["encrypted", "=", True]]])

    for dataset in datasets:
        dataset_id = dataset['id']

        if dataset_id not in passphrases:
            print(f"    SKIP '{dataset_id}': no passphrase", file=sys.stderr)
            continue

        passphrase = passphrases[dataset_id]

        print(f"    WS pool.dataset.unlock (unlocking {dataset_id})...", end='', file=sys.stderr)

        unlock_params = {
            "recursive": True,
            "datasets": [{
                "name": dataset_id,
                "passphrase": passphrase
            }]
        }

        result = client.call("pool.dataset.unlock", [dataset_id, unlock_params])
        print(" unlocked", file=sys.stderr)

    print("Done", file=sys.stderr)

except Exception as e:
    print(f"ERROR: {e}", file=sys.stderr)
    sys.exit(1)
finally:
    client.close()

if name == “main”:
main()
PYTHON_EOF

Create temporary passphrases file

TEMP_PASSPHRASES_FILE=“/tmp/truenas_passphrases_$$.txt”
echo “${TRUENAS_ZFS_PASSPHRASES}” | grep -v -E ‘^\s*$’ > “${TEMP_PASSPHRASES_FILE}”

Run Python script

python3 “${TEMP_PYTHON_SCRIPT}” “${TRUENAS_BASE_URL}” “${TRUENAS_USERNAME}” “${TRUENAS_PASSWORD}” “${TEMP_PASSPHRASES_FILE}”

Cleanup

rm -f “${TEMP_PYTHON_SCRIPT}” “${TEMP_PASSPHRASES_FILE}”

If you’re using python, why don’t you simply use our python api client? It’d be much simpler and less error prone than rolling your own through AI slop.

1 Like

Thanks for the hint. I would love to use it, unfortunately I lack the knowledge - last coding was 2001 - HTML ^^

You can have claude ingest the above library and examples and have it output a script using it that does what you want.

1 Like