随机
Enter 搜索 ↑↓ 切换 Esc 清空

compose_all_up

脚本

批量启动 Docker Compose 项目

一键脚本

bash <(curl -sL gitee.com/meimolihan/cmdbox/raw/master/sh/compose_all_up.sh) /vol1/1000/compose
bash <(curl -sL ../sh/compose_all_up.sh) /vol1/1000/compose
模式 命令 说明
默认模式 ./脚本名.sh 不传参,交互式选择工作目录
指定目录 ./脚本名.sh /vol1/1000/compose 启动指定目录下所有 Compose 项目
指定项目 ./脚本名.sh /vol1/1000/compose 项目A 项目B 仅启动指定项目,其余项目不动
自动确认 ./脚本名.sh -y /compose -y 可放在任意位置,跳过确认直接启动
帮助 ./脚本名.sh -h 查看帮助

效果预览

补充说明

该脚本用于交互式批量启动指定目录下的 Docker Compose 项目,基于 docker-compose/docker compose 命令实现,支持一键启动全部项目,也可指定项目名精确定位启动,适合需要一键上线多个容器项目的场景。

功能特点

输出说明

脚本输出包含以下字段:

字段 说明
工作目录 显示当前操作的 Compose 项目根目录
待处理项目列表 指定项目模式下显示要启动的项目名
扫描到的项目 列出扫描到的项目目录及运行状态(运行中 / 已停止)
推荐的工作目录 标记含未运行项目的预设目录(★ 推荐)
项目状态 显示每个项目的启动结果(✓ 已启动 / 启动失败)
启动完成统计 总计项目、成功启动数

注意事项

用法示例

脚本源码

#!/bin/bash

list_color_init() {
    export gl_hui=$'\033[38;5;59m'
    export gl_hong=$'\033[38;5;9m'
    export gl_lv=$'\033[38;5;10m'
    export gl_huang=$'\033[38;5;11m'
    export gl_lan=$'\033[38;5;32m'
    export gl_bai=$'\033[38;5;15m'
    export gl_zi=$'\033[38;5;13m'
    export gl_bufan=$'\033[38;5;14m'
    export reset=$'\033[0m'
}

list_color_init

log_info()  { echo -e "${gl_lan}[信息]${gl_bai} $*"; }
log_ok()    { echo -e "${gl_lv}[成功]${gl_bai} $*"; }
log_warn()  { echo -e "${gl_huang}[警告]${gl_bai} $*"; }
log_error() { echo -e "${gl_hong}[错误]${gl_bai} $*" >&2; }

sleep_fractional() {
    local seconds=$1
    if sleep "$seconds" 2>/dev/null; then
        return 0
    fi

    if command -v perl >/dev/null 2>&1; then
        perl -e "select(undef, undef, undef, $seconds)"
        return 0
    fi

    if command -v python3 >/dev/null 2>&1; then
        python3 -c "import time; time.sleep($seconds)"
        return 0
    elif command -v python >/dev/null 2>&1; then
        python -c "import time; time.sleep($seconds)"
        return 0
    fi

    local int_seconds=$(echo "$seconds" | awk '{print int($1+0.999)}')
    sleep "$int_seconds"
}

exit_animation() {
    echo -ne "${gl_lv}即将退出 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.5
    echo -ne "${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}\c"
    sleep_fractional 0.6
    echo ""
    clear
}

