OPNsense Forum

English Forums => 26.1, 26,4 Series => Topic started by: Rene78 on May 04, 2026, 07:31:11 PM

Title: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: Rene78 on May 04, 2026, 07:31:11 PM
Hi,

I have a working ACME client setup with a wildcard Let's Encrypt certificate for my domain. Also have a working nginx based reverse proxy to three services. Those services are running on a TrueNAS SCALE 25.10.3 (latest patch) system.

While all https access to the services is working fine through nginx with A+ trusted HTTPS (reverse proxy handles upstream stuff on the LAN to TrueNAS) the services on the TrueNAS system still use selfsigned certs from the TrueNAS box.

Now, while not essential (I trust my home lan ;-)) I am trying to get the whole certificate chain proper. Just a hobby thing.

Therefore I made an API key (root) on my TrueNAS and created the automation in the ACME client. Used the websocket (not deprecated one). Filled in all the fields, which are self explanatory. Reran the automations from the commands in OPNsense but the upload errors out.

[Mon May 4 18:02:46 CEST 2026] TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable.

I tried all automation modes (none, ws and wss) but error remains. The API key is really in the appropriate field. The plugin however does not seem to set the value from the field in the environment variable.

I am a little at hand (no ssh) from my phone currently so no CLI attempt possible.

Anybody recognize this? Seems a bug...


Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: sopex on May 05, 2026, 01:02:05 PM
It will be fixed on the next version. You can use the deprecated until then. Truenas 26+ deprecates it.
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: franco on May 05, 2026, 01:32:05 PM
There was an unintended issue with our merge tooling (on a case insensitive file system) that ended up in the file not being renamed correctly although I'm not sure that's the problem here:

https://github.com/opnsense/plugins/commit/251c7a5e93

It was since hotfixed but perhaps it needs a reinstall if you caught the other version:

# opnsense-revert os-acme-client

Could be unrelated, though.


Cheers,
Franco
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: fraenki on May 05, 2026, 01:59:14 PM
Quotealthough I'm not sure that's the problem here

This issue is unrelated to the rename hiccup. 😊

Quote> [Mon May 4 18:02:46 CEST 2026] TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable.

I have tested this and was unable to reproduce this issue.
Please try again and provide the full ACME Log and all "AcmeClient" entries from the System Log.
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: Rene78 on May 05, 2026, 07:16:09 PM

QuoteI have tested this and was unable to reproduce this issue.
Please try again and provide the full ACME Log and all "AcmeClient" entries from the System Log.

I can do this when I have computer access again in a few days.

However, sopex mentions it will be fixed in the next version.... This indicates bug...

Maybe the setenv variable had been set in your case earlier and therefore it works? Is that possible?

Regardless, i'll dig into it in a few days time and help out isolating any issue. Thanks


Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: sopex on May 05, 2026, 07:27:27 PM
Quote from: Rene78 on May 05, 2026, 07:16:09 PMHowever, sopex mentions it will be fixed in the next version.... This indicates bug...


I made the truenas websocket addition and it was working, but then there were some complications with the naming conventions that fraenki fixed.

So I jumped the gun, and thought something broke there.

But if Frankie says it's not that, he is correct.
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: Rene78 on May 05, 2026, 07:59:26 PM
QuoteBut if Frankie says it's not that, he is correct.

Copy all. I'll try and get all the logs on the forum asap.
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: Rene78 on May 05, 2026, 09:30:22 PM
OK... So, I flicked open my iPad and tried again, as suggested.

Firstly I tested the deprecated API system. That worked as expected when filling in the values and with a new API-key. Used the HTTPS scheme and it exported the certificate as it should. No errors. Ohh, and leaving out the "X-" integer index does break the API and generated an API key error. Tested both, so answered my own question.

