#!/bin/sh set -eu RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' CYAN='\033[0;36m' WHITE='\033[1;37m' NC='\033[0m' log_info() { printf '%b\n' "${NC} $*"; } log_success() { printf '%b\n' "${GREEN}${NC} $*"; } log_warning() { printf '%b\n' "${YELLOW}[WARNING]${NC} $*"; } log_error() { printf '%b\n' "${RED}[ERROR]${NC} $*" >&2; } log_config() { printf '%b\n' "${CYAN}[CONFIG]${NC} $*"; } repo="luodaoyi/komari-zig-agent" service_name="komari-agent" target_dir="/opt/komari" github_proxy="" github_proxy_list="${KOMARI_GITHUB_PROXIES:-https://gh.llkk.cc https://gh-proxy.com https://ghproxy.net https://ghfast.top https://ghproxy.cc}" install_version="" install_dir_specified=false user_service=false komari_args="" download_connect_timeout="${KOMARI_DOWNLOAD_CONNECT_TIMEOUT:-8}" download_max_time="${KOMARI_DOWNLOAD_MAX_TIME:-20}" download_low_speed_limit="${KOMARI_DOWNLOAD_LOW_SPEED_LIMIT:-1024}" download_low_speed_time="${KOMARI_DOWNLOAD_LOW_SPEED_TIME:-10}" os_type="$(uname -s)" case "$os_type" in Linux) os_name="linux" ;; FreeBSD) os_name="freebsd" ;; Darwin) os_name="darwin" target_dir="/usr/local/komari" if [ ! -w "/usr/local" ] && [ "${EUID:-$(id -u)}" -ne 0 ]; then target_dir="$HOME/.komari" fi ;; *) log_error "Unsupported operating system: $os_type"; exit 1 ;; esac while [ "$#" -gt 0 ]; do case "$1" in --install-dir) target_dir="$2"; install_dir_specified=true; shift 2 ;; --install-service-name) service_name="$2"; shift 2 ;; --install-ghproxy) github_proxy="$2"; shift 2 ;; --install-version) install_version="$2"; shift 2 ;; --install*) log_warning "Unknown install parameter: $1"; shift ;; *) komari_args="${komari_args} $1"; shift ;; esac done komari_args="${komari_args# }" require_root=true if [ "$os_name" = "darwin" ] && command -v brew >/dev/null 2>&1; then require_root=false fi effective_uid="${EUID:-$(id -u)}" if [ "$effective_uid" -ne 0 ] && [ "$os_name" = "linux" ]; then if [ -z "${HOME:-}" ]; then log_error "HOME is required for a non-root installation" exit 1 fi if [ "$install_dir_specified" = false ]; then target_dir="${XDG_DATA_HOME:-$HOME/.local/share}/komari" fi if command -v systemctl >/dev/null 2>&1 && systemctl --user show-environment >/dev/null 2>&1; then user_service=true else log_error "A non-root Linux installation requires a running systemd user session" exit 1 fi elif [ "$effective_uid" -ne 0 ] && [ "$require_root" = true ]; then log_error "Please run as root" exit 1 fi agent_path="${target_dir}/agent" arch="$(uname -m)" case "$arch" in x86_64|amd64) arch="amd64" ;; aarch64|arm64) arch="arm64" ;; i386|i686) case "$os_name" in linux|freebsd) arch="386" ;; *) log_error "32-bit x86 not supported on $os_name"; exit 1 ;; esac ;; armv6*|armv7*) case "$os_name" in linux|freebsd) arch="arm" ;; *) log_error "32-bit ARM not supported on $os_name"; exit 1 ;; esac ;; mips) [ "$os_name" = "linux" ] && arch="mips" || { log_error "MIPS not supported on $os_name"; exit 1; } ;; mipsel) [ "$os_name" = "linux" ] && arch="mipsel" || { log_error "MIPS little-endian not supported on $os_name"; exit 1; } ;; mips64) [ "$os_name" = "linux" ] && arch="mips64" || { log_error "MIPS64 not supported on $os_name"; exit 1; } ;; mips64el) [ "$os_name" = "linux" ] && arch="mips64el" || { log_error "MIPS64 little-endian not supported on $os_name"; exit 1; } ;; riscv64) [ "$os_name" = "linux" ] && arch="riscv64" || { log_error "RISC-V not supported on $os_name"; exit 1; } ;; s390x) [ "$os_name" = "linux" ] && arch="s390x" || { log_error "s390x not supported on $os_name"; exit 1; } ;; loongarch64|loong64) [ "$os_name" = "linux" ] && arch="loong64" || { log_error "LoongArch64 not supported on $os_name"; exit 1; } ;; *) log_error "Unsupported architecture: $arch"; exit 1 ;; esac printf '%b\n' "${WHITE}===========================================${NC}" printf '%b\n' "${WHITE} Komari Agent Installation Script${NC}" printf '%b\n' "${WHITE}===========================================${NC}" log_config "Service name: ${GREEN}$service_name${NC}" log_config "Install directory: ${GREEN}$target_dir${NC}" log_config "GitHub proxy: ${GREEN}${github_proxy:-"(auto fallback)"}${NC}" log_config "Binary arguments: ${GREEN}$komari_args${NC}" log_config "Version: ${GREEN}${install_version:-Latest}${NC}" install_dependencies() { command -v curl >/dev/null 2>&1 && return if [ "$effective_uid" -ne 0 ]; then log_error "Missing required dependency: curl" log_info "Install it with your system package manager, then run this script again." exit 1 fi log_info "Installing dependency: curl" if command -v apt >/dev/null 2>&1; then apt update && apt install -y curl elif command -v yum >/dev/null 2>&1; then yum install -y curl elif command -v apk >/dev/null 2>&1; then apk add curl elif command -v opkg >/dev/null 2>&1; then opkg update && opkg install curl elif command -v pkg >/dev/null 2>&1; then pkg install -y curl elif command -v brew >/dev/null 2>&1; then brew install curl else log_error "No supported package manager found for curl" exit 1 fi } uninstall_previous() { log_info "Checking for previous installation..." if [ "$user_service" = true ]; then user_service_file="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/${service_name}.service" if systemctl --user list-unit-files 2>/dev/null | grep -q "^${service_name}.service"; then systemctl --user stop "${service_name}.service" 2>/dev/null || true systemctl --user disable "${service_name}.service" 2>/dev/null || true fi rm -f "$user_service_file" systemctl --user daemon-reload || true elif command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files 2>/dev/null | grep -q "^${service_name}.service"; then systemctl stop "${service_name}.service" 2>/dev/null || true systemctl disable "${service_name}.service" 2>/dev/null || true rm -f "/etc/systemd/system/${service_name}.service" systemctl daemon-reload || true fi if command -v rc-service >/dev/null 2>&1 && [ -f "/etc/init.d/${service_name}" ]; then rc-service "$service_name" stop 2>/dev/null || true rc-update del "$service_name" default 2>/dev/null || true rm -f "/etc/init.d/${service_name}" fi if command -v uci >/dev/null 2>&1 && [ -f "/etc/init.d/${service_name}" ]; then "/etc/init.d/${service_name}" stop 2>/dev/null || true "/etc/init.d/${service_name}" disable 2>/dev/null || true rm -f "/etc/init.d/${service_name}" fi if command -v initctl >/dev/null 2>&1 && [ -f "/etc/init/${service_name}.conf" ]; then initctl stop "$service_name" 2>/dev/null || true rm -f "/etc/init/${service_name}.conf" fi if [ "$os_name" = "freebsd" ] && [ -f "/usr/local/etc/rc.d/${service_name}" ]; then service "$service_name" stop 2>/dev/null || true rm -f "/usr/local/etc/rc.d/${service_name}" fi if [ "$os_name" = "darwin" ] && command -v launchctl >/dev/null 2>&1; then system_plist="/Library/LaunchDaemons/com.komari.${service_name}.plist" user_plist="$HOME/Library/LaunchAgents/com.komari.${service_name}.plist" [ -f "$system_plist" ] && launchctl bootout system "$system_plist" 2>/dev/null || true [ -f "$user_plist" ] && launchctl bootout "gui/$(id -u)" "$user_plist" 2>/dev/null || true rm -f "$system_plist" "$user_plist" fi rm -f "$agent_path" } detect_init_system() { [ "$user_service" = true ] && { echo systemd-user; return; } [ -f /etc/NIXOS ] && { echo nixos; return; } [ "$os_name" = "freebsd" ] && { echo freebsd; return; } [ "$os_name" = "darwin" ] && command -v launchctl >/dev/null 2>&1 && { echo launchd; return; } [ -f /etc/alpine-release ] && command -v rc-service >/dev/null 2>&1 && { echo openrc; return; } if command -v uci >/dev/null 2>&1 && [ -f /etc/rc.common ]; then echo procd; return; fi pid1="$(ps -p 1 -o comm= 2>/dev/null | tr -d ' ' || true)" if { [ "$pid1" = systemd ] || [ -d /run/systemd/system ]; } && command -v systemctl >/dev/null 2>&1 && systemctl list-units >/dev/null 2>&1; then echo systemd; return fi if command -v rc-service >/dev/null 2>&1 && { [ -d /run/openrc ] || [ -f /sbin/openrc ] || [ "$pid1" = openrc-init ]; }; then echo openrc; return fi if command -v initctl >/dev/null 2>&1 && [ -d /etc/init ]; then echo upstart; return; fi echo unknown } install_dependencies uninstall_previous mkdir -p "$target_dir" download_file() { df_url="$1" df_out="$2" df_attempt="${3:-1}" df_max_attempts="${4:-1}" while [ "$df_attempt" -le "$df_max_attempts" ]; do curl -fL \ --connect-timeout "$download_connect_timeout" \ --max-time "$download_max_time" \ --speed-limit "$download_low_speed_limit" \ --speed-time "$download_low_speed_time" \ -o "$df_out" "$df_url" && return 0 rm -f "$df_out" log_warning "Download failed or too slow, retry ${df_attempt}/${df_max_attempts}" df_attempt=$((df_attempt + 1)) sleep 1 done return 1 } proxy_url() { proxy="$1" url="$2" printf '%s/%s\n' "${proxy%/}" "$url" } probe_url() { url="$1" curl -fIL --connect-timeout 5 --max-time 12 -o /dev/null -w '%{time_total}' "$url" 2>/dev/null || return 1 } time_less_than() { command -v awk >/dev/null 2>&1 || return 1 awk "BEGIN { exit !($1 < $2) }" } github_proxy_items() { printf '%s\n' "$github_proxy_list" | tr ',;' ' ' } select_fastest_proxy_url() { base_url="$1" best_proxy="" best_time="" for proxy in $(github_proxy_items); do candidate="$(proxy_url "$proxy" "$base_url")" elapsed="$(probe_url "$candidate" || true)" [ -n "$elapsed" ] || continue log_info "Proxy probe: ${CYAN}$proxy${NC} ${elapsed}s" >&2 if [ -z "$best_time" ] || time_less_than "$elapsed" "$best_time"; then best_time="$elapsed" best_proxy="$proxy" fi done [ -n "$best_proxy" ] || return 1 printf '%s\n' "$best_proxy" } download_attempts_for_url() { case "$1" in https://github.com/*) printf '1\n' ;; *) printf '2\n' ;; esac } try_download_binary() { tdb_url="$1" tdb_sums_url="$2" tdb_out="$3" tdb_sums_fallback_url="${4:-}" tdb_sums_out="${tdb_out}.sha256.$$" rm -f "$tdb_sums_out" tdb_binary_attempts="$(download_attempts_for_url "$tdb_url")" tdb_sums_attempts="$(download_attempts_for_url "$tdb_sums_url")" log_info "Downloading: ${CYAN}$tdb_url${NC}" download_file "$tdb_url" "$tdb_out" 1 "$tdb_binary_attempts" || { rm -f "$tdb_sums_out"; return 1; } if ! download_file "$tdb_sums_url" "$tdb_sums_out" 1 "$tdb_sums_attempts"; then if [ -n "$tdb_sums_fallback_url" ]; then tdb_sums_fallback_attempts="$(download_attempts_for_url "$tdb_sums_fallback_url")" download_file "$tdb_sums_fallback_url" "$tdb_sums_out" 1 "$tdb_sums_fallback_attempts" || { rm -f "$tdb_out" "$tdb_sums_out"; return 1; } else rm -f "$tdb_out" "$tdb_sums_out" return 1 fi fi verify_sha256sum "$tdb_out" "$tdb_sums_out" || { rm -f "$tdb_out" "$tdb_sums_out" log_warning "Downloaded file failed SHA256 verification" return 1 } rm -f "$tdb_sums_out" chmod +x "$tdb_out" "$tdb_out" --show-warning >/dev/null 2>&1 && return 0 rm -f "$tdb_out" log_warning "Downloaded file failed binary preflight" return 1 } sha256_file() { file="$1" if command -v sha256sum >/dev/null 2>&1; then sha256sum "$file" | awk '{print $1}' elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$file" | awk '{print $1}' elif command -v sha256 >/dev/null 2>&1; then sha256 -q "$file" else log_error "sha256sum, shasum, or sha256 is required" return 1 fi } verify_sha256sum() { file="$1" sums="$2" expected="$(awk -v name="$asset" '($2 == name || $2 == "*" name) { print $1; exit }' "$sums")" [ -n "$expected" ] || return 1 actual="$(sha256_file "$file")" || return 1 [ "$expected" = "$actual" ] } download_binary_with_fallback() { db_base_url="$1" db_sums_url="$2" db_out="$3" if [ -n "$github_proxy" ]; then try_download_binary "$(proxy_url "$github_proxy" "$db_base_url")" "$db_sums_url" "$db_out" "$(proxy_url "$github_proxy" "$db_sums_url")" return fi try_download_binary "$db_base_url" "$db_sums_url" "$db_out" && return 0 log_warning "Direct GitHub download failed, probing GitHub proxy mirrors" db_fastest_proxy="$(select_fastest_proxy_url "$db_base_url" || true)" if [ -n "$db_fastest_proxy" ]; then try_download_binary "$(proxy_url "$db_fastest_proxy" "$db_base_url")" "$db_sums_url" "$db_out" "$(proxy_url "$db_fastest_proxy" "$db_sums_url")" && return 0 log_warning "Fastest proxy failed, trying remaining proxy mirrors" fi for proxy in $(github_proxy_items); do [ "$proxy" = "$db_fastest_proxy" ] && continue try_download_binary "$(proxy_url "$proxy" "$db_base_url")" "$db_sums_url" "$db_out" "$(proxy_url "$proxy" "$db_sums_url")" && return 0 done return 1 } asset="komari-agent-${os_name}-${arch}" if [ -n "$install_version" ]; then release_path="download/${install_version}" else release_path="latest/download" fi download_url="https://github.com/${repo}/releases/${release_path}/${asset}" checksums_url="https://github.com/${repo}/releases/${release_path}/SHA256SUMS" log_info "Detected OS: ${GREEN}$os_name${NC}, Architecture: ${GREEN}$arch${NC}" download_binary_with_fallback "$download_url" "$checksums_url" "$agent_path" || { log_error "Failed to download release asset" exit 1 } log_success "Installed binary: $agent_path" init_system="$(detect_init_system)" log_info "Detected init system: ${GREEN}$init_system${NC}" case "$init_system" in nixos) log_warning "NixOS detected. Add this service declaratively:" cat < "$service_file" < "/etc/systemd/system/${service_name}.service" < "/etc/init.d/${service_name}" < "/etc/init.d/${service_name}" < "/etc/init/${service_name}.conf" < "$rc_file" </dev/null 2>&1 || true service "$service_name" restart ;; launchd) case "$target_dir" in /Users/*) user_launchd=true ;; *) user_launchd=false ;; esac if [ "$user_launchd" = true ] || [ "${EUID:-$(id -u)}" -ne 0 ]; then plist_dir="$HOME/Library/LaunchAgents" plist_file="$plist_dir/com.komari.${service_name}.plist" domain="gui/$(id -u)" service_user="$(whoami)" log_dir="$HOME/Library/Logs" else plist_dir="/Library/LaunchDaemons" plist_file="$plist_dir/com.komari.${service_name}.plist" domain="system" service_user="root" log_dir="/var/log" fi mkdir -p "$plist_dir" "$log_dir" cat > "$plist_file" < Labelcom.komari.${service_name} ProgramArguments${agent_path} EOF if [ -n "$komari_args" ]; then # shellcheck disable=SC2086 for arg in $komari_args; do printf '%s\n' "$arg" >> "$plist_file"; done fi cat >> "$plist_file" < WorkingDirectory${target_dir} RunAtLoad KeepAlive UserName${service_user} StandardOutPath${log_dir}/${service_name}.log StandardErrorPath${log_dir}/${service_name}.log EOF launchctl bootout "$domain" "$plist_file" 2>/dev/null || true launchctl bootstrap "$domain" "$plist_file" ;; *) log_error "Unsupported or unknown init system: $init_system" exit 1 ;; esac log_success "Komari-agent installation completed" log_config "Service: ${GREEN}$service_name${NC}" log_config "Arguments: ${GREEN}$komari_args${NC}"