Ctrl K
ComunidadGuías…

install.sh

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/bin/sh

# Overridden variables from Go template: {{.Overrides}}

require_tenant() {
  [ -z "$TENANT_ID" ] && {
    echo "ERROR: TENANT_ID is required for this installation method."
    exit 1
  }
}

require_namespace_credential() {
  [ -z "$TENANT_ID" ] && [ -z "$PROVISIONING_KEY" ] && {
    echo "ERROR: TENANT_ID or PROVISIONING_KEY is required for this installation method."
    exit 1
  }
}

install_agent_wrapper() {
  _RUNTIME="$1"

  _FSUDO=""
  [ "$(id -u)" -ne 0 ] && _FSUDO="sudo"
  WRAPPER_PATH="${INSTALL_DIR:-/usr/local/bin}/shellhub-agent"

  if ! cat <<EOF | $_FSUDO tee "$WRAPPER_PATH" > /dev/null
#!/bin/sh
_containers=\$($_RUNTIME ps --filter label=shellhub.role=agent --format '{{.Names}}')
if [ -z "\$_containers" ]; then
  echo "shellhub-agent: no running agent container found" >&2
  echo "Start the agent or run 'install.sh uninstall' to remove this wrapper" >&2
  exit 1
fi
if [ "\$(printf '%s\n' "\$_containers" | wc -l)" -gt 1 ]; then
  echo "shellhub-agent: multiple agent containers found, stop all but one:" >&2
  printf '%s\n' "\$_containers" | sed 's/^/  /' >&2
  exit 1
fi

if [ "\$1" = "login" ] && command -v xdg-open > /dev/null 2>&1; then
  _STATUS_FILE=\$(mktemp)
  trap 'rm -f "\$_STATUS_FILE"' EXIT

  { $_RUNTIME exec "\$_containers" agent "\$@" 2>&1; echo \$? > "\$_STATUS_FILE"; } | while IFS= read -r _LINE; do
    printf '%s\n' "\$_LINE"
    case "\$_LINE" in
    *"/accept-device?code="*)
      _URL=\$(printf '%s' "\$_LINE" | tr -d ' ')
      { xdg-open "\$_URL" > /dev/null 2>&1 && printf '%s\n' "(opened in your browser)"; } &
      ;;
    esac
  done

  read -r _CODE < "\$_STATUS_FILE" || _CODE=1
  exit "\${_CODE:-1}"
fi

exec $_RUNTIME exec "\$_containers" agent "\$@"
EOF
  then
    echo "❌ Failed to install shellhub-agent wrapper at $WRAPPER_PATH."
    exit 1
  fi

  $_FSUDO chmod +x "$WRAPPER_PATH"
  echo "✅ Installed shellhub-agent wrapper at $WRAPPER_PATH."
}

tenant_file() {
  _KEY="${PRIVATE_KEY:-/etc/shellhub.key}"

  echo "${_KEY#/host}.tenant"
}

persisted_tenant() {
  _TENANT_FILE=$(tenant_file)

  [ -e "$_TENANT_FILE" ] || return 0

  _TSUDO=""
  [ "$(id -u)" -ne 0 ] && _TSUDO="sudo"

  $_TSUDO head -n 1 "$_TENANT_FILE" 2>/dev/null | tr -d ' \t\r\n'
}

enrollment_summary() {
  if [ -n "$PERSISTED_TENANT" ]; then
    echo "tenant $PERSISTED_TENANT (persisted by a previous enrollment)"
  elif [ -n "$PROVISIONING_KEY" ]; then
    echo "provisioning key"
  elif [ -n "$TENANT_ID" ]; then
    echo "tenant $TENANT_ID"
  else
    echo "none — enroll with 'shellhub-agent login'"
  fi
}

refusal_reason() {
  _LOG_LINE="$1"

  case "$_LOG_LINE" in
  *'error="'*) ;;
  *)
    echo "$_LOG_LINE"

    return 0
    ;;
  esac

  echo "$_LOG_LINE" | sed -e 's/\\"/@@Q@@/g' -e 's/.*error="\([^"]*\)".*/\1/' -e 's/@@Q@@/"/g'
}