ACME log working:
2026-05-05T20:59:25acme.sh [Tue May 5 20:59:25 CEST 2026] Success
2026-05-05T20:59:25acme.sh [Tue May 5 20:59:25 CEST 2026] Reloading TrueNAS web UI
2026-05-05T20:59:25acme.sh [Tue May 5 20:59:25 CEST 2026] Deleting old certificate
2026-05-05T20:59:25acme.sh [Tue May 5 20:59:25 CEST 2026] FTP certificate is not configured or is not the same as TrueNAS web UI
2026-05-05T20:59:24acme.sh [Tue May 5 20:59:24 CEST 2026] Checking if FTP certificate is the same as the TrueNAS web UI
2026-05-05T20:59:24acme.sh [Tue May 5 20:59:24 CEST 2026] S3 certificate is not configured or is not the same as TrueNAS web UI
2026-05-05T20:59:24acme.sh [Tue May 5 20:59:24 CEST 2026] Checking if S3 certificate is the same as the TrueNAS web UI
2026-05-05T20:59:24acme.sh [Tue May 5 20:59:24 CEST 2026] WebDAV certificate is not configured or is not the same as TrueNAS web UI
2026-05-05T20:59:24acme.sh [Tue May 5 20:59:24 CEST 2026] Checking if WebDAV certificate is the same as the TrueNAS web UI
2026-05-05T20:59:24acme.sh [Tue May 5 20:59:24 CEST 2026] Current activate certificate ID: 5
2026-05-05T20:59:24acme.sh [Tue May 5 20:59:24 CEST 2026] Fetching list of installed certificates
2026-05-05T20:59:21acme.sh [Tue May 5 20:59:21 CEST 2026] Uploading new certificate to TrueNAS
2026-05-05T20:59:20acme.sh [Tue May 5 20:59:20 CEST 2026] Getting current active certificate from TrueNAS
2026-05-05T20:59:20acme.sh [Tue May 5 20:59:20 CEST 2026] Detected TrueNAS system version: unknown
2026-05-05T20:59:20acme.sh [Tue May 5 20:59:20 CEST 2026] Detected TrueNAS system os: unknown
2026-05-05T20:59:20acme.sh [Tue May 5 20:59:20 CEST 2026] Getting TrueNAS version
2026-05-05T20:59:20acme.sh [Tue May 5 20:59:20 CEST 2026] TrueNAS system state: "READY".
2026-05-05T20:59:20acme.sh [Tue May 5 20:59:20 CEST 2026] Testing Connection TrueNAS

Knowing that the deprecated API HTTPS scheme works gives confidence in the input fields. Using the same API key, but now with the websocket (ws, wss, none) results in a different error now. The environment variables seem OK, but it is 100% the same API key that works in HTTPS scheme. I cloned the working HTTPS one. And double checked.

The ACME log:
2026-05-05T21:12:22acme.sh [Tue May 5 21:12:22 CEST 2026] Error encountered while deploying.
2026-05-05T21:12:22acme.sh [Tue May 5 21:12:22 CEST 2026] Error deploying for domain: *.<REDACTED>.nl
2026-05-05T21:12:22acme.sh [Tue May 5 21:12:22 CEST 2026] Verify API key.
2026-05-05T21:12:22acme.sh [Tue May 5 21:12:22 CEST 2026] Please check environment variables DEPLOY_TRUENAS_APIKEY, DEPLOY_TRUENAS_HOSTNAME and DEPLOY_TRUENAS_PROTOCOL.
2026-05-05T21:12:22acme.sh [Tue May 5 21:12:22 CEST 2026] TrueNAS is not ready.
2026-05-05T21:12:22acme.sh [Tue May 5 21:12:22 CEST 2026] Checking TrueNAS health...
2026-05-05T21:12:22acme.sh [Tue May 5 21:12:22 CEST 2026] Environment variables: OK
2026-05-05T21:12:22acme.sh [Tue May 5 21:12:22 CEST 2026] Checking environment variables...

