Initial implementation: extend TrueCloud Backup to S3 and B2 providers

Patches middlewared at runtime via sitecustomize.py (no file edits to /usr/)
and widens the UI credential dropdown from Storj-only to S3+B2+Storj.
Persists across TrueNAS updates via PREINIT initshutdownscript stored in DB.
This commit is contained in:
2026-06-15 02:02:17 +00:00
commit 21b9333324
7 changed files with 693 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""
Patches the TrueNAS webui Angular bundle to show S3 and B2 credentials
in the TrueCloud Backup task form, instead of Storj only.
The compiled bundle contains:
"filterByProviders",["STORJ_IX"]
which is the template binding [filterByProviders]="[CloudSyncProviderName.Storj]".
We replace the array with ["STORJ_IX","S3","B2"] so all three providers appear.
Run automatically by apply.sh on every boot. Safe to run multiple times.
"""
import os
import re
import shutil
import sys
WEBUI_CANDIDATES = [
"/usr/share/truenas/webui",
"/usr/share/truenas-ui",
"/var/www/truenas",
]
# The pattern as compiled by Angular's Ivy into minified JS.
# String literals survive minification; the function name before the comma
# is mangled and not part of our match.
FIND = re.compile(r'("filterByProviders",)\["STORJ_IX"\]')
REPLACE = r'\1["STORJ_IX","S3","B2"]'
# Presence of this string means we already patched this file.
MARKER = '"STORJ_IX","S3","B2"'
def find_webui():
for d in WEBUI_CANDIDATES:
if os.path.isdir(d):
return d
return None
def find_bundle(webui):
for root, _, names in os.walk(webui):
for name in names:
if not name.endswith(".js"):
continue
path = os.path.join(root, name)
try:
with open(path) as fh:
content = fh.read()
if FIND.search(content):
return path, content
except (UnicodeDecodeError, PermissionError, OSError):
continue
return None, None
def main():
webui = find_webui()
if not webui:
print("[truecloud-patch] WARNING: webui directory not found, skipping UI patch")
sys.exit(0)
path, content = find_bundle(webui)
if path is None:
print(
"[truecloud-patch] WARNING: filterByProviders pattern not found in webui bundle.\n"
"[truecloud-patch] The UI patch may need updating for this TrueNAS version.\n"
"[truecloud-patch] File an issue at https://github.com/sudolulo/truenas-truecloud-patch"
)
sys.exit(0)
if MARKER in content:
print(f"[truecloud-patch] UI already patched: {path}")
sys.exit(0)
backup = path + ".pre-truecloud-patch"
if not os.path.exists(backup):
shutil.copy2(path, backup)
patched, count = FIND.subn(REPLACE, content)
with open(path, "w") as fh:
fh.write(patched)
print(f"[truecloud-patch] UI bundle patched ({count} replacement(s)): {path}")
if __name__ == "__main__":
main()