observe_enrollment() {
  _LOG_CMD="$1"

  if [ -z "$_LOG_CMD" ]; then
    echo "ℹ️ This install method does not expose the agent's output to the installer."
    echo "   Check the console to confirm the device enrolled, and accept it there if it is pending."

    return 0
  fi

  _OBSERVED=""
  _WAITED=0

  while [ "$_WAITED" -lt "${ENROLLMENT_OBSERVE_SECONDS:-20}" ]; do
    _OBSERVED=$($_LOG_CMD 2>&1)

    case "$_OBSERVED" in
    *"Listening for connections"*)
      _LISTENING=$(echo "$_OBSERVED" | grep "Listening for connections" | tail -n 1)

      case "$_LISTENING" in
      *" status=accepted"*)
        echo "✅ The device is accepted and the agent is listening for connections."
        ;;
      *" status=pending"*)
        echo "✅ The agent is listening for connections. The device is pending: accept it in the console."
        ;;
      *" status=rejected"*)
        echo "❌ The agent connected, but the device is rejected."

        if [ -n "$PROVISIONING_KEY" ]; then
          echo "   The provisioning key's allowlist or webhook refused it, or a member rejected it in the console."
        else
          echo "   A member rejected it in the console. Accept it there to enroll it."
        fi

        return 1
        ;;
      *)
        echo "✅ The agent connected to the server and is listening for connections."
        echo "   Check the console to see whether the device is accepted or still pending."
        ;;
      esac

      return 0
      ;;
    *"Failed to authorize the device"*)
      _REFUSAL=$(echo "$_OBSERVED" | grep "Failed to authorize the device" | tail -n 1)

      echo "❌ The server refused this device:"
      echo "   $(refusal_reason "$_REFUSAL")"

      return 1
      ;;
    *"Failed to load the configuration"*)
      _INVALID=$(echo "$_OBSERVED" | sed -n -e 's/.*msg="\(SHELLHUB_[^"]*\)".*/\1/p' -e 's/.*msg="\(\/[^"]*\)".*/\1/p' | sed 's/^\/host\//\//' | awk '!seen[$0]++')

      echo "❌ The agent refused its own configuration:"

      if [ -n "$_INVALID" ]; then
        echo "$_INVALID" | sed 's/^/   /'
      else
        _REFUSAL=$(echo "$_OBSERVED" | grep "Failed to load the configuration" | tail -n 1)

        echo "   $(refusal_reason "$_REFUSAL")"
      fi

      return 1
      ;;
    esac

    sleep 1
    _WAITED=$((_WAITED + 1))
  done

  case "$_OBSERVED" in
  *"Cannot authorize the device"*)
    _REFUSAL=$(echo "$_OBSERVED" | grep "Cannot authorize the device" | tail -n 1)

    echo "⚠️ The server is still refusing this device, and the agent is still retrying:"
    echo "   $(refusal_reason "$_REFUSAL")"

    return 1
    ;;
  *)
    echo "⚠️ The agent has not enrolled yet. Inspect it with:"
    echo "   $_LOG_CMD"

    return 0
    ;;
  esac
}

enroll_agent_interactively() {
  _AGENT_CMD="$1"
  _WAIT_KEY="$2"
  _AGENT_LOG="$3"

  if [ -n "$PERSISTED_TENANT" ] || [ -n "$PROVISIONING_KEY" ] || [ -n "$TENANT_ID" ]; then
    echo ""

    if [ -n "$PERSISTED_TENANT" ]; then
      echo "The device will enroll into tenant $PERSISTED_TENANT, remembered at $(tenant_file)."
      echo "Delete that file to enroll it somewhere else."
    fi

    observe_enrollment "$_AGENT_LOG"

    return "$?"
  fi

  _i=0
  while [ "$_i" -lt 30 ] && [ ! -f "$_WAIT_KEY" ]; do
    sleep 1
    _i=$((_i + 1))
  done

  echo ""
  trap ':' INT
  $_AGENT_CMD login
  _LOGIN_STATUS=$?
  trap - INT

  case "$_LOGIN_STATUS" in
  0 | 130) return 0 ;;
  *) return 1 ;;
  esac
}

check_podman_boot_restart() {
  [ "${SKIP_BOOT_RESTART_CHECK}" = "1" ] && {
    printf "⚠️  SKIP_BOOT_RESTART_CHECK is set; skipping podman-restart.service check.\n"
    printf "   The agent container will NOT auto-start on boot unless you manage it yourself\n"
    printf "   (Quadlet, an orchestrator, etc.).\n"
    return 0
  }

  if ! command -v systemctl >/dev/null 2>&1; then
    printf "❌ ERROR: systemctl not found. Podman needs podman-restart.service to start\n"
    printf "   the agent container on boot, which requires systemd.\n"
    printf "   Set SKIP_BOOT_RESTART_CHECK=1 to install anyway (if you manage the container\n"
    printf "   lifecycle yourself), or use INSTALL_METHOD=docker or standalone.\n"
    exit 1
  fi

  if ! systemctl is-enabled podman-restart.service >/dev/null 2>&1; then
    printf "❌ ERROR: podman-restart.service is not enabled. Without it the agent container\n"
    printf "   will not start on boot.\n\n"
    printf "   Enable it with:\n\n"
    printf "     sudo systemctl enable podman-restart.service\n\n"
    printf "   Then re-run this script. Set SKIP_BOOT_RESTART_CHECK=1 to install anyway\n"
    printf "   (if you manage the container lifecycle yourself).\n"
    exit 1
  fi
}