System log in ACME client:
2026-05-05T21:12:22opnsense AcmeClient: running acme.sh deploy hook failed (acme_truenas_ws)
2026-05-05T21:12:22opnsense AcmeClient: AcmeClient: The shell command returned exit code '1': '/usr/local/sbin/acme.sh --deploy --syslog 6 --log-level 1 --server 'letsencrypt' --home '/var/etc/acme-client/home' --cert-home '/var/etc/acme-client/cert-home/67e6acd371dce3.58753914' --certpath '/var/etc/acme-client/certs/67e6acd371dce3.58753914/cert.pem' --keypath '/var/etc/acme-client/keys/67e6acd371dce3.58753914/private.key' --capath '/var/etc/acme-client/certs/67e6acd371dce3.58753914/chain.pem' --fullchainpath '/var/etc/acme-client/certs/67e6acd371dce3.58753914/fullchain.pem' --domain '*.<REDACTED>.nl' --ecc --deploy-hook truenas_ws --insecure'
2026-05-05T21:12:22opnsense AcmeClient: running automation (acme.sh): TrueNAS-export
2026-05-05T21:12:22opnsense AcmeClient: running automations for certificate: *.<REDACTED>.nl

Yesterday the ACME logs were different doing the same. Does this has something to do with setenv being done once (at the HTTPS scheme that worked) and that the system now has an API key but with older value..? I tested the old scheme first today to check my understanding before retrying the Websocket with new key. So, initialization thing?

AMCE logs yesterday:
2026-05-04T20:48:12acme.sh [Mon May 4 20:48:12 CEST 2026] Error encountered while deploying.
2026-05-04T20:48:12acme.sh [Mon May 4 20:48:12 CEST 2026] Error deploying for domain: *.<REDACTED>.nl
2026-05-04T20:48:12acme.sh [Mon May 4 20:48:12 CEST 2026] TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable.
2026-05-04T20:48:12acme.sh [Mon May 4 20:48:12 CEST 2026] Checking environment variables...

System logs yesterday:
2026-05-04T20:48:12opnsense AcmeClient: running acme.sh deploy hook failed (acme_truenas_ws)
2026-05-04T20:48:12opnsense AcmeClient: AcmeClient: The shell command returned exit code '1': '/usr/local/sbin/acme.sh --deploy --syslog 6 --log-level 1 --server 'letsencrypt' --home '/var/etc/acme-client/home' --cert-home '/var/etc/acme-client/cert-home/67e6acd371dce3.58753914' --certpath '/var/etc/acme-client/certs/67e6acd371dce3.58753914/cert.pem' --keypath '/var/etc/acme-client/keys/67e6acd371dce3.58753914/private.key' --capath '/var/etc/acme-client/certs/67e6acd371dce3.58753914/chain.pem' --fullchainpath '/var/etc/acme-client/certs/67e6acd371dce3.58753914/fullchain.pem' --domain '*.<REDACTED>.nl' --ecc --deploy-hook truenas_ws --insecure'
2026-05-04T20:48:12opnsense AcmeClient: running automation (acme.sh): TrueNAS_cert
2026-05-04T20:48:12opnsense AcmeClient: running automations for certificate: *.<REDACTED>.nl
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: fraenki on May 06, 2026, 11:11:34 AM
Quote2026-05-05T21:12:22acme.sh [Tue May 5 21:12:22 CEST 2026] Verify API key.
2026-05-05T21:12:22acme.sh [Tue May 5 21:12:22 CEST 2026] Please check environment variables DEPLOY_TRUENAS_APIKEY, DEPLOY_TRUENAS_HOSTNAME and DEPLOY_TRUENAS_PROTOCOL.

If you're seeing this message, it means that os-acme-client is working perfectly fine.

This error message is raised by acme.sh when a communication error with your TrueNAS occured:
https://github.com/acmesh-official/acme.sh/blob/7735cdf3abe84bce8c1e37e7fa46c71e38606262/deploy/truenas_ws.sh#L219
This code checks the "system.ready" TrueNAS API endpoint and seems to receive an error or invalid result.

