linux_command
一键克隆、构建 linux-command 项目,自动生成 Docker 镜像与 compose 配置并启动服务,输出可直接访问的地址。
一键脚本
bash <(curl -sL gitee.com/meimolihan/linux-command_sh/raw/master/linux_command.sh)
效果预览
脚本源码
#!/bin/bash
set -uo pipefail
gl_hui='\033[38;5;59m'
gl_hong='\033[38;5;9m'
gl_lv='\033[38;5;10m'
gl_huang='\033[38;5;11m'
gl_lan='\033[38;5;32m'
gl_bai='\033[38;5;15m'
gl_zi='\033[38;5;13m'
gl_bufan='\033[38;5;14m'
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; exit 1; }
handle_invalid_input() {
echo -ne "\r${gl_huang}无效的输入,请重新输入! ${gl_zi} 1 ${gl_huang} 秒后返回"
sleep 1
echo -e "\r${gl_lv}无效的输入,请重新输入! ${gl_zi}0${gl_lv} 秒后返回"
sleep 0.5
return 2
}
handle_y_n() {
echo -e "${gl_hong}无效的选择,请输入 ${gl_bai}(${gl_lv}y${gl_bai}或${gl_hong}N${gl_bai})${gl_hong}。${gl_bai}"
sleep 1
echo -e "${gl_huang}无效的选择,请输入 ${gl_bai}(${gl_lv}y${gl_bai}或${gl_hong}N${gl_bai})${gl_huang}。${gl_bai}"
sleep 1
echo -e "${gl_lv}无效的选择,请输入 ${gl_bai}(${gl_lv}y${gl_bai}或${gl_hong}N${gl_bai})${gl_lv}。${gl_bai}"
sleep 0.5
return 2
}
break_end() {
echo -e "${gl_lv}操作完成${gl_bai}"
echo -e "${gl_bai}按任意键退出${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai} \c"
read -r -n 1 -s -p ""
echo ""
}
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() {
echo ""
echo -ne "${gl_hong}感谢使用,再见!${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
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 0.6
echo ""
clear
}
docker_check_env() {
if ! command -v docker &>/dev/null; then
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
log_info "正在检查 Docker 运行环境${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
log_warn "Docker 未安装,即将自动安装 Docker 环境${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
bash <(curl -sL gitee.com/meimolihan/linux-command_sh/raw/master/linux_install_docker.sh)
if ! command -v docker &>/dev/null; then
log_error "Docker 安装失败,请手动安装后重试!"
sleep 1
exit 1
fi
log_ok "Docker 安装成功!"
fi
if ! command -v docker-compose &>/dev/null; then
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
log_info "正在检查 Docker Compose 环境${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
log_warn "Docker Compose 未安装,即将自动安装${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
bash <(curl -sL gitee.com/meimolihan/linux-command_sh/raw/master/linux_install_compose.sh)
if ! command -v docker-compose &>/dev/null; then
log_error "Docker Compose 安装失败,请手动安装后重试!"
sleep 1
exit 1
fi
log_ok "Docker Compose 安装成功!"
fi
}
install() {
[[ $# -eq 0 ]] && {
log_error "未提供软件包参数!"
return 1
}
local pkg mgr ver cmd_ver pkg_ver installed=false
for pkg in "$@"; do
installed=false
ver=""
if command -v "$pkg" &>/dev/null; then
cmd_ver=$("$pkg" --version 2>/dev/null | head -n1 | tr -cd '[:print:]' | grep -oE '[0-9]+(\.[0-9]+)+' | head -n1 || echo "")
[[ -n "$cmd_ver" ]] && ver="$cmd_ver"
installed=true
fi
if [[ "$pkg" == "7zip" || "$pkg" == "7z" ]]; then
if command -v 7z &>/dev/null; then
ver=$(7z 2>&1 | grep -oE '[0-9]+(\.[0-9]+)+' | head -n1 || echo "")
[[ -n "$ver" ]] && installed=true
fi
fi
if [[ "$installed" == false ]]; then
if command -v opkg &>/dev/null; then
if opkg list-installed | grep -q "^${pkg} "; then
installed=true
ver=$(opkg list-installed | grep "^${pkg} " | awk '{print $3}' 2>/dev/null || echo "")
fi
elif command -v dpkg-query &>/dev/null; then
if dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "install ok installed"; then
installed=true
ver=$(dpkg-query -W -f='${Version}' "$pkg" 2>/dev/null || echo "")
fi
elif command -v rpm &>/dev/null; then
if rpm -q "$pkg" &>/dev/null; then
installed=true
ver=$(rpm -q --qf '%{VERSION}' "$pkg" 2>/dev/null || echo "")
fi
elif command -v apk &>/dev/null; then
if apk info "$pkg" 2>/dev/null | grep -q "^installed"; then
installed=true
ver=$(apk info -a "$pkg" 2>/dev/null | grep -oE '[0-9]+(\.[0-9]+)+' | head -n1 || echo "")
fi
elif command -v pacman &>/dev/null; then
if pacman -Qi "$pkg" &>/dev/null; then
installed=true
ver=$(pacman -Qi "$pkg" 2>/dev/null | grep -i "version" | grep -oE '[0-9]+(\.[0-9]+)+' | head -n1 || echo "")
fi
fi
fi
if [[ "$installed" == true ]]; then
echo -e "${gl_huang}${pkg}${gl_bai} ${gl_lv}已安装${gl_bai}" \
"$([[ -n "$ver" ]] && echo "版本 ${gl_lv}${ver}${gl_bai}")"
continue
fi
echo -e ""
echo -e "${gl_huang}开始安装:${gl_bai}${pkg}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
local install_success=false
for mgr in opkg dnf yum apt apk pacman zypper pkg; do
if ! command -v "$mgr" &>/dev/null; then
continue
fi
case $mgr in
opkg)
echo -e "${gl_bai}使用包管理器: ${gl_zi}opkg (OpenWrt/iStoreOS)${gl_bai}"
if [[ "$pkg" == "7zip" || "$pkg" == "7z" ]]; then
echo -e "${gl_bai}正在安装: ${gl_lv}p7zip${gl_bai}"
opkg update && opkg install p7zip && install_success=true
else
opkg update && opkg install "$pkg" && install_success=true
fi
;;
dnf)
echo -e "${gl_bai}使用包管理器: ${gl_zi}dnf (Fedora/RHEL)${gl_bai}"
dnf -y update && dnf install -y "$pkg" && install_success=true
;;
yum)
echo -e "${gl_bai}使用包管理器: ${gl_zi}yum (CentOS/RHEL)${gl_bai}"
yum -y update && yum install -y "$pkg" && install_success=true
;;
apt)
echo -e "${gl_bai}使用包管理器: ${gl_zi}apt (Debian/Ubuntu)${gl_bai}"
apt update -y && apt install -y "$pkg" && install_success=true
;;
apk)
echo -e "${gl_bai}使用包管理器: ${gl_zi}apk (Alpine)${gl_bai}"
apk update && apk add "$pkg" && install_success=true
;;
pacman)
echo -e "${gl_bai}使用包管理器: ${gl_zi}pacman (Arch/Manjaro)${gl_bai}"
pacman -Syu --noconfirm && pacman -S --noconfirm "$pkg" && install_success=true
;;
zypper)
echo -e "${gl_bai}使用包管理器: ${gl_zi}zypper (openSUSE)${gl_bai}"
zypper refresh && zypper install -y "$pkg" && install_success=true
;;
pkg)
echo -e "${gl_bai}使用包管理器: ${gl_zi}pkg (FreeBSD)${gl_bai}"
pkg update && pkg install -y "$pkg" && install_success=true
;;
esac
[[ "$install_success" == true ]] && break
done
if [[ "$install_success" == true ]]; then
echo -e "${gl_lv}✓ ${pkg} 安装成功${gl_bai}"
else
echo -e "${gl_hong}✗ ${pkg} 安装失败${gl_bai}"
fi
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
done
}
column_if_available() {
if command -v column &> /dev/null; then
column -t -s $'\t'
else
cat
fi
}
docker-ps-cn() {
{
gl_hui=$'\033[38;5;59m'
gl_hong=$'\033[38;5;9m'
gl_lv=$'\033[38;5;10m'
gl_huang=$'\033[38;5;11m'
gl_lan=$'\033[38;5;32m'
gl_bai=$'\033[38;5;15m'
gl_zi=$'\033[38;5;13m'
gl_bufan=$'\033[38;5;14m'
reset=$'\033[0m'
local filter_name="${1:-linux-command}"
printf "%s%s\t%s\t%s\t%s\t%s\t%s%s\n" "$gl_hui" "容器ID" "名称" "状态" "端口" "创建时间" "镜像" "$reset"
printf "%s%s\t%s\t%s\t%s\t%s\t%s%s\n" "$gl_hui" "----------" "----------" "----------" "----------" "----------" "----------" "$reset"
docker ps --filter "name=${filter_name}" --format "{{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}\t{{.RunningFor}}\t{{.Image}}" | \
awk -v green="$gl_lv" -v yellow="$gl_huang" -v cyan="$gl_bufan" -v blue="$gl_lan" -v white="$gl_bai" -v reset="$reset" '
BEGIN {FS="\t"; OFS="\t"}
{
id = substr($1, 1, 12)
name = $2
status = $3
ports = $4
time = $5
image = $6
gsub(/ years ago/, "年前", time)
gsub(/ year ago/, "年前", time)
gsub(/ months ago/, "个月前", time)
gsub(/ month ago/, "个月前", time)
gsub(/ weeks ago/, "周前", time)
gsub(/ week ago/, "周前", time)
gsub(/ days ago/, "天前", time)
gsub(/ day ago/, "天前", time)
gsub(/ hours ago/, "小时前", time)
gsub(/ hour ago/, "小时前", time)
gsub(/ minutes ago/, "分钟前", time)
gsub(/ minute ago/, "分钟前", time)
gsub(/ seconds ago/, "秒前", time)
gsub(/ second ago/, "秒前", time)
gsub(/About /, "", time)
print cyan id reset, green name reset, yellow status reset, blue ports reset, white time reset, gl_bai image reset
}'
} | column_if_available
}
DEFAULT_TAG="latest"
DEFAULT_DIR="/vol1/1000/compose/linux-command"
DEFAULT_PORT="9665"
REPO_URL="https://github.com/meimolihan/linux-command.git"
PROJECT_DIR="/tmp/linux-command"
check_node_env() {
echo -e ""
echo -e "${gl_huang}>>> 检查 Node.js 环境${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
if command -v node >/dev/null 2>&1 && command -v npm >/dev/null 2>&1 && command -v git >/dev/null 2>&1; then
log_ok "Node.js 已安装:${gl_lv}$(node -v)${gl_bai}"
log_ok "npm 已安装:${gl_lv}$(npm -v)${gl_bai}"
log_ok "git 安装完成:${gl_lv}$(git -v)${gl_bai}"
log_info "检测到已存在 Node.js 环境,跳过安装步骤"
return 0
fi
log_info "未检测到 Node.js,开始自动安装 Node.js 20"
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
install -y nodejs || log_error "Node.js 安装失败"
log_ok "Node.js 安装完成:${gl_lv}$(node -v)${gl_bai}"
log_ok "npm 安装完成:${gl_lv}$(npm -v)${gl_bai}"
log_ok "git 安装完成:${gl_lv}$(git -v)${gl_bai}"
}
clean_old_container() {
echo -e "${gl_huang}>>> 清理旧容器${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
local container_name="linux-command"
local compose_container="command"
if docker ps -a --filter "name=^/${container_name}$" | grep -q "${container_name}"; then
log_info "检测到旧容器 ${gl_huang}${container_name}${gl_bai},正在停止并删除${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
docker stop "${container_name}" >/dev/null 2>&1
docker rm -f "${container_name}" >/dev/null 2>&1
log_ok "旧容器 ${container_name} 清理完成"
fi
if docker ps -a --filter "name=^/${compose_container}$" | grep -q "${compose_container}"; then
log_info "检测到旧容器 ${gl_huang}${compose_container}${gl_bai},正在停止并删除${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
docker stop "${compose_container}" >/dev/null 2>&1
docker rm -f "${compose_container}" >/dev/null 2>&1
log_ok "旧容器 ${compose_container} 清理完成"
fi
log_info "清理Docker残留资源${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
docker container prune -f >/dev/null 2>&1
docker network prune -f >/dev/null 2>&1
log_ok "所有旧容器/残留清理完成,无名称冲突"
}
main() {
install git
clear
echo -e "${gl_zi}>>> linux-command 一键部署脚本${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
docker_check_env
if [ "$(id -u)" -ne 0 ]; then
log_error "请使用 root 权限运行脚本"
fi
echo -e ""
echo -e "${gl_huang}>>> 克隆 ${gl_lv}linux-command${gl_bai} ${gl_huang}项目${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
echo -e "${gl_bai}项目地址:${gl_lv}https://github.com/meimolihan/linux-command${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
log_info "开始克隆项目到 ${gl_huang}${PROJECT_DIR}${gl_bai}"
rm -rf "${PROJECT_DIR}"
git clone "${REPO_URL}" "${PROJECT_DIR}" || log_error "项目克隆失败"
cd "${PROJECT_DIR}" || log_error "进入项目目录失败"
log_ok "项目克隆完成"
check_node_env
echo -e ""
echo -e "${gl_huang}>>> 安装项目依赖并构建项目${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
npm install || log_error "依赖安装失败"
log_ok "依赖安装完成"
log_info "构建项目生成 .deploy 目录"
npm run build || log_error "项目构建失败"
log_ok "项目构建完成"
echo -e ""
echo -e "${gl_huang}>>> 构建 Docker 镜像${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
read -r -e -p "$(echo -e "${gl_bai}请输入 Docker 镜像标签 [默认:${gl_huang}${DEFAULT_TAG}${gl_bai}]: ")" INPUT_TAG
IMAGE_TAG="${INPUT_TAG:-${DEFAULT_TAG}}"
IMAGE_NAME="mobufan/linux-command:${IMAGE_TAG}"
log_info "构建镜像:${gl_huang}${IMAGE_NAME}${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
docker build -t "${IMAGE_NAME}" . || log_error "镜像构建失败"
log_ok "镜像构建成功"
echo -e ""
echo -e "${gl_huang}>>> 部署 Docker 容器${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
read -r -e -p "$(echo -e "${gl_bai}请输入部署目录 [默认:${gl_huang}${DEFAULT_DIR}${gl_bai}]: ")" INPUT_DIR
COMPOSE_DIR="${INPUT_DIR:-${DEFAULT_DIR}}"
mkdir -p "${COMPOSE_DIR}" || log_error "目录创建失败"
cd "${COMPOSE_DIR}" || log_error "进入部署目录失败"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
read -r -e -p "$(echo -e "${gl_bai}请输入映射端口 [默认:${gl_huang}${DEFAULT_PORT}${gl_bai}]: ")" INPUT_PORT
HOST_PORT="${INPUT_PORT:-${DEFAULT_PORT}}"
log_info "生成 docker-compose.yml"
cat > docker-compose.yml << EOF
services:
linux-command:
image: ${IMAGE_NAME}
container_name: linux-command
ports:
- ${HOST_PORT}:3000
restart: always
EOF
log_ok "配置文件生成完成"
echo -e ""
clean_old_container
echo -e ""
echo -e "${gl_huang}>>> 启动 Docker 容器${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
read -r -e -p "$(echo -e "${gl_bai}是否启动 Docker 容器(${gl_lv}y${gl_bai}/${gl_hong}N${gl_bai}): ")" START_CHOICE
case "${START_CHOICE}" in
[Yy])
log_info "尝试启动容器 (docker compose up -d)${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
if docker compose up -d; then
log_ok "容器启动成功"
else
log_warn "docker compose 启动失败,尝试兼容版 docker-compose${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
docker-compose up -d || log_error "容器启动失败"
log_ok "容器启动成功"
fi
;;
[Nn])
cancel_return "主菜单"
return
;;
*) handle_y_n ;;
esac
echo -e ""
echo -e "${gl_huang}>>> 容器运行状态${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
docker-ps-cn linux-command
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
log_ok "部署完成!"
LOCAL_IP=$(hostname -I | awk '{print $1}')
log_info "访问地址:${gl_lv}http://${LOCAL_IP}:${HOST_PORT}${gl_bai}"
log_info "部署目录:${gl_huang}${COMPOSE_DIR}${gl_bai}"
log_info "使用镜像:${gl_huang}${IMAGE_NAME}${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
log_info "开始清理临克隆项目目录:${gl_huang}${PROJECT_DIR}${gl_bai}"
rm -rf "${PROJECT_DIR}"
if [ ! -d "${PROJECT_DIR}" ]; then
log_ok "临时目录清理成功"
else
log_warn "临时目录清理失败,请手动检查"
fi
echo -e "${gl_bufan}————————————————————————————————————————————————${gl_bai}"
break_end
}
main
exit_script
一键完全卸载命令
# 停止并删除容器 + 删除镜像 + 删除部署目录(按需修改)
docker rm -f linux-command && docker rmi -f mobufan/linux-command:latest && rm -rf /vol1/1000/compose/linux-command
创建本地脚本
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"