podman_install() {
  check_podman_boot_restart

  [ -n "${KEEPALIVE_INTERVAL}" ] && ARGS="$ARGS -e SHELLHUB_KEEPALIVE_INTERVAL=$KEEPALIVE_INTERVAL"
  [ -n "${PREFERRED_HOSTNAME}" ] && ARGS="$ARGS -e SHELLHUB_PREFERRED_HOSTNAME=$PREFERRED_HOSTNAME"
  [ -n "${PREFERRED_IDENTITY}" ] && ARGS="$ARGS -e SHELLHUB_PREFERRED_IDENTITY=$PREFERRED_IDENTITY"
  [ -n "${PROVISIONING_KEY}" ] && ARGS="$ARGS -e SHELLHUB_PROVISIONING_KEY=$PROVISIONING_KEY"
  [ -n "${TENANT_ID}" ] && ARGS="$ARGS -e SHELLHUB_TENANT_ID=$TENANT_ID"

  if [ -n "$AGENT_IMAGE_OVERRIDDEN" ]; then
    echo "📦 Using image $AGENT_IMAGE (skipping pull)..."
  else
    require_release_version

    echo "📥 Downloading ShellHub container image..."

    {
      $SUDO podman pull -q "$AGENT_IMAGE"
    } || {
      echo "❌ Failed to download shellhub container image."
      exit 1
    }
  fi

  MODE=""
  DEFAULT_CONTAINER_NAME="shellhub"

  case "$1" in
  "") ;;
  "agent")
    shift 1
    ;;
  "connector")
    require_namespace_credential
    MODE="connector"
    DEFAULT_CONTAINER_NAME="shellhub-connector"
    ARGS="$ARGS -e SHELLHUB_PRIVATE_KEYS=${PRIVATE_KEYS:-/host/etc/shellhub/connector/keys}"
    ARGS="$ARGS -e SHELLHUB_CONNECTOR_LABEL=${CONNECTOR_LABEL}"

    echo "🚀 Starting ShellHub container in Docker Connector mode..."
    shift 1
    ;;
  *)
    echo "❌ Invalid mode: $1"
    exit 1
    ;;
  esac

  if [ -z "$MODE" ]; then
    ARGS="$ARGS -e SHELLHUB_PRIVATE_KEY=${PRIVATE_KEY:-/host/etc/shellhub.key}"

    echo "🚀 Starting ShellHub container in Agent mode..."
  fi

  CONTAINER_NAME="${CONTAINER_NAME:-$DEFAULT_CONTAINER_NAME}"

  LABEL_ARGS=""
  [ -z "$MODE" ] && LABEL_ARGS="--label shellhub.role=agent"

  $SUDO podman run -d \
    --name=$CONTAINER_NAME \
    --replace \
    --restart=unless-stopped \
    --privileged \
    --pid=host \
    --security-opt label=disable \
    --network host \
    -v /:/host \
    -v /dev:/dev \
    -v /var/run/podman/podman.sock:/var/run/docker.sock \
    -v /proc:/proc \
    -v /var/run:/var/run \
    -v /var/log:/var/log \
    -v /tmp:/tmp \
    -e SHELLHUB_SERVER_ADDRESS=$SERVER_ADDRESS \
    $ARGS \
    $LABEL_ARGS \
    "$AGENT_IMAGE" \
    $MODE || {
    echo "❌ Failed to start the ShellHub container."
    exit 1
  }

  if [ -z "$MODE" ]; then
    install_agent_wrapper "podman"
    _CKEY="${PRIVATE_KEY:-/host/etc/shellhub.key}"
    enroll_agent_interactively "$SUDO $WRAPPER_PATH" "${_CKEY#/host}" "$SUDO podman logs --tail 50 $CONTAINER_NAME"
  fi
}