I can't give you any advice for setting up TrueNAS. Maybe you need to configure something to make the TrueNAS websocket API work.
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: Rene78 on May 06, 2026, 01:35:34 PM
Hmmm... okay.. wasn't aware that TrueNAS API needed any configuration, but I'll dive into that one next. Thanks! I'll report back when I find something.
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: fraenki on May 06, 2026, 01:42:23 PM
If you want to dive even deeper: try to query the TrueNAS API endpoint "system.ready" using `curl` and your API key. The API documentation is available here:
https://www.truenas.com/docs/api/scale_websocket_api.html
It should make the root cause more obvious, but crafting the `curl` command might be a challenge.
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: Rene78 on May 06, 2026, 03:31:59 PM
Quote from: fraenki on May 06, 2026, 01:42:23 PMIf you want to dive even deeper: try to query the TrueNAS API endpoint "system.ready" using `curl` and your API key. The API documentation is available here:
https://www.truenas.com/docs/api/scale_websocket_api.html
It should make the root cause more obvious, but crafting the `curl` command might be a challenge.

I'll start by fiddling with cli on the TrueNAS box, the api client. See where I end up there
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: ceel on May 14, 2026, 03:10:38 PM
Quote from: fraenki on May 06, 2026, 01:42:23 PMIf you want to dive even deeper: try to query the TrueNAS API endpoint "system.ready" using `curl` and your API key. The API documentation is available here:
https://www.truenas.com/docs/api/scale_websocket_api.html
It should make the root cause more obvious, but crafting the `curl` command might be a challenge.

Hi Fraenki,
I have the same problem as OP and when I copied the command from the logs and tried running it manually from an SSH shell (with the environment variables set) I got the follwing output:

root@router:/ # /usr/local/sbin/acme.sh --deploy --syslog 6 --log-level 1 --server 'letsencrypt' --home '/var/etc/acme-client/home' --cert-home '/var/etc/acme-client/cert-home/689ca6dd3b9044.57803608' --certpath '/var/etc/acme-client/certs/689ca6dd3b9044.57803608/cert.pem' --keypath '/var/etc/acme-client/keys/689ca6dd3b9044.57803608/private.key' --capath '/var/etc/acme-client/certs/689ca6dd3b9044.57803608/chain.pem' --fullchainpath '/var/etc/acme-client/certs/689ca6dd3b9044.57803608/fullchain.pem' --domain 'truenas.mgmt.lofjard.se' --ecc --deploy-hook truenas_ws --insecure
[Thu May 14 14:56:38 CEST 2026] Checking environment variables...
[Thu May 14 14:56:38 CEST 2026] Environment variables: OK
[Thu May 14 14:56:38 CEST 2026] Checking TrueNAS health...
/usr/local/sbin/acme.sh: midclt: not found
[Thu May 14 14:56:38 CEST 2026] TrueNAS is not ready.
[Thu May 14 14:56:38 CEST 2026] Please check environment variables DEPLOY_TRUENAS_APIKEY, DEPLOY_TRUENAS_HOSTNAME and DEPLOY_TRUENAS_PROTOCOL.
[Thu May 14 14:56:38 CEST 2026] Verify API key.
[Thu May 14 14:56:38 CEST 2026] Error deploying for domain: truenas.mgmt.lofjard.se
[Thu May 14 14:56:38 CEST 2026] Error encountered while deploying.

A quick "find / | grep midclt" returns nothing. Is my install broken or is there a file missing from the ACME plugin?
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: franco on May 18, 2026, 09:02:21 AM
https://github.com/truenas/midcli ???


Cheers,
Franco
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: ceel on May 18, 2026, 09:25:23 AM
Quote from: franco on May 18, 2026, 09:02:21 AMhttps://github.com/truenas/midcli ???


Cheers,
Franco

Nope (I found that one as well since it looked like a spelling error initially)
 
