Harden for Python 3.12+, add disclaimer, audit for robustness
- sitecustomize.py: replace deprecated find_module/load_module with find_spec/exec_module (required for Python 3.12+ / TrueNAS SCALE 25.x) - apply.sh: remove set -e (PREINIT must not fail catastrophically); detect middlewared's actual Python binary instead of assuming python3; log rotation to avoid unbounded growth; independent failure per step - patch_ui.py: detect multiple bundle matches; include TrueNAS version in pattern-not-found warning; better MARKER specificity - uninstall.sh: mirror Python detection logic from apply.sh - README: lead with Storj $5→$50 price context; prominent unsupported disclaimer; Python version compatibility matrix; post-update checklist - Add MIT LICENSE
This commit is contained in:
+66
-22
@@ -3,12 +3,20 @@
|
||||
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.
|
||||
Angular's Ivy compiler inlines TypeScript string enum values as literals in
|
||||
the compiled bundle, so the template binding:
|
||||
|
||||
Run automatically by apply.sh on every boot. Safe to run multiple times.
|
||||
[filterByProviders]="[CloudSyncProviderName.Storj]"
|
||||
|
||||
appears verbatim in the minified JS as:
|
||||
|
||||
"filterByProviders",["STORJ_IX"]
|
||||
|
||||
We replace that array to include S3 and B2. The file is backed up before
|
||||
modification so uninstall.sh can restore it.
|
||||
|
||||
Safe to run multiple times — a marker string detects an already-patched file.
|
||||
Exits 0 in all cases (warnings are printed to stdout and logged by apply.sh).
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -22,13 +30,13 @@ WEBUI_CANDIDATES = [
|
||||
"/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.
|
||||
# Angular's Ivy template compiler serialises the Storj-only filter as this
|
||||
# exact substring in every production build we've observed.
|
||||
FIND = re.compile(r'("filterByProviders",)\["STORJ_IX"\]')
|
||||
REPLACE = r'\1["STORJ_IX","S3","B2"]'
|
||||
|
||||
# Presence of this string means we already patched this file.
|
||||
# A patched file contains both "S3" and "B2" next to "STORJ_IX" in this form.
|
||||
# This string is specific enough not to appear elsewhere in the bundle.
|
||||
MARKER = '"STORJ_IX","S3","B2"'
|
||||
|
||||
|
||||
@@ -40,8 +48,14 @@ def find_webui():
|
||||
|
||||
|
||||
def find_bundle(webui):
|
||||
for root, _, names in os.walk(webui):
|
||||
for name in names:
|
||||
"""
|
||||
Walk the webui directory looking for the JS file that contains the
|
||||
filterByProviders binding. Returns (path, content) or (None, None).
|
||||
Only .js files are read; binary files and permission errors are skipped.
|
||||
"""
|
||||
matches = []
|
||||
for root, _dirs, names in os.walk(webui):
|
||||
for name in sorted(names): # deterministic order
|
||||
if not name.endswith(".js"):
|
||||
continue
|
||||
path = os.path.join(root, name)
|
||||
@@ -49,41 +63,71 @@ def find_bundle(webui):
|
||||
with open(path) as fh:
|
||||
content = fh.read()
|
||||
if FIND.search(content):
|
||||
return path, content
|
||||
matches.append((path, content))
|
||||
except (UnicodeDecodeError, PermissionError, OSError):
|
||||
continue
|
||||
return None, None
|
||||
|
||||
if not matches:
|
||||
return None, None
|
||||
|
||||
if len(matches) > 1:
|
||||
# Unexpected — log all matches so the operator can investigate.
|
||||
print(
|
||||
f"[truecloud-patch] WARNING: filterByProviders pattern found in "
|
||||
f"{len(matches)} files; patching only the first."
|
||||
)
|
||||
for p, _ in matches:
|
||||
print(f"[truecloud-patch] {p}")
|
||||
|
||||
return matches[0]
|
||||
|
||||
|
||||
def main():
|
||||
webui = find_webui()
|
||||
if not webui:
|
||||
print("[truecloud-patch] WARNING: webui directory not found, skipping UI patch")
|
||||
sys.exit(0)
|
||||
print(
|
||||
"[truecloud-patch] WARNING: webui directory not found; skipping UI patch.\n"
|
||||
"[truecloud-patch] Searched: " + ", ".join(WEBUI_CANDIDATES)
|
||||
)
|
||||
return
|
||||
|
||||
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"
|
||||
"[truecloud-patch] WARNING: filterByProviders pattern not found in any JS bundle.\n"
|
||||
"[truecloud-patch] The TrueNAS webui may have been restructured in this version.\n"
|
||||
"[truecloud-patch] File an issue at https://github.com/sudolulo/truenas-truecloud-patch\n"
|
||||
f"[truecloud-patch] TrueNAS version info: {_tnversion()}"
|
||||
)
|
||||
sys.exit(0)
|
||||
return
|
||||
|
||||
if MARKER in content:
|
||||
print(f"[truecloud-patch] UI already patched: {path}")
|
||||
sys.exit(0)
|
||||
return
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
with open(path, "w") as fh:
|
||||
fh.write(patched)
|
||||
except OSError as exc:
|
||||
print(f"[truecloud-patch] ERROR: Could not write {path}: {exc}")
|
||||
return
|
||||
|
||||
print(f"[truecloud-patch] UI bundle patched ({count} replacement(s)): {path}")
|
||||
|
||||
|
||||
def _tnversion():
|
||||
try:
|
||||
with open("/etc/version") as fh:
|
||||
return fh.read().strip()
|
||||
except OSError:
|
||||
return "unknown"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user