docker_install() {
  [ -n "${KEEPALIVE_INTERVAL}" ] && ARGS="$ARGS -e SHELLHUB_KEEPALIVE_INTERVAL=$KEEPALIVE_INTERVAL"
  [ -n "${PREFERRED_HOSTNAME}" ] && ARGS="$ARGS -e SHELLHUB_PREFERRED_HOSTNAME=$PREFERRED_HOSTNAME"
  [ -n "${PREFERRED_IDENTITY}" ] && ARGS="$ARGS -e SHELLHUB_PREFERRED_IDENTITY=$PREFERRED_IDENTITY"
  [ -n "${PROVISIONING_KEY}" ] && ARGS="$ARGS -e SHELLHUB_PROVISIONING_KEY=$PROVISIONING_KEY"
  [ -n "${TENANT_ID}" ] && ARGS="$ARGS -e SHELLHUB_TENANT_ID=$TENANT_ID"

  if [ -n "$AGENT_IMAGE_OVERRIDDEN" ]; then
    echo "📦 Using image $AGENT_IMAGE (skipping pull)..."
  else
    require_release_version

    echo "📥 Downloading ShellHub container image..."

    {
      $SUDO docker pull -q "$AGENT_IMAGE"
    } || {
      echo "❌ Failed to download shellhub container image."
      exit 1
    }
  fi

  MODE=""
  DEFAULT_CONTAINER_NAME="shellhub"

  case "$1" in
  "") ;;
  "agent")
    shift 1
    ;;
  "connector")
    require_namespace_credential
    MODE="connector"
    DEFAULT_CONTAINER_NAME="shellhub-connector"
    ARGS="$ARGS -e SHELLHUB_PRIVATE_KEYS=${PRIVATE_KEYS:-/host/etc/shellhub/connector/keys}"
    ARGS="$ARGS -e SHELLHUB_CONNECTOR_LABEL=${CONNECTOR_LABEL}"

    echo "🚀 Starting ShellHub container in Docker Connector mode..."
    shift 1
    ;;
  *)
    echo "❌ Invalid mode: $1"
    exit 1
    ;;
  esac

  if [ -z "$MODE" ]; then
    ARGS="$ARGS -e SHELLHUB_PRIVATE_KEY=${PRIVATE_KEY:-/host/etc/shellhub.key}"

    echo "🚀 Starting ShellHub container in Agent mode..."
  fi

  CONTAINER_NAME="${CONTAINER_NAME:-$DEFAULT_CONTAINER_NAME}"

  LABEL_ARGS=""
  [ -z "$MODE" ] && LABEL_ARGS="--label shellhub.role=agent"

  $SUDO docker rm -f "$CONTAINER_NAME" 2>/dev/null || true

  $SUDO docker run -d \
    --name=$CONTAINER_NAME \
    --restart=unless-stopped \
    --privileged \
    --net=host \
    --pid=host \
    -v /:/host \
    -v /dev:/dev \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /etc/resolv.conf:/etc/resolv.conf \
    -v /var/run:/var/run \
    -v /var/log:/var/log \
    -v /tmp:/tmp \
    -e SHELLHUB_SERVER_ADDRESS=$SERVER_ADDRESS \
    $ARGS \
    $LABEL_ARGS \
    "$AGENT_IMAGE" \
    $MODE || {
    echo "❌ Failed to start the ShellHub container."
    exit 1
  }

  if [ -z "$MODE" ]; then
    install_agent_wrapper "docker"
    _CKEY="${PRIVATE_KEY:-/host/etc/shellhub.key}"
    enroll_agent_interactively "$SUDO $WRAPPER_PATH" "${_CKEY#/host}" "$SUDO docker logs --tail 50 $CONTAINER_NAME"
  fi
}

snap_install() {
  require_tenant

  if [ -n "$PROVISIONING_KEY" ]; then
    echo "❌ ERROR: PROVISIONING_KEY is not supported by the snap install method."
    echo "Use the docker, podman or standalone method to enroll with a provisioning key."
    exit 1
  fi

  if ! type snap >/dev/null 2>&1; then
    echo "❌ Snap is not installed or not supported on this system."
    exit 1
  fi

  echo "📥 Downloading ShellHub snap package..."

  {
    sudo snap install --classic --channel=latest/stable shellhub
  } || {
    echo "❌ Failed to download and install ShellHub snap package."
    exit 1
  }

  echo "🚀 Starting ShellHub snap service..."

  {
    if [ -n "${PREFERRED_HOSTNAME}" ]; then
      sudo snap set shellhub preferred-hostname="${PREFERRED_HOSTNAME}"
    fi

    if [ -n "${PREFERRED_IDENTITY}" ]; then
      sudo snap set shellhub preferred-identity="${PREFERRED_IDENTITY}"
    fi

    if [ -n "${KEEPALIVE_INTERVAL}" ]; then
      sudo snap set shellhub keepalive-interval="${KEEPALIVE_INTERVAL}"
    fi

    sudo snap set shellhub server-address="$SERVER_ADDRESS"
    sudo snap set shellhub tenant-id="$TENANT_ID"
    sudo snap set shellhub private-key="${PRIVATE_KEY:-/etc/shellhub.key}"

    sudo snap start shellhub
  } || {
    echo "❌ Failed to start ShellHub snap service."
    exit 1
  }
}