It seems to be a part of this:
https://github.com/truenas/api_client
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: franco on May 18, 2026, 09:38:40 AM
In either case it looks like it expects TrueNAS as OS, not OPNsense.


Cheers,
Franco
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: Rene78 on May 19, 2026, 09:02:00 AM
Quote from: ceel on May 14, 2026, 03:10:38 PM[Thu May 14 14:56:38 CEST 2026] Checking TrueNAS health...
/usr/local/sbin/acme.sh: midclt: not found

QuoteIt seems to be a part of this:
https://github.com/truenas/api_client

Thanks for finding this apparently missing API client. I reproduced this missing midclt also from a shell. Not sure why it does not show up in the system logs though. ;-)

Quote from: franco on May 18, 2026, 09:38:40 AMIn either case it looks like it expects TrueNAS as OS, not OPNsense.

Cheers,
Franco

What's in a name... the TrueNAS client apparently called midcli and the one ceel references (https://github.com/truenas/api_client) is midclt. To make it more confusing both the clients seem to be preinstalled on a TrueNAS box according to the GitHub documentation.

Anyway,

Hence, imho the midclt could be used (assumption here, if the code works on FreeBSD...) to complete the deployment task.
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: Patrick M. Hausen on May 19, 2026, 09:11:40 AM
Shouldn't the automation "simply" use SSH to execute whatever is necessary on the TrueNAS system, including midclt?
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: Rene78 on May 19, 2026, 12:31:13 PM
Quote from: Patrick M. Hausen on May 19, 2026, 09:11:40 AMShouldn't the automation "simply" use SSH to execute whatever is necessary on the TrueNAS system, including midclt?

I am not skilled enough to check what the acme client and plugin does on OPNSense (use local midctl OR remote calling through SSH or something else HTTP calls etc.). What I understand is that midctl is/was intended to make calling the TrueNAS API as easy as possible.
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: Monviech (Cedrik) on May 19, 2026, 02:26:52 PM
Sadly it's just mid.

(Sorry had to make that joke.)
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: TheCrackedCube on June 03, 2026, 03:43:03 AM
Hi,

  Did anyone find a solution to this?
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: amichel on June 05, 2026, 06:19:52 AM
I "fixed" it by using the ACME plugin on the truenas scale box. I am using easydns as provider and decided to go for a script to set the DNS records. after some fiddling I was able to get that working.
#!/bin/sh

# ============================================================
# TrueNAS SCALE ACME Shell Authenticator for easyDNS
#
# Script:
#   /mnt/Pool/Scripts/ACME/acmedns.sh
#
#:
#
# TrueNAS-Format:
#   acmedns.sh set   scale.domain.com _acme-challenge.scale.domain.com TOKEN
#   acmedns.sh unset scale.domain.com _acme-challenge.scale.domain.com TOKEN
#
# Fallback-Format:
#   acmedns.sh set   _acme-challenge.scale.domain.com TOKEN
#   acmedns.sh unset _acme-challenge.scale.domain.com TOKEN
# ============================================================

set -eu

LOG_DIR="/mnt/Pool/Scripts/ACME"
LOG_FILE="${LOG_DIR}/acmedns.log"
MAX_LOG_BYTES=$((1024 * 1024))

API_BASE="https://rest.easydns.net"
EASYDNS_DOMAIN="domain.com"
EASYDNS_TTL=300

EASYDNS_TOKEN_FILE="/mnt/Pool/Scripts/ACME/easydns.token"
EASYDNS_KEY_FILE="/mnt/Pool/Scripts/ACME/easydns.key"

PROPAGATION_SLEEP=60

mkdir -p "$LOG_DIR"

rotate_log_if_needed() {
  if [ -f "$LOG_FILE" ]; then
    size="$(stat -c%s "$LOG_FILE" 2>/dev/null || echo 0)"
    if [ "$size" -gt "$MAX_LOG_BYTES" ]; then
      : > "$LOG_FILE"
    fi
  fi
}

