Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee190f558f | ||
|
|
c8de9971e7 |
@@ -1,5 +1,30 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v0.0.3 — 2026-06-22
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **`patch/apply.sh` silently killed by the 10-second PREINIT timeout.**
|
||||||
|
TrueNAS PREINIT initshutdownscripts have a 10-second default timeout. The
|
||||||
|
previous apply.sh ran approximately 8 Python subprocesses (each ~1-2 s), so it
|
||||||
|
was routinely killed mid-run. Symptoms: patches not applied after reboot, but
|
||||||
|
re-running `bash apply.sh` manually (no timeout) always succeeded.
|
||||||
|
|
||||||
|
Fix: `install.sh` now registers the hook with `"timeout": 120`. Existing
|
||||||
|
installations are updated to the new timeout on the next `bash install.sh` run.
|
||||||
|
|
||||||
|
Additionally, `patch/apply.sh` consolidates its Python subprocess invocations
|
||||||
|
from ~8 down to 2, reducing startup overhead from ~12-16 s to ~2-4 s — well
|
||||||
|
within the new 120-second budget.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `find_mw_python` in `apply.sh` no longer spawns a separate Python process to
|
||||||
|
verify the interpreter can import `middlewared`. Verification is now implicit in
|
||||||
|
the combined path-discovery subprocess that follows.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v0.0.2 — 2026-06-19
|
## v0.0.2 — 2026-06-19
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -120,6 +120,30 @@ call that path on every boot.
|
|||||||
Refresh your browser. S3 and B2 credentials now appear in the
|
Refresh your browser. S3 and B2 credentials now appear in the
|
||||||
**Data Protection → TrueCloud Backup → Add** credential dropdown.
|
**Data Protection → TrueCloud Backup → Add** credential dropdown.
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
To update to a new version of the patch:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /mnt/tank/truenas-truecloud-patch
|
||||||
|
|
||||||
|
# If install.sh was previously run as root, the .git directory may be owned
|
||||||
|
# by root. Fix it first, or just pull as root:
|
||||||
|
sudo git pull # easiest option
|
||||||
|
# — or —
|
||||||
|
sudo chown -R $(whoami) .git && git pull
|
||||||
|
|
||||||
|
bash install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
`install.sh` clears any stale kill switch, re-applies the updated patches,
|
||||||
|
and restarts middlewared. Run `python3 patch/create_task.py verify` afterwards
|
||||||
|
to confirm the patches loaded successfully.
|
||||||
|
|
||||||
|
Check [CHANGELOG.md](CHANGELOG.md) to see what changed between versions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Creating a task via CLI
|
## Creating a task via CLI
|
||||||
|
|
||||||
If the UI still shows only Storj after refreshing (e.g. the JS bundle pattern
|
If the UI still shows only Storj after refreshing (e.g. the JS bundle pattern
|
||||||
|
|||||||
+6
-4
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION="0.0.3"
|
||||||
|
|
||||||
# The directory containing install.sh is the permanent install location.
|
# The directory containing install.sh is the permanent install location.
|
||||||
PATCH_DIR="$(cd "$(dirname "$0")" && pwd)"
|
PATCH_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
_HOOK_COMMENT='TrueCloud provider patch (S3/B2)'
|
_HOOK_COMMENT='TrueCloud provider patch (S3/B2)'
|
||||||
@@ -29,7 +31,7 @@ if [ ! -f "$PATCH_DIR/patch/apply.sh" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "=== TrueNAS TrueCloud Provider Patch — Install ==="
|
echo "=== TrueNAS TrueCloud Provider Patch v${VERSION} — Install ==="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ── Preflight ─────────────────────────────────────────────────────────────────
|
# ── Preflight ─────────────────────────────────────────────────────────────────
|
||||||
@@ -71,9 +73,9 @@ for s in json.load(sys.stdin):
|
|||||||
" 2>/dev/null || true)
|
" 2>/dev/null || true)
|
||||||
|
|
||||||
if [ -n "$EXISTING_ID" ]; then
|
if [ -n "$EXISTING_ID" ]; then
|
||||||
echo "Already registered (id=$EXISTING_ID). Updating path and enabling ..."
|
echo "Already registered (id=$EXISTING_ID). Updating path, timeout, and enabling ..."
|
||||||
if ! _midclt_out=$(midclt call initshutdownscript.update "$EXISTING_ID" \
|
if ! _midclt_out=$(midclt call initshutdownscript.update "$EXISTING_ID" \
|
||||||
"{\"enabled\": true, \"script\": \"$PATCH_DIR/patch/apply.sh\"}" 2>&1); then
|
"{\"enabled\": true, \"script\": \"$PATCH_DIR/patch/apply.sh\", \"timeout\": 120}" 2>&1); then
|
||||||
echo "ERROR: Failed to update PREINIT hook (id=$EXISTING_ID)." >&2
|
echo "ERROR: Failed to update PREINIT hook (id=$EXISTING_ID)." >&2
|
||||||
[ -n "$_midclt_out" ] && echo " midclt: $_midclt_out" >&2
|
[ -n "$_midclt_out" ] && echo " midclt: $_midclt_out" >&2
|
||||||
echo " To remove the stale entry and retry:" >&2
|
echo " To remove the stale entry and retry:" >&2
|
||||||
@@ -82,7 +84,7 @@ if [ -n "$EXISTING_ID" ]; then
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if ! _midclt_out=$(midclt call initshutdownscript.create \
|
if ! _midclt_out=$(midclt call initshutdownscript.create \
|
||||||
"{\"type\":\"SCRIPT\",\"script\":\"$PATCH_DIR/patch/apply.sh\",\"when\":\"PREINIT\",\"enabled\":true,\"comment\":\"$_HOOK_COMMENT\"}" \
|
"{\"type\":\"SCRIPT\",\"script\":\"$PATCH_DIR/patch/apply.sh\",\"when\":\"PREINIT\",\"enabled\":true,\"timeout\":120,\"comment\":\"$_HOOK_COMMENT\"}" \
|
||||||
2>&1); then
|
2>&1); then
|
||||||
echo "ERROR: Failed to register PREINIT hook." >&2
|
echo "ERROR: Failed to register PREINIT hook." >&2
|
||||||
[ -n "$_midclt_out" ] && echo " midclt: $_midclt_out" >&2
|
[ -n "$_midclt_out" ] && echo " midclt: $_midclt_out" >&2
|
||||||
|
|||||||
+89
-116
@@ -18,6 +18,7 @@
|
|||||||
# Derive PATCH_DIR from this script's location (parent of the patch/ directory).
|
# Derive PATCH_DIR from this script's location (parent of the patch/ directory).
|
||||||
PATCH_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
PATCH_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
LOG="$PATCH_DIR/apply.log"
|
LOG="$PATCH_DIR/apply.log"
|
||||||
|
VERSION="0.0.3"
|
||||||
|
|
||||||
# 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 two prior generations (.1 and .2) so the last three boots are always available.
|
# Keep two prior generations (.1 and .2) so the last three boots are always available.
|
||||||
@@ -27,7 +28,7 @@ if [ -f "$LOG" ] && [ "$(wc -c < "$LOG")" -gt 524288 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
exec >> "$LOG" 2>&1
|
exec >> "$LOG" 2>&1
|
||||||
echo "=== $(date -Iseconds) ==="
|
echo "=== $(date -Iseconds) [v${VERSION}] ==="
|
||||||
|
|
||||||
# Kill switch: if this file exists, skip all patching and exit cleanly.
|
# Kill switch: if this file exists, skip all patching and exit cleanly.
|
||||||
# Recovery: touch "$PATCH_DIR/disabled" (then reboot or restart middlewared).
|
# Recovery: touch "$PATCH_DIR/disabled" (then reboot or restart middlewared).
|
||||||
@@ -67,6 +68,8 @@ _ensure_writable() {
|
|||||||
# 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).
|
||||||
|
# The shebang is read with dd (no Python startup cost); import verification is
|
||||||
|
# deferred to the combined subprocess below which handles failure gracefully.
|
||||||
find_mw_python() {
|
find_mw_python() {
|
||||||
local py="python3"
|
local py="python3"
|
||||||
local shebang=""
|
local shebang=""
|
||||||
@@ -82,47 +85,56 @@ find_mw_python() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify the chosen interpreter can actually import middlewared.
|
|
||||||
# Use >&2 so this message goes to stderr, not captured by $(...) substitution.
|
|
||||||
if ! "$py" -c "import middlewared" 2>/dev/null; then
|
|
||||||
echo "WARNING: '$py' cannot import middlewared; falling back to python3" >&2
|
|
||||||
py="python3"
|
|
||||||
if ! "$py" -c "import middlewared" 2>/dev/null; then
|
|
||||||
echo "WARNING: 'python3' also cannot import middlewared; backend patch will be skipped" >&2
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$py"
|
echo "$py"
|
||||||
}
|
}
|
||||||
|
|
||||||
PYTHON=$(find_mw_python)
|
PYTHON=$(find_mw_python)
|
||||||
echo "Using Python: $PYTHON"
|
echo "Using Python: $PYTHON"
|
||||||
|
|
||||||
# ── Native support check ──────────────────────────────────────────────────────
|
# ── Discover paths + native support check (single Python subprocess) ──────────
|
||||||
# If TrueNAS has shipped native B2 restic support, this patch is no longer
|
# Combines what were previously four separate Python invocations into one to
|
||||||
# needed. Set the kill switch and instruct the user to uninstall cleanly.
|
# avoid repeated interpreter startup overhead under the PREINIT timeout budget.
|
||||||
|
|
||||||
|
_tc_info=$("$PYTHON" -c "
|
||||||
|
import inspect, os, sys
|
||||||
|
|
||||||
|
result = {'native': 'no', 'site_pkg': '', 'mw_dir': ''}
|
||||||
|
|
||||||
|
try:
|
||||||
|
import middlewared
|
||||||
|
mw_file = os.path.abspath(middlewared.__file__)
|
||||||
|
result['mw_dir'] = os.path.dirname(mw_file)
|
||||||
|
result['site_pkg'] = os.path.dirname(os.path.dirname(mw_file))
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
import site
|
||||||
|
result['site_pkg'] = site.getsitepackages()[0]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
_tc_native=$("$PYTHON" -c "
|
|
||||||
try:
|
try:
|
||||||
import inspect
|
|
||||||
import middlewared.rclone.remote.b2 as _b2_mod
|
import middlewared.rclone.remote.b2 as _b2_mod
|
||||||
from middlewared.rclone.remote.b2 import B2RcloneRemote
|
from middlewared.rclone.remote.b2 import B2RcloneRemote
|
||||||
if 'get_restic_config' not in B2RcloneRemote.__dict__:
|
if 'get_restic_config' in B2RcloneRemote.__dict__:
|
||||||
print('no')
|
|
||||||
else:
|
|
||||||
src = open(inspect.getfile(_b2_mod), encoding='utf-8', errors='replace').read()
|
src = open(inspect.getfile(_b2_mod), encoding='utf-8', errors='replace').read()
|
||||||
if 'TRUECLOUD_PATCH' in src:
|
if 'TRUECLOUD_PATCH' not in src:
|
||||||
print('no')
|
|
||||||
else:
|
|
||||||
# Distinguish a real implementation from a stub that raises NotImplementedError.
|
|
||||||
try:
|
try:
|
||||||
method_src = inspect.getsource(B2RcloneRemote.get_restic_config)
|
method_src = inspect.getsource(B2RcloneRemote.get_restic_config)
|
||||||
except (OSError, TypeError):
|
except (OSError, TypeError):
|
||||||
method_src = ''
|
method_src = ''
|
||||||
print('no' if 'NotImplementedError' in method_src else 'yes')
|
if 'NotImplementedError' not in method_src:
|
||||||
|
result['native'] = 'yes'
|
||||||
except Exception:
|
except Exception:
|
||||||
print('no')
|
pass
|
||||||
" 2>/dev/null || echo "no")
|
|
||||||
|
print(result['native'])
|
||||||
|
print(result['site_pkg'])
|
||||||
|
print(result['mw_dir'])
|
||||||
|
" 2>/dev/null || printf 'no\n\n\n')
|
||||||
|
|
||||||
|
_tc_native=$(printf '%s' "$_tc_info" | sed -n '1p')
|
||||||
|
SITE_PKG=$(printf '%s' "$_tc_info" | sed -n '2p')
|
||||||
|
_MW_DIR=$(printf '%s' "$_tc_info" | sed -n '3p')
|
||||||
|
|
||||||
if [ "$_tc_native" = "yes" ]; then
|
if [ "$_tc_native" = "yes" ]; then
|
||||||
echo "NOTICE: TrueNAS now provides native B2 restic support — truecloud-patch is no longer needed."
|
echo "NOTICE: TrueNAS now provides native B2 restic support — truecloud-patch is no longer needed."
|
||||||
@@ -144,29 +156,6 @@ fi
|
|||||||
|
|
||||||
echo "--- backend patch ---"
|
echo "--- backend patch ---"
|
||||||
|
|
||||||
# Derive site-packages from where middlewared actually lives.
|
|
||||||
# getsitepackages()[0] may return the wrong directory; using middlewared.__file__
|
|
||||||
# ensures we patch files in the directory Python will actually read.
|
|
||||||
SITE_PKG=$("$PYTHON" -c "
|
|
||||||
import os
|
|
||||||
try:
|
|
||||||
import middlewared
|
|
||||||
print(os.path.dirname(os.path.dirname(os.path.abspath(middlewared.__file__))))
|
|
||||||
except ImportError:
|
|
||||||
import site
|
|
||||||
print(site.getsitepackages()[0])
|
|
||||||
" 2>/dev/null || true)
|
|
||||||
|
|
||||||
# MW_DIR is the middlewared package directory itself (one level below SITE_PKG).
|
|
||||||
_MW_DIR=$("$PYTHON" -c "
|
|
||||||
import os
|
|
||||||
try:
|
|
||||||
import middlewared
|
|
||||||
print(os.path.dirname(os.path.abspath(middlewared.__file__)))
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
" 2>/dev/null || true)
|
|
||||||
|
|
||||||
_b2_ok=0
|
_b2_ok=0
|
||||||
_restic_ok=0
|
_restic_ok=0
|
||||||
|
|
||||||
@@ -181,12 +170,13 @@ else
|
|||||||
_B2_PY="$_MW_DIR/rclone/remote/b2.py"
|
_B2_PY="$_MW_DIR/rclone/remote/b2.py"
|
||||||
_RESTIC_PY="$_MW_DIR/plugins/cloud_backup/restic.py"
|
_RESTIC_PY="$_MW_DIR/plugins/cloud_backup/restic.py"
|
||||||
|
|
||||||
# ── b2.py ─────────────────────────────────────────────────────────────
|
# ── patch b2.py + restic.py + hook_status.json (single subprocess) ──────
|
||||||
if [ -f "$_B2_PY" ]; then
|
if "$PYTHON" - "$_B2_PY" "$_RESTIC_PY" "$PATCH_DIR/hook_status.json" << 'PYEOF'
|
||||||
if "$PYTHON" - "$_B2_PY" << 'PYEOF'
|
import json, os, sys, time
|
||||||
import sys
|
|
||||||
|
|
||||||
BLOCK = """
|
b2_path, restic_path, status_path = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||||
|
|
||||||
|
B2_BLOCK = """
|
||||||
# TRUECLOUD_PATCH — added by truenas-truecloud-patch/patch/apply.sh
|
# TRUECLOUD_PATCH — added by truenas-truecloud-patch/patch/apply.sh
|
||||||
def _tc_get_restic_config(task):
|
def _tc_get_restic_config(task):
|
||||||
p = task["credentials"]["provider"]
|
p = task["credentials"]["provider"]
|
||||||
@@ -196,34 +186,7 @@ B2RcloneRemote.get_restic_config = staticmethod(_tc_get_restic_config)
|
|||||||
B2RcloneRemote.restic = True
|
B2RcloneRemote.restic = True
|
||||||
"""
|
"""
|
||||||
|
|
||||||
path = sys.argv[1]
|
RESTIC_BLOCK = """
|
||||||
with open(path, encoding="utf-8") as fh:
|
|
||||||
content = fh.read()
|
|
||||||
|
|
||||||
marker = "\n# TRUECLOUD_PATCH"
|
|
||||||
idx = content.find(marker)
|
|
||||||
base = content[:idx] if idx != -1 else content
|
|
||||||
patched = base.rstrip("\n") + "\n" + BLOCK
|
|
||||||
|
|
||||||
with open(path, "w", encoding="utf-8") as fh:
|
|
||||||
fh.write(patched)
|
|
||||||
PYEOF
|
|
||||||
then
|
|
||||||
echo "OK: Patched b2.py → $_B2_PY"
|
|
||||||
_b2_ok=1
|
|
||||||
else
|
|
||||||
echo "WARNING: Failed to patch b2.py"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "WARNING: b2.py not found at $_B2_PY"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── restic.py ─────────────────────────────────────────────────────────
|
|
||||||
if [ -f "$_RESTIC_PY" ]; then
|
|
||||||
if "$PYTHON" - "$_RESTIC_PY" << 'PYEOF'
|
|
||||||
import sys
|
|
||||||
|
|
||||||
BLOCK = """
|
|
||||||
# TRUECLOUD_PATCH — added by truenas-truecloud-patch/patch/apply.sh
|
# TRUECLOUD_PATCH — added by truenas-truecloud-patch/patch/apply.sh
|
||||||
try:
|
try:
|
||||||
_tc_orig_get_restic_config = get_restic_config
|
_tc_orig_get_restic_config = get_restic_config
|
||||||
@@ -266,56 +229,66 @@ else:
|
|||||||
get_restic_config._truecloud_patched = True
|
get_restic_config._truecloud_patched = True
|
||||||
"""
|
"""
|
||||||
|
|
||||||
path = sys.argv[1]
|
def patch_file(path, block):
|
||||||
with open(path, encoding="utf-8") as fh:
|
with open(path, encoding="utf-8") as fh:
|
||||||
content = fh.read()
|
content = fh.read()
|
||||||
|
marker = "\n# TRUECLOUD_PATCH"
|
||||||
|
idx = content.find(marker)
|
||||||
|
base = content[:idx] if idx != -1 else content
|
||||||
|
with open(path, "w", encoding="utf-8") as fh:
|
||||||
|
fh.write(base.rstrip("\n") + "\n" + block)
|
||||||
|
|
||||||
marker = "\n# TRUECLOUD_PATCH"
|
b2_ok = restic_ok = False
|
||||||
idx = content.find(marker)
|
|
||||||
base = content[:idx] if idx != -1 else content
|
if os.path.exists(b2_path):
|
||||||
patched = base.rstrip("\n") + "\n" + BLOCK
|
try:
|
||||||
|
patch_file(b2_path, B2_BLOCK)
|
||||||
|
b2_ok = True
|
||||||
|
print(f"OK: Patched b2.py → {b2_path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"WARNING: Failed to patch b2.py: {e}")
|
||||||
|
else:
|
||||||
|
print(f"WARNING: b2.py not found at {b2_path}")
|
||||||
|
|
||||||
|
if os.path.exists(restic_path):
|
||||||
|
try:
|
||||||
|
patch_file(restic_path, RESTIC_BLOCK)
|
||||||
|
restic_ok = True
|
||||||
|
print(f"OK: Patched restic.py → {restic_path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"WARNING: Failed to patch restic.py: {e}")
|
||||||
|
else:
|
||||||
|
print(f"WARNING: restic.py not found at {restic_path}")
|
||||||
|
|
||||||
with open(path, "w", encoding="utf-8") as fh:
|
|
||||||
fh.write(patched)
|
|
||||||
PYEOF
|
|
||||||
then
|
|
||||||
echo "OK: Patched restic.py → $_RESTIC_PY"
|
|
||||||
_restic_ok=1
|
|
||||||
else
|
|
||||||
echo "WARNING: Failed to patch restic.py"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "WARNING: restic.py not found at $_RESTIC_PY"
|
|
||||||
fi
|
|
||||||
# Write hook_status.json so 'verify' reflects the current patch state.
|
|
||||||
"$PYTHON" -c "
|
|
||||||
import json, os, sys, time
|
|
||||||
b2_ok = sys.argv[1] == '1'
|
|
||||||
restic_ok = sys.argv[2] == '1'
|
|
||||||
patches = {
|
patches = {
|
||||||
'middlewared.rclone.remote.b2': {
|
'middlewared.rclone.remote.b2': {
|
||||||
'ok': b2_ok,
|
'ok': b2_ok,
|
||||||
'detail': ('patched on disk in overlay at boot' if b2_ok
|
'detail': 'patched on disk in overlay at boot' if b2_ok else 'b2.py not found or write failed',
|
||||||
else 'b2.py not found or write failed'),
|
|
||||||
},
|
},
|
||||||
'middlewared.plugins.cloud_backup.restic': {
|
'middlewared.plugins.cloud_backup.restic': {
|
||||||
'ok': restic_ok,
|
'ok': restic_ok,
|
||||||
'detail': ('patched on disk in overlay at boot' if restic_ok
|
'detail': 'patched on disk in overlay at boot' if restic_ok else 'restic.py not found or write failed',
|
||||||
else 'restic.py not found or write failed'),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
payload = {'patched_at': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
|
payload = {'patched_at': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), 'patches': patches}
|
||||||
'patches': patches}
|
tmp = status_path + '.tmp'
|
||||||
sf = sys.argv[3]
|
|
||||||
tmp = sf + '.tmp'
|
|
||||||
try:
|
try:
|
||||||
with open(tmp, 'w') as f:
|
with open(tmp, 'w') as f:
|
||||||
json.dump(payload, f, indent=2)
|
json.dump(payload, f, indent=2)
|
||||||
os.replace(tmp, sf)
|
os.replace(tmp, status_path)
|
||||||
print('OK: Wrote hook_status.json')
|
print('OK: Wrote hook_status.json')
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
print(f'WARNING: Could not write hook_status.json: {e}')
|
print(f'WARNING: Could not write hook_status.json: {e}')
|
||||||
" "$_b2_ok" "$_restic_ok" "$PATCH_DIR/hook_status.json" || true
|
|
||||||
|
sys.exit(0 if (b2_ok and restic_ok) else 1)
|
||||||
|
PYEOF
|
||||||
|
then
|
||||||
|
_b2_ok=1
|
||||||
|
_restic_ok=1
|
||||||
|
else
|
||||||
|
# Individual results already printed above; exit code 1 means at least one failed.
|
||||||
|
true
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Step 2: Angular bundle ────────────────────────────────────────────────────
|
# ── Step 2: Angular bundle ────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ import sys
|
|||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
|
__version__ = "0.0.3"
|
||||||
|
|
||||||
_PATCH_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
_PATCH_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
_STATUS_FILE = os.path.join(_PATCH_DIR, "hook_status.json")
|
_STATUS_FILE = os.path.join(_PATCH_DIR, "hook_status.json")
|
||||||
|
|
||||||
@@ -194,6 +196,7 @@ def main():
|
|||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
epilog=__doc__.split("Examples")[1] if __doc__ and "Examples" in __doc__ else "",
|
epilog=__doc__.split("Examples")[1] if __doc__ and "Examples" in __doc__ else "",
|
||||||
)
|
)
|
||||||
|
p.add_argument("--version", "-V", action="version", version=f"truecloud-patch {__version__}")
|
||||||
p.add_argument("--host", default=None, metavar="HOST",
|
p.add_argument("--host", default=None, metavar="HOST",
|
||||||
help="TrueNAS hostname or IP address (required except for verify)")
|
help="TrueNAS hostname or IP address (required except for verify)")
|
||||||
p.add_argument("--api-key", default=None, metavar="KEY",
|
p.add_argument("--api-key", default=None, metavar="KEY",
|
||||||
|
|||||||
@@ -16,8 +16,13 @@
|
|||||||
# rm /mnt/tank/truenas-truecloud-patch/disabled
|
# rm /mnt/tank/truenas-truecloud-patch/disabled
|
||||||
# bash /mnt/tank/truenas-truecloud-patch/patch/apply.sh
|
# bash /mnt/tank/truenas-truecloud-patch/patch/apply.sh
|
||||||
|
|
||||||
|
VERSION="0.0.3"
|
||||||
|
|
||||||
PATCH_DIR="$(cd "$(dirname "$0")" && pwd)"
|
PATCH_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
echo "=== TrueNAS TrueCloud Provider Patch v${VERSION} — Recover ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
echo "ERROR: must be run as root." >&2
|
echo "ERROR: must be run as root." >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
+3
-1
@@ -3,10 +3,12 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION="0.0.3"
|
||||||
|
|
||||||
PATCH_DIR="$(cd "$(dirname "$0")" && pwd)"
|
PATCH_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
_HOOK_COMMENT='TrueCloud provider patch (S3/B2)'
|
_HOOK_COMMENT='TrueCloud provider patch (S3/B2)'
|
||||||
|
|
||||||
echo "=== TrueNAS TrueCloud Provider Patch — Uninstall ==="
|
echo "=== TrueNAS TrueCloud Provider Patch v${VERSION} — Uninstall ==="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user