standalone_install() {
  if [ "$(id -u)" -ne 0 ]; then
    printf "⚠️ NOTE: This install method requires root privileges.\n"
    SUDO="sudo"
  fi

  INSTALL_BIN="$INSTALL_DIR/shellhub-agent"
  $SUDO mkdir -p "$INSTALL_DIR"

  if [ -n "${AGENT_BINARY}" ]; then
    echo "📂 Using local binary: $AGENT_BINARY"
    { $SUDO cp "$AGENT_BINARY" "$INSTALL_BIN.new" && $SUDO chmod 755 "$INSTALL_BIN.new"; } ||
      abort_staged_install "❌ Could not copy $AGENT_BINARY into $INSTALL_DIR."
  else
    require_release_version

    echo "📥 Downloading ShellHub agent binary..."

    RELEASE_URL="https://github.com/shellhub-io/shellhub/releases/download/$AGENT_VERSION"
    RELEASE_ASSET="shellhub-agent-linux-$BINARY_ARCH.gz"

    {
      download "$RELEASE_URL/$RELEASE_ASSET" "$TMP_DIR/shellhub-agent.gz"
    } || { rm -rf "$TMP_DIR" && echo "❌ Failed to download agent binary." && exit 1; }

    echo "🔐 Verifying agent binary checksum..."

    download "$RELEASE_URL/checksums.txt" "$TMP_DIR/checksums.txt" || {
      rm -rf "$TMP_DIR"
      echo "❌ ERROR: Could not download the checksums published with release $AGENT_VERSION."
      echo "Releases made before checksums were published have none. Set AGENT_VERSION to a newer release, or set AGENT_BINARY to a binary you have verified."
      exit 1
    }

    verify_checksum "$TMP_DIR/shellhub-agent.gz" "$RELEASE_ASSET" "$TMP_DIR/checksums.txt" || {
      rm -rf "$TMP_DIR"
      echo "❌ ERROR: The agent binary does not match the checksum published with release $AGENT_VERSION."
      exit 1
    }

    echo "📂 Extracting binary..."

    {
      gzip -d "$TMP_DIR/shellhub-agent.gz" &&
        chmod 755 "$TMP_DIR/shellhub-agent" &&
        $SUDO mv "$TMP_DIR/shellhub-agent" "$INSTALL_BIN.new"
    } || abort_staged_install "❌ Failed to extract the agent binary."
  fi

  promote_staged_agent_binary

  echo "🚀 Installing ShellHub system service..."

  INSTALL_ARGS="--server-address=$SERVER_ADDRESS"
  [ -n "${TENANT_ID}" ] && INSTALL_ARGS="$INSTALL_ARGS --tenant-id=$TENANT_ID"
  [ -n "${PROVISIONING_KEY}" ] && INSTALL_ARGS="$INSTALL_ARGS --provisioning-key=$PROVISIONING_KEY"
  [ -n "${PREFERRED_HOSTNAME}" ] && INSTALL_ARGS="$INSTALL_ARGS --preferred-hostname=$PREFERRED_HOSTNAME"
  [ -n "${PREFERRED_IDENTITY}" ] && INSTALL_ARGS="$INSTALL_ARGS --preferred-identity=$PREFERRED_IDENTITY"
  [ -n "${KEEPALIVE_INTERVAL}" ] && INSTALL_ARGS="$INSTALL_ARGS --keepalive-interval=$KEEPALIVE_INTERVAL"

  replace_agent_service

  echo "✅ ShellHub agent installed and started."

  AGENT_LOG_CMD=""
  command -v journalctl >/dev/null 2>&1 && AGENT_LOG_CMD="$SUDO journalctl -u shellhub-agent --no-pager -n 50 --since @$_SERVICE_STARTED"

  enroll_agent_interactively "$SUDO $INSTALL_BIN" "${PRIVATE_KEY:-/etc/shellhub.key}" "$AGENT_LOG_CMD"
  _ENROLLMENT_STATUS=$?

  rm -rf "$TMP_DIR"

  return "$_ENROLLMENT_STATUS"
}

promote_staged_agent_binary() {
  $SUDO "$INSTALL_BIN.new" install --help >/dev/null 2>&1 ||
    abort_staged_install "❌ The agent binary cannot run its installer on this system. Check BINARY_ARCH, or that AGENT_BINARY was built with the installer tag."

  $SUDO mv -f "$INSTALL_BIN.new" "$INSTALL_BIN" ||
    abort_staged_install "❌ Could not replace $INSTALL_BIN."
}

abort_staged_install() {
  echo "$1"
  $SUDO rm -f "$INSTALL_BIN.new"
  rm -rf "$TMP_DIR"
  exit 1
}