log() {
  echo "[$(date '+%F %T')] $*" >> "$LOG_FILE"
}

fail() {
  log "ERROR: $*"
  exit 1
}

rotate_log_if_needed

[ -f "$EASYDNS_TOKEN_FILE" ] || fail "Missing EasyDNS token file: $EASYDNS_TOKEN_FILE"
[ -f "$EASYDNS_KEY_FILE" ] || fail "Missing EasyDNS key file: $EASYDNS_KEY_FILE"

EASYDNS_TOKEN="$(tr -d '\r\n' < "$EASYDNS_TOKEN_FILE")"
EASYDNS_KEY="$(tr -d '\r\n' < "$EASYDNS_KEY_FILE")"

[ -n "$EASYDNS_TOKEN" ] || fail "EasyDNS token file is empty"
[ -n "$EASYDNS_KEY" ] || fail "EasyDNS key file is empty"

record_to_host() {
  record="$1"

  # trailing dot entfernen und lowercase
  record="$(printf '%s' "$record" | sed 's/\.$//' | tr '[:upper:]' '[:lower:]')"

  case "$record" in
    *."$EASYDNS_DOMAIN")
      printf '%s\n' "${record%.$EASYDNS_DOMAIN}"
      ;;
    "$EASYDNS_DOMAIN")
      printf '%s\n' "@"
      ;;
    *)
      fail "Challenge record '$record' is not inside zone '$EASYDNS_DOMAIN'"
      ;;
  esac
}

json_body_for_add() {
  host="$1"
  token="$2"

  python3 - "$host" "$EASYDNS_DOMAIN" "$EASYDNS_TTL" "$token" <<'PY'
import sys, json

host = sys.argv[1]
domain = sys.argv[2]
ttl = int(sys.argv[3])
token = sys.argv[4]

body = {
    "host": host,
    "domain": domain,
    "ttl": ttl,
    "prio": 0,
    "type": "txt",
    "rdata": token,
}

print(json.dumps(body, separators=(",", ":")))
PY
}

api_get_records() {
  curl -fsS \
    -u "${EASYDNS_TOKEN}:${EASYDNS_KEY}" \
    --connect-timeout 15 \
    --max-time 60 \
    "${API_BASE}/zones/records/all/${EASYDNS_DOMAIN}?format=json"
}

api_add_txt() {
  body="$1"

  curl -fsS \
    -u "${EASYDNS_TOKEN}:${EASYDNS_KEY}" \
    -X PUT \
    --connect-timeout 15 \
    --max-time 60 \
    -H "Content-Type: application/json" \
    -d "$body" \
    "${API_BASE}/zones/records/add/${EASYDNS_DOMAIN}/txt?format=json"
}

api_delete_record() {
  id="$1"

  log "Trying DELETE with confirm body for id=$id"

  if curl -fsS \
    -u "${EASYDNS_TOKEN}:${EASYDNS_KEY}" \
    -X DELETE \
    --connect-timeout 15 \
    --max-time 60 \
    -H "Content-Type: application/json" \
    -d '{"confirm":"DELETE"}' \
    "${API_BASE}/zones/records/${EASYDNS_DOMAIN}/${id}?format=json" >> "$LOG_FILE" 2>&1; then
    log "DELETE with confirm body succeeded for id=$id"
    return 0
  fi

  log "DELETE with confirm body failed for id=$id, trying fallback DELETE without body"

  curl -fsS \
    -u "${EASYDNS_TOKEN}:${EASYDNS_KEY}" \
    -X DELETE \
    --connect-timeout 15 \
    --max-time 60 \
    "${API_BASE}/zones/records/${EASYDNS_DOMAIN}/${id}?format=json" >> "$LOG_FILE" 2>&1

  log "Fallback DELETE succeeded for id=$id"
}

