install.sh
Rama master · réplica de lectura del repositorio original.
#!/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 "$@"