mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
Add TrueNAS SCALE persistence for host agent (Related to #718)
This commit is contained in:
parent
964923985b
commit
408e113f35
5 changed files with 676 additions and 22 deletions
|
|
@ -74,6 +74,15 @@ curl -fsSL http://pulse.example.local:7655/install-host-agent.sh | \
|
||||||
bash -s -- --url http://pulse.example.local:7655 --token <api-token>
|
bash -s -- --url http://pulse.example.local:7655 --token <api-token>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- **TrueNAS SCALE**: Use `--platform truenas` (or rely on auto-detect) so the installer writes everything to `/data/pulse-host-agent`, installs a systemd unit from that location, and registers a POSTINIT Init/Shutdown task that re-links and restarts the service on boot.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL http://pulse.example.local:7655/install-host-agent.sh | \
|
||||||
|
bash -s -- --platform truenas --url http://pulse.example.local:7655 --token <api-token>
|
||||||
|
```
|
||||||
|
|
||||||
|
The TrueNAS flow stores the binary, service unit, and logs under `/data/pulse-host-agent` and creates a POSTINIT entry in **System Settings → Advanced → Init/Shutdown Scripts** pointing at `/data/pulse-host-agent/bootstrap-pulse-host-agent.sh`. Uninstall with `/uninstall-host-agent.sh`; the script removes the init task and the persistent directory. SATA HDD temperatures are not collected yet; CPU/NVMe/GPU sensors continue to report when lm-sensors is available.
|
||||||
|
|
||||||
- On systemd machines the script installs the binary, wires up `/etc/systemd/system/pulse-host-agent.service`, enables it, and tails the registration status.
|
- On systemd machines the script installs the binary, wires up `/etc/systemd/system/pulse-host-agent.service`, enables it, and tails the registration status.
|
||||||
- On Unraid hosts it starts the agent under `nohup`, creates `/var/log/pulse`, and (optionally) inserts the auto-start line into `/boot/config/go`.
|
- On Unraid hosts it starts the agent under `nohup`, creates `/var/log/pulse`, and (optionally) inserts the auto-start line into `/boot/config/go`.
|
||||||
- On minimalist distros without systemd (e.g. Alpine) it creates/updates `/etc/rc.local`, adds the background runner, and verifies it launches.
|
- On minimalist distros without systemd (e.g. Alpine) it creates/updates `/etc/rc.local`, adds the background runner, and verifies it launches.
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,11 @@ INTERVAL="30s"
|
||||||
UNINSTALL="false"
|
UNINSTALL="false"
|
||||||
PLATFORM=""
|
PLATFORM=""
|
||||||
FORCE=false
|
FORCE=false
|
||||||
|
KEYCHAIN_ENABLED=true
|
||||||
|
KEYCHAIN_OPT_OUT=false
|
||||||
|
KEYCHAIN_OPT_OUT_REASON=""
|
||||||
|
USE_KEYCHAIN=false
|
||||||
|
AGENT_ID="${PULSE_AGENT_ID:-}"
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
|
@ -82,6 +87,10 @@ while [[ $# -gt 0 ]]; do
|
||||||
INTERVAL="$2"
|
INTERVAL="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
--agent-id)
|
||||||
|
AGENT_ID="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
--platform)
|
--platform)
|
||||||
PLATFORM="$2"
|
PLATFORM="$2"
|
||||||
shift 2
|
shift 2
|
||||||
|
|
@ -94,6 +103,12 @@ while [[ $# -gt 0 ]]; do
|
||||||
FORCE=true
|
FORCE=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--no-keychain)
|
||||||
|
KEYCHAIN_ENABLED=false
|
||||||
|
KEYCHAIN_OPT_OUT=true
|
||||||
|
KEYCHAIN_OPT_OUT_REASON="flag"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown option: $1"
|
echo "Unknown option: $1"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
@ -119,6 +134,15 @@ if [[ -f "$UNRAID_GO_FILE" ]] || [[ -f /etc/unraid-version ]]; then
|
||||||
UNRAID=true
|
UNRAID=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
TRUENAS=false
|
||||||
|
TRUENAS_STATE_DIR="/data/pulse-host-agent"
|
||||||
|
TRUENAS_LOG_DIR="$TRUENAS_STATE_DIR/logs"
|
||||||
|
TRUENAS_SERVICE_STORAGE="$TRUENAS_STATE_DIR/pulse-host-agent.service"
|
||||||
|
TRUENAS_BOOTSTRAP_SCRIPT="$TRUENAS_STATE_DIR/bootstrap-pulse-host-agent.sh"
|
||||||
|
TRUENAS_ENV_FILE="$TRUENAS_STATE_DIR/pulse-host-agent.env"
|
||||||
|
TRUENAS_INIT_COMMENT="Pulse host agent bootstrap"
|
||||||
|
TRUENAS_SYSTEMD_LINK="/etc/systemd/system/pulse-host-agent.service"
|
||||||
|
|
||||||
# Uninstall function
|
# Uninstall function
|
||||||
if [[ "$UNINSTALL" == "true" ]]; then
|
if [[ "$UNINSTALL" == "true" ]]; then
|
||||||
log_warn "The --uninstall flag is deprecated."
|
log_warn "The --uninstall flag is deprecated."
|
||||||
|
|
@ -136,6 +160,10 @@ fi
|
||||||
|
|
||||||
print_header
|
print_header
|
||||||
|
|
||||||
|
if [[ "$FORCE" == true ]]; then
|
||||||
|
log_warn "--force enabled: skipping interactive confirmations and accepting secure defaults."
|
||||||
|
fi
|
||||||
|
|
||||||
# Interactive prompts if parameters not provided (unless --force is used)
|
# Interactive prompts if parameters not provided (unless --force is used)
|
||||||
if [[ -z "$PULSE_URL" ]]; then
|
if [[ -z "$PULSE_URL" ]]; then
|
||||||
if [[ "$FORCE" == false ]]; then
|
if [[ "$FORCE" == false ]]; then
|
||||||
|
|
@ -148,7 +176,11 @@ fi
|
||||||
|
|
||||||
if [[ -z "$PULSE_URL" ]]; then
|
if [[ -z "$PULSE_URL" ]]; then
|
||||||
log_error "Pulse URL is required"
|
log_error "Pulse URL is required"
|
||||||
echo "Usage: $0 --url <pulse-url> --token <api-token> [--interval 30s] [--platform linux|darwin|windows] [--force]"
|
echo "Usage: $0 --url <pulse-url> --token <api-token> [--interval 30s] [--agent-id <id>] [--platform linux|darwin|windows|truenas] [--force] [--no-keychain]"
|
||||||
|
echo ""
|
||||||
|
echo " --force Skip interactive prompts and accept secure defaults (including Keychain storage)."
|
||||||
|
echo " --agent-id Override the identifier used to deduplicate hosts (defaults to machine-id)."
|
||||||
|
echo " --no-keychain Disable Keychain storage and embed the token in the launch agent plist instead."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -165,6 +197,19 @@ if [[ -z "$PULSE_TOKEN" ]] && [[ "$FORCE" == false ]]; then
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
is_truenas_scale() {
|
||||||
|
if [[ -f /etc/truenas-version ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -f /etc/version ]] && grep -qi "truenas" /etc/version 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -d /data/ix-applications ]] || [[ -d /etc/ix-apps.d ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# Detect platform if not specified
|
# Detect platform if not specified
|
||||||
if [[ -z "$PLATFORM" ]]; then
|
if [[ -z "$PLATFORM" ]]; then
|
||||||
case "$(uname -s)" in
|
case "$(uname -s)" in
|
||||||
|
|
@ -183,6 +228,11 @@ if [[ -z "$PLATFORM" ]]; then
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
PLATFORM=$(echo "$PLATFORM" | tr '[:upper:]' '[:lower:]')
|
||||||
|
if [[ "$PLATFORM" == "truenas" ]]; then
|
||||||
|
PLATFORM="linux"
|
||||||
|
TRUENAS=true
|
||||||
|
fi
|
||||||
|
|
||||||
# Detect architecture
|
# Detect architecture
|
||||||
ARCH="$(uname -m)"
|
ARCH="$(uname -m)"
|
||||||
|
|
@ -196,12 +246,31 @@ case "$ARCH" in
|
||||||
armv7l|armhf)
|
armv7l|armhf)
|
||||||
ARCH="armv7"
|
ARCH="armv7"
|
||||||
;;
|
;;
|
||||||
*)
|
armv6l)
|
||||||
log_warn "Unknown architecture $ARCH, defaulting to amd64"
|
ARCH="armv6"
|
||||||
ARCH="amd64"
|
|
||||||
;;
|
;;
|
||||||
|
i386|i686)
|
||||||
|
ARCH="386"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_warn "Unknown architecture $ARCH, defaulting to amd64"
|
||||||
|
ARCH="amd64"
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
if [[ "$PLATFORM" == "linux" && "$TRUENAS" == false ]]; then
|
||||||
|
if is_truenas_scale; then
|
||||||
|
TRUENAS=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$TRUENAS" == true ]]; then
|
||||||
|
AGENT_PATH="$TRUENAS_STATE_DIR/pulse-host-agent"
|
||||||
|
SYSTEMD_SERVICE="$TRUENAS_SYSTEMD_LINK"
|
||||||
|
LINUX_LOG_DIR="$TRUENAS_LOG_DIR"
|
||||||
|
LINUX_LOG_FILE="$LINUX_LOG_DIR/host-agent.log"
|
||||||
|
fi
|
||||||
|
|
||||||
log_info "Configuration:"
|
log_info "Configuration:"
|
||||||
echo " Pulse URL: $PULSE_URL"
|
echo " Pulse URL: $PULSE_URL"
|
||||||
if [[ -n "$PULSE_TOKEN" ]]; then
|
if [[ -n "$PULSE_TOKEN" ]]; then
|
||||||
|
|
@ -211,8 +280,16 @@ if [[ -n "$PULSE_TOKEN" ]]; then
|
||||||
else
|
else
|
||||||
echo " Token: none"
|
echo " Token: none"
|
||||||
fi
|
fi
|
||||||
|
if [[ -n "$AGENT_ID" ]]; then
|
||||||
|
echo " Agent ID: $AGENT_ID"
|
||||||
|
else
|
||||||
|
echo " Agent ID: machine-id (default)"
|
||||||
|
fi
|
||||||
echo " Interval: $INTERVAL"
|
echo " Interval: $INTERVAL"
|
||||||
echo " Platform: $PLATFORM/$ARCH"
|
echo " Platform: $PLATFORM/$ARCH"
|
||||||
|
if [[ "$TRUENAS" == true ]]; then
|
||||||
|
echo " TrueNAS SCALE mode: enabled (immutable root detected)"
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
log_info "Installing Pulse host agent for $PLATFORM/$ARCH..."
|
log_info "Installing Pulse host agent for $PLATFORM/$ARCH..."
|
||||||
|
|
@ -363,8 +440,18 @@ else
|
||||||
log_info "Checksum not available (server doesn't provide it yet)"
|
log_info "Checksum not available (server doesn't provide it yet)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo mv "$TEMP_BINARY" "$AGENT_PATH"
|
# Use install command instead of mv to ensure correct SELinux context
|
||||||
sudo chmod +x "$AGENT_PATH"
|
# The install command creates a new file with the correct label for the target directory
|
||||||
|
sudo install -D -m 0755 "$TEMP_BINARY" "$AGENT_PATH"
|
||||||
|
rm -f "$TEMP_BINARY"
|
||||||
|
|
||||||
|
# On SELinux systems, explicitly restore context to ensure policy compliance
|
||||||
|
if command -v selinuxenabled &> /dev/null && selinuxenabled 2>/dev/null; then
|
||||||
|
if command -v restorecon &> /dev/null; then
|
||||||
|
sudo restorecon -F "$AGENT_PATH" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
log_success "Agent binary installed to $AGENT_PATH"
|
log_success "Agent binary installed to $AGENT_PATH"
|
||||||
|
|
||||||
# Build reusable agent command strings
|
# Build reusable agent command strings
|
||||||
|
|
@ -373,11 +460,156 @@ if [[ -n "$PULSE_TOKEN" ]]; then
|
||||||
AGENT_CMD="$AGENT_CMD --token $PULSE_TOKEN"
|
AGENT_CMD="$AGENT_CMD --token $PULSE_TOKEN"
|
||||||
fi
|
fi
|
||||||
AGENT_CMD="$AGENT_CMD --interval $INTERVAL"
|
AGENT_CMD="$AGENT_CMD --interval $INTERVAL"
|
||||||
|
if [[ -n "$AGENT_ID" ]]; then
|
||||||
|
AGENT_CMD="$AGENT_CMD --agent-id $AGENT_ID"
|
||||||
|
fi
|
||||||
MANUAL_START_CMD="$AGENT_CMD"
|
MANUAL_START_CMD="$AGENT_CMD"
|
||||||
MANUAL_START_WRAPPED="nohup $MANUAL_START_CMD >$LINUX_LOG_FILE 2>&1 &"
|
MANUAL_START_WRAPPED="nohup $MANUAL_START_CMD >$LINUX_LOG_FILE 2>&1 &"
|
||||||
|
|
||||||
|
write_truenas_env_file() {
|
||||||
|
sudo install -d -m 0700 "$TRUENAS_STATE_DIR" "$TRUENAS_LOG_DIR"
|
||||||
|
local tmp_env
|
||||||
|
tmp_env=$(mktemp)
|
||||||
|
{
|
||||||
|
echo "PULSE_URL=$PULSE_URL"
|
||||||
|
echo "PULSE_INTERVAL=$INTERVAL"
|
||||||
|
echo "PULSE_LOG_FILE=$LINUX_LOG_FILE"
|
||||||
|
if [[ -n "$PULSE_TOKEN" ]]; then
|
||||||
|
echo "PULSE_TOKEN=$PULSE_TOKEN"
|
||||||
|
fi
|
||||||
|
if [[ -n "$AGENT_ID" ]]; then
|
||||||
|
echo "PULSE_AGENT_ID=$AGENT_ID"
|
||||||
|
fi
|
||||||
|
} > "$tmp_env"
|
||||||
|
sudo install -m 0600 "$tmp_env" "$TRUENAS_ENV_FILE"
|
||||||
|
rm -f "$tmp_env"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_truenas_service_unit() {
|
||||||
|
local exec_start="$AGENT_PATH --url \$PULSE_URL --interval \$PULSE_INTERVAL"
|
||||||
|
if [[ -n "$PULSE_TOKEN" ]]; then
|
||||||
|
exec_start="$exec_start --token \$PULSE_TOKEN"
|
||||||
|
fi
|
||||||
|
if [[ -n "$AGENT_ID" ]]; then
|
||||||
|
exec_start="$exec_start --agent-id \$PULSE_AGENT_ID"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo tee "$TRUENAS_SERVICE_STORAGE" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Pulse Host Agent (TrueNAS SCALE)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
ConditionPathExists=$AGENT_PATH
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
EnvironmentFile=$TRUENAS_ENV_FILE
|
||||||
|
ExecStart=$exec_start
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5s
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
StandardOutput=append:$LINUX_LOG_FILE
|
||||||
|
StandardError=append:$LINUX_LOG_FILE
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
write_truenas_bootstrap_script() {
|
||||||
|
sudo tee "$TRUENAS_BOOTSTRAP_SCRIPT" > /dev/null <<EOF
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
STATE_DIR="$TRUENAS_STATE_DIR"
|
||||||
|
UNIT_SRC="$TRUENAS_SERVICE_STORAGE"
|
||||||
|
UNIT_DST="$TRUENAS_SYSTEMD_LINK"
|
||||||
|
LOG_DIR="$LINUX_LOG_DIR"
|
||||||
|
AGENT_PATH="$AGENT_PATH"
|
||||||
|
|
||||||
|
if [ ! -x "\$AGENT_PATH" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "\$LOG_DIR"
|
||||||
|
ln -sf "\$UNIT_SRC" "\$UNIT_DST"
|
||||||
|
systemctl daemon-reload
|
||||||
|
if systemctl is-enabled pulse-host-agent >/dev/null 2>&1; then
|
||||||
|
systemctl restart pulse-host-agent >/dev/null 2>&1 || true
|
||||||
|
else
|
||||||
|
systemctl enable --now pulse-host-agent >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
sudo chmod 0755 "$TRUENAS_BOOTSTRAP_SCRIPT"
|
||||||
|
}
|
||||||
|
|
||||||
|
register_truenas_init_task() {
|
||||||
|
if ! command -v midclt >/dev/null 2>&1; then
|
||||||
|
log_warn "midclt not found - add a POSTINIT task for $TRUENAS_BOOTSTRAP_SCRIPT manually in the TrueNAS UI."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
log_warn "python3 not found - cannot parse init task state; add the POSTINIT task manually if needed."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local query existing_id payload
|
||||||
|
query='[["script","=","'"$TRUENAS_BOOTSTRAP_SCRIPT"'"]]'
|
||||||
|
local query_output
|
||||||
|
query_output=$(midclt call initshutdownscript.query "$query" 2>/dev/null || true)
|
||||||
|
existing_id=$(printf '%s' "$query_output" | python3 - <<'PY'
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
print(data[0]["id"] if data else "")
|
||||||
|
except Exception:
|
||||||
|
print("")
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
|
||||||
|
payload=$(cat <<EOF
|
||||||
|
{"type":"SCRIPT","script":"$TRUENAS_BOOTSTRAP_SCRIPT","when":"POSTINIT","enabled":true,"timeout":120,"comment":"$TRUENAS_INIT_COMMENT"}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ -n "$existing_id" ]]; then
|
||||||
|
if midclt call initshutdownscript.update "$existing_id" "$payload" >/dev/null 2>&1; then
|
||||||
|
log_info "Updated existing TrueNAS init task (id $existing_id)"
|
||||||
|
else
|
||||||
|
log_warn "Failed to update existing TrueNAS init task (id $existing_id)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if midclt call initshutdownscript.create "$payload" >/dev/null 2>&1; then
|
||||||
|
log_success "Registered TrueNAS init task to restore the service on boot"
|
||||||
|
else
|
||||||
|
log_warn "Failed to register TrueNAS init task; add it manually via System Settings → Advanced → Init/Shutdown Scripts."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_truenas_service() {
|
||||||
|
log_info "Detected TrueNAS SCALE (immutable root). Storing agent under $TRUENAS_STATE_DIR"
|
||||||
|
write_truenas_env_file
|
||||||
|
write_truenas_service_unit
|
||||||
|
write_truenas_bootstrap_script
|
||||||
|
|
||||||
|
sudo ln -sf "$TRUENAS_SERVICE_STORAGE" "$TRUENAS_SYSTEMD_LINK"
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
if sudo systemctl is-enabled pulse-host-agent >/dev/null 2>&1; then
|
||||||
|
sudo systemctl restart pulse-host-agent || true
|
||||||
|
else
|
||||||
|
sudo systemctl enable --now pulse-host-agent || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
register_truenas_init_task
|
||||||
|
SERVICE_MODE="truenas"
|
||||||
|
log_success "TrueNAS SCALE service installed and started"
|
||||||
|
}
|
||||||
|
|
||||||
# Set up service based on platform
|
# Set up service based on platform
|
||||||
if [[ "$PLATFORM" == "linux" ]] && command -v systemctl &> /dev/null; then
|
if [[ "$TRUENAS" == true ]]; then
|
||||||
|
setup_truenas_service
|
||||||
|
elif [[ "$PLATFORM" == "linux" ]] && command -v systemctl &> /dev/null; then
|
||||||
log_info "Setting up systemd service..."
|
log_info "Setting up systemd service..."
|
||||||
|
|
||||||
# Create log directory
|
# Create log directory
|
||||||
|
|
@ -416,8 +648,32 @@ elif [[ "$PLATFORM" == "darwin" ]] && command -v launchctl &> /dev/null; then
|
||||||
mkdir -p "$MACOS_LOG_DIR"
|
mkdir -p "$MACOS_LOG_DIR"
|
||||||
mkdir -p "$HOME/Library/LaunchAgents"
|
mkdir -p "$HOME/Library/LaunchAgents"
|
||||||
|
|
||||||
|
if [[ -n "$PULSE_TOKEN" && "$KEYCHAIN_ENABLED" == true && "$FORCE" == false ]]; then
|
||||||
|
echo ""
|
||||||
|
log_info "It is recommended to store the token in your Keychain so it never lands on disk."
|
||||||
|
KEYCHAIN_PROMPTED=false
|
||||||
|
if [[ -t 0 ]]; then
|
||||||
|
read -r -p "Store the token in the macOS Keychain? [Y/n]: " KEYCHAIN_RESPONSE
|
||||||
|
KEYCHAIN_PROMPTED=true
|
||||||
|
elif [[ -r /dev/tty ]]; then
|
||||||
|
read -r -p "Store the token in the macOS Keychain? [Y/n]: " KEYCHAIN_RESPONSE </dev/tty
|
||||||
|
KEYCHAIN_PROMPTED=true
|
||||||
|
else
|
||||||
|
log_warn "No interactive terminal detected; defaulting to Keychain storage. Use --no-keychain to opt out."
|
||||||
|
fi
|
||||||
|
if [[ "$KEYCHAIN_PROMPTED" == true && "$KEYCHAIN_RESPONSE" =~ ^[Nn] ]]; then
|
||||||
|
KEYCHAIN_ENABLED=false
|
||||||
|
KEYCHAIN_OPT_OUT=true
|
||||||
|
KEYCHAIN_OPT_OUT_REASON="prompt"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
# Store token in macOS Keychain for better security
|
# Store token in macOS Keychain for better security
|
||||||
if [[ -n "$PULSE_TOKEN" ]]; then
|
if [[ -n "$PULSE_TOKEN" && "$KEYCHAIN_ENABLED" == true ]]; then
|
||||||
|
log_info "For security, the token is stored in your macOS Keychain so it never lands on disk."
|
||||||
|
log_info "macOS may ask to allow access the first time the agent runs."
|
||||||
|
log_info "Use --no-keychain to opt out (the token will be embedded in the launchd plist instead)."
|
||||||
log_info "Storing token in macOS Keychain..."
|
log_info "Storing token in macOS Keychain..."
|
||||||
|
|
||||||
# Delete existing keychain entry if it exists
|
# Delete existing keychain entry if it exists
|
||||||
|
|
@ -429,12 +685,13 @@ elif [[ "$PLATFORM" == "darwin" ]] && command -v launchctl &> /dev/null; then
|
||||||
|
|
||||||
KEYCHAIN_APPS=(
|
KEYCHAIN_APPS=(
|
||||||
"/usr/local/bin/pulse-host-agent"
|
"/usr/local/bin/pulse-host-agent"
|
||||||
"/usr/local/bin/pulse-host-agent-wrapper.sh"
|
|
||||||
"/usr/bin/security"
|
"/usr/bin/security"
|
||||||
)
|
)
|
||||||
KEYCHAIN_ARGS=()
|
KEYCHAIN_ARGS=()
|
||||||
for app in "${KEYCHAIN_APPS[@]}"; do
|
for app in "${KEYCHAIN_APPS[@]}"; do
|
||||||
KEYCHAIN_ARGS+=(-T "$app")
|
if [[ -e "$app" ]]; then
|
||||||
|
KEYCHAIN_ARGS+=(-T "$app")
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if security add-generic-password \
|
if security add-generic-password \
|
||||||
|
|
@ -456,10 +713,27 @@ elif [[ "$PLATFORM" == "darwin" ]] && command -v launchctl &> /dev/null; then
|
||||||
log_info "You may need to grant Keychain access permissions"
|
log_info "You may need to grant Keychain access permissions"
|
||||||
USE_KEYCHAIN=false
|
USE_KEYCHAIN=false
|
||||||
fi
|
fi
|
||||||
|
elif [[ -n "$PULSE_TOKEN" ]]; then
|
||||||
|
if [[ "$KEYCHAIN_OPT_OUT" == true ]]; then
|
||||||
|
if [[ "$KEYCHAIN_OPT_OUT_REASON" == "flag" ]]; then
|
||||||
|
log_warn "Keychain storage disabled via --no-keychain; token will be embedded in the launchd plist."
|
||||||
|
elif [[ "$KEYCHAIN_OPT_OUT_REASON" == "prompt" ]]; then
|
||||||
|
log_warn "Keychain storage skipped at user prompt; token will be embedded in the launchd plist."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_warn "Keychain storage disabled; token will be embedded in the launchd plist."
|
||||||
|
fi
|
||||||
|
USE_KEYCHAIN=false
|
||||||
else
|
else
|
||||||
USE_KEYCHAIN=false
|
USE_KEYCHAIN=false
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
LAUNCHD_AGENT_ID_ARGS=""
|
||||||
|
if [[ -n "$AGENT_ID" ]]; then
|
||||||
|
LAUNCHD_AGENT_ID_ARGS=" <string>--agent-id</string>
|
||||||
|
<string>$AGENT_ID</string>"
|
||||||
|
fi
|
||||||
|
|
||||||
# Create wrapper script if using Keychain
|
# Create wrapper script if using Keychain
|
||||||
if [[ "$USE_KEYCHAIN" == true ]]; then
|
if [[ "$USE_KEYCHAIN" == true ]]; then
|
||||||
WRAPPER_SCRIPT="/usr/local/bin/pulse-host-agent-wrapper.sh"
|
WRAPPER_SCRIPT="/usr/local/bin/pulse-host-agent-wrapper.sh"
|
||||||
|
|
@ -519,6 +793,7 @@ WRAPPER_EOF
|
||||||
<string>$PULSE_URL</string>
|
<string>$PULSE_URL</string>
|
||||||
<string>--interval</string>
|
<string>--interval</string>
|
||||||
<string>$INTERVAL</string>
|
<string>$INTERVAL</string>
|
||||||
|
$LAUNCHD_AGENT_ID_ARGS
|
||||||
</array>
|
</array>
|
||||||
<key>RunAtLoad</key>
|
<key>RunAtLoad</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
|
@ -550,6 +825,7 @@ EOF
|
||||||
<string>$PULSE_TOKEN</string>
|
<string>$PULSE_TOKEN</string>
|
||||||
<string>--interval</string>
|
<string>--interval</string>
|
||||||
<string>$INTERVAL</string>
|
<string>$INTERVAL</string>
|
||||||
|
$LAUNCHD_AGENT_ID_ARGS
|
||||||
</array>
|
</array>
|
||||||
<key>RunAtLoad</key>
|
<key>RunAtLoad</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
|
@ -720,7 +996,7 @@ VALIDATION_SUCCESS=false
|
||||||
SERVICE_RUNNING=false
|
SERVICE_RUNNING=false
|
||||||
|
|
||||||
# Check if service is running
|
# Check if service is running
|
||||||
if [[ "$SERVICE_MODE" == "systemd" ]]; then
|
if [[ "$SERVICE_MODE" == "systemd" || "$SERVICE_MODE" == "truenas" ]]; then
|
||||||
SERVICE_STATUS=$(systemctl is-active pulse-host-agent 2>/dev/null || echo "inactive")
|
SERVICE_STATUS=$(systemctl is-active pulse-host-agent 2>/dev/null || echo "inactive")
|
||||||
if [[ "$SERVICE_STATUS" == "active" ]]; then
|
if [[ "$SERVICE_STATUS" == "active" ]]; then
|
||||||
SERVICE_RUNNING=true
|
SERVICE_RUNNING=true
|
||||||
|
|
@ -811,10 +1087,13 @@ else
|
||||||
echo ""
|
echo ""
|
||||||
log_info "Troubleshooting:"
|
log_info "Troubleshooting:"
|
||||||
echo ""
|
echo ""
|
||||||
if [[ "$SERVICE_MODE" == "systemd" ]]; then
|
if [[ "$SERVICE_MODE" == "systemd" || "$SERVICE_MODE" == "truenas" ]]; then
|
||||||
echo " View logs: sudo journalctl -u pulse-host-agent -f"
|
echo " View logs: sudo journalctl -u pulse-host-agent -f"
|
||||||
echo " Check status: sudo systemctl status pulse-host-agent"
|
echo " Check status: sudo systemctl status pulse-host-agent"
|
||||||
echo " Restart: sudo systemctl restart pulse-host-agent"
|
echo " Restart: sudo systemctl restart pulse-host-agent"
|
||||||
|
if [[ "$SERVICE_MODE" == "truenas" ]]; then
|
||||||
|
echo " Persist: Confirm the POSTINIT task named \"$TRUENAS_INIT_COMMENT\" exists in the TrueNAS UI"
|
||||||
|
fi
|
||||||
elif [[ "$SERVICE_MODE" == "launchd" ]]; then
|
elif [[ "$SERVICE_MODE" == "launchd" ]]; then
|
||||||
echo " View logs: tail -f $MACOS_LOG_FILE"
|
echo " View logs: tail -f $MACOS_LOG_FILE"
|
||||||
echo " Check status: launchctl list | grep pulse"
|
echo " Check status: launchctl list | grep pulse"
|
||||||
|
|
@ -839,12 +1118,15 @@ fi
|
||||||
print_footer
|
print_footer
|
||||||
|
|
||||||
log_info "Service Management Commands:"
|
log_info "Service Management Commands:"
|
||||||
if [[ "$SERVICE_MODE" == "systemd" ]]; then
|
if [[ "$SERVICE_MODE" == "systemd" || "$SERVICE_MODE" == "truenas" ]]; then
|
||||||
echo " Start: sudo systemctl start pulse-host-agent"
|
echo " Start: sudo systemctl start pulse-host-agent"
|
||||||
echo " Stop: sudo systemctl stop pulse-host-agent"
|
echo " Stop: sudo systemctl stop pulse-host-agent"
|
||||||
echo " Restart: sudo systemctl restart pulse-host-agent"
|
echo " Restart: sudo systemctl restart pulse-host-agent"
|
||||||
echo " Status: sudo systemctl status pulse-host-agent"
|
echo " Status: sudo systemctl status pulse-host-agent"
|
||||||
echo " Logs: sudo journalctl -u pulse-host-agent -f"
|
echo " Logs: sudo journalctl -u pulse-host-agent -f"
|
||||||
|
if [[ "$SERVICE_MODE" == "truenas" ]]; then
|
||||||
|
echo " Persist: TrueNAS Init/Shutdown task stores $TRUENAS_BOOTSTRAP_SCRIPT as POSTINIT"
|
||||||
|
fi
|
||||||
elif [[ "$SERVICE_MODE" == "launchd" ]]; then
|
elif [[ "$SERVICE_MODE" == "launchd" ]]; then
|
||||||
echo " Start: launchctl load $LAUNCHD_PLIST"
|
echo " Start: launchctl load $LAUNCHD_PLIST"
|
||||||
echo " Stop: launchctl unload $LAUNCHD_PLIST"
|
echo " Stop: launchctl unload $LAUNCHD_PLIST"
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,15 @@ SYSTEMD_SERVICE="/etc/systemd/system/pulse-host-agent.service"
|
||||||
LAUNCHD_PLIST="$HOME/Library/LaunchAgents/com.pulse.host-agent.plist"
|
LAUNCHD_PLIST="$HOME/Library/LaunchAgents/com.pulse.host-agent.plist"
|
||||||
MACOS_LOG_DIR="$HOME/Library/Logs/Pulse"
|
MACOS_LOG_DIR="$HOME/Library/Logs/Pulse"
|
||||||
LINUX_LOG_DIR="/var/log/pulse"
|
LINUX_LOG_DIR="/var/log/pulse"
|
||||||
|
LINUX_LOG_FILE="$LINUX_LOG_DIR/host-agent.log"
|
||||||
|
|
||||||
|
TRUENAS=false
|
||||||
|
TRUENAS_STATE_DIR="/data/pulse-host-agent"
|
||||||
|
TRUENAS_LOG_DIR="$TRUENAS_STATE_DIR/logs"
|
||||||
|
TRUENAS_SERVICE_STORAGE="$TRUENAS_STATE_DIR/pulse-host-agent.service"
|
||||||
|
TRUENAS_BOOTSTRAP_SCRIPT="$TRUENAS_STATE_DIR/bootstrap-pulse-host-agent.sh"
|
||||||
|
TRUENAS_ENV_FILE="$TRUENAS_STATE_DIR/pulse-host-agent.env"
|
||||||
|
TRUENAS_SYSTEMD_LINK="/etc/systemd/system/pulse-host-agent.service"
|
||||||
|
|
||||||
print_header
|
print_header
|
||||||
|
|
||||||
|
|
@ -83,9 +92,63 @@ case "$(uname -s)" in
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
is_truenas_scale() {
|
||||||
|
if [[ -f /etc/truenas-version ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -f /etc/version ]] && grep -qi "truenas" /etc/version 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -d /data/ix-applications ]] || [[ -d /etc/ix-apps.d ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$PLATFORM" == "linux" ]] && is_truenas_scale; then
|
||||||
|
TRUENAS=true
|
||||||
|
AGENT_PATH="$TRUENAS_STATE_DIR/pulse-host-agent"
|
||||||
|
SYSTEMD_SERVICE="$TRUENAS_SYSTEMD_LINK"
|
||||||
|
LINUX_LOG_DIR="$TRUENAS_LOG_DIR"
|
||||||
|
LINUX_LOG_FILE="$LINUX_LOG_DIR/host-agent.log"
|
||||||
|
fi
|
||||||
|
|
||||||
log_info "Detected platform: $PLATFORM"
|
log_info "Detected platform: $PLATFORM"
|
||||||
|
if [[ "$TRUENAS" == true ]]; then
|
||||||
|
log_info "TrueNAS SCALE detected (immutable root). Using $TRUENAS_STATE_DIR for cleanup."
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
remove_truenas_init_task() {
|
||||||
|
if [[ "$TRUENAS" != true ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! command -v midclt >/dev/null 2>&1 || ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
log_warn "midclt/python3 not available - remove the POSTINIT task for $TRUENAS_BOOTSTRAP_SCRIPT manually if it exists."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local query_output task_id
|
||||||
|
query_output=$(midclt call initshutdownscript.query '[["script","=","'"$TRUENAS_BOOTSTRAP_SCRIPT"'"]]' 2>/dev/null || true)
|
||||||
|
task_id=$(printf '%s' "$query_output" | python3 - <<'PY'
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
print(data[0]["id"] if data else "")
|
||||||
|
except Exception:
|
||||||
|
print("")
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ -n "$task_id" ]]; then
|
||||||
|
if midclt call initshutdownscript.delete "$task_id" >/dev/null 2>&1; then
|
||||||
|
log_success "Removed TrueNAS Init/Shutdown task (id $task_id)"
|
||||||
|
else
|
||||||
|
log_warn "Failed to remove TrueNAS Init/Shutdown task id $task_id; remove it manually in the TrueNAS UI."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Stop and remove systemd service (Linux)
|
# Stop and remove systemd service (Linux)
|
||||||
if [[ "$PLATFORM" == "linux" ]]; then
|
if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
if [[ -f "$SYSTEMD_SERVICE" ]] && command -v systemctl &> /dev/null; then
|
if [[ -f "$SYSTEMD_SERVICE" ]] && command -v systemctl &> /dev/null; then
|
||||||
|
|
@ -115,12 +178,35 @@ if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
log_success "Processes terminated"
|
log_success "Processes terminated"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "$TRUENAS" == true ]]; then
|
||||||
|
remove_truenas_init_task
|
||||||
|
|
||||||
|
if [[ -f "$TRUENAS_SERVICE_STORAGE" ]]; then
|
||||||
|
log_info "Removing stored TrueNAS service unit..."
|
||||||
|
sudo rm -f "$TRUENAS_SERVICE_STORAGE"
|
||||||
|
fi
|
||||||
|
if [[ -f "$TRUENAS_BOOTSTRAP_SCRIPT" ]]; then
|
||||||
|
log_info "Removing TrueNAS bootstrap script..."
|
||||||
|
sudo rm -f "$TRUENAS_BOOTSTRAP_SCRIPT"
|
||||||
|
fi
|
||||||
|
if [[ -f "$TRUENAS_ENV_FILE" ]]; then
|
||||||
|
log_info "Removing TrueNAS environment file..."
|
||||||
|
sudo rm -f "$TRUENAS_ENV_FILE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Remove log directory
|
# Remove log directory
|
||||||
if [[ -d "$LINUX_LOG_DIR" ]]; then
|
if [[ -d "$LINUX_LOG_DIR" ]]; then
|
||||||
log_info "Removing log directory..."
|
log_info "Removing log directory..."
|
||||||
sudo rm -rf "$LINUX_LOG_DIR"
|
sudo rm -rf "$LINUX_LOG_DIR"
|
||||||
log_success "Log directory removed: $LINUX_LOG_DIR"
|
log_success "Log directory removed: $LINUX_LOG_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "$TRUENAS" == true ]] && [[ -d "$TRUENAS_STATE_DIR" ]]; then
|
||||||
|
log_info "Removing persistent state directory..."
|
||||||
|
sudo rm -rf "$TRUENAS_STATE_DIR"
|
||||||
|
log_success "Removed $TRUENAS_STATE_DIR"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Stop and remove launchd service (macOS)
|
# Stop and remove launchd service (macOS)
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,15 @@ if [[ -f "$UNRAID_GO_FILE" ]] || [[ -f /etc/unraid-version ]]; then
|
||||||
UNRAID=true
|
UNRAID=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
TRUENAS=false
|
||||||
|
TRUENAS_STATE_DIR="/data/pulse-host-agent"
|
||||||
|
TRUENAS_LOG_DIR="$TRUENAS_STATE_DIR/logs"
|
||||||
|
TRUENAS_SERVICE_STORAGE="$TRUENAS_STATE_DIR/pulse-host-agent.service"
|
||||||
|
TRUENAS_BOOTSTRAP_SCRIPT="$TRUENAS_STATE_DIR/bootstrap-pulse-host-agent.sh"
|
||||||
|
TRUENAS_ENV_FILE="$TRUENAS_STATE_DIR/pulse-host-agent.env"
|
||||||
|
TRUENAS_INIT_COMMENT="Pulse host agent bootstrap"
|
||||||
|
TRUENAS_SYSTEMD_LINK="/etc/systemd/system/pulse-host-agent.service"
|
||||||
|
|
||||||
# Uninstall function
|
# Uninstall function
|
||||||
if [[ "$UNINSTALL" == "true" ]]; then
|
if [[ "$UNINSTALL" == "true" ]]; then
|
||||||
log_warn "The --uninstall flag is deprecated."
|
log_warn "The --uninstall flag is deprecated."
|
||||||
|
|
@ -167,7 +176,7 @@ fi
|
||||||
|
|
||||||
if [[ -z "$PULSE_URL" ]]; then
|
if [[ -z "$PULSE_URL" ]]; then
|
||||||
log_error "Pulse URL is required"
|
log_error "Pulse URL is required"
|
||||||
echo "Usage: $0 --url <pulse-url> --token <api-token> [--interval 30s] [--agent-id <id>] [--platform linux|darwin|windows] [--force] [--no-keychain]"
|
echo "Usage: $0 --url <pulse-url> --token <api-token> [--interval 30s] [--agent-id <id>] [--platform linux|darwin|windows|truenas] [--force] [--no-keychain]"
|
||||||
echo ""
|
echo ""
|
||||||
echo " --force Skip interactive prompts and accept secure defaults (including Keychain storage)."
|
echo " --force Skip interactive prompts and accept secure defaults (including Keychain storage)."
|
||||||
echo " --agent-id Override the identifier used to deduplicate hosts (defaults to machine-id)."
|
echo " --agent-id Override the identifier used to deduplicate hosts (defaults to machine-id)."
|
||||||
|
|
@ -188,6 +197,19 @@ if [[ -z "$PULSE_TOKEN" ]] && [[ "$FORCE" == false ]]; then
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
is_truenas_scale() {
|
||||||
|
if [[ -f /etc/truenas-version ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -f /etc/version ]] && grep -qi "truenas" /etc/version 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -d /data/ix-applications ]] || [[ -d /etc/ix-apps.d ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# Detect platform if not specified
|
# Detect platform if not specified
|
||||||
if [[ -z "$PLATFORM" ]]; then
|
if [[ -z "$PLATFORM" ]]; then
|
||||||
case "$(uname -s)" in
|
case "$(uname -s)" in
|
||||||
|
|
@ -206,6 +228,11 @@ if [[ -z "$PLATFORM" ]]; then
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
PLATFORM=$(echo "$PLATFORM" | tr '[:upper:]' '[:lower:]')
|
||||||
|
if [[ "$PLATFORM" == "truenas" ]]; then
|
||||||
|
PLATFORM="linux"
|
||||||
|
TRUENAS=true
|
||||||
|
fi
|
||||||
|
|
||||||
# Detect architecture
|
# Detect architecture
|
||||||
ARCH="$(uname -m)"
|
ARCH="$(uname -m)"
|
||||||
|
|
@ -226,11 +253,24 @@ case "$ARCH" in
|
||||||
ARCH="386"
|
ARCH="386"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log_warn "Unknown architecture $ARCH, defaulting to amd64"
|
log_warn "Unknown architecture $ARCH, defaulting to amd64"
|
||||||
ARCH="amd64"
|
ARCH="amd64"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
if [[ "$PLATFORM" == "linux" && "$TRUENAS" == false ]]; then
|
||||||
|
if is_truenas_scale; then
|
||||||
|
TRUENAS=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$TRUENAS" == true ]]; then
|
||||||
|
AGENT_PATH="$TRUENAS_STATE_DIR/pulse-host-agent"
|
||||||
|
SYSTEMD_SERVICE="$TRUENAS_SYSTEMD_LINK"
|
||||||
|
LINUX_LOG_DIR="$TRUENAS_LOG_DIR"
|
||||||
|
LINUX_LOG_FILE="$LINUX_LOG_DIR/host-agent.log"
|
||||||
|
fi
|
||||||
|
|
||||||
log_info "Configuration:"
|
log_info "Configuration:"
|
||||||
echo " Pulse URL: $PULSE_URL"
|
echo " Pulse URL: $PULSE_URL"
|
||||||
if [[ -n "$PULSE_TOKEN" ]]; then
|
if [[ -n "$PULSE_TOKEN" ]]; then
|
||||||
|
|
@ -247,6 +287,9 @@ else
|
||||||
fi
|
fi
|
||||||
echo " Interval: $INTERVAL"
|
echo " Interval: $INTERVAL"
|
||||||
echo " Platform: $PLATFORM/$ARCH"
|
echo " Platform: $PLATFORM/$ARCH"
|
||||||
|
if [[ "$TRUENAS" == true ]]; then
|
||||||
|
echo " TrueNAS SCALE mode: enabled (immutable root detected)"
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
log_info "Installing Pulse host agent for $PLATFORM/$ARCH..."
|
log_info "Installing Pulse host agent for $PLATFORM/$ARCH..."
|
||||||
|
|
@ -399,7 +442,7 @@ fi
|
||||||
|
|
||||||
# Use install command instead of mv to ensure correct SELinux context
|
# Use install command instead of mv to ensure correct SELinux context
|
||||||
# The install command creates a new file with the correct label for the target directory
|
# The install command creates a new file with the correct label for the target directory
|
||||||
sudo install -m 0755 "$TEMP_BINARY" "$AGENT_PATH"
|
sudo install -D -m 0755 "$TEMP_BINARY" "$AGENT_PATH"
|
||||||
rm -f "$TEMP_BINARY"
|
rm -f "$TEMP_BINARY"
|
||||||
|
|
||||||
# On SELinux systems, explicitly restore context to ensure policy compliance
|
# On SELinux systems, explicitly restore context to ensure policy compliance
|
||||||
|
|
@ -423,8 +466,150 @@ fi
|
||||||
MANUAL_START_CMD="$AGENT_CMD"
|
MANUAL_START_CMD="$AGENT_CMD"
|
||||||
MANUAL_START_WRAPPED="nohup $MANUAL_START_CMD >$LINUX_LOG_FILE 2>&1 &"
|
MANUAL_START_WRAPPED="nohup $MANUAL_START_CMD >$LINUX_LOG_FILE 2>&1 &"
|
||||||
|
|
||||||
|
write_truenas_env_file() {
|
||||||
|
sudo install -d -m 0700 "$TRUENAS_STATE_DIR" "$TRUENAS_LOG_DIR"
|
||||||
|
local tmp_env
|
||||||
|
tmp_env=$(mktemp)
|
||||||
|
{
|
||||||
|
echo "PULSE_URL=$PULSE_URL"
|
||||||
|
echo "PULSE_INTERVAL=$INTERVAL"
|
||||||
|
echo "PULSE_LOG_FILE=$LINUX_LOG_FILE"
|
||||||
|
if [[ -n "$PULSE_TOKEN" ]]; then
|
||||||
|
echo "PULSE_TOKEN=$PULSE_TOKEN"
|
||||||
|
fi
|
||||||
|
if [[ -n "$AGENT_ID" ]]; then
|
||||||
|
echo "PULSE_AGENT_ID=$AGENT_ID"
|
||||||
|
fi
|
||||||
|
} > "$tmp_env"
|
||||||
|
sudo install -m 0600 "$tmp_env" "$TRUENAS_ENV_FILE"
|
||||||
|
rm -f "$tmp_env"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_truenas_service_unit() {
|
||||||
|
local exec_start="$AGENT_PATH --url \$PULSE_URL --interval \$PULSE_INTERVAL"
|
||||||
|
if [[ -n "$PULSE_TOKEN" ]]; then
|
||||||
|
exec_start="$exec_start --token \$PULSE_TOKEN"
|
||||||
|
fi
|
||||||
|
if [[ -n "$AGENT_ID" ]]; then
|
||||||
|
exec_start="$exec_start --agent-id \$PULSE_AGENT_ID"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo tee "$TRUENAS_SERVICE_STORAGE" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Pulse Host Agent (TrueNAS SCALE)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
ConditionPathExists=$AGENT_PATH
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
EnvironmentFile=$TRUENAS_ENV_FILE
|
||||||
|
ExecStart=$exec_start
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5s
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
StandardOutput=append:$LINUX_LOG_FILE
|
||||||
|
StandardError=append:$LINUX_LOG_FILE
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
write_truenas_bootstrap_script() {
|
||||||
|
sudo tee "$TRUENAS_BOOTSTRAP_SCRIPT" > /dev/null <<EOF
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
STATE_DIR="$TRUENAS_STATE_DIR"
|
||||||
|
UNIT_SRC="$TRUENAS_SERVICE_STORAGE"
|
||||||
|
UNIT_DST="$TRUENAS_SYSTEMD_LINK"
|
||||||
|
LOG_DIR="$LINUX_LOG_DIR"
|
||||||
|
AGENT_PATH="$AGENT_PATH"
|
||||||
|
|
||||||
|
if [ ! -x "\$AGENT_PATH" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "\$LOG_DIR"
|
||||||
|
ln -sf "\$UNIT_SRC" "\$UNIT_DST"
|
||||||
|
systemctl daemon-reload
|
||||||
|
if systemctl is-enabled pulse-host-agent >/dev/null 2>&1; then
|
||||||
|
systemctl restart pulse-host-agent >/dev/null 2>&1 || true
|
||||||
|
else
|
||||||
|
systemctl enable --now pulse-host-agent >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
sudo chmod 0755 "$TRUENAS_BOOTSTRAP_SCRIPT"
|
||||||
|
}
|
||||||
|
|
||||||
|
register_truenas_init_task() {
|
||||||
|
if ! command -v midclt >/dev/null 2>&1; then
|
||||||
|
log_warn "midclt not found - add a POSTINIT task for $TRUENAS_BOOTSTRAP_SCRIPT manually in the TrueNAS UI."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
log_warn "python3 not found - cannot parse init task state; add the POSTINIT task manually if needed."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local query existing_id payload
|
||||||
|
query='[["script","=","'"$TRUENAS_BOOTSTRAP_SCRIPT"'"]]'
|
||||||
|
local query_output
|
||||||
|
query_output=$(midclt call initshutdownscript.query "$query" 2>/dev/null || true)
|
||||||
|
existing_id=$(printf '%s' "$query_output" | python3 - <<'PY'
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
print(data[0]["id"] if data else "")
|
||||||
|
except Exception:
|
||||||
|
print("")
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
|
||||||
|
payload=$(cat <<EOF
|
||||||
|
{"type":"SCRIPT","script":"$TRUENAS_BOOTSTRAP_SCRIPT","when":"POSTINIT","enabled":true,"timeout":120,"comment":"$TRUENAS_INIT_COMMENT"}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ -n "$existing_id" ]]; then
|
||||||
|
if midclt call initshutdownscript.update "$existing_id" "$payload" >/dev/null 2>&1; then
|
||||||
|
log_info "Updated existing TrueNAS init task (id $existing_id)"
|
||||||
|
else
|
||||||
|
log_warn "Failed to update existing TrueNAS init task (id $existing_id)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if midclt call initshutdownscript.create "$payload" >/dev/null 2>&1; then
|
||||||
|
log_success "Registered TrueNAS init task to restore the service on boot"
|
||||||
|
else
|
||||||
|
log_warn "Failed to register TrueNAS init task; add it manually via System Settings → Advanced → Init/Shutdown Scripts."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_truenas_service() {
|
||||||
|
log_info "Detected TrueNAS SCALE (immutable root). Storing agent under $TRUENAS_STATE_DIR"
|
||||||
|
write_truenas_env_file
|
||||||
|
write_truenas_service_unit
|
||||||
|
write_truenas_bootstrap_script
|
||||||
|
|
||||||
|
sudo ln -sf "$TRUENAS_SERVICE_STORAGE" "$TRUENAS_SYSTEMD_LINK"
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
if sudo systemctl is-enabled pulse-host-agent >/dev/null 2>&1; then
|
||||||
|
sudo systemctl restart pulse-host-agent || true
|
||||||
|
else
|
||||||
|
sudo systemctl enable --now pulse-host-agent || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
register_truenas_init_task
|
||||||
|
SERVICE_MODE="truenas"
|
||||||
|
log_success "TrueNAS SCALE service installed and started"
|
||||||
|
}
|
||||||
|
|
||||||
# Set up service based on platform
|
# Set up service based on platform
|
||||||
if [[ "$PLATFORM" == "linux" ]] && command -v systemctl &> /dev/null; then
|
if [[ "$TRUENAS" == true ]]; then
|
||||||
|
setup_truenas_service
|
||||||
|
elif [[ "$PLATFORM" == "linux" ]] && command -v systemctl &> /dev/null; then
|
||||||
log_info "Setting up systemd service..."
|
log_info "Setting up systemd service..."
|
||||||
|
|
||||||
# Create log directory
|
# Create log directory
|
||||||
|
|
@ -811,7 +996,7 @@ VALIDATION_SUCCESS=false
|
||||||
SERVICE_RUNNING=false
|
SERVICE_RUNNING=false
|
||||||
|
|
||||||
# Check if service is running
|
# Check if service is running
|
||||||
if [[ "$SERVICE_MODE" == "systemd" ]]; then
|
if [[ "$SERVICE_MODE" == "systemd" || "$SERVICE_MODE" == "truenas" ]]; then
|
||||||
SERVICE_STATUS=$(systemctl is-active pulse-host-agent 2>/dev/null || echo "inactive")
|
SERVICE_STATUS=$(systemctl is-active pulse-host-agent 2>/dev/null || echo "inactive")
|
||||||
if [[ "$SERVICE_STATUS" == "active" ]]; then
|
if [[ "$SERVICE_STATUS" == "active" ]]; then
|
||||||
SERVICE_RUNNING=true
|
SERVICE_RUNNING=true
|
||||||
|
|
@ -902,10 +1087,13 @@ else
|
||||||
echo ""
|
echo ""
|
||||||
log_info "Troubleshooting:"
|
log_info "Troubleshooting:"
|
||||||
echo ""
|
echo ""
|
||||||
if [[ "$SERVICE_MODE" == "systemd" ]]; then
|
if [[ "$SERVICE_MODE" == "systemd" || "$SERVICE_MODE" == "truenas" ]]; then
|
||||||
echo " View logs: sudo journalctl -u pulse-host-agent -f"
|
echo " View logs: sudo journalctl -u pulse-host-agent -f"
|
||||||
echo " Check status: sudo systemctl status pulse-host-agent"
|
echo " Check status: sudo systemctl status pulse-host-agent"
|
||||||
echo " Restart: sudo systemctl restart pulse-host-agent"
|
echo " Restart: sudo systemctl restart pulse-host-agent"
|
||||||
|
if [[ "$SERVICE_MODE" == "truenas" ]]; then
|
||||||
|
echo " Persist: Confirm the POSTINIT task named \"$TRUENAS_INIT_COMMENT\" exists in the TrueNAS UI"
|
||||||
|
fi
|
||||||
elif [[ "$SERVICE_MODE" == "launchd" ]]; then
|
elif [[ "$SERVICE_MODE" == "launchd" ]]; then
|
||||||
echo " View logs: tail -f $MACOS_LOG_FILE"
|
echo " View logs: tail -f $MACOS_LOG_FILE"
|
||||||
echo " Check status: launchctl list | grep pulse"
|
echo " Check status: launchctl list | grep pulse"
|
||||||
|
|
@ -930,12 +1118,15 @@ fi
|
||||||
print_footer
|
print_footer
|
||||||
|
|
||||||
log_info "Service Management Commands:"
|
log_info "Service Management Commands:"
|
||||||
if [[ "$SERVICE_MODE" == "systemd" ]]; then
|
if [[ "$SERVICE_MODE" == "systemd" || "$SERVICE_MODE" == "truenas" ]]; then
|
||||||
echo " Start: sudo systemctl start pulse-host-agent"
|
echo " Start: sudo systemctl start pulse-host-agent"
|
||||||
echo " Stop: sudo systemctl stop pulse-host-agent"
|
echo " Stop: sudo systemctl stop pulse-host-agent"
|
||||||
echo " Restart: sudo systemctl restart pulse-host-agent"
|
echo " Restart: sudo systemctl restart pulse-host-agent"
|
||||||
echo " Status: sudo systemctl status pulse-host-agent"
|
echo " Status: sudo systemctl status pulse-host-agent"
|
||||||
echo " Logs: sudo journalctl -u pulse-host-agent -f"
|
echo " Logs: sudo journalctl -u pulse-host-agent -f"
|
||||||
|
if [[ "$SERVICE_MODE" == "truenas" ]]; then
|
||||||
|
echo " Persist: TrueNAS Init/Shutdown task stores $TRUENAS_BOOTSTRAP_SCRIPT as POSTINIT"
|
||||||
|
fi
|
||||||
elif [[ "$SERVICE_MODE" == "launchd" ]]; then
|
elif [[ "$SERVICE_MODE" == "launchd" ]]; then
|
||||||
echo " Start: launchctl load $LAUNCHD_PLIST"
|
echo " Start: launchctl load $LAUNCHD_PLIST"
|
||||||
echo " Stop: launchctl unload $LAUNCHD_PLIST"
|
echo " Stop: launchctl unload $LAUNCHD_PLIST"
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,15 @@ SYSTEMD_SERVICE="/etc/systemd/system/pulse-host-agent.service"
|
||||||
LAUNCHD_PLIST="$HOME/Library/LaunchAgents/com.pulse.host-agent.plist"
|
LAUNCHD_PLIST="$HOME/Library/LaunchAgents/com.pulse.host-agent.plist"
|
||||||
MACOS_LOG_DIR="$HOME/Library/Logs/Pulse"
|
MACOS_LOG_DIR="$HOME/Library/Logs/Pulse"
|
||||||
LINUX_LOG_DIR="/var/log/pulse"
|
LINUX_LOG_DIR="/var/log/pulse"
|
||||||
|
LINUX_LOG_FILE="$LINUX_LOG_DIR/host-agent.log"
|
||||||
|
|
||||||
|
TRUENAS=false
|
||||||
|
TRUENAS_STATE_DIR="/data/pulse-host-agent"
|
||||||
|
TRUENAS_LOG_DIR="$TRUENAS_STATE_DIR/logs"
|
||||||
|
TRUENAS_SERVICE_STORAGE="$TRUENAS_STATE_DIR/pulse-host-agent.service"
|
||||||
|
TRUENAS_BOOTSTRAP_SCRIPT="$TRUENAS_STATE_DIR/bootstrap-pulse-host-agent.sh"
|
||||||
|
TRUENAS_ENV_FILE="$TRUENAS_STATE_DIR/pulse-host-agent.env"
|
||||||
|
TRUENAS_SYSTEMD_LINK="/etc/systemd/system/pulse-host-agent.service"
|
||||||
|
|
||||||
print_header
|
print_header
|
||||||
|
|
||||||
|
|
@ -83,9 +92,63 @@ case "$(uname -s)" in
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
is_truenas_scale() {
|
||||||
|
if [[ -f /etc/truenas-version ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -f /etc/version ]] && grep -qi "truenas" /etc/version 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -d /data/ix-applications ]] || [[ -d /etc/ix-apps.d ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$PLATFORM" == "linux" ]] && is_truenas_scale; then
|
||||||
|
TRUENAS=true
|
||||||
|
AGENT_PATH="$TRUENAS_STATE_DIR/pulse-host-agent"
|
||||||
|
SYSTEMD_SERVICE="$TRUENAS_SYSTEMD_LINK"
|
||||||
|
LINUX_LOG_DIR="$TRUENAS_LOG_DIR"
|
||||||
|
LINUX_LOG_FILE="$LINUX_LOG_DIR/host-agent.log"
|
||||||
|
fi
|
||||||
|
|
||||||
log_info "Detected platform: $PLATFORM"
|
log_info "Detected platform: $PLATFORM"
|
||||||
|
if [[ "$TRUENAS" == true ]]; then
|
||||||
|
log_info "TrueNAS SCALE detected (immutable root). Using $TRUENAS_STATE_DIR for cleanup."
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
remove_truenas_init_task() {
|
||||||
|
if [[ "$TRUENAS" != true ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! command -v midclt >/dev/null 2>&1 || ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
log_warn "midclt/python3 not available - remove the POSTINIT task for $TRUENAS_BOOTSTRAP_SCRIPT manually if it exists."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local query_output task_id
|
||||||
|
query_output=$(midclt call initshutdownscript.query '[["script","=","'"$TRUENAS_BOOTSTRAP_SCRIPT"'"]]' 2>/dev/null || true)
|
||||||
|
task_id=$(printf '%s' "$query_output" | python3 - <<'PY'
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
print(data[0]["id"] if data else "")
|
||||||
|
except Exception:
|
||||||
|
print("")
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ -n "$task_id" ]]; then
|
||||||
|
if midclt call initshutdownscript.delete "$task_id" >/dev/null 2>&1; then
|
||||||
|
log_success "Removed TrueNAS Init/Shutdown task (id $task_id)"
|
||||||
|
else
|
||||||
|
log_warn "Failed to remove TrueNAS Init/Shutdown task id $task_id; remove it manually in the TrueNAS UI."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Stop and remove systemd service (Linux)
|
# Stop and remove systemd service (Linux)
|
||||||
if [[ "$PLATFORM" == "linux" ]]; then
|
if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
if [[ -f "$SYSTEMD_SERVICE" ]] && command -v systemctl &> /dev/null; then
|
if [[ -f "$SYSTEMD_SERVICE" ]] && command -v systemctl &> /dev/null; then
|
||||||
|
|
@ -115,12 +178,35 @@ if [[ "$PLATFORM" == "linux" ]]; then
|
||||||
log_success "Processes terminated"
|
log_success "Processes terminated"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "$TRUENAS" == true ]]; then
|
||||||
|
remove_truenas_init_task
|
||||||
|
|
||||||
|
if [[ -f "$TRUENAS_SERVICE_STORAGE" ]]; then
|
||||||
|
log_info "Removing stored TrueNAS service unit..."
|
||||||
|
sudo rm -f "$TRUENAS_SERVICE_STORAGE"
|
||||||
|
fi
|
||||||
|
if [[ -f "$TRUENAS_BOOTSTRAP_SCRIPT" ]]; then
|
||||||
|
log_info "Removing TrueNAS bootstrap script..."
|
||||||
|
sudo rm -f "$TRUENAS_BOOTSTRAP_SCRIPT"
|
||||||
|
fi
|
||||||
|
if [[ -f "$TRUENAS_ENV_FILE" ]]; then
|
||||||
|
log_info "Removing TrueNAS environment file..."
|
||||||
|
sudo rm -f "$TRUENAS_ENV_FILE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Remove log directory
|
# Remove log directory
|
||||||
if [[ -d "$LINUX_LOG_DIR" ]]; then
|
if [[ -d "$LINUX_LOG_DIR" ]]; then
|
||||||
log_info "Removing log directory..."
|
log_info "Removing log directory..."
|
||||||
sudo rm -rf "$LINUX_LOG_DIR"
|
sudo rm -rf "$LINUX_LOG_DIR"
|
||||||
log_success "Log directory removed: $LINUX_LOG_DIR"
|
log_success "Log directory removed: $LINUX_LOG_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "$TRUENAS" == true ]] && [[ -d "$TRUENAS_STATE_DIR" ]]; then
|
||||||
|
log_info "Removing persistent state directory..."
|
||||||
|
sudo rm -rf "$TRUENAS_STATE_DIR"
|
||||||
|
log_success "Removed $TRUENAS_STATE_DIR"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Stop and remove launchd service (macOS)
|
# Stop and remove launchd service (macOS)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue