Files
truenas-truecloud-patch/install.sh
T
flan 73233865e8 Fix backend patch never loading at boot: schedule deferred middlewared restart
PREINIT initshutdownscripts are executed by middlewared itself
(ix-preinit.service, ordered after ix-zfs pool import), so the running
process had already imported the stock modules when apply.sh patched
them in the overlay — S3/B2 support silently reverted on every reboot
until something restarted middlewared. install.sh masked the bug with
its explicit restart.

apply.sh now detects boot context (parent process is middlewared) and
schedules a single detached restart via a transient systemd unit
(truecloud-mw-restart, After=multi-user.target and ix-postinit.service).
Manual runs never trigger a restart.

create_task.py verify no longer trusts hook_status.json alone: it
compares the middlewared main-process start time (derived from
/proc/<pid>/stat and btime) against patched_at and reports FAIL when
the running process predates the patch.

recover.sh and uninstall.sh cancel a still-queued deferred restart
before their own; docs updated to match the real boot ordering.
2026-07-06 05:42:04 +00:00

145 lines
6.0 KiB
Bash
Executable File

#!/bin/bash
# install.sh — run once on the TrueNAS box to set up truecloud-patch.
#
# Prerequisites: run as root on TrueNAS SCALE with middlewared running.
# Clone this repository to a persistent ZFS pool first:
#
# git clone https://github.com/sudolulo/truenas-truecloud-patch \
# /mnt/<pool>/truenas-truecloud-patch
# cd /mnt/<pool>/truenas-truecloud-patch && bash install.sh
#
# What this does:
# 1. Registers a PREINIT initshutdownscript so patch/apply.sh re-runs on
# every boot. At boot, apply.sh re-patches the overlay and schedules a
# one-time deferred middlewared restart to load the patched modules
# (PREINIT runs after middlewared starts, so a restart is required).
# 2. Applies the patches immediately (no reboot required).
# 3. Restarts middlewared so the backend change takes effect now.
set -euo pipefail
VERSION="0.0.4"
# The directory containing install.sh is the permanent install location.
PATCH_DIR="$(cd "$(dirname "$0")" && pwd)"
_HOOK_COMMENT='TrueCloud provider patch (S3/B2)'
if [ ! -f "$PATCH_DIR/patch/apply.sh" ]; then
echo "ERROR: patch files not found at $PATCH_DIR/patch/" >&2
echo "Run install.sh from a clone of the repository on a persistent pool:" >&2
echo " git clone https://github.com/sudolulo/truenas-truecloud-patch \\" >&2
echo " /mnt/<pool>/truenas-truecloud-patch" >&2
echo " cd /mnt/<pool>/truenas-truecloud-patch && bash install.sh" >&2
exit 1
fi
echo "=== TrueNAS TrueCloud Provider Patch v${VERSION} — Install ==="
echo ""
# ── Preflight ─────────────────────────────────────────────────────────────────
if [ "$(id -u)" -ne 0 ]; then
echo "ERROR: must be run as root." >&2
exit 1
fi
if ! command -v midclt &>/dev/null; then
echo "ERROR: midclt not found. Run this script on TrueNAS SCALE." >&2
exit 1
fi
if ! midclt call core.ping &>/dev/null; then
echo "ERROR: middlewared is not responding. Is TrueNAS fully booted?" >&2
exit 1
fi
# ── Set permissions ───────────────────────────────────────────────────────────
echo "Setting permissions ..."
chmod +x "$PATCH_DIR/patch/apply.sh" "$PATCH_DIR/patch/create_task.py" \
"$PATCH_DIR/recover.sh" "$PATCH_DIR/uninstall.sh"
echo "Done."
echo ""
# ── Register PREINIT script ───────────────────────────────────────────────────
echo "Registering PREINIT boot hook ..."
EXISTING_ID=$(midclt call initshutdownscript.query '[]' | \
python3 -c "
import sys, json
for s in json.load(sys.stdin):
if s.get('comment') == '$_HOOK_COMMENT':
print(s['id'])
break
" 2>/dev/null || true)
if [ -n "$EXISTING_ID" ]; then
echo "Already registered (id=$EXISTING_ID). Updating path, timeout, and enabling ..."
if ! _midclt_out=$(midclt call initshutdownscript.update "$EXISTING_ID" \
"{\"enabled\": true, \"script\": \"$PATCH_DIR/patch/apply.sh\", \"timeout\": 120}" 2>&1); then
echo "ERROR: Failed to update PREINIT hook (id=$EXISTING_ID)." >&2
[ -n "$_midclt_out" ] && echo " midclt: $_midclt_out" >&2
echo " To remove the stale entry and retry:" >&2
echo " midclt call initshutdownscript.delete $EXISTING_ID" >&2
exit 1
fi
else
if ! _midclt_out=$(midclt call initshutdownscript.create \
"{\"type\":\"SCRIPT\",\"script\":\"$PATCH_DIR/patch/apply.sh\",\"when\":\"PREINIT\",\"enabled\":true,\"timeout\":120,\"comment\":\"$_HOOK_COMMENT\"}" \
2>&1); then
echo "ERROR: Failed to register PREINIT hook." >&2
[ -n "$_midclt_out" ] && echo " midclt: $_midclt_out" >&2
exit 1
fi
echo "Registered."
fi
echo ""
# ── Clear kill switch if set ──────────────────────────────────────────────────
if [ -f "$PATCH_DIR/disabled" ]; then
rm "$PATCH_DIR/disabled"
echo "Removed kill switch ($PATCH_DIR/disabled) left from a previous recovery."
echo ""
fi
# ── Apply now ─────────────────────────────────────────────────────────────────
echo "Applying patches ..."
_log_start=0
[ -f "$PATCH_DIR/apply.log" ] && _log_start=$(wc -c < "$PATCH_DIR/apply.log")
bash "$PATCH_DIR/patch/apply.sh"
echo ""
echo "Patch log (this run):"
tail -c "+$((_log_start + 1))" "$PATCH_DIR/apply.log" 2>/dev/null || true
echo ""
if tail -c "+$((_log_start + 1))" "$PATCH_DIR/apply.log" 2>/dev/null | grep -qE "WARNING:|ERROR:"; then
echo "WARNING: apply.sh reported one or more issues — see log above for details."
echo ""
fi
# ── Restart middlewared ───────────────────────────────────────────────────────
echo "Restarting middlewared so the backend patch takes effect ..."
if ! systemctl restart middlewared; then
echo "" >&2
echo "ERROR: middlewared failed to restart." >&2
echo " The patch IS installed and will activate automatically on the next boot." >&2
echo " To activate now, resolve the issue below and run: systemctl restart middlewared" >&2
echo " 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 ""
echo "Verify the backend patch loaded correctly:"
echo " python3 $PATCH_DIR/patch/create_task.py verify"
echo ""
echo "Refresh your browser to pick up the UI change."
echo ""
echo "To create a TrueCloud Backup task with S3 or B2 credentials:"
echo " python3 $PATCH_DIR/patch/create_task.py --help"