Files
scripts/netbird-install-update/netbird-install-update.sh
jeremy d2ca6c6787 Add pacman support for Arch Linux systems
- Detect netbird installed via pacman
- Remove netbird packages using pacman -Rns when replacing with binary install
- Update README to document pacman support
2026-07-25 22:49:39 -04:00

440 lines
16 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
# Color codes for output formatting
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m' # No Color
# Icons
ICON_SUCCESS="✓"
ICON_ERROR="✗"
ICON_INFO=""
ICON_WARNING="⚠"
ICON_ARROW="→"
ICON_DOWNLOAD="⬇"
ICON_UPLOAD="⬆"
ICON_INSTALL="📦"
ICON_UPDATE="🔄"
ICON_DEPLOY="🚀"
ICON_TIMER="⏰"
ICON_CHECK="🔍"
ICON_CONFIG="⚙"
ICON_NETWORK="🌐"
ICON_VERSION="📋"
ICON_LOG="📝"
ICON_HOST="🖥"
SSH_HOST=""
HOSTS_FILE=""
INSTALL_TIMER=false
TIMER_TIME="daily"
DEFAULT_PASSWORD=""
REMOTE_ARGS=()
SSH_TIMEOUT=30
# Arrays to track deployment results
declare -a SUCCESS_HOSTS=()
declare -a FAILED_HOSTS=()
declare -a HOST_ACTIONS=()
# Helper functions for formatted output
print_header() {
echo -e "\n${BLUE}${BOLD}╔═══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}${BOLD}$1${NC}"
echo -e "${BLUE}${BOLD}╚═══════════════════════════════════════════════════════════════╝${NC}\n"
}
print_info() {
echo -e " ${BLUE}${ICON_INFO}${NC} $1"
}
print_success() {
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
}
print_error() {
echo -e " ${RED}${ICON_ERROR}${NC} $1"
}
print_warning() {
echo -e " ${YELLOW}${ICON_WARNING}${NC} $1"
}
print_status() {
echo -e " ${CYAN}${ICON_ARROW}${NC} $1"
}
print_step() {
echo -e " ${DIM}${NC} $1"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--ssh)
SSH_HOST="$2"
shift 2
;;
--hosts-file|--file)
HOSTS_FILE="$2"
shift 2
;;
--timer)
INSTALL_TIMER=true
REMOTE_ARGS+=("--timer")
shift
;;
--time)
TIMER_TIME="$2"
REMOTE_ARGS+=("--time" "$2")
shift 2
;;
--password|-p)
DEFAULT_PASSWORD="$2"
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
deploy_to_host() {
local host="$1"
local sudo_pass="${2:-}"
echo -e "${CYAN}${BOLD}┌─────────────────────────────────────────────────────────────┐${NC}"
echo -e "${CYAN}${BOLD}${NC} ${ICON_DEPLOY} ${YELLOW}${host}${NC}"
echo -e "${CYAN}${BOLD}└─────────────────────────────────────────────────────────────┘${NC}"
REMOTE_SCRIPT="/tmp/.netbird-install-update-$$"
REMOTE_ACTION_FILE="/tmp/.netbird-action-$$"
LOCAL_ACTION_FILE="/tmp/.netbird-deploy-action-$$"
print_step "Copying script..."
if ! scp -o ConnectTimeout=$SSH_TIMEOUT -o BatchMode=yes "$0" "$host:$REMOTE_SCRIPT" 2>&1; then
print_error "Failed to copy script to $host (timeout or connection error)"
return 1
fi
print_step "Executing script..."
# Build SSH command based on whether we have a password
if [ -n "$sudo_pass" ]; then
# Write password to remote temp file using printf to handle special characters
# Use base64 to safely transfer the password
local encoded_pass=$(echo -n "$sudo_pass" | base64)
if ! ssh -o ConnectTimeout=$SSH_TIMEOUT -o StrictHostKeyChecking=no "$host" "echo '$encoded_pass' | base64 -d > /tmp/.netbird-pass-$$ && chmod 600 /tmp/.netbird-pass-$$" 2>&1; then
print_error "Failed to setup password file on $host"
return 1
fi
# Execute script with sudo, reading password from file
if ! ssh -o ConnectTimeout=$SSH_TIMEOUT -o StrictHostKeyChecking=no "$host" "sudo -S bash $REMOTE_SCRIPT ${REMOTE_ARGS[*]} < /tmp/.netbird-pass-$$" 2>&1; then
print_error "Failed to execute script on $host"
ssh -o ConnectTimeout=$SSH_TIMEOUT -o StrictHostKeyChecking=no "$host" "rm -f $REMOTE_SCRIPT $REMOTE_ACTION_FILE /tmp/.netbird-pass-$$" 2>/dev/null || true
return 1
fi
# Clean up password file
ssh -o ConnectTimeout=$SSH_TIMEOUT -o StrictHostKeyChecking=no "$host" "rm -f /tmp/.netbird-pass-$$" 2>/dev/null || true
else
# Interactive sudo with terminal
if ! ssh -o ConnectTimeout=$SSH_TIMEOUT -o BatchMode=yes -t "$host" "sudo bash $REMOTE_SCRIPT ${REMOTE_ARGS[*]}" 2>&1; then
print_error "Failed to execute script on $host"
ssh -o ConnectTimeout=$SSH_TIMEOUT -o BatchMode=yes "$host" "rm -f $REMOTE_SCRIPT $REMOTE_ACTION_FILE" 2>/dev/null || true
return 1
fi
fi
# Read the action from the remote action file and write to local action file
if action_output=$(ssh -o ConnectTimeout=$SSH_TIMEOUT -o StrictHostKeyChecking=no "$host" "cat $REMOTE_ACTION_FILE 2>/dev/null" < /dev/null 2>&1); then
echo "$action_output" > "$LOCAL_ACTION_FILE"
else
echo "unknown" > "$LOCAL_ACTION_FILE"
fi
# Clean up remote files
ssh -o ConnectTimeout=$SSH_TIMEOUT -o StrictHostKeyChecking=no "$host" "rm -f $REMOTE_SCRIPT $REMOTE_ACTION_FILE" < /dev/null 2>/dev/null || true
print_success "Completed: $host"
return 0
}
if [ -n "$HOSTS_FILE" ]; then
if [ ! -f "$HOSTS_FILE" ]; then
print_error "Hosts file not found: $HOSTS_FILE"
exit 1
fi
print_header "${ICON_NETWORK} Multi-Host Deployment"
print_info "Reading hosts from: ${YELLOW}$HOSTS_FILE${NC}"
print_info "SSH timeout: ${YELLOW}${SSH_TIMEOUT}s${NC}"
echo ""
SUCCESS_COUNT=0
FAIL_COUNT=0
ACTION_FILE="/tmp/.netbird-deploy-action-$$"
# Use file descriptor 3 to read hosts file (prevents SSH from consuming stdin)
exec 3< "$HOSTS_FILE"
while IFS= read -r line <&3 || [ -n "$line" ]; do
# Skip empty lines and comments
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
# Trim whitespace
line=$(echo "$line" | xargs)
# Parse hostname and optional password
host=$(echo "$line" | awk '{print $1}')
sudo_pass=$(echo "$line" | awk '{print $2}')
# Use default password if no host-specific password is provided
if [ -z "$sudo_pass" ] && [ -n "$DEFAULT_PASSWORD" ]; then
sudo_pass="$DEFAULT_PASSWORD"
fi
# Run deployment (output streams to terminal)
if deploy_to_host "$host" "$sudo_pass"; then
# Read the action from the temp file
action="unknown"
if [ -f "$ACTION_FILE" ]; then
action=$(cat "$ACTION_FILE")
rm -f "$ACTION_FILE"
fi
SUCCESS_HOSTS+=("$host")
HOST_ACTIONS+=("$action")
((SUCCESS_COUNT++)) || true
else
FAILED_HOSTS+=("$host")
((FAIL_COUNT++)) || true
fi
echo ""
done
exec 3<&-
# Print detailed summary
echo -e "\n${GREEN}${BOLD}╔═══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}${BOLD}${NC} ${ICON_SUCCESS} ${GREEN}${BOLD}Deployment Summary${NC}"
echo -e "${GREEN}${BOLD}╠═══════════════════════════════════════════════════════════════╣${NC}"
echo -e "${GREEN}${BOLD}${NC} ${GREEN}${ICON_SUCCESS}${NC} Successful: ${BOLD}$SUCCESS_COUNT${NC}"
echo -e "${GREEN}${BOLD}${NC} ${RED}${ICON_ERROR}${NC} Failed: ${BOLD}$FAIL_COUNT${NC}"
echo -e "${GREEN}${BOLD}╚═══════════════════════════════════════════════════════════════╝${NC}"
# Show successful hosts with actions
if [ ${#SUCCESS_HOSTS[@]} -gt 0 ]; then
echo -e "\n${GREEN}${BOLD}Successful Hosts:${NC}"
for i in "${!SUCCESS_HOSTS[@]}"; do
host="${SUCCESS_HOSTS[$i]}"
action="${HOST_ACTIONS[$i]}"
echo -e " ${GREEN}${ICON_SUCCESS}${NC} ${YELLOW}$host${NC} ${DIM}($action)${NC}"
done
fi
# Show failed hosts
if [ ${#FAILED_HOSTS[@]} -gt 0 ]; then
echo -e "\n${RED}${BOLD}Failed Hosts:${NC}"
for host in "${FAILED_HOSTS[@]}"; do
echo -e " ${RED}${ICON_ERROR}${NC} ${YELLOW}$host${NC}"
done
fi
echo ""
exit 0
fi
if [ -n "$SSH_HOST" ]; then
print_header "${ICON_DEPLOY} Single Host Deployment"
ACTION_FILE="/tmp/.netbird-deploy-action-$$"
if deploy_to_host "$SSH_HOST" "$DEFAULT_PASSWORD"; then
# Read and display the action
if [ -f "$ACTION_FILE" ]; then
action=$(cat "$ACTION_FILE")
rm -f "$ACTION_FILE"
print_info "Action performed: ${YELLOW}$action${NC}"
fi
fi
echo ""
exit 0
fi
if [ "$EUID" -ne 0 ]; then
print_warning "Elevating to root..."
exec sudo "$0" "$@"
fi
WORK_DIR=$(mktemp -d)
trap 'rm -rf "$WORK_DIR"' EXIT
detect_install() {
if ! command -v netbird &>/dev/null; then
echo "none"
return
fi
if [ -f /etc/netbird/install.conf ]; then
local pm
pm=$(grep -oP 'package_manager=\K.*' /etc/netbird/install.conf 2>/dev/null || true)
if [ "$pm" = "bin" ]; then
echo "bin"
return
elif [ -n "$pm" ]; then
echo "pkg:$pm"
return
fi
fi
if dpkg -s netbird &>/dev/null; then
echo "pkg:apt"
return
fi
if rpm -q netbird &>/dev/null; then
if command -v dnf &>/dev/null; then
echo "pkg:dnf"
else
echo "pkg:yum"
fi
return
fi
if pacman -Q netbird &>/dev/null; then
echo "pkg:pacman"
return
fi
echo "bin"
}
install_timer() {
print_header "${ICON_TIMER} Installing Auto-Update Timer"
if systemctl is-active --quiet netbird-update.timer; then
print_warning "Updating existing timer..."
systemctl stop netbird-update.timer
systemctl disable netbird-update.timer
fi
cat > /etc/systemd/system/netbird-update.service <<'UNIT'
[Unit]
Description=Netbird Auto-Update
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'curl -fsSL https://pkgs.netbird.io/install.sh | UPDATE_NETBIRD=true sh && chown root:root /usr/bin/netbird 2>/dev/null || true && chmod +x /usr/bin/netbird 2>/dev/null || true && restorecon -v /usr/bin/netbird 2>/dev/null || true && systemctl restart netbird 2>/dev/null || true'
StandardOutput=append:/var/log/netbird-update.log
StandardError=append:/var/log/netbird-update.log
UNIT
cat > /etc/systemd/system/netbird-update.timer <<UNIT
[Unit]
Description=Run Netbird update daily
[Timer]
OnCalendar=$TIMER_TIME
Persistent=true
RandomizedDelaySec=600
[Install]
WantedBy=timers.target
UNIT
systemctl daemon-reload
systemctl enable --now netbird-update.timer
print_success "Timer installed successfully"
print_info "Schedule: ${YELLOW}$TIMER_TIME${NC} (with random 0-10 minute delay)"
print_info "Logs: ${YELLOW}/var/log/netbird-update.log${NC}"
}
print_header "${ICON_INSTALL} NetBird Install/Update"
print_status "${ICON_DOWNLOAD} Downloading Netbird install script..."
curl -fsSL -o "$WORK_DIR/install.sh" https://pkgs.netbird.io/install.sh
chmod +x "$WORK_DIR/install.sh"
print_success "Download complete"
print_status "${ICON_CHECK} Detecting installation state..."
state=$(detect_install)
print_info "Detected: ${YELLOW}$state${NC}"
echo ""
case "$state" in
none)
print_header "${ICON_INSTALL} Fresh Installation"
print_info "Netbird not found. Installing..."
USE_BIN_INSTALL=true SKIP_UI_APP=true "$WORK_DIR/install.sh"
chown root:root /usr/bin/netbird 2>/dev/null || true
restorecon -v /usr/bin/netbird 2>/dev/null || true
systemctl restart netbird 2>/dev/null || true
print_success "Netbird installed successfully"
echo "fresh install" > /tmp/.netbird-action-$$
;;
pkg:*)
print_header "${ICON_UPDATE} Package Manager Replacement"
pm="${state#pkg:}"
print_info "Netbird installed via ${YELLOW}$pm${NC}"
print_info "Replacing with binary install..."
cat > /tmp/.netbird_replace.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
sleep 3
systemctl stop netbird 2>/dev/null || true
case "$1" in
apt) apt-get remove -y netbird netbird-ui 2>/dev/null || true ;;
yum) yum remove -y netbird netbird-ui 2>/dev/null || true ;;
dnf) dnf remove -y netbird netbird-ui 2>/dev/null || true ;;
zypper) zypper remove -y netbird netbird-ui 2>/dev/null || true ;;
rpm-ostree) rpm-ostree uninstall -y netbird netbird-ui 2>/dev/null || true ;;
pacman) pacman -Rns --noconfirm netbird netbird-ui 2>/dev/null || true ;;
esac
USE_BIN_INSTALL=true SKIP_UI_APP=true bash /tmp/.netbird_install.sh
chown root:root /usr/bin/netbird 2>/dev/null || true
chmod +x /usr/bin/netbird 2>/dev/null || true
restorecon -v /usr/bin/netbird 2>/dev/null || true
systemctl restart netbird 2>/dev/null || true
rm -f /tmp/.netbird_replace.sh /tmp/.netbird_install.sh
EOF
chmod +x /tmp/.netbird_replace.sh
cp "$WORK_DIR/install.sh" /tmp/.netbird_install.sh
setsid bash /tmp/.netbird_replace.sh "$pm" </dev/null >/var/log/netbird-update.log 2>&1 &
print_warning "Replacement scheduled in background"
print_info "Connection may drop momentarily"
print_info "Check ${YELLOW}/var/log/netbird-update.log${NC} for progress"
echo "pkg replacement" > /tmp/.netbird-action-$$
;;
bin)
print_header "${ICON_VERSION} Binary Update Check"
if [ ! -f /etc/netbird/install.conf ]; then
mkdir -p /etc/netbird
echo "package_manager=bin" > /etc/netbird/install.conf
fi
current=$(netbird version 2>/dev/null || echo "unknown")
latest=$(curl -fsSL https://pkgs.netbird.io/releases/latest 2>/dev/null | grep -oP '"tag_name":\s*"v?\K[^"]+' || echo "unknown")
print_info "Current version: ${YELLOW}$current${NC}"
print_info "Latest version: ${YELLOW}$latest${NC}"
echo ""
if [ "$current" = "$latest" ] && [ "$current" != "unknown" ]; then
print_success "Netbird is up to date"
echo "up to date" > /tmp/.netbird-action-$$
else
print_info "Updating Netbird: ${YELLOW}$current${NC}${GREEN}$latest${NC}"
cp "$WORK_DIR/install.sh" /tmp/.netbird_update.sh
setsid bash -c 'sleep 3 && bash /tmp/.netbird_update.sh --update && chown root:root /usr/bin/netbird 2>/dev/null || true && chmod +x /usr/bin/netbird 2>/dev/null || true && restorecon -v /usr/bin/netbird 2>/dev/null || true && systemctl restart netbird 2>/dev/null || true && rm -f /tmp/.netbird_update.sh' \
</dev/null >/var/log/netbird-update.log 2>&1 &
print_warning "Update scheduled in background"
print_info "Connection may drop momentarily"
print_info "Check ${YELLOW}/var/log/netbird-update.log${NC} for progress"
echo "updated" > /tmp/.netbird-action-$$
fi
;;
esac
if $INSTALL_TIMER; then
install_timer
fi
echo ""