find_txt_record_ids() {
  host="$1"
  token="${2:-}"
  records_json="$3"

  printf '%s' "$records_json" | python3 - "$host" "$token" <<'PY'
import sys, json

host = sys.argv[1].lower()
token = sys.argv[2]

try:
    data = json.load(sys.stdin)
except Exception as e:
    print(f"JSON_ERROR:{e}", file=sys.stderr)
    sys.exit(2)

for r in data.get("data", []):
    rtype = str(r.get("type", "")).lower()
    rhost = str(r.get("host", "")).lower()

    rrdata = r.get("rdata", r.get("rData", ""))
    rrdata = str(rrdata).strip()
    rrdata_unquoted = rrdata.strip("\"")

    if rtype == "txt" and rhost == host:
        if not token or rrdata == token or rrdata_unquoted == token:
            rid = r.get("id")
            if rid is not None:
                print(rid)
PY
}

delete_txt_record() {
  host="$1"
  token="${2:-}"

  log "Searching TXT records for host=$host token=${token:-none}"

  records_json="$(api_get_records)"
  log "easyDNS records fetched"

  ids="$(find_txt_record_ids "$host" "$token" "$records_json" || true)"

  if [ -z "$ids" ] && [ -n "$token" ]; then
    log "No TXT record found with exact token. Searching again by host only."
    ids="$(find_txt_record_ids "$host" "" "$records_json" || true)"
  fi

  if [ -z "$ids" ]; then
    log "No matching TXT record found for host=$host"
    return 0
  fi

  echo "$ids" | while IFS= read -r id; do
    [ -z "$id" ] && continue
    log "Deleting TXT record host=$host id=$id"
    api_delete_record "$id"
    log "Delete completed for TXT record id=$id"
    sleep 2
  done
}

set_record() {
  challenge_fqdn="$1"
  token="$2"

  host="$(record_to_host "$challenge_fqdn")"

  log "SET requested"
  log "Challenge FQDN=$challenge_fqdn"
  log "Zone=$EASYDNS_DOMAIN"
  log "Host=$host"

  delete_txt_record "$host" "$token" || true

  body="$(json_body_for_add "$host" "$token")"

  log "Adding TXT record host=$host ttl=$EASYDNS_TTL"
  api_add_txt "$body" >> "$LOG_FILE" 2>&1
  log "TXT record added for host=$host"

  log "Waiting ${PROPAGATION_SLEEP}s for DNS propagation"
  sleep "$PROPAGATION_SLEEP"

  log "SET completed"
}

unset_record() {
  challenge_fqdn="$1"
  token="${2:-}"

  host="$(record_to_host "$challenge_fqdn")"

  log "UNSET requested"
  log "Challenge FQDN=$challenge_fqdn"
  log "Zone=$EASYDNS_DOMAIN"
  log "Host=$host"
  log "Token=${token:-none}"

  delete_txt_record "$host" "$token"

  log "UNSET completed"
}

# ------------------------------------------------------------
# Parameter Parsing
# ------------------------------------------------------------
mode="${1:-}"

arg2="${2:-}"
arg3="${3:-}"
arg4="${4:-}"

domain_fqdn=""
challenge_fqdn=""
txt_token=""

log "============================================================"
log "Raw args: mode='${mode:-}' arg2='${arg2:-}' arg3='${arg3:-}' arg4='${arg4:-}'"

case "$mode" in
  set)
    case "$arg2" in
      _acme-challenge*)
        # Fallback-Format:
        # set challenge token
        challenge_fqdn="$arg2"
        txt_token="$arg3"
        ;;
      *)
        # TrueNAS-Format:
        # set domain challenge token
        domain_fqdn="$arg2"
        challenge_fqdn="$arg3"
        txt_token="$arg4"
        ;;
    esac

    [ -n "$challenge_fqdn" ] || fail "Missing challenge FQDN argument"
    [ -n "$txt_token" ] || fail "Missing TXT token argument"

    set_record "$challenge_fqdn" "$txt_token"
    ;;

  unset)
    case "$arg2" in
      _acme-challenge*)
        # Fallback-Format:
        # unset challenge token
        challenge_fqdn="$arg2"
        txt_token="$arg3"
        ;;
      *)
        # TrueNAS-Format:
        # unset domain challenge token
        domain_fqdn="$arg2"
        challenge_fqdn="$arg3"
        txt_token="$arg4"
        ;;
    esac

    [ -n "$challenge_fqdn" ] || fail "Missing challenge FQDN argument"

    unset_record "$challenge_fqdn" "$txt_token"
    ;;

  *)
    fail "Unknown mode '$mode'. Expected 'set' or 'unset'."
    ;;
