Fix TypeError creating B2 tasks on TrueNAS 24.10: handle both credential schemas

On 24.10 (Electric Eel) credentials["provider"] is the type string with
account/key in credentials["attributes"]; 25.04+ moved them into a
provider dict. The injected get_restic_config only handled the newer
shape and raised TypeError on 24.10 at task creation (#1).

The method now detects the schema and reads credentials from the right
place on both. create_task.py list-credentials and list-tasks use the
same schema-agnostic lookup.
This commit is contained in:
2026-07-06 05:43:09 +00:00
parent 73233865e8
commit 04ac71deb3
3 changed files with 24 additions and 3 deletions
+9
View File
@@ -21,6 +21,15 @@
own job runner, and later `ix-*` boot units still need midclt. Manual runs
of `apply.sh` never trigger a restart.
- **`TypeError: string indices must be integers` when creating a B2 task on
TrueNAS 24.10 (Electric Eel)** (#1). The credential schema differs between
releases: on 24.10 `credentials["provider"]` is the type string (`"B2"`)
with the account/key in `credentials["attributes"]`, while 25.04+ moved
them into a provider dict. The injected `get_restic_config` only handled
the 25.04+ shape. It now detects the schema and reads the credentials from
the right place on both; `create_task.py list-credentials` and `list-tasks`
got the same treatment.
- **`create_task.py verify` false-positive after reboot.** `verify` trusted
`hook_status.json`, which only records that the files were patched on disk —
not that the running process loaded them. `verify` now also compares the
+5
View File
@@ -186,6 +186,11 @@ B2_BLOCK = """
# TRUECLOUD_PATCH — added by truenas-truecloud-patch/patch/apply.sh
def _tc_get_restic_config(task):
p = task["credentials"]["provider"]
if not isinstance(p, dict):
# TrueNAS <= 24.10: provider is the type string ("B2") and the
# account/key live in the credential's attributes dict. 25.04+
# moved them into a provider dict.
p = task["credentials"]["attributes"]
return "", {"B2_ACCOUNT_ID": p["account"], "B2_ACCOUNT_KEY": p["key"]}
B2RcloneRemote.get_restic_config = staticmethod(_tc_get_restic_config)
+10 -3
View File
@@ -177,6 +177,14 @@ def cmd_verify():
print(f"Check {os.path.join(_PATCH_DIR, 'apply.log')} and journalctl -u middlewared")
sys.exit(1)
def _provider_type(cred):
"""Provider type string across schemas (<=24.10 plain str, >=25.04 dict)."""
p = (cred or {}).get("provider")
if isinstance(p, dict):
return p.get("type", "?")
return p or "?"
def cmd_list_credentials(client, _args):
creds = client("GET", "/cloudsync/credentials")
if not creds:
@@ -185,7 +193,7 @@ def cmd_list_credentials(client, _args):
print(f"{'ID':>4} {'Provider':<14} Name")
print("─" * 55)
for c in sorted(creds, key=lambda x: x["id"]):
print(f"{c['id']:>4} {c['provider']['type']:<14} {c['name']}")
print(f"{c['id']:>4} {_provider_type(c):<14} {c['name']}")
def cmd_list_tasks(client, _args):
@@ -196,8 +204,7 @@ def cmd_list_tasks(client, _args):
print(f"{'ID':>4} {'Enabled':<8} {'Provider':<14} Name")
print("─" * 60)
for t in sorted(tasks, key=lambda x: x["id"]):
creds = t.get("credentials") or {}
ptype = (creds.get("provider") or {}).get("type", "?")
ptype = _provider_type(t.get("credentials"))
enabled = "yes" if t.get("enabled") else "no"
print(f"{t['id']:>4} {enabled:<8} {ptype:<14} {t.get('description', '')}")