#!/bin/sh : "${REPO_API_URL:=https://api.github.com/repos/caidaoli/ccload/releases/latest}" : "${RUNTIME_DIR:=/tmp/runtime}" : "${RELEASES_DIR:=$RUNTIME_DIR/releases}" : "${CURRENT_LINK:=$RUNTIME_DIR/current}" : "${CURRENT_TAG_FILE:=$RUNTIME_DIR/current.tag}" : "${DOWNLOADS_DIR:=$RUNTIME_DIR/downloads}" : "${SUPERVISOR_CONF:=/etc/supervisord.conf}" : "${SUPERVISOR_PROGRAM_NAME:=app}" : "${SUPERVISOR_UPDATER_PROGRAM_NAME:=${SUPERVISOR_PROGRAM_NAME}-updater}" : "${CURRENT_BINARY_NAME:=app}" : "${APP_BINARY_NAMES:=}" log() { timestamp=$(date '+%Y-%m-%d %H:%M:%S %z') printf '[%s] %s\n' "$timestamp" "$*" >&2 } tag_to_dir_name() { printf '%s\n' "$1" | tr '/:' '__' } release_dir_for_tag() { printf '%s/%s\n' "$RELEASES_DIR" "$(tag_to_dir_name "$1")" } ensure_runtime_dirs() { mkdir -p "$RUNTIME_DIR" "$RELEASES_DIR" "$DOWNLOADS_DIR" } current_tag() { if [ -f "$CURRENT_TAG_FILE" ]; then tr -d '\n' < "$CURRENT_TAG_FILE" fi } current_binary_path() { printf '%s/%s\n' "$CURRENT_LINK" "$CURRENT_BINARY_NAME" } fetch_latest_release_info() { response=$(curl -fsSL "$REPO_API_URL") || return 1 tag_name=$( printf '%s\n' "$response" \ | grep -Eo '"tag_name"[[:space:]]*:[[:space:]]*"[^"]+"' \ | head -n 1 \ | cut -d '"' -f 4 \ || true ) asset_url=$( printf '%s\n' "$response" \ | grep -Eo '"browser_download_url"[[:space:]]*:[[:space:]]*"[^"]*"' \ | cut -d '"' -f 4 \ | grep -E '/[^/]*linux[-_]amd64(\.tar\.gz)?$' \ | head -n 1 \ || true ) if [ -z "$tag_name" ] || [ -z "$asset_url" ]; then return 1 fi printf '%s|%s\n' "$tag_name" "$asset_url" } load_latest_release() { release_info=$(fetch_latest_release_info) || return 1 LATEST_TAG=${release_info%%|*} LATEST_ASSET_URL=${release_info#*|} } find_release_binary() { release_dir=$1 for name in $APP_BINARY_NAMES; do if [ -f "$release_dir/$name" ]; then printf '%s\n' "$release_dir/$name" return 0 fi done for name in $APP_BINARY_NAMES; do binary_path=$(find "$release_dir" -maxdepth 3 -type f -name "$name" | head -n 1) if [ -n "$binary_path" ]; then printf '%s\n' "$binary_path" return 0 fi done file_list=$(find "$release_dir" -maxdepth 3 -type f ! -name '._*' ! -path '*/__MACOSX/*' | sort) first_file=$(printf '%s\n' "$file_list" | sed -n '1p') second_file=$(printf '%s\n' "$file_list" | sed -n '2p') if [ -n "$first_file" ] && [ -z "$second_file" ]; then printf '%s\n' "$first_file" return 0 fi return 1 } activate_release() { tag_name=$1 release_dir=$2 rm -f "$CURRENT_LINK" ln -s "$release_dir" "$CURRENT_LINK" printf '%s\n' "$tag_name" > "$CURRENT_TAG_FILE" } extract_release_binary() { source_dir=$1 target_path=$2 binary_path=$(find_release_binary "$source_dir" || true) if [ -z "$binary_path" ]; then return 1 fi if [ "$binary_path" != "$target_path" ]; then cp "$binary_path" "$target_path" fi chmod +x "$target_path" } cleanup_workspace() { workspace=$1 if [ -n "$workspace" ] && [ -d "$workspace" ]; then rm -rf "$workspace" fi } install_release() { tag_name=$1 asset_url=$2 ensure_runtime_dirs release_dir=$(release_dir_for_tag "$tag_name") release_binary="$release_dir/$CURRENT_BINARY_NAME" if [ -x "$release_binary" ]; then activate_release "$tag_name" "$release_dir" return 0 fi workspace=$(mktemp -d "$RUNTIME_DIR/install.XXXXXX") || return 1 download_name=$(basename "$asset_url") if [ -z "$download_name" ] || [ "$download_name" = "/" ] || [ "$download_name" = "." ]; then download_name="release.asset" fi download_path="$DOWNLOADS_DIR/$download_name" stage_dir="$workspace/stage" rm -f "$download_path" || { cleanup_workspace "$workspace" return 1 } mkdir -p "$stage_dir" || { cleanup_workspace "$workspace" return 1 } curl -fsSL "$asset_url" -o "$download_path" || { cleanup_workspace "$workspace" return 1 } case "$download_path" in *.tar.gz) tar -xzf "$download_path" -C "$stage_dir" || { cleanup_workspace "$workspace" return 1 } extract_release_binary "$stage_dir" "$stage_dir/$CURRENT_BINARY_NAME" || { cleanup_workspace "$workspace" return 1 } ;; *) cp "$download_path" "$stage_dir/$CURRENT_BINARY_NAME" || { cleanup_workspace "$workspace" return 1 } chmod +x "$stage_dir/$CURRENT_BINARY_NAME" || { cleanup_workspace "$workspace" return 1 } ;; esac rm -rf "$release_dir" || { cleanup_workspace "$workspace" return 1 } mv "$stage_dir" "$release_dir" || { cleanup_workspace "$workspace" return 1 } cleanup_workspace "$workspace" activate_release "$tag_name" "$release_dir" } ensure_initial_release() { if load_latest_release; then if install_release "$LATEST_TAG" "$LATEST_ASSET_URL"; then return 0 fi log "警告: 安装最新版本失败,尝试使用已有版本继续启动" fi [ -x "$(current_binary_path)" ] } restart_managed_service() { supervisorctl -c "$SUPERVISOR_CONF" restart "$SUPERVISOR_PROGRAM_NAME" } update_if_needed() { load_latest_release || { log "检查最新版本失败" return 1 } installed_tag=$(current_tag) if [ "$LATEST_TAG" = "$installed_tag" ]; then log "当前已是最新版本: $LATEST_TAG" return 0 fi log "发现新版本: ${installed_tag:-} -> $LATEST_TAG" install_release "$LATEST_TAG" "$LATEST_ASSET_URL" || { log "安装新版本失败: $LATEST_TAG" return 1 } if [ -n "$installed_tag" ]; then restart_managed_service fi }