esac

exit 0

and then use this script when requesting the certificate. The only thing that is not working is the automatic cleanup of the DNS recored, therefore I created this script:'
#!/bin/sh

set -eu

LOG_DIR="/mnt/Pool/Scripts/ACME"
LOG_FILE="${LOG_DIR}/cleanup-acme-txt.log"

API_BASE="https://rest.easydns.net"
EASYDNS_DOMAIN="domain.com"

EASYDNS_TOKEN_FILE="/mnt/Pool/Scripts/ACME/easydns.token"
EASYDNS_KEY_FILE="/mnt/Pool/Scripts/ACME/easydns.key"

DRY_RUN=0

if [ "${1:-}" = "--dry-run" ]; then
  DRY_RUN=1
fi

mkdir -p "$LOG_DIR"

log() {
  echo "[$(date '+%F %T')] $*" | tee -a "$LOG_FILE"
}

fail() {
  log "ERROR: $*"
  exit 1
}

[ -f "$EASYDNS_TOKEN_FILE" ] || fail "Token file missing: $EASYDNS_TOKEN_FILE"
[ -f "$EASYDNS_KEY_FILE" ] || fail "Key file missing: $EASYDNS_KEY_FILE"

EASYDNS_TOKEN="$(tr -d '\r\n' < "$EASYDNS_TOKEN_FILE")"
EASYDNS_KEY="$(tr -d '\r\n' < "$EASYDNS_KEY_FILE")"

[ -n "$EASYDNS_TOKEN" ] || fail "Token file is empty"
[ -n "$EASYDNS_KEY" ] || fail "Key file is empty"

TMP_RECORDS="$(mktemp)"
TMP_IDS="$(mktemp)"

cleanup_tmp() {
  rm -f "$TMP_RECORDS" "$TMP_IDS"
}

trap cleanup_tmp EXIT INT TERM

log "============================================================"
log "Starting ACME TXT cleanup for zone: $EASYDNS_DOMAIN"

if [ "$DRY_RUN" -eq 1 ]; then
  log "Mode: DRY-RUN - nothing will be deleted"
else
  log "Mode: DELETE - matching records will be deleted"
fi

to run this once a week to clean up the old records. It is not as smart as centralizing the task from opnsense but it does the job.
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: fragrance744 on June 11, 2026, 08:19:30 PM
I ended up setting up ACME in TrueNAS instead.
Title: Re: 26.1.7_2: issue with ACME client automation upload to TrueNAS websocket API
Post by: Creat on June 22, 2026, 02:48:40 AM
Did this ever get resolved or anyone find out the actual cause?

My problem is that I did setup the (new) websocket cert push, and I know it worked fine as I swapped over to that cert. Now it needed a renew, and for some reason it failed to push, showing the same symptoms as described here.

I do NOT want to run letsencrypt on the TrueNAS box as well. That's the whole point of having it on the router/firewall.

To be more explicit:
The only normally accessible error in the log is this:
AcmeClient: AcmeClient: The shell command returned exit code '1': '/usr/local/sbin/acme.sh ...When manually running that command that fails, I get the same errors the others posted (TrueNAS is not ready. and all that). to my knowledge, the TrueNAS API also doesn't need (or allow) any configuration. I'm currently still on 25.10.3.1, but .4 seems available.

So what gives?