Merge audit/exhaustive-review: 9 quality/safety fixes from exhaustive review
All findings from four consecutive full-codebase audit passes plus an adversarial iXsystems-perspective audit. No new candidates surfaced in the final clean-pass — branch declared complete. Fixes: - apply.sh: gate sitecustomize.py install on backup success; a failed backup cp previously fell through and could destroy the vendor file - apply.sh: correct comment (keeps two prior log generations, not one) - patch_ui.py: catch OSError on bundle backup with specific diagnostic - patch_ui.py: find_bundle now matches MARKER so already-patched files return 'UI already patched' instead of misleading 'pattern not found' - install.sh: early guard detects pipe-install (bash <(curl ...)) and exits with a clear error pointing to the git clone workflow - install.sh: scope WARNING grep to current run only (record log offset before apply.sh, tail -c +N to read only new bytes) - install.sh: systemctl restart failure now surfaces a recovery hint - create_task.py: add MITM risk warning to --insecure flag help text - create_task.py: handle unexpected 2xx response schema in cmd_create - uninstall.sh: add import-middlewared verification after Python detection - uninstall.sh: mv failure in JS bundle restore loop no longer aborts under set -e before cleanup; emits WARNING and continues - uninstall.sh: add sync comment on find paths to match WEBUI_CANDIDATES
This commit is contained in:
+17
-1
@@ -15,6 +15,14 @@ set -euo pipefail
|
|||||||
PATCH_DIR="/data/truecloud-patch"
|
PATCH_DIR="/data/truecloud-patch"
|
||||||
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
|
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
if [ ! -f "$REPO_DIR/patch/sitecustomize.py" ]; then
|
||||||
|
echo "ERROR: patch files not found at $REPO_DIR/patch/" >&2
|
||||||
|
echo "Run install.sh from the cloned repository, not via pipe:" >&2
|
||||||
|
echo " git clone https://github.com/sudolulo/truenas-truecloud-patch" >&2
|
||||||
|
echo " cd truenas-truecloud-patch && bash install.sh" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "=== TrueNAS TrueCloud Provider Patch — Install ==="
|
echo "=== TrueNAS TrueCloud Provider Patch — Install ==="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -98,7 +106,15 @@ fi
|
|||||||
# ── Restart middlewared ───────────────────────────────────────────────────────
|
# ── Restart middlewared ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
echo "Restarting middlewared so the backend patch takes effect ..."
|
echo "Restarting middlewared so the backend patch takes effect ..."
|
||||||
systemctl restart middlewared
|
if ! systemctl restart middlewared; then
|
||||||
|
echo "" >&2
|
||||||
|
echo "ERROR: middlewared failed to restart. The patch files are installed but" >&2
|
||||||
|
echo "the hook is not yet active. Check the system log for the root cause:" >&2
|
||||||
|
echo " journalctl -u middlewared -n 50" >&2
|
||||||
|
echo "If the problem is unrelated to this patch, recover with:" >&2
|
||||||
|
echo " bash $PATCH_DIR/recover.sh" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
echo "Done."
|
echo "Done."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Refresh your browser to pick up the UI change."
|
echo "Refresh your browser to pick up the UI change."
|
||||||
|
|||||||
+10
-3
@@ -22,7 +22,7 @@ PATCH_DIR="/data/truecloud-patch"
|
|||||||
LOG="$PATCH_DIR/apply.log"
|
LOG="$PATCH_DIR/apply.log"
|
||||||
|
|
||||||
# Rotate log at 512 KB to avoid unbounded growth on a system volume.
|
# Rotate log at 512 KB to avoid unbounded growth on a system volume.
|
||||||
# Keep one prior generation (.1) so the last two boots are always available.
|
# Keep two prior generations (.1 and .2) so the last three boots are always available.
|
||||||
if [ -f "$LOG" ] && [ "$(wc -c < "$LOG")" -gt 524288 ]; then
|
if [ -f "$LOG" ] && [ "$(wc -c < "$LOG")" -gt 524288 ]; then
|
||||||
[ -f "${LOG}.1" ] && mv "${LOG}.1" "${LOG}.2"
|
[ -f "${LOG}.1" ] && mv "${LOG}.1" "${LOG}.2"
|
||||||
mv "$LOG" "${LOG}.1"
|
mv "$LOG" "${LOG}.1"
|
||||||
@@ -85,18 +85,25 @@ if [ -z "$SITE_PKG" ]; then
|
|||||||
echo " Run: $PYTHON -c \"import site; print(site.getsitepackages())\""
|
echo " Run: $PYTHON -c \"import site; print(site.getsitepackages())\""
|
||||||
else
|
else
|
||||||
# Back up any pre-existing sitecustomize.py that isn't ours.
|
# Back up any pre-existing sitecustomize.py that isn't ours.
|
||||||
|
_can_install=true
|
||||||
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" \
|
if cp "$SITE_PKG/sitecustomize.py" \
|
||||||
"$SITE_PKG/sitecustomize.py.pre-truecloud-patch"
|
"$SITE_PKG/sitecustomize.py.pre-truecloud-patch"; then
|
||||||
echo "OK: Backed up existing sitecustomize.py"
|
echo "OK: Backed up existing sitecustomize.py"
|
||||||
|
else
|
||||||
|
echo "WARNING: Could not back up existing sitecustomize.py; skipping install to avoid data loss."
|
||||||
|
_can_install=false
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if $_can_install; then
|
||||||
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
|
||||||
echo "OK: Installed sitecustomize.py → $SITE_PKG/sitecustomize.py"
|
echo "OK: Installed sitecustomize.py → $SITE_PKG/sitecustomize.py"
|
||||||
else
|
else
|
||||||
echo "WARNING: Failed to write $SITE_PKG/sitecustomize.py (permission error?)"
|
echo "WARNING: Failed to write $SITE_PKG/sitecustomize.py (permission error?)"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Step 2: Angular bundle ────────────────────────────────────────────────────
|
# ── Step 2: Angular bundle ────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -179,7 +179,10 @@ def cmd_create(client, args):
|
|||||||
}
|
}
|
||||||
|
|
||||||
result = client("POST", "/cloud_backup", body)
|
result = client("POST", "/cloud_backup", body)
|
||||||
|
try:
|
||||||
print(f"Created task id={result['id']} name={result['description']!r}")
|
print(f"Created task id={result['id']} name={result['description']!r}")
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
print(f"Task created but response schema was unexpected: {result}")
|
||||||
|
|
||||||
|
|
||||||
# ── CLI ───────────────────────────────────────────────────────────────────────
|
# ── CLI ───────────────────────────────────────────────────────────────────────
|
||||||
@@ -195,7 +198,9 @@ def main():
|
|||||||
p.add_argument("--api-key", default=None, metavar="KEY",
|
p.add_argument("--api-key", default=None, metavar="KEY",
|
||||||
help="TrueNAS API key — System → API Keys (required except for verify)")
|
help="TrueNAS API key — System → API Keys (required except for verify)")
|
||||||
p.add_argument("--insecure", action="store_true",
|
p.add_argument("--insecure", action="store_true",
|
||||||
help="Skip TLS certificate verification (self-signed certs)")
|
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.")
|
||||||
|
|
||||||
sub = p.add_subparsers(dest="cmd", required=True)
|
sub = p.add_subparsers(dest="cmd", required=True)
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,11 @@ def main():
|
|||||||
|
|
||||||
backup = path + ".pre-truecloud-patch"
|
backup = path + ".pre-truecloud-patch"
|
||||||
if not os.path.exists(backup):
|
if not os.path.exists(backup):
|
||||||
|
try:
|
||||||
shutil.copy2(path, backup)
|
shutil.copy2(path, backup)
|
||||||
|
except OSError as exc:
|
||||||
|
print(f"[truecloud-patch] ERROR: Could not create backup {backup}: {exc}")
|
||||||
|
return
|
||||||
|
|
||||||
patched, count = FIND.subn(REPLACE, content)
|
patched, count = FIND.subn(REPLACE, content)
|
||||||
if count != 1:
|
if count != 1:
|
||||||
|
|||||||
+5
-1
@@ -104,9 +104,13 @@ echo "Restoring UI bundle backup ..."
|
|||||||
RESTORED=0
|
RESTORED=0
|
||||||
while IFS= read -r backup; do
|
while IFS= read -r backup; do
|
||||||
original="${backup%.pre-truecloud-patch}"
|
original="${backup%.pre-truecloud-patch}"
|
||||||
mv "$backup" "$original"
|
if mv "$backup" "$original"; then
|
||||||
echo " Restored: $original"
|
echo " Restored: $original"
|
||||||
RESTORED=1
|
RESTORED=1
|
||||||
|
else
|
||||||
|
echo " WARNING: Could not restore $original — backup left at $backup"
|
||||||
|
fi
|
||||||
|
# Keep these paths in sync with WEBUI_CANDIDATES in patch/patch_ui.py
|
||||||
done < <(find /usr/share/truenas /usr/share/truenas-ui /var/www/truenas \
|
done < <(find /usr/share/truenas /usr/share/truenas-ui /var/www/truenas \
|
||||||
-name "*.js.pre-truecloud-patch" 2>/dev/null)
|
-name "*.js.pre-truecloud-patch" 2>/dev/null)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user