Best way to automate a system config.save?

I have a script using the API which saves the output of config.save to an external system so that I don’t lose the latest config changes.

But the API is going to be deprecated in an upcoming release, so this method will no longer work.

What is the official recommended way to do this now that actually works? I’ve tried the more obvious approaches in the past and they all failed.

curl -X POST "http://localhost/api/v2.0/config/save" \
     -H "Authorization: Bearer 3-nottherealtokenofcoursekBSI74CVy" \
     -H "Content-Type: application/json" \
     -d '{"secretseed": true, "root_authorized_keys": true}' \
     -o /secret-destination/truenas-config-$(date +%Y%m%d-%H%M%S).tar

No, it isn’t. An API is already deprecated, and it’s been replaced with another. So the best way to automate this is to call the current API (which has been out for a few years now). Some discussion and examples:

1 Like

There is an official api client written in python. I wrote a tool to perform configuration backups using that API client (which I attached below). Maybe it will help you.

Example Usage:

$ python backup.py --verbose --secretseed --secure --host my-truenas --port 8443 --username truenas_admin --password mypassword backup.tar
computed urls (ws_url=wss://my-truenas:8443/api/current, http_url=https://my-truenas:8443)
call to config.save succeeded (JobID=46061, DownloadURL=/_download/46061?auth_token=xxxx)
starting download (FullURL=xxxx)
successfully opened output file (outfile=backup.tar)
successfully wrote configuration backup

An api key (-key) can also be supplied as an alternative to --username/--password.

This also works if you execute it locally on the TrueNAS system, in which case you do not have to supply a hostname/port. If you execute it as root (for example using sudo) you also don’t have to provide any authentication.

import requests
import argparse
import sys
import subprocess

from truenas_api_client import Client

SOCK_PATH = "/var/run/middleware/middlewared.sock"

parser = argparse.ArgumentParser(prog='ConfigDL', description='Downloads TrueNAS Scale Configuration Backup')
parser.add_argument('-s', '--secretseed', action='store_true', help='if specified, output will be a tar file that includes the the secret seed')
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('outfile', help='Output File')

parser.add_argument("--disable-ssl-verify", action="store_true", help="When using --secure, skips ssl certificate verification")
parser.add_argument("--secure", action="store_true", help="If set, use secure protocols (HTTPS and WSS)")
parser.add_argument("--host", type=str, help="Hostname or IP Address of TrueNAS instance")
parser.add_argument("--port", type=int, help="WebServer Port of the TrueNAS instance")
parser.add_argument("--key", type=str, help="TrueNAS API Key")
parser.add_argument("--username", type=str)
parser.add_argument("--password", type=str)

args = parser.parse_args()

if args.host is not None and args.port is None:
    print("you must set --port when using --host", file=sys.stderr)
    sys.exit(1)

if args.username is not None and args.password is None:
    print("you must set --password when using --username", file=sys.stderr)
    sys.exit(1)

if args.password is not None and args.username is None:
    print("you must set --username when using --password", file=sys.stderr)
    sys.exit(1)

if args.username is not None and args.key is not None:
    print("--key is incompatible with --username", file=sys.stderr)
    sys.exit(1)

use_unix_socket = args.host is None

def derive_ws_url(host, port, secure):
    if host is None:
        return "ws+unix://" + SOCK_PATH
    else:
        return ("wss://" if secure else "ws://") + host + ":" + str(port) + "/api/current"

def derive_http_url(host, port, secure):
    if host is None:
        return "http://127.0.0.1"
    else:
        return ("https://" if secure else "http://") + host + ":" + str(port)

http_url = derive_http_url(args.host, args.port, args.secure)
ws_url = derive_ws_url(args.host, args.port, args.secure)

if args.verbose:
    print("computed urls (ws_url={}, http_url={})".format(ws_url, http_url))

with Client(uri=ws_url, verify_ssl=not args.disable_ssl_verify) as c:
    if args.key:
      c.call("auth.login_with_api_key", args.key)
    elif args.username:
      c.call("auth.login", args.username, args.password)

    config_save_opts = {}
    if args.secretseed:
        config_save_opts['secretseed'] = True

    job_id, download_url = c.call("core.download", "config.save", [config_save_opts], "backup")
    assert isinstance(job_id, int)
    assert isinstance(download_url, str)

    if args.verbose:
        print("call to config.save succeeded (JobID={}, DownloadURL={})".format(job_id, download_url))

    full_url = http_url + download_url
    if args.verbose:
        print("starting download (FullURL={})".format(full_url))

    if use_unix_socket:
        argv = ["curl", "--unix-socket", SOCK_PATH, "--output", args.outfile, "--", full_url]
        result = subprocess.run(argv, check=True)
    else:
        rv = requests.get(full_url)
        rv.raise_for_status()

        with open(args.outfile, "wb") as f:
            if args.verbose:
                print("successfully opened output file (outfile={})".format(args.outfile))

            for chunk in rv.iter_content(chunk_size=8192):
                f.write(chunk)

    print("successfully wrote configuration backup")
1 Like

I have a well established way called: Email TrueNAS Configuration Backup, which was born from Multi-Report, for those who only wanted the TrueNAS Config files. See my signature for a link.

It will not help you with the API, I do not use the API.

1 Like

thanks. That’s helpful. Truenas latest release gave me a warning about my API usage and I falsely assumed it meant the whole API was going away. I’ll take a screen grab of the message; i may have misread it.

Thanks for the quick response.

awesome I copied this and it worked flawlessly!

Ah.. here was the message I got. I just read it way too fast, so my fault.

The deprecated REST API was used to authenticate 1 times in the last 24 hours from the following IP addresses:
127.0.0.1.

The REST API will be removed in version 26.04. To avoid service disruption, migrate any remaining integrations to the supported JSON-RPC 2.0 over WebSocket API before upgrading. For migration guidance, see the documentation.