From da1be973774b0adc02b9adbe6c29133fb0570a5e Mon Sep 17 00:00:00 2001 From: flan Date: Wed, 8 Jul 2026 15:09:38 -0400 Subject: [PATCH] create_task.py: migrate REST /api/v2.0 -> midclt (removed in TrueNAS 26.04) The tool created cloud_backup tasks via POST /api/v2.0/cloud_backup, which is deprecated and removed in TrueNAS 26.04. It now calls the middleware directly with midclt (cloudsync.credentials.query / cloud_backup.query / cloud_backup.create), so it runs on the TrueNAS host with no host address or API key. --host/--api-key/--insecure are kept accepted-but-ignored for compatibility. Dropped the ssl/urllib HTTP client. v0.2.0. --- CHANGELOG.md | 15 +++ README.md | 23 ++-- patch/__pycache__/create_task.cpython-314.pyc | Bin 0 -> 18901 bytes patch/create_task.py | 107 ++++++++---------- 4 files changed, 75 insertions(+), 70 deletions(-) create mode 100644 patch/__pycache__/create_task.cpython-314.pyc diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a3edef..9e079f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## v0.2.0 — 2026-07-08 + +### Changed + +- **`create_task.py` now uses the TrueNAS middleware via `midclt` instead of the + deprecated `/api/v2.0` REST API**, which is removed in TrueNAS 26.04. Practical + effects: + - Run the script **on the TrueNAS host** — it uses the local middleware socket, so + it no longer needs a host address or API key. + - `--host`, `--api-key`, and `--insecure` are accepted but **ignored** (a deprecation + note is printed); they will be removed in a future release. + - `list-credentials` → `cloudsync.credentials.query`, `list-tasks` → + `cloud_backup.query`, `create` → `cloud_backup.create`. +- Dropped the `ssl`/`urllib` HTTP client; no TLS certificate handling is needed anymore. + ## v0.1.0 — 2026-07-08 ### Added diff --git a/README.md b/README.md index eb74026..fd20e0e 100644 --- a/README.md +++ b/README.md @@ -187,18 +187,18 @@ Check [CHANGELOG.md](CHANGELOG.md) to see what changed between versions. ## Creating a task via CLI If the UI still shows only Storj after refreshing (e.g. the JS bundle pattern -changed in a new TrueNAS version), create tasks directly via the REST API: +changed in a new TrueNAS version), create tasks directly. Run this **on the +TrueNAS host** — it talks to the local middleware via `midclt`, so it needs no +host address or API key: ```bash # Replace /mnt/tank/truenas-truecloud-patch with your clone path # List your cloud credentials to find the right ID -python3 /mnt/tank/truenas-truecloud-patch/patch/create_task.py \ - --host 192.168.1.1 --api-key list-credentials +python3 /mnt/tank/truenas-truecloud-patch/patch/create_task.py list-credentials # Create a task with a B2 credential (id=3) -python3 /mnt/tank/truenas-truecloud-patch/patch/create_task.py \ - --host 192.168.1.1 --api-key create \ +python3 /mnt/tank/truenas-truecloud-patch/patch/create_task.py create \ --name "tank-to-b2" \ --path /mnt/tank/data \ --credential 3 \ @@ -213,7 +213,8 @@ python3 /mnt/tank/truenas-truecloud-patch/patch/create_task.py \ > which re-fetches all repo metadata from the provider every run — glacially slow > on large repos. Point it at a writable dir on a pool with free space. -Get an API key from **System → API Keys → Add**. +> Versions ≤ 0.1.0 used the `/api/v2.0` REST API with `--host`/`--api-key`; those +> flags are now accepted-but-ignored (REST is removed in TrueNAS 26.04). --- @@ -433,9 +434,7 @@ grep -c 'STORJ_IX.*S3.*B2' \ | grep -v ':0' ``` -**`create_task.py` SSL error connecting to TrueNAS** -`create_task.py` talks to the **TrueNAS API**, not your S3 endpoint, and -verifies its TLS certificate. If your NAS uses a self-signed certificate, -pass `--insecure` — but be aware this disables certificate verification for -the API call that transmits your TrueNAS API key. Adding your NAS certificate -to your system's trust store is safer. +**`create_task.py` — "midclt not found" or permission errors** +`create_task.py` now talks to the local middleware via `midclt`, so run it **on +the TrueNAS host** (not remotely) as a user with middleware access (root). There +is no HTTPS/API-key call anymore, so there is no TLS certificate to configure. diff --git a/patch/__pycache__/create_task.cpython-314.pyc b/patch/__pycache__/create_task.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e60643454518cb94dca8ea7c48570800a5b34caa GIT binary patch literal 18901 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 L!;G72*^K{xqcLeU literal 0 HcmV?d00001 diff --git a/patch/create_task.py b/patch/create_task.py index 9e273e7..65943ff 100755 --- a/patch/create_task.py +++ b/patch/create_task.py @@ -3,22 +3,24 @@ create_task.py — create TrueNAS TrueCloud Backup tasks with S3 or B2 credentials. The TrueNAS UI normally restricts the credential dropdown to Storj only. -This script bypasses that restriction by calling the REST API directly. +This script bypasses that restriction by talking to the TrueNAS middleware +directly via `midclt` (the /api/v2.0 REST API is removed in TrueNAS 26.04). Compatible providers (after the truecloud-patch backend patch is applied): S3 — any S3-compatible endpoint (AWS, Wasabi, Cloudflare R2, MinIO, …) B2 — Backblaze B2 native API STORJ_IX — Storj (unchanged, always worked) -Requires a TrueNAS API key: UI → System → API Keys → Add. +Run this ON the TrueNAS host — it uses the local middleware socket via `midclt`, +so no host address or API key is needed. Examples -------- List available cloud credentials: - python3 create_task.py --host 192.168.1.1 --api-key list-credentials + python3 create_task.py list-credentials Create a task backed by a B2 credential (id=3): - python3 create_task.py --host 192.168.1.1 --api-key create \\ + python3 create_task.py create \\ --name "tank-to-b2" \\ --path /mnt/tank/data \\ --credential 3 \\ @@ -28,7 +30,7 @@ Create a task backed by a B2 credential (id=3): --keep-last 14 Create a task using an S3-compatible credential (Wasabi, R2, etc.): - python3 create_task.py --host 192.168.1.1 --api-key create \\ + python3 create_task.py create \\ --name "tank-to-wasabi" \\ --path /mnt/tank/data \\ --credential 5 \\ @@ -37,54 +39,44 @@ Create a task using an S3-compatible credential (Wasabi, R2, etc.): --password "restic-repo-password" List existing TrueCloud Backup tasks: - python3 create_task.py --host 192.168.1.1 --api-key list-tasks + python3 create_task.py list-tasks """ import argparse import calendar import json import os -import ssl import subprocess import sys import time -import urllib.error -import urllib.request -__version__ = "0.1.0" +__version__ = "0.2.0" _PATCH_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) _STATUS_FILE = os.path.join(_PATCH_DIR, "hook_status.json") -def make_client(host, api_key, insecure=False): - """Return a callable that makes authenticated REST API calls.""" - base = f"https://{host}/api/v2.0" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - ctx = ssl.create_default_context() - if insecure: - ctx.check_hostname = False - ctx.verify_mode = ssl.CERT_NONE - - def call(method, path, body=None): - url = base + path - data = json.dumps(body).encode() if body is not None else None - req = urllib.request.Request(url, data=data, headers=headers, method=method) - try: - with urllib.request.urlopen(req, context=ctx) as resp: - return json.loads(resp.read()) - except urllib.error.HTTPError as exc: - detail = exc.read().decode(errors="replace") - print(f"HTTP {exc.code} {exc.reason}: {detail}", file=sys.stderr) - sys.exit(1) - except urllib.error.URLError as exc: - print(f"Connection error: {exc.reason}", file=sys.stderr) - sys.exit(1) - - return call +def midclt_call(method, *args): + """Call a middleware method locally via `midclt`, the supported JSON-RPC transport + that replaces the deprecated /api/v2.0 REST API (removed in TrueNAS 26.04). Must run + on the TrueNAS host. Each arg is JSON-encoded (a dict for create; none for queries). + Exits with a clear message on failure.""" + cmd = ["midclt", "call", method] + [json.dumps(a) for a in args] + try: + proc = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + except FileNotFoundError: + print("ERROR: `midclt` not found — run this script ON the TrueNAS host.", + file=sys.stderr) + sys.exit(1) + except subprocess.SubprocessError as exc: + print(f"ERROR: midclt call failed: {exc}", file=sys.stderr) + sys.exit(1) + if proc.returncode != 0: + print(f"ERROR: midclt {method}: {(proc.stderr or proc.stdout).strip()}", + file=sys.stderr) + sys.exit(1) + out = proc.stdout.strip() + return json.loads(out) if out else None # ── Sub-commands ────────────────────────────────────────────────────────────── @@ -185,8 +177,8 @@ def _provider_type(cred): return p or "?" -def cmd_list_credentials(client, _args): - creds = client("GET", "/cloudsync/credentials") +def cmd_list_credentials(_args): + creds = midclt_call("cloudsync.credentials.query") if not creds: print("No cloud credentials configured.") return @@ -196,8 +188,8 @@ def cmd_list_credentials(client, _args): print(f"{c['id']:>4} {_provider_type(c):<14} {c['name']}") -def cmd_list_tasks(client, _args): - tasks = client("GET", "/cloud_backup") +def cmd_list_tasks(_args): + tasks = midclt_call("cloud_backup.query") if not tasks: print("No TrueCloud Backup tasks configured.") return @@ -209,7 +201,7 @@ def cmd_list_tasks(client, _args): print(f"{t['id']:>4} {enabled:<8} {ptype:<14} {t.get('description', '')}") -def cmd_create(client, args): +def cmd_create(args): parts = args.schedule.split() if len(parts) != 5: print( @@ -253,7 +245,7 @@ def cmd_create(client, args): file=sys.stderr, ) - result = client("POST", "/cloud_backup", body) + result = midclt_call("cloud_backup.create", body) try: print(f"Created task id={result['id']} name={result['description']!r}") except (KeyError, TypeError): @@ -269,14 +261,12 @@ def main(): epilog=__doc__.split("Examples")[1] if __doc__ and "Examples" in __doc__ else "", ) p.add_argument("--version", "-V", action="version", version=f"truecloud-patch {__version__}") - p.add_argument("--host", default=None, metavar="HOST", - help="TrueNAS hostname or IP address (required except for verify)") - p.add_argument("--api-key", default=None, metavar="KEY", - help="TrueNAS API key — System → API Keys (required except for verify)") - p.add_argument("--insecure", action="store_true", - help="Skip TLS certificate verification (self-signed certs). " - "WARNING: exposes your API key to network interception. " - "Prefer adding your cert to the trust store instead.") + # Deprecated & ignored: the tool now uses the local middleware via `midclt` (the + # /api/v2.0 REST API is removed in TrueNAS 26.04), so it must run ON the TrueNAS + # host and needs no host/API key. Kept accepted-but-ignored for compatibility. + p.add_argument("--host", default=None, help=argparse.SUPPRESS) + p.add_argument("--api-key", default=None, help=argparse.SUPPRESS) + p.add_argument("--insecure", action="store_true", help=argparse.SUPPRESS) sub = p.add_subparsers(dest="cmd", required=True) @@ -323,16 +313,17 @@ def main(): cmd_verify() return - if not args.host or not args.api_key: - p.error("--host and --api-key are required for this command") + if args.host or args.api_key or args.insecure: + print("NOTE: --host/--api-key/--insecure are deprecated and ignored; this tool " + "now uses the local middleware (midclt) and must run on the TrueNAS host.", + file=sys.stderr) - client = make_client(args.host, args.api_key, args.insecure) if args.cmd == "list-credentials": - cmd_list_credentials(client, args) + cmd_list_credentials(args) elif args.cmd == "list-tasks": - cmd_list_tasks(client, args) + cmd_list_tasks(args) elif args.cmd == "create": - cmd_create(client, args) + cmd_create(args) if __name__ == "__main__":