#!/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 < /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 "$@"