replace_agent_service() {
  _AGENT_WAS_RUNNING=""
  $SUDO systemctl is-active --quiet shellhub-agent >/dev/null 2>&1 && _AGENT_WAS_RUNNING=1

  $SUDO systemctl stop shellhub-agent >/dev/null 2>&1
  sleep 1
  _SERVICE_STARTED=$(date +%s)

  $SUDO "$INSTALL_BIN" install $INSTALL_ARGS || {
    echo "❌ Failed to install ShellHub agent service."

    if [ -n "$_AGENT_WAS_RUNNING" ]; then
      $SUDO systemctl enable --now shellhub-agent >/dev/null 2>&1
    else
      $SUDO rm -f "$INSTALL_BIN"
    fi

    rm -rf "$TMP_DIR"
    exit 1
  }
}

report_files_left_behind() {
  echo "ℹ️ The private key file was left in place. Remove it manually if no longer needed."

  [ -e "$(tenant_file)" ] || return 0

  echo "ℹ️ The namespace this device enrolled into is remembered in $(tenant_file)."
  echo "   Remove it too, or a reinstall will enroll into the same namespace."
}

remove_agent_container() {
  _RUNTIME="$1"

  _CONTAINER=$($SUDO $_RUNTIME ps -a -q -f "name=^${CONTAINER_NAME}$") || {
    echo "❌ Failed to look up container '$CONTAINER_NAME'."
    exit 1
  }

  if [ -z "$_CONTAINER" ]; then
    echo "⚠️ Container '$CONTAINER_NAME' not found (may already be removed)."
  else
    echo "🗑️ Stopping and removing ShellHub container..."
    $SUDO $_RUNTIME rm -f "$CONTAINER_NAME" >/dev/null || {
      echo "❌ Failed to remove container '$CONTAINER_NAME'."
      exit 1
    }
  fi
}

docker_uninstall() {
  _FSUDO=""
  [ "$(id -u)" -ne 0 ] && _FSUDO="sudo"

  SUDO="${SUDO:-$_FSUDO}"

  CONTAINER_NAME="${CONTAINER_NAME:-shellhub}"

  remove_agent_container docker

  WRAPPER_PATH="${INSTALL_DIR:-/usr/local/bin}/shellhub-agent"
  if [ -f "$WRAPPER_PATH" ]; then
    echo "🗑️ Removing shellhub-agent wrapper..."
    $_FSUDO rm -f "$WRAPPER_PATH"
  fi

  echo "✅ ShellHub agent uninstalled."
  report_files_left_behind
}

podman_uninstall() {
  _FSUDO=""
  [ "$(id -u)" -ne 0 ] && _FSUDO="sudo"

  SUDO="${SUDO:-$_FSUDO}"

  CONTAINER_NAME="${CONTAINER_NAME:-shellhub}"

  remove_agent_container podman

  WRAPPER_PATH="${INSTALL_DIR:-/usr/local/bin}/shellhub-agent"
  if [ -f "$WRAPPER_PATH" ]; then
    echo "🗑️ Removing shellhub-agent wrapper..."
    $_FSUDO rm -f "$WRAPPER_PATH"
  fi

  echo "✅ ShellHub agent uninstalled."
  report_files_left_behind
}

standalone_uninstall() {
  if [ "$(id -u)" -ne 0 ]; then
    printf "⚠️ NOTE: This operation requires root privileges.\n"
    SUDO="sudo"
  fi

  INSTALL_BIN="${INSTALL_DIR:-/usr/local/bin}/shellhub-agent"

  if [ ! -f "$INSTALL_BIN" ]; then
    echo "❌ ShellHub agent binary not found at $INSTALL_BIN."
    exit 1
  fi

  echo "🗑️ Stopping and removing ShellHub agent service..."

  $SUDO "$INSTALL_BIN" uninstall || {
    echo "⚠️ Service uninstall reported an error (it may already be removed)."
  }

  echo "🗑️ Removing ShellHub agent binary..."
  $SUDO rm -f "$INSTALL_BIN"

  echo "✅ ShellHub agent uninstalled."
  report_files_left_behind
}

wsl_install() {
  if ! systemctl show-environment >/dev/null 2>&1; then
    printf "❌ ERROR: This install method requires systemd to be enabled.\n"
    printf "Please refer to the following link for instructions on how to enable systemd:\n"
    printf "https://learn.microsoft.com/en-us/windows/wsl/wsl-config#systemd-support\n"
    printf "Once systemd is enabled, run this script again to complete the installation.\n"
    exit 1
  fi

  if [ "$(wslinfo --networking-mode)" != "mirrored" ]; then
    printf "❌ ERROR: WSL networking mode must be set to mirrored.\n"
    printf "Please refer to the following link for instructions on how to set the networking mode:\n"
    printf "https://learn.microsoft.com/en-us/windows/wsl/networking#mirrored-mode-networking\n"
    printf "Once the networking mode is set to mirrored, run this script again to complete the installation.\n"
    exit 1
  fi

  standalone_install
}

