#!/bin/sh # ZimaOS Blue Install/Update Script # Linux/macOS: curl -fsSL https://ota.zimaos.com/blue | sh # Windows: powershell -c "irm https://ota.zimaos.com/blue/windows | iex" set -e VERSION="0.10.39" DOWNLOAD_BASE="https://github.com/IceWhaleTech/ZimaOS-Blue/releases/download/0.10.39" RAW_PACKAGE_NAME="zimaos-blue.raw" RAW_DOWNLOAD_URL="https://github.com/IceWhaleTech/ZimaOS-Blue/releases/download/0.10.39/zimaos-blue.raw" INSTALL_DIR="$HOME/.local/bin" BINARY_NAME="blue" PROCESS_NAME="blue" ZIMAOS_SERVICE_NAME="zimaos-blue.service" ZIMAOS_EXT_DIR="/var/lib/extensions" ZIMAOS_RAW_PATH="${ZIMAOS_EXT_DIR}/${RAW_PACKAGE_NAME}" ZIMAOS_RAW_BACKUP_PATH="${ZIMAOS_EXT_DIR}/zimaos-blue.previous" BLUE_WAS_RUNNING=0 BLUE_SERVICE_WAS_RUNNING=0 USED_ZIMAOS_RAW=0 ZIMAOS_RAW_FALLBACK_REASON="" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE_C='\033[0;34m' NC='\033[0m' log_info() { printf '%b\n' "${BLUE_C}[INFO]${NC} $1"; } log_warn() { printf '%b\n' "${YELLOW}[WARN]${NC} $1"; } log_error() { printf '%b\n' "${RED}[ERROR]${NC} $1"; } log_ok() { printf '%b\n' "${GREEN}[OK]${NC} $1"; } detect_lang() { case "${LANG:-}${LC_ALL:-}" in zh_*|zh-*) echo "zh" ;; *) echo "en" ;; esac } msg() { local lang=$(detect_lang) case "$1" in installer) [ "$lang" = "zh" ] && echo "ZimaOS Blue 安装程序 v$2" || echo "ZimaOS Blue installer v$2" ;; platform) [ "$lang" = "zh" ] && echo "平台: $2" || echo "Platform: $2" ;; stopping) [ "$lang" = "zh" ] && echo "发现已运行的 Blue 进程,正在停止以进行更新..." || echo "Existing Blue process found, stopping for update..." ;; downloading) [ "$lang" = "zh" ] && echo "正在下载 $2..." || echo "Downloading $2..." ;; extracting) [ "$lang" = "zh" ] && echo "正在解压..." || echo "Extracting..." ;; not_found) [ "$lang" = "zh" ] && echo "在压缩包中找不到 blue 可执行文件" || echo "Could not find blue binary in archive" ;; starting) [ "$lang" = "zh" ] && echo "正在启动 Blue..." || echo "Starting Blue..." ;; updated) [ "$lang" = "zh" ] && echo "Blue 已更新到 v$2 并重新启动!" || echo "Blue updated to v$2 and restarted!" ;; installed) [ "$lang" = "zh" ] && echo "Blue v$2 已安装并启动!" || echo "Blue v$2 installed and started!" ;; open_prompt) [ "$lang" = "zh" ] && echo "按回车键在浏览器中打开 $2(Ctrl+C 跳过)..." || echo "Press Enter to open $2 in your browser (or Ctrl+C to skip)..." ;; open_fallback) [ "$lang" = "zh" ] && echo "请在浏览器中打开 $2" || echo "Please open $2 in your browser" ;; path_warn) [ "$lang" = "zh" ] && echo "$2 不在你的 PATH 中" || echo "$2 is not in your PATH" ;; path_hint) [ "$lang" = "zh" ] && echo "运行以下命令添加: export PATH=\"\$HOME/.local/bin:\$PATH\"" || echo "Add it with: export PATH=\"\$HOME/.local/bin:\$PATH\"" ;; unsupported_os) [ "$lang" = "zh" ] && echo "不支持的操作系统: $2" || echo "Unsupported OS: $2" ;; unsupported_arch) [ "$lang" = "zh" ] && echo "不支持的架构: $2" || echo "Unsupported architecture: $2" ;; need_curl) [ "$lang" = "zh" ] && echo "需要 curl 或 wget" || echo "curl or wget is required" ;; raw_detected) [ "$lang" = "zh" ] && echo "检测到 ZimaOS,尝试使用 raw 包安装..." || echo "Detected ZimaOS, trying raw package installation..." ;; raw_fallback) [ "$lang" = "zh" ] && echo "raw 包不可用($2),回退到标准 tar.gz 安装..." || echo "Raw package unavailable ($2), falling back to standard tar.gz install..." ;; raw_installing) [ "$lang" = "zh" ] && echo "正在安装 ZimaOS raw 模块..." || echo "Installing ZimaOS raw module..." ;; raw_refreshing) [ "$lang" = "zh" ] && echo "正在刷新 systemd-sysext 并重新加载 systemd..." || echo "Refreshing systemd-sysext and reloading systemd..." ;; raw_rollback) [ "$lang" = "zh" ] && echo "升级失败,正在回滚到之前的 raw 模块..." || echo "Upgrade failed, rolling back to the previous raw module..." ;; raw_missing_tools) [ "$lang" = "zh" ] && echo "缺少 systemd-sysext 或 systemctl" || echo "systemd-sysext or systemctl is missing" ;; raw_missing_ext) [ "$lang" = "zh" ] && echo "/var/lib/extensions 不存在" || echo "/var/lib/extensions is unavailable" ;; raw_need_privilege) [ "$lang" = "zh" ] && echo "安装 raw 包需要 root 或 sudo" || echo "root or sudo is required for raw package installation" ;; raw_legacy_service) [ "$lang" = "zh" ] && echo "检测到旧的 systemd 服务仍指向 $2,避免半迁移,改用标准安装" || echo "Existing systemd service still points to $2; using standard install to avoid a partial migration" ;; raw_no_existing_image) [ "$lang" = "zh" ] && echo "检测到已安装服务但没有现有 raw 镜像,改用标准安装以保证兼容" || echo "Detected an installed service without an existing raw image; using standard install for compatibility" ;; raw_start_failed) [ "$lang" = "zh" ] && echo "Blue 在 raw 更新后启动失败" || echo "Blue failed to start after the raw update" ;; raw_restore_previous) [ "$lang" = "zh" ] && echo "正在恢复之前的 Blue 运行状态..." || echo "Restoring the previous Blue runtime..." ;; raw_restore_failed) [ "$lang" = "zh" ] && echo "回滚完成,但恢复之前的 Blue 运行状态失败" || echo "Rollback completed, but restoring the previous Blue runtime failed" ;; service_start_fallback) [ "$lang" = "zh" ] && echo "systemd 服务启动失败,回退到直接启动二进制文件..." || echo "Failed to start the systemd service, falling back to launching the binary directly..." ;; esac } set_zimaos_raw_fallback_reason() { ZIMAOS_RAW_FALLBACK_REASON="$1" } detect_os() { case "$(uname -s)" in Linux*) echo "linux" ;; Darwin*) echo "darwin" ;; *) log_error "$(msg unsupported_os "$(uname -s)")"; exit 1 ;; esac } detect_arch() { case "$(uname -m)" in x86_64|amd64) echo "amd64" ;; aarch64|arm64) echo "arm64" ;; *) log_error "$(msg unsupported_arch "$(uname -m)")"; exit 1 ;; esac } is_zimaos() { [ -n "${ZIMAOS_VERSION:-}" ] || [ -f /etc/zimaos-release ] } run_privileged() { if [ "$(id -u)" -eq 0 ]; then "$@" elif command -v sudo >/dev/null 2>&1; then sudo "$@" else return 127 fi } download_file() { local url="$1" local output="$2" if command -v curl >/dev/null 2>&1; then curl -fSL --progress-bar -o "$output" "$url" elif command -v wget >/dev/null 2>&1; then wget -q --show-progress -O "$output" "$url" else log_error "$(msg need_curl)" return 1 fi } get_systemd_fragment_path() { if ! command -v systemctl >/dev/null 2>&1; then return 1 fi local fragment fragment=$(systemctl show -p FragmentPath --value "$ZIMAOS_SERVICE_NAME" 2>/dev/null | tr -d '\r') if [ -n "$fragment" ] && [ -f "$fragment" ]; then echo "$fragment" return 0 fi return 1 } get_service_exec_path() { local fragment=$(get_systemd_fragment_path || true) [ -n "$fragment" ] || return 1 local exec_line exec_line=$(sed -n 's/^[[:space:]]*ExecStart=//p' "$fragment" | head -1) [ -n "$exec_line" ] || return 1 local exec_path="${exec_line%% *}" while [ "${exec_path#-}" != "$exec_path" ]; do exec_path="${exec_path#-}" done [ -n "$exec_path" ] || return 1 echo "$exec_path" } has_legacy_non_raw_service() { local exec_path=$(get_service_exec_path || true) [ -n "$exec_path" ] || return 1 [ "$exec_path" != "/usr/bin/blue" ] } service_exists_without_raw_image() { get_systemd_fragment_path >/dev/null 2>&1 && [ ! -f "$ZIMAOS_RAW_PATH" ] } is_blue_service_active() { command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet "$ZIMAOS_SERVICE_NAME" >/dev/null 2>&1 } cleanup_zimaos_raw_backup() { run_privileged rm -f "$ZIMAOS_RAW_BACKUP_PATH" >/dev/null 2>&1 || true } can_use_zimaos_raw() { local os="$1" local arch="$2" ZIMAOS_RAW_FALLBACK_REASON="" if [ "$os" != "linux" ]; then return 1 fi if ! is_zimaos; then return 1 fi if [ "$arch" != "amd64" ]; then set_zimaos_raw_fallback_reason "$(msg unsupported_arch "$arch")" return 1 fi if ! command -v systemd-sysext >/dev/null 2>&1 || ! command -v systemctl >/dev/null 2>&1; then set_zimaos_raw_fallback_reason "$(msg raw_missing_tools)" return 1 fi if [ ! -d "$ZIMAOS_EXT_DIR" ]; then set_zimaos_raw_fallback_reason "$(msg raw_missing_ext)" return 1 fi if has_legacy_non_raw_service; then set_zimaos_raw_fallback_reason "$(msg raw_legacy_service "$(get_service_exec_path || echo unknown)")" return 1 fi if service_exists_without_raw_image; then set_zimaos_raw_fallback_reason "$(msg raw_no_existing_image)" return 1 fi if [ -w "$ZIMAOS_EXT_DIR" ] || [ "$(id -u)" -eq 0 ] || command -v sudo >/dev/null 2>&1; then return 0 fi set_zimaos_raw_fallback_reason "$(msg raw_need_privilege)" return 1 } stop_blue_for_raw() { BLUE_WAS_RUNNING=0 BLUE_SERVICE_WAS_RUNNING=0 if is_blue_service_active; then BLUE_WAS_RUNNING=1 BLUE_SERVICE_WAS_RUNNING=1 if ! run_privileged systemctl stop "$ZIMAOS_SERVICE_NAME" >/dev/null 2>&1; then log_warn "Failed to stop ${ZIMAOS_SERVICE_NAME}; continuing with process stop" fi sleep 1 fi if pgrep -x "$PROCESS_NAME" >/dev/null 2>&1; then BLUE_WAS_RUNNING=1 pkill -x "$PROCESS_NAME" 2>/dev/null || true sleep 1 fi } launch_blue_binary() { local binary_path="$1" case "$(uname -s)" in Darwin*) launchctl asuser $(id -u) "$binary_path" >/dev/null 2>&1 & ;; *) nohup "$binary_path" >/dev/null 2>&1 & ;; esac } start_blue_after_raw() { if [ "$BLUE_SERVICE_WAS_RUNNING" -eq 1 ]; then if run_privileged systemctl start "$ZIMAOS_SERVICE_NAME" >/dev/null 2>&1; then sleep 2 if is_blue_service_active || pgrep -x "$PROCESS_NAME" >/dev/null 2>&1; then return 0 fi fi log_warn "$(msg service_start_fallback)" fi launch_blue_binary "/usr/bin/blue" sleep 2 pgrep -x "$PROCESS_NAME" >/dev/null 2>&1 } rollback_zimaos_raw() { log_warn "$(msg raw_rollback)" if [ -f "$ZIMAOS_RAW_BACKUP_PATH" ]; then run_privileged rm -f "$ZIMAOS_RAW_PATH" >/dev/null 2>&1 || true if ! run_privileged mv "$ZIMAOS_RAW_BACKUP_PATH" "$ZIMAOS_RAW_PATH"; then return 1 fi else if ! run_privileged rm -f "$ZIMAOS_RAW_PATH"; then return 1 fi fi log_info "$(msg raw_refreshing)" if ! run_privileged systemd-sysext refresh; then return 1 fi if ! run_privileged systemctl daemon-reload; then return 1 fi return 0 } install_or_update_zimaos_raw() { local tmp_dir tmp_dir=$(mktemp -d) local raw_tmp="$tmp_dir/$RAW_PACKAGE_NAME" log_info "$(msg downloading "$RAW_PACKAGE_NAME")" if ! download_file "$RAW_DOWNLOAD_URL" "$raw_tmp"; then rm -rf "$tmp_dir" return 1 fi log_info "$(msg raw_installing)" run_privileged rm -f "$ZIMAOS_RAW_BACKUP_PATH" >/dev/null 2>&1 || true if [ -f "$ZIMAOS_RAW_PATH" ]; then if ! run_privileged mv "$ZIMAOS_RAW_PATH" "$ZIMAOS_RAW_BACKUP_PATH"; then rm -rf "$tmp_dir" return 1 fi fi if ! run_privileged mkdir -p "$ZIMAOS_EXT_DIR"; then rm -rf "$tmp_dir" rollback_zimaos_raw || true return 1 fi if ! run_privileged cp "$raw_tmp" "$ZIMAOS_RAW_PATH"; then rm -rf "$tmp_dir" rollback_zimaos_raw || true return 1 fi rm -rf "$tmp_dir" log_info "$(msg raw_refreshing)" if ! run_privileged systemd-sysext refresh; then rollback_zimaos_raw || true return 1 fi if ! run_privileged systemctl daemon-reload; then rollback_zimaos_raw || true return 1 fi return 0 } install_or_update_standard() { local os="$1" local arch="$2" local pkg="blue-${VERSION}-${os}-${arch}.tar.gz" local url="${DOWNLOAD_BASE}/${pkg}" local is_update=0 mkdir -p "$INSTALL_DIR" if pgrep -x "$PROCESS_NAME" >/dev/null 2>&1; then is_update=1 log_info "$(msg stopping)" pkill -x "$PROCESS_NAME" 2>/dev/null || true sleep 1 fi local tmp_dir tmp_dir=$(mktemp -d) log_info "$(msg downloading "${pkg}")" if ! download_file "$url" "$tmp_dir/$pkg"; then rm -rf "$tmp_dir" exit 1 fi log_info "$(msg extracting)" tar -xzf "$tmp_dir/$pkg" -C "$tmp_dir" if [ -f "$tmp_dir/blue" ]; then mv "$tmp_dir/blue" "$INSTALL_DIR/$BINARY_NAME" elif [ -f "$tmp_dir/blue/blue" ]; then mv "$tmp_dir/blue/blue" "$INSTALL_DIR/$BINARY_NAME" else local found found=$(find "$tmp_dir" -name "blue" -type f | head -1) if [ -n "$found" ]; then mv "$found" "$INSTALL_DIR/$BINARY_NAME" else log_error "$(msg not_found)" rm -rf "$tmp_dir" exit 1 fi fi chmod +x "$INSTALL_DIR/$BINARY_NAME" rm -rf "$tmp_dir" log_info "$(msg starting)" launch_blue_binary "$INSTALL_DIR/$BINARY_NAME" sleep 2 if [ $is_update -eq 1 ]; then log_ok "$(msg updated "${VERSION}")" else log_ok "$(msg installed "${VERSION}")" fi } main() { local os=$(detect_os) local arch=$(detect_arch) log_info "$(msg installer "${VERSION}")" log_info "$(msg platform "${os}/${arch}")" if can_use_zimaos_raw "$os" "$arch"; then local is_update=0 log_info "$(msg raw_detected)" if is_blue_service_active || pgrep -x "$PROCESS_NAME" >/dev/null 2>&1; then is_update=1 log_info "$(msg stopping)" stop_blue_for_raw fi if ! install_or_update_zimaos_raw; then if [ $is_update -eq 1 ]; then log_warn "$(msg raw_restore_previous)" if ! start_blue_after_raw; then log_error "$(msg raw_restore_failed)" fi fi exit 1 fi log_info "$(msg starting)" if ! start_blue_after_raw; then log_error "$(msg raw_start_failed)" if rollback_zimaos_raw; then if [ $is_update -eq 1 ]; then log_warn "$(msg raw_restore_previous)" if ! start_blue_after_raw; then log_error "$(msg raw_restore_failed)" fi fi else log_error "$(msg raw_restore_failed)" fi exit 1 fi cleanup_zimaos_raw_backup USED_ZIMAOS_RAW=1 if [ $is_update -eq 1 ]; then log_ok "$(msg updated "${VERSION}")" else log_ok "$(msg installed "${VERSION}")" fi else if is_zimaos && [ "$os" = "linux" ] && [ -n "$ZIMAOS_RAW_FALLBACK_REASON" ]; then log_warn "$(msg raw_fallback "$ZIMAOS_RAW_FALLBACK_REASON")" fi install_or_update_standard "$os" "$arch" fi local url="http://localhost" printf "${BLUE_C}[INFO]${NC} $(msg open_prompt "$url")" read -r open_ack || true case "$(uname -s)" in Darwin*) open "$url" ;; *) xdg-open "$url" 2>/dev/null || log_warn "$(msg open_fallback "$url")" ;; esac if [ "$USED_ZIMAOS_RAW" -ne 1 ]; then case ":$PATH:" in *":$INSTALL_DIR:"*) ;; *) log_warn "$(msg path_warn "$INSTALL_DIR")" log_warn "$(msg path_hint)" ;; esac fi } main