exit_script() {
    local frames=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
    local dots=(
        "${gl_hong}."
        "${gl_huang}."
        "${gl_lv}."
        "${gl_bufan}."
        "${gl_zi}."
    )
    local dot_buffer=""
    local frame_len=${#frames[@]}
    local dot_idx=0
    local total_dots=${#dots[@]}
    for ((i=0; i<20; i++)); do
        if (( i > 0 && i % 3 == 0 && dot_idx < total_dots )); then
            dot_buffer+=${dots[$dot_idx]}
            ((dot_idx++))
        fi
        echo -ne "\r\033[K${gl_bufan}${frames[i % frame_len]}${gl_bai} 正在退出 ${dot_buffer}"
        sleep_fractional 0.06
    done
    echo -e "\r\033[K${gl_lv}✓${gl_bai} 成功退出\n"
    clear
    exit 0
}

cancel_return() {
    local menu_name="${1:-上一级选单}"
    echo -e "${gl_lv}即将返回到 ${gl_huang}${menu_name}${gl_lv} ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai} \c"
    sleep_fractional 0.6
    echo ""
    clear
}

start_selected_compose_projects() {
    local COMPOSE_WORK_DIR="$1"
    shift
    local PROJECT_LIST=("$@")
    local SKIP_CONFIRM="${PROJECT_LIST[-1]}"
    unset PROJECT_LIST[-1]

    clear
    echo -e ""
    echo -e "${gl_zi}>>> 启动指定 Compose 项目${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    if ! docker info &>/dev/null; then
        log_error "Docker 服务未运行"
        exit_animation
        return 1
    fi

    if [[ ! -d "$COMPOSE_WORK_DIR" ]]; then
        log_error "目录不存在: $COMPOSE_WORK_DIR"
        exit_animation
        return 1
    fi

    echo -e "${gl_bai}工作目录: ${gl_huang}$COMPOSE_WORK_DIR${gl_bai}"
    echo -e "${gl_bai}待处理项目列表: ${gl_huang}${PROJECT_LIST[*]}${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    if [[ "$SKIP_CONFIRM" != "true" ]]; then
        read -r -e -p "$(echo -e "${gl_hong}警告: ${gl_bai}确定启动上面列出项目吗?(${gl_lv}y${gl_bai}/${gl_hong}N${gl_bai}): ")" confirm
        if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
            echo -e "${gl_huang}已取消${gl_bai}"
            exit_animation
            return
        fi
    fi

    local started_count=0
    local warn_count=0
    local fail_count=0
    local total_count=${#PROJECT_LIST[@]}

    for proj in "${PROJECT_LIST[@]}"; do
        local proj_dir="${COMPOSE_WORK_DIR}/${proj}"
        local yml_file=""

        if [[ -f "${proj_dir}/docker-compose.yml" ]]; then
            yml_file="${proj_dir}/docker-compose.yml"
        elif [[ -f "${proj_dir}/docker-compose.yaml" ]]; then
            yml_file="${proj_dir}/docker-compose.yaml"
        else
            log_warn "项目 ${gl_huang}$proj${gl_bai} 目录下找不到 docker-compose.yml / yaml,跳过"
            ((warn_count++))
            continue
        fi

        echo -e ""
        echo -e "${gl_huang}>>> 启动项目: ${gl_lv}$proj${gl_bai}"
        local out
        local ret

        if command -v docker-compose &>/dev/null; then
            out=$(docker-compose -f "$yml_file" up -d)
            ret=$?
        elif docker compose version &>/dev/null; then
            out=$(docker compose -f "$yml_file" up -d)
            ret=$?
        else
            log_error "未找到 docker-compose / docker compose 命令"
            return 1
        fi

        [[ -n "$out" ]] && echo "$out"

        if [[ $ret -eq 0 ]]; then
            log_ok "已完成: $proj"
            ((started_count++))
        else
            log_error "项目 $proj up 执行失败"
            ((fail_count++))
        fi
        echo -e ""
    done

    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    echo -e "${gl_bai}任务结束${gl_bai}"
    echo -e "${gl_bai}总计项目: ${gl_huang}$total_count${gl_bai}"
    echo -e "${gl_lv}成功启动: $started_count${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    if [[ "$SKIP_CONFIRM" == "true" ]]; then
        return
    fi

    echo -e "${gl_bai}按任意键继续 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai} \c"
    read -r -n 1 -s -r -p ""
    echo ""
    clear
}

start_all_compose_projects() {
    local COMPOSE_WORK_DIR="$1"
    local SKIP_CONFIRM="${2:-false}"

    clear
    echo -e ""
    echo -e "${gl_zi}>>> 启动所有 Compose 项目${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    if ! docker info &>/dev/null; then
        log_error "Docker 服务未运行"
        exit_animation
        return 1
    fi

    if [[ ! -d "$COMPOSE_WORK_DIR" ]]; then
        log_error "目录不存在: $COMPOSE_WORK_DIR"
        exit_animation
        return 1
    fi

    echo -e "${gl_bai}工作目录: ${gl_huang}$COMPOSE_WORK_DIR${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    if [[ "$SKIP_CONFIRM" != "true" ]]; then
        read -r -e -p "$(echo -e "${gl_hong}警告: ${gl_bai}确定启动 ${gl_huang}$COMPOSE_WORK_DIR${gl_bai} 下的所有 Compose 项目吗?(${gl_lv}y${gl_bai}/${gl_hong}N${gl_bai}): ")" confirm
        if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
            echo -e "${gl_huang}已取消${gl_bai}"
            exit_animation
            return
        fi
    fi

    if ! cd "$COMPOSE_WORK_DIR" 2>/dev/null; then
        log_error "无法进入目录: $COMPOSE_WORK_DIR"
        exit_animation
        return 1
    fi

    local started_count=0
    local total_count=0

    echo -e "${gl_bai}正在扫描 ${gl_huang}$COMPOSE_WORK_DIR${gl_bai} 下的 Compose 项目${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    if [[ -f "docker-compose.yml" ]] || [[ -f "docker-compose.yaml" ]]; then
        ((total_count++))
        local current_dir_name=$(basename "$(pwd)")
        echo -e ""
        echo -e "${gl_huang}>>> 启动项目: ${gl_lv}$current_dir_name${gl_bai} (当前目录)"

        local out
        local ret
        if command -v docker-compose &>/dev/null; then
            out=$(docker-compose up -d)
            ret=$?
        elif docker compose version &>/dev/null; then
            out=$(docker compose up -d)
            ret=$?
        else
            log_error "未找到 docker-compose 命令"
            return 1
        fi
        [[ -n "$out" ]] && echo "$out"

        if [[ $ret -eq 0 ]]; then
            log_ok "已启动: $current_dir_name"
            ((started_count++))
        else
            log_error "启动失败: $current_dir_name"
        fi
    fi

    for dir in */; do
        if [[ -d "$dir" ]]; then
            cd "$dir" 2>/dev/null || continue
            if [[ -f "docker-compose.yml" ]] || [[ -f "docker-compose.yaml" ]]; then
                ((total_count++))
                local project_name=$(basename "$dir")
                echo -e ""
                echo -e "${gl_huang}>>> 启动项目: ${gl_lv}$project_name${gl_bai}"

                local out
                local ret
                if command -v docker-compose &>/dev/null; then
                    out=$(docker-compose up -d)
                    ret=$?
                elif docker compose version &>/dev/null; then
                    out=$(docker compose up -d)
                    ret=$?
                else
                    log_error "未找到 docker-compose 命令"
                    cd ..
                    continue
                fi
                [[ -n "$out" ]] && echo "$out"

                if [[ $ret -eq 0 ]]; then
                    log_ok "已启动: $project_name"
                    ((started_count++))
                else
                    log_error "启动失败: $project_name"
                fi
            fi
            cd .. 2>/dev/null
        fi
    done

    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    if [[ $total_count -eq 0 ]]; then
        echo -e "${gl_huang}在 ${gl_hong}$COMPOSE_WORK_DIR${gl_huang} 中没有找到 Compose 项目${gl_bai}"
    else
        echo -e "${gl_bai}启动完成${gl_bai}"
        echo -e "${gl_bai}总计项目: ${gl_huang}$total_count${gl_bai}"
        echo -e "${gl_lv}成功启动: $started_count${gl_bai}"
    fi
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    if [[ "$SKIP_CONFIRM" == "true" ]]; then
        return
    fi

    echo -e "${gl_bai}按任意键继续 ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai} \c"
    read -r -n 1 -s -r -p ""
    echo ""
    clear
}

interactive_start() {
    clear
    echo -e ""
    echo -e "${gl_zi}>>> 启动所有 Compose 项目${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    if ! docker info &>/dev/null; then
        log_error "Docker 服务未运行"
        exit_animation
        return 1
    fi

    local preset_dirs=(
        "/compose"
        "/mnt/compose"
        "/vol1/1000/compose"
        "/vol2/1000/compose"
    )
    log_info "正在扫描所有 docker-compose 项目${gl_bai}"
    local all_projects=()

    for preset_dir in "${preset_dirs[@]}"; do
        if [[ -d "$preset_dir" ]]; then
            if [[ -f "$preset_dir/docker-compose.yml" ]] || [[ -f "$preset_dir/docker-compose.yaml" ]]; then
                all_projects+=("$preset_dir")
            fi
            for dir in "$preset_dir"/*/; do
                if [[ -d "$dir" ]] && ([[ -f "$dir/docker-compose.yml" ]] || [[ -f "$dir/docker-compose.yaml" ]]); then
                    all_projects+=("$(realpath "$dir")")
                fi
            done
        fi
    done
    all_projects=($(printf "%s\n" "${all_projects[@]}" | sort -u))

    local running_projects=()
    if command -v docker &>/dev/null && docker info &>/dev/null; then
        while IFS= read -r dir; do
            [[ -z "$dir" ]] && continue
            [[ ! -d "$dir" ]] && continue
            running_projects+=("$(realpath "$dir")")
        done < <(
            docker ps --format '{{.Names}}' 2>/dev/null |
                xargs -I{} sh -c 'docker inspect {} 2>/dev/null | jq -r ".[0].Config.Labels[\"com.docker.compose.project.working_dir\"]" 2>/dev/null || echo ""' |
                sort -u
        )
    fi

    local recommended_dirs=()
    for preset_dir in "${preset_dirs[@]}"; do
        local has_projects=false
        for project in "${all_projects[@]}"; do
            if [[ "$project" == "$preset_dir" ]] || [[ "$project" == "$preset_dir"* ]]; then
                has_projects=true
                break
            fi
        done
        if $has_projects; then
            local all_running=true
            for project in "${all_projects[@]}"; do
                if [[ "$project" == "$preset_dir" ]] || [[ "$project" == "$preset_dir"* ]]; then
                    local is_running=false
                    for running_dir in "${running_projects[@]}"; do
                        if [[ "$running_dir" == "$project" ]]; then
                            is_running=true
                            break
                        fi
                    done
                    if ! $is_running; then
                        all_running=false
                        break
                    fi
                fi
            done
            if ! $all_running; then
                recommended_dirs+=("$preset_dir")
            fi
        fi
    done

    if [[ ${#all_projects[@]} -gt 0 ]]; then
        echo -e "${gl_bai}扫描到的 docker-compose 项目目录:${gl_bai}"
        for dir in "${all_projects[@]}"; do
            local is_running=""
            for running_dir in "${running_projects[@]}"; do
                if [[ "$running_dir" == "$dir" ]]; then
                    is_running=" ${gl_lv}[运行中]${gl_bai}"
                    break
                fi
            done
            [[ -z "$is_running" ]] && is_running=" ${gl_huang}[已停止]${gl_bai}"
            echo -e "  ${gl_lv}•${gl_bai} ${dir}$is_running"
        done
        if [[ ${#recommended_dirs[@]} -gt 0 ]]; then
            echo -e "${gl_bai}推荐的工作目录(有未运行的项目):${gl_bai}"
            for dir in "${recommended_dirs[@]}"; do
                echo -e "  ${gl_hong}★${gl_bai} ${dir}"
            done
        fi
    else
        echo -e "${gl_huang}未找到任何 docker-compose 项目${gl_bai}"
    fi

    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    echo -e ""
    echo -e "${gl_huang}>>> 请选择要启动的工作目录:${gl_bai}"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    for i in "${!preset_dirs[@]}"; do
        local marker=""
        local dir_status=""
        for rd in "${recommended_dirs[@]}"; do
            if [[ "${preset_dirs[i]}" == "$rd" ]]; then
                marker=" ${gl_hong}(推荐)${gl_bai}"
                break
            fi
        done
        if [[ -d "${preset_dirs[i]}" ]]; then
            local compose_count=$(find "${preset_dirs[i]}" -maxdepth 2 -type f \( -name "docker-compose.yml" -o -name "docker-compose.yaml" \) 2>/dev/null | wc -l)
            if [[ $compose_count -gt 0 ]]; then
                local running_count=0
                for project in "${running_projects[@]}"; do
                    if [[ "$project" == "${preset_dirs[i]}" ]] || [[ "$project" == "${preset_dirs[i]}"/* ]]; then
                        running_count=$((running_count + 1))
                    fi
                done
                if [[ $running_count -eq 0 ]]; then
                    dir_status="${gl_huang}[${compose_count}个项目,均未运行]${gl_bai}"
                elif [[ $running_count -eq $compose_count ]]; then
                    dir_status="${gl_lv}[${compose_count}个项目,全部运行中]${gl_bai}"
                else
                    dir_status="${gl_huang}[${compose_count}个项目,${running_count}个运行中]${gl_bai}"
                fi
            else
                dir_status="${gl_huang}[无 compose 项目]${gl_bai}"
            fi
        else
            dir_status="${gl_huang}[目录不存在]${gl_bai}"
        fi
        echo -e "${gl_bufan}$((i + 1)).${gl_bai} ${preset_dirs[i]} $dir_status$marker"
    done

    echo -e "${gl_bufan}$((${#preset_dirs[@]} + 1)).${gl_bai} 手动指定路径"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
    echo -e "${gl_huang}0.${gl_bai} 返回上一级选单"
    echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"

    read -r -e -p "$(echo -e "${gl_bai}请输入你的选择 (${gl_huang}0${gl_bai}-${gl_hong}$((${#preset_dirs[@]} + 1))${gl_bai}): ")" dir_choice
    local COMPOSE_WORK_DIR=""

    if [[ -z "$dir_choice" ]] && [[ ${#recommended_dirs[@]} -gt 0 ]]; then
        COMPOSE_WORK_DIR="${recommended_dirs[0]}"
        echo -e "${gl_bai}使用推荐目录: ${gl_huang}$COMPOSE_WORK_DIR${gl_bai}"
    elif [[ "$dir_choice" =~ ^[0-9]+$ ]]; then
        if [[ $dir_choice -eq 0 ]]; then
            cancel_return "Compose 项目管理"
            return
        elif [[ $dir_choice -le ${#preset_dirs[@]} ]]; then
            COMPOSE_WORK_DIR="${preset_dirs[$((dir_choice - 1))]}"
        elif [[ $dir_choice -eq $((${#preset_dirs[@]} + 1)) ]]; then
            read -r -e -p "$(echo -e "${gl_bai}请输入自定义路径: ")" COMPOSE_WORK_DIR
        else
            log_error "无效选择"
            exit_animation
            return
        fi
    else
        COMPOSE_WORK_DIR="$dir_choice"
    fi

    if [[ -z "$COMPOSE_WORK_DIR" ]]; then
        log_warn "路径不能为空"
        exit_animation
        return
    fi
    if [[ ! -d "$COMPOSE_WORK_DIR" ]]; then
        log_warn "目录不存在: $COMPOSE_WORK_DIR"
        exit_animation
        return
    fi

    start_all_compose_projects "$COMPOSE_WORK_DIR" "false"
}

show_help() {
    echo -e "${gl_lv}使用说明:${gl_bai}"
    echo -e "  ${gl_bai}$0 ${gl_huang}[-y] ${gl_lan}[目录] [项目1 项目2 ...]${gl_bai}"
    echo -e ""
    echo -e "${gl_lv}选项:${gl_bai}"
    echo -e "  ${gl_huang}-y, --yes${gl_bai}      自动确认,跳过提示,可放在任意位置"
    echo -e "  ${gl_huang}-h, --help${gl_bai}    显示此帮助信息"
    echo -e ""
    echo -e "${gl_lv}示例:${gl_bai}"
    echo -e "  ${gl_bai}$0${gl_bai}                             # 不传参,交互式选择目录"
    echo -e "  ${gl_bai}$0 .${gl_bai}                           # 当前目录,交互式确认,启动全部compose"
    echo -e "  ${gl_bai}$0 -y .${gl_bai}                        # 当前目录免确认,全部上线"
    echo -e "  ${gl_bai}$0 /vol1/1000/compose${gl_bai}          # 指定目录交互式启动全部"
    echo -e "  ${gl_bai}$0 /vol1/1000/compose -y${gl_bai}       # 指定目录,-y放末尾免确认"
    echo -e "  ${gl_bai}$0 -y /vol1/1000/compose projA projB${gl_bai} # 仅启动projA projB,免确认"
    echo -e "  ${gl_bai}$0 /vol1/1000/compose projA projB${gl_bai}    # 仅启动projA projB,交互式确认"
    echo -e ""
    exit 0
}

main() {
    local SKIP_CONFIRM="false"
    local positional=()

    for arg in "$@"; do
        case "$arg" in
            -y|--yes)
                SKIP_CONFIRM="true"
                ;;
            -h|--help)
                show_help
                ;;
            *)
                positional+=("$arg")
                ;;
        esac
    done

    if [[ ${#positional[@]} -ge 1 ]]; then
        local TARGET_DIR="${positional[0]}"
        local PROJECTS=("${positional[@]:1}")
        if [[ ${#PROJECTS[@]} -ge 1 ]]; then
            start_selected_compose_projects "$TARGET_DIR" "${PROJECTS[@]}" "$SKIP_CONFIRM"
        else
            start_all_compose_projects "$TARGET_DIR" "$SKIP_CONFIRM"
        fi
    else
        interactive_start
    fi
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi

创建本地脚本

new_script="new_test.sh"

cat > "$new_script" << 'EOF'
#!/bin/bash

# 粘贴脚本源码

EOF

# 保留本地脚本,去掉 rm -f "$new_script"
chmod +x "$new_script" && ./"$new_script" && rm -f "$new_script"

相关命令