download() {
  _DOWNLOAD_URL=$1
  _DOWNLOAD_OUTPUT=$2

  if type curl >/dev/null 2>&1; then
    curl -fsSL "$_DOWNLOAD_URL" --output "$_DOWNLOAD_OUTPUT"
  elif type wget >/dev/null 2>&1; then
    wget -q -O "$_DOWNLOAD_OUTPUT" "$_DOWNLOAD_URL"
  fi
}

http_get() {
  _HTTP_GET_URL=$1

  if type curl >/dev/null 2>&1; then
    curl -fsS "$_HTTP_GET_URL"
  elif type wget >/dev/null 2>&1; then
    wget -q -O - "$_HTTP_GET_URL"
  fi
}

verify_checksum() {
  _VERIFY_CHECKSUM_FILE=$1
  _VERIFY_CHECKSUM_NAME=$2
  _VERIFY_CHECKSUM_LIST=$3

  _VERIFY_CHECKSUM_EXPECTED=$(awk -v name="$_VERIFY_CHECKSUM_NAME" '$2 == name { print $1 }' "$_VERIFY_CHECKSUM_LIST")
  _VERIFY_CHECKSUM_ACTUAL=$(sha256sum "$_VERIFY_CHECKSUM_FILE" | cut -d ' ' -f 1)

  [ -n "$_VERIFY_CHECKSUM_EXPECTED" ] && [ "$_VERIFY_CHECKSUM_EXPECTED" = "$_VERIFY_CHECKSUM_ACTUAL" ]
}

require_release_version() {
  case "$AGENT_VERSION" in
  *[!0-9A-Za-z.-]*) ;;
  *)
    printf '%s\n' "$AGENT_VERSION" | grep -Eqx 'latest|v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?' && return 0
    ;;
  esac

  if [ -z "$AGENT_VERSION" ]; then
    echo "❌ ERROR: $SERVER_ADDRESS did not report an agent version."
    echo "Set AGENT_VERSION to a release tag (such as v0.27.0), or make this system trust the certificate of $SERVER_ADDRESS."
  else
    echo "❌ ERROR: The agent version is not a ShellHub release tag (such as v0.27.0)."
    echo "Set AGENT_VERSION to a release tag."
  fi
  exit 1
}

detect_install_method() {
  if [ -z "$INSTALL_METHOD" ] && type docker >/dev/null 2>&1; then
    echo "🔍 Checking if Docker is available and accessible in rootful mode..."

    export DOCKER_HOST="${DOCKER_HOST:-unix:///var/run/docker.sock}"

    for prefix in "" "sudo"; do
      if $prefix docker info >/dev/null 2>&1; then
        SUDO=$prefix
        INSTALL_METHOD="docker"
        break
      fi
    done

    [ -z "$INSTALL_METHOD" ] && echo "ℹ️ Docker is not accessible in rootful mode."
  fi

  if [ -z "$INSTALL_METHOD" ] && type podman >/dev/null 2>&1; then
    echo "🔍 Checking if Podman is available and accessible in rootful mode..."

    export CONTAINER_HOST="${CONTAINER_HOST:-unix:///var/run/podman/podman.sock}"

    for prefix in "" "sudo"; do
      if $prefix podman info >/dev/null 2>&1; then
        SUDO=$prefix
        INSTALL_METHOD="podman"
        break
      fi
    done

    [ -z "$INSTALL_METHOD" ] && echo "ℹ️ Podman is not accessible in rootful mode."
  fi

  CONTAINER_RUNTIME_METHOD="$INSTALL_METHOD"

  if [ -z "$INSTALL_METHOD" ] && type snap >/dev/null 2>&1; then
    echo "🔍 Detected Snap package manager..."
    INSTALL_METHOD="snap"
  fi

  if grep -qi Microsoft "${PROC_VERSION:-/proc/version}"; then
    echo "🔍 Detected WSL environment..."

    case "$(awk '{ print $3 }' "${PROC_VERSION:-/proc/version}")" in
    4.4.0-*-Microsoft|3.4.0-Microsoft)
      echo "❌ ERROR: WSL version 2 is required to run ShellHub."
      exit 1
      ;;
    esac

    if grep -qi 'NAME="Ubuntu"' "${OS_RELEASE:-/etc/os-release}"; then
      INSTALL_METHOD="wsl"
    else
      echo "❌ Error: Only Ubuntu is supported in WSL."
      exit 1
    fi
  fi

  [ -z "$INSTALL_METHOD" ] && INSTALL_METHOD="standalone"

  return 0
}

