Reduce accidental complexity across three files
sitecustomize.py: - Replace _PATCHES dispatch dict with if/elif in exec_module; removes coupling between dispatch and _record_status's count barrier - Drop _record_status count barrier entirely; both patches fire within milliseconds during the same import sequence, write-on-every-call is safe - Replace _broken_url regex with str.partition + startswith checks; same semantics, no regex knowledge required to read - Replace @staticmethod decorator inside plain function with explicit staticmethod() assignment; decorator form creates a descriptor object, not a callable, which confuses readers expecting class-body usage apply.sh: - Inline warn/ok helpers; each was one echo with a prefix, the indirection cost more than the abstraction saved - Collapse patch_ui.py if/else (whose if branch was a no-op comment) to a single || fallback line create_task.py: - Remove vestigial (_client, _args) params from cmd_verify; it was pulled out of dispatch, the params were never used - Replace 3-entry dispatch dict with if/elif; dict implied a uniform calling convention that verify already broke
This commit is contained in:
+6
-15
@@ -38,11 +38,6 @@ if [ -f "$PATCH_DIR/disabled" ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
warn() { echo "WARNING: $*"; }
|
|
||||||
ok() { echo "OK: $*"; }
|
|
||||||
|
|
||||||
# Find the Python interpreter that middlewared actually uses.
|
# Find the Python interpreter that middlewared actually uses.
|
||||||
# On TrueNAS SCALE, /usr/bin/middlewared is usually a Python entry-point script
|
# On TrueNAS SCALE, /usr/bin/middlewared is usually a Python entry-point script
|
||||||
# with a shebang pointing at the right interpreter (system or venv).
|
# with a shebang pointing at the right interpreter (system or venv).
|
||||||
@@ -81,21 +76,21 @@ echo "Using Python: $PYTHON"
|
|||||||
SITE_PKG=$("$PYTHON" -c "import site; print(site.getsitepackages()[0])" 2>/dev/null || true)
|
SITE_PKG=$("$PYTHON" -c "import site; print(site.getsitepackages()[0])" 2>/dev/null || true)
|
||||||
|
|
||||||
if [ -z "$SITE_PKG" ]; then
|
if [ -z "$SITE_PKG" ]; then
|
||||||
warn "Cannot determine site-packages directory; skipping backend patch."
|
echo "WARNING: Cannot determine site-packages directory; skipping backend patch."
|
||||||
warn "Verify that '$PYTHON -c \"import site; print(site.getsitepackages())\"' works."
|
echo "WARNING: Verify that '$PYTHON -c \"import site; print(site.getsitepackages())\"' works."
|
||||||
else
|
else
|
||||||
# Back up any pre-existing sitecustomize.py that isn't ours.
|
# Back up any pre-existing sitecustomize.py that isn't ours.
|
||||||
if [ -f "$SITE_PKG/sitecustomize.py" ] && \
|
if [ -f "$SITE_PKG/sitecustomize.py" ] && \
|
||||||
! grep -q "truecloud-patch" "$SITE_PKG/sitecustomize.py" 2>/dev/null; then
|
! grep -q "truecloud-patch" "$SITE_PKG/sitecustomize.py" 2>/dev/null; then
|
||||||
cp "$SITE_PKG/sitecustomize.py" \
|
cp "$SITE_PKG/sitecustomize.py" \
|
||||||
"$SITE_PKG/sitecustomize.py.pre-truecloud-patch"
|
"$SITE_PKG/sitecustomize.py.pre-truecloud-patch"
|
||||||
ok "Backed up existing sitecustomize.py"
|
echo "OK: Backed up existing sitecustomize.py"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if cp "$PATCH_DIR/sitecustomize.py" "$SITE_PKG/sitecustomize.py" 2>/dev/null; then
|
if cp "$PATCH_DIR/sitecustomize.py" "$SITE_PKG/sitecustomize.py" 2>/dev/null; then
|
||||||
ok "Installed sitecustomize.py → $SITE_PKG/sitecustomize.py"
|
echo "OK: Installed sitecustomize.py → $SITE_PKG/sitecustomize.py"
|
||||||
else
|
else
|
||||||
warn "Failed to write $SITE_PKG/sitecustomize.py (permission error?)"
|
echo "WARNING: Failed to write $SITE_PKG/sitecustomize.py (permission error?)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -103,11 +98,7 @@ fi
|
|||||||
|
|
||||||
echo "--- UI patch ---"
|
echo "--- UI patch ---"
|
||||||
|
|
||||||
if "$PYTHON" "$PATCH_DIR/patch_ui.py"; then
|
"$PYTHON" "$PATCH_DIR/patch_ui.py" || echo "WARNING: patch_ui.py exited non-zero; UI dropdown may still show Storj only."
|
||||||
: # patch_ui.py prints its own status
|
|
||||||
else
|
|
||||||
warn "patch_ui.py exited non-zero; UI dropdown may still show Storj only."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Done ──────────────────────────────────────────────────────────────────────
|
# ── Done ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ def make_client(host, api_key, insecure=False):
|
|||||||
|
|
||||||
# ── Sub-commands ──────────────────────────────────────────────────────────────
|
# ── Sub-commands ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def cmd_verify(_client, _args):
|
def cmd_verify():
|
||||||
"""Print the hook status written by sitecustomize.py at middlewared startup."""
|
"""Print the hook status written by sitecustomize.py at middlewared startup."""
|
||||||
if not os.path.exists(_STATUS_FILE):
|
if not os.path.exists(_STATUS_FILE):
|
||||||
print("No hook status file found.")
|
print("No hook status file found.")
|
||||||
@@ -233,19 +233,19 @@ def main():
|
|||||||
args = p.parse_args()
|
args = p.parse_args()
|
||||||
|
|
||||||
if args.cmd == "verify":
|
if args.cmd == "verify":
|
||||||
cmd_verify(None, args)
|
cmd_verify()
|
||||||
return
|
return
|
||||||
|
|
||||||
if not args.host or not args.api_key:
|
if not args.host or not args.api_key:
|
||||||
p.error("--host and --api-key are required for this command")
|
p.error("--host and --api-key are required for this command")
|
||||||
|
|
||||||
client = make_client(args.host, args.api_key, args.insecure)
|
client = make_client(args.host, args.api_key, args.insecure)
|
||||||
dispatch = {
|
if args.cmd == "list-credentials":
|
||||||
"list-credentials": cmd_list_credentials,
|
cmd_list_credentials(client, args)
|
||||||
"list-tasks": cmd_list_tasks,
|
elif args.cmd == "list-tasks":
|
||||||
"create": cmd_create,
|
cmd_list_tasks(client, args)
|
||||||
}
|
elif args.cmd == "create":
|
||||||
dispatch[args.cmd](client, args)
|
cmd_create(client, args)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+14
-30
@@ -106,7 +106,10 @@ class _Loader:
|
|||||||
|
|
||||||
# exec_module succeeded — apply our patch.
|
# exec_module succeeded — apply our patch.
|
||||||
try:
|
try:
|
||||||
_PATCHES[fullname](module)
|
if fullname == "middlewared.rclone.remote.b2":
|
||||||
|
_patch_b2(module)
|
||||||
|
elif fullname == "middlewared.plugins.cloud_backup.restic":
|
||||||
|
_patch_restic(module)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
f"[truecloud-patch] patch failed for {fullname}: {exc}\n"
|
f"[truecloud-patch] patch failed for {fullname}: {exc}\n"
|
||||||
@@ -125,15 +128,11 @@ def _patch_b2(module):
|
|||||||
detail="native support present; patch not needed")
|
detail="native support present; patch not needed")
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
def _b2_restic_config(task):
|
||||||
def get_restic_config(task):
|
|
||||||
p = task["credentials"]["provider"]
|
p = task["credentials"]["provider"]
|
||||||
return "", {
|
return "", {"B2_ACCOUNT_ID": p["account"], "B2_ACCOUNT_KEY": p["key"]}
|
||||||
"B2_ACCOUNT_ID": p["account"],
|
|
||||||
"B2_ACCOUNT_KEY": p["key"],
|
|
||||||
}
|
|
||||||
|
|
||||||
cls.get_restic_config = get_restic_config
|
cls.get_restic_config = staticmethod(_b2_restic_config)
|
||||||
cls.restic = True
|
cls.restic = True
|
||||||
sys.stderr.write("[truecloud-patch] B2 restic support enabled\n")
|
sys.stderr.write("[truecloud-patch] B2 restic support enabled\n")
|
||||||
_record_status("middlewared.rclone.remote.b2", ok=True)
|
_record_status("middlewared.rclone.remote.b2", ok=True)
|
||||||
@@ -146,37 +145,29 @@ def _patch_restic(module):
|
|||||||
return
|
return
|
||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import re
|
|
||||||
|
|
||||||
_orig = module.get_restic_config
|
_orig = module.get_restic_config
|
||||||
|
|
||||||
# Matches "scheme:/path" — the broken form the stock URL builder produces
|
|
||||||
# when a provider has no hostname component (url == "").
|
|
||||||
# Does NOT match "scheme://path" (Storj and similar legitimately use ://).
|
|
||||||
_broken_url = re.compile(r'^(\w[\w+.-]*):/(?!/)(.+)$')
|
|
||||||
|
|
||||||
def get_restic_config(cloud_backup):
|
def get_restic_config(cloud_backup):
|
||||||
# Call the original — it handles transfer_setting, cache, RESTIC_PASSWORD,
|
# Call the original — it handles transfer_setting, cache, RESTIC_PASSWORD,
|
||||||
# env construction, and everything else we don't own.
|
# env construction, and everything else we don't own.
|
||||||
result = _orig(cloud_backup)
|
result = _orig(cloud_backup)
|
||||||
|
|
||||||
# Scan the built command for the -r <repo> argument and fix the URL if
|
# Scan the built command for the -r <repo> argument and fix the URL if
|
||||||
# it contains a stray leading slash: "b2:/bucket/path" → "b2:bucket/path".
|
# it has a stray leading slash: "b2:/bucket/path" → "b2:bucket/path".
|
||||||
|
# "scheme://path" is intentional (Storj) and must not be touched.
|
||||||
cmd = list(result.cmd)
|
cmd = list(result.cmd)
|
||||||
for i, part in enumerate(cmd):
|
for i, part in enumerate(cmd):
|
||||||
if i and cmd[i - 1] == "-r":
|
if i and cmd[i - 1] == "-r":
|
||||||
m = _broken_url.match(part)
|
scheme, sep, rest = part.partition(":")
|
||||||
if m:
|
if sep and rest.startswith("/") and not rest.startswith("//"):
|
||||||
cmd[i] = f"{m.group(1)}:{m.group(2)}"
|
cmd[i] = f"{scheme}:{rest[1:]}"
|
||||||
# Prefer dataclasses.replace (passes unknown future fields
|
|
||||||
# through automatically). Fall back to NamedTuple._replace
|
|
||||||
# in case ResticConfig is refactored.
|
|
||||||
try:
|
try:
|
||||||
return dataclasses.replace(result, cmd=cmd)
|
return dataclasses.replace(result, cmd=cmd)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return result._replace(cmd=cmd)
|
return result._replace(cmd=cmd)
|
||||||
break # -r arg present and already correct
|
break
|
||||||
return result # URL was fine; return original unchanged
|
return result
|
||||||
|
|
||||||
get_restic_config._truecloud_patched = True
|
get_restic_config._truecloud_patched = True
|
||||||
module.get_restic_config = get_restic_config
|
module.get_restic_config = get_restic_config
|
||||||
@@ -184,11 +175,6 @@ def _patch_restic(module):
|
|||||||
_record_status("middlewared.plugins.cloud_backup.restic", ok=True)
|
_record_status("middlewared.plugins.cloud_backup.restic", ok=True)
|
||||||
|
|
||||||
|
|
||||||
_PATCHES = {
|
|
||||||
"middlewared.rclone.remote.b2": _patch_b2,
|
|
||||||
"middlewared.plugins.cloud_backup.restic": _patch_restic,
|
|
||||||
}
|
|
||||||
|
|
||||||
_STATUS_FILE = "/data/truecloud-patch/hook_status.json"
|
_STATUS_FILE = "/data/truecloud-patch/hook_status.json"
|
||||||
_hook_status: dict = {}
|
_hook_status: dict = {}
|
||||||
|
|
||||||
@@ -201,8 +187,6 @@ def _record_status(fullname: str, ok: bool, detail: str = "") -> None:
|
|||||||
if fullname in _hook_status:
|
if fullname in _hook_status:
|
||||||
return # idempotent: first call wins
|
return # idempotent: first call wins
|
||||||
_hook_status[fullname] = {"ok": ok, "detail": detail}
|
_hook_status[fullname] = {"ok": ok, "detail": detail}
|
||||||
if len(_hook_status) < len(_PATCHES):
|
|
||||||
return # wait until all patches have reported
|
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"patched_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
"patched_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||||
|
|||||||
Reference in New Issue
Block a user