recommend_container_runtime() {
  [ -z "$CONTAINER_RUNTIME_METHOD" ] || return 0

  echo
  echo "⚠️  NOTE: No recommended installation method was detected."
  echo "⚠️  For best performance, easier updates, and better isolation, it is strongly recommended to use Docker or Podman."
  echo "ℹ️  The installer will proceed with an alternative method (Snap, Standalone, or WSL), but these may have limitations."
  echo
}

uninstall_agent() {
  case "$INSTALL_METHOD" in
  standalone|wsl)
    echo "🗑️ Uninstalling ShellHub using standalone method..."
    standalone_uninstall
    ;;
  docker)
    echo "🐳 Uninstalling ShellHub using docker method..."
    docker_uninstall
    ;;
  podman)
    echo "🐳 Uninstalling ShellHub using podman method..."
    podman_uninstall
    ;;
  *)
    echo "❌ Uninstall is not yet supported for '$INSTALL_METHOD' install method."
    exit 1
    ;;
  esac
}

install_agent() {
  case "$INSTALL_METHOD" in
  podman)
    echo "🐳 Installing ShellHub using podman method..."
    podman_install "$@"
    ;;
  docker)
    echo "🐳 Installing ShellHub using docker method..."
    docker_install "$@"
    ;;
  snap)
    echo "📦 Installing ShellHub using snap method..."
    snap_install
    ;;
  standalone)
    echo "🐧 Installing ShellHub using standalone method..."
    standalone_install
    ;;
  wsl)
    echo "🪟 Installing ShellHub using WSL method..."
    wsl_install
    ;;
  *)
    echo "❌ Install method not supported."
    exit 1
    ;;
  esac
}

main() {
  if [ "$(uname -s)" = "FreeBSD" ]; then
    echo "👹 This system is running FreeBSD."
    echo "❌ ERROR: Automatic installation is not supported on FreeBSD."
    echo
    echo "Please refer to the ShellHub port at https://github.com/shellhub-io/ports"
    exit 1
  fi

  if [ "$1" = "uninstall" ]; then
    detect_install_method
    uninstall_agent

    return
  fi

  SERVER_ADDRESS="${SERVER_ADDRESS:-https://cloud.shellhub.io}"
  TENANT_ID="${TENANT_ID}"
  INSTALL_METHOD="$INSTALL_METHOD"
  AGENT_VERSION="${AGENT_VERSION:-$(http_get "$SERVER_ADDRESS/info" | sed -E 's/.*"version":\s?"?([^,"]*)"?.*/\1/')}"
  [ -n "$AGENT_IMAGE" ] && AGENT_IMAGE_OVERRIDDEN="1"
  AGENT_IMAGE="${AGENT_IMAGE:-docker.io/shellhubio/agent:$AGENT_VERSION}"
  BINARY_ARCH="$BINARY_ARCH"
  INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"
  TMP_DIR="${TMP_DIR:-$(mktemp -d -t shellhub-installer-XXXXXX)}"

  if [ -z "$BINARY_ARCH" ]; then
    case $(uname -m) in
    x86_64)
      BINARY_ARCH=amd64
      ;;
    armv6l)
      BINARY_ARCH=armv6
      ;;
    armv7l)
      BINARY_ARCH=armv7
      ;;
    aarch64)
      BINARY_ARCH=arm64
      ;;
    i386|i486|i586|i686)
      BINARY_ARCH=386
      ;;
    esac
  fi

  echo "🛠️ ShellHub Agent Installer"
  echo
  if [ -z "$INSTALL_METHOD" ]; then
    echo "This script will install the ShellHub agent on your system."
    echo "It will auto-detect the best available installation method."
    echo
    echo "Installation methods (priority order):"
    echo "  1. Docker     - If Docker is installed and accessible in rootful mode"
    echo "  2. Podman     - If Podman is installed and accessible in rootful mode"
    echo "  3. Snap       - If Snap package manager is available"
    echo "  4. WSL        - If running in WSL2 with systemd and mirrored networking"
    echo "  5. Standalone - Native binary with systemd"
    echo
  fi

  PERSISTED_TENANT=""
  [ -z "$TENANT_ID" ] && PERSISTED_TENANT=$(persisted_tenant)

  echo "⚙️ Detected settings:"
  echo "- Server address: $SERVER_ADDRESS"
  echo "- Enrollment: $(enrollment_summary)"
  echo "- Agent version: $AGENT_VERSION"
  echo "- Architecture: $BINARY_ARCH"
  [ -n "$INSTALL_METHOD" ] && echo "- Install method: $INSTALL_METHOD"
  echo

  detect_install_method
  recommend_container_runtime
  install_agent "$@"
}

[ "${INSTALL_SH_LIB:-}" = "1" ] || main "$@"