From 62e962f216d157bce6cc4a87a3bcf6418b47652f Mon Sep 17 00:00:00 2001 From: yuanyuanxiang <962914132@qq.com> Date: Fri, 22 May 2026 22:02:38 +0200 Subject: [PATCH] doc(linux): Add linux client install.sh & uninstall.sh --- .gitattributes | 4 ++ linux/install.sh | 152 +++++++++++++++++++++++++++++++++++++++++++++ linux/uninstall.sh | 121 ++++++++++++++++++++++++++++++++++++ 3 files changed, 277 insertions(+) create mode 100755 linux/install.sh create mode 100755 linux/uninstall.sh diff --git a/.gitattributes b/.gitattributes index bdb0cab..665b3b7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,10 @@ # Auto detect text files and perform LF normalization * text=auto +# Shell scripts must keep LF line endings even when checked out on Windows, +# otherwise Linux refuses them with "bad interpreter: /usr/bin/env^M". +*.sh text eol=lf + # Custom for Visual Studio *.cs diff=csharp diff --git a/linux/install.sh b/linux/install.sh new file mode 100755 index 0000000..0157698 --- /dev/null +++ b/linux/install.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +# YAMA Ghost (Linux client) — install + autostart deployment +# +# 用法(在解压/克隆后的 linux/ 目录下): +# ./install.sh # 默认安装到 ~/.local/bin/ghost +# ./install.sh /opt/yama # 安装到 /opt/yama/ghost(如需要会自动 sudo) +# +# 行为: +# 1. 复制 ghost 二进制到目标位置并加可执行权 +# 2. 注册 XDG Autostart(~/.config/autostart/ghost.desktop) +# 3. 可选立即启动一次(继承当前桌面会话的 X 环境) + +set -euo pipefail + +# ---- 防止以 root 直接运行 ---- +# 用 sudo 跑会让 $HOME 变成 /root(或 sudo 配置决定的值), +# autostart 写到 /root/.config/autostart/,桌面用户的 session 看不见, +# 自启动完全失效。需要 sudo 的地方(如装到 /opt/...),脚本会按需自调用 sudo。 +if [[ "${EUID:-$(id -u)}" -eq 0 ]]; then + echo "请用普通用户身份运行此脚本,不要 sudo。" >&2 + echo "如目标目录需要 root 权限,脚本会按需自动调用 sudo。" >&2 + exit 1 +fi + +# ---- 颜色 ---- +if [[ -t 1 ]]; then + C_RED=$'\033[31m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m' + C_BLUE=$'\033[34m'; C_BOLD=$'\033[1m'; C_RESET=$'\033[0m' +else + C_RED=''; C_GREEN=''; C_YELLOW=''; C_BLUE=''; C_BOLD=''; C_RESET='' +fi + +info() { echo "${C_BLUE}[INFO]${C_RESET} $*"; } +ok() { echo "${C_GREEN}[ OK ]${C_RESET} $*"; } +warn() { echo "${C_YELLOW}[WARN]${C_RESET} $*"; } +error() { echo "${C_RED}[FAIL]${C_RESET} $*" >&2; } + +# ---- 路径解析 ---- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SRC_BIN="${SCRIPT_DIR}/ghost" + +# 安装目标目录(参数 $1,默认 ~/.local/bin) +INSTALL_DIR="${1:-${HOME}/.local/bin}" +DEST_BIN="${INSTALL_DIR}/ghost" + +AUTOSTART_DIR="${XDG_CONFIG_HOME:-${HOME}/.config}/autostart" +AUTOSTART_FILE="${AUTOSTART_DIR}/ghost.desktop" + +echo "${C_BOLD}YAMA Ghost Linux 安装${C_RESET}" +echo " 源: ${SRC_BIN}" +echo " 目标: ${DEST_BIN}" +echo " 自启动: ${AUTOSTART_FILE}" +echo "" + +# ---- 前置检查 ---- +if [[ ! -f "${SRC_BIN}" ]]; then + error "找不到 ghost 二进制 ${SRC_BIN}" + error "请把 install.sh 放在 ghost 同目录后再运行" + exit 1 +fi + +if ! file "${SRC_BIN}" 2>/dev/null | grep -q "ELF.*executable"; then + error "${SRC_BIN} 不是有效的 ELF 可执行文件" + exit 1 +fi + +# 判断目标目录是否需要 sudo +# 三种情况都要走 sudo 分支: +# a) 目录不存在且父目录无写权(如 /opt/yama 父是 /opt root-owned) +# b) 目录已存在但当前用户无写权(如已存在的 /usr/local/bin root-owned) +# c) 介于两者之间的情况由 mkdir 的退出码决定 +NEED_SUDO="" +if [[ -d "${INSTALL_DIR}" ]]; then + [[ -w "${INSTALL_DIR}" ]] || NEED_SUDO="sudo" +else + if ! mkdir -p "${INSTALL_DIR}" 2>/dev/null; then + NEED_SUDO="sudo" + fi +fi +if [[ -n "${NEED_SUDO}" ]]; then + info "目标目录需要 root 权限,将使用 sudo(可能需要输入密码)" + ${NEED_SUDO} mkdir -p "${INSTALL_DIR}" +fi + +# ---- 1. 如已运行则先停止 ---- +if pgrep -x ghost > /dev/null; then + warn "检测到 ghost 进程正在运行,先停止以替换二进制" + pkill -x ghost || true + sleep 1 + if pgrep -x ghost > /dev/null; then + warn "进程未优雅退出,强制 kill" + pkill -9 -x ghost || true + sleep 1 + fi + ok "旧进程已停止" +fi + +# ---- 2. 复制二进制 ---- +info "复制 ghost 到 ${DEST_BIN}" +${NEED_SUDO} install -m 0755 "${SRC_BIN}" "${DEST_BIN}" +ok "二进制已部署 (mode 0755)" + +# ---- 3. 写 XDG Autostart 文件 ---- +mkdir -p "${AUTOSTART_DIR}" +cat > "${AUTOSTART_FILE}" </dev/null 2>&1; then + if desktop-file-validate "${AUTOSTART_FILE}" >/dev/null 2>&1; then + ok "Autostart 文件格式验证通过" + else + warn "desktop-file-validate 报告了警告,但通常不影响功能" + fi +fi + +# ---- 4. 可选:立即启动 ---- +echo "" +echo -n "${C_BOLD}是否立即启动 ghost(验证 X 环境)?[Y/n]${C_RESET} " +read -r ans +if [[ -z "${ans}" || "${ans}" =~ ^[Yy]$ ]]; then + if [[ -z "${DISPLAY:-}" ]]; then + warn "当前 shell 没有 DISPLAY 变量,可能不在桌面会话内 — 启动后远控仍可能 0x0" + warn "建议在 GNOME 终端/桌面环境的终端里运行此脚本" + fi + nohup "${DEST_BIN}" >/dev/null 2>&1 & + sleep 1 + if pgrep -x ghost > /dev/null; then + ok "ghost 已启动 (PID=$(pgrep -x ghost | head -1))" + else + error "启动失败,请手动跑 ${DEST_BIN} 看错误输出" + exit 1 + fi +fi + +echo "" +echo "${C_GREEN}${C_BOLD}✓ 安装完成${C_RESET}" +echo "" +echo "下次开机将自动启动;如需立即测试,重启或在桌面终端跑:" +echo " ${DEST_BIN}" +echo "" +echo "卸载请运行同目录的 ./uninstall.sh" diff --git a/linux/uninstall.sh b/linux/uninstall.sh new file mode 100755 index 0000000..0b7d1fb --- /dev/null +++ b/linux/uninstall.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# YAMA Ghost (Linux client) — uninstall +# +# 用法: +# ./uninstall.sh # 默认从 ~/.local/bin/ghost 卸载 +# ./uninstall.sh /opt/yama # 从指定目录卸载 +# ./uninstall.sh --yes # 跳过确认(自动化场景) +# +# 行为(幂等 — 重复运行不会报错): +# 1. 停止运行中的 ghost 进程 +# 2. 删除 XDG Autostart 文件 +# 3. 删除已安装的二进制 +# 4. 询问是否清理用户配置(~/.config/ghost) + +set -euo pipefail + +# ---- 颜色 ---- +if [[ -t 1 ]]; then + C_RED=$'\033[31m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m' + C_BLUE=$'\033[34m'; C_BOLD=$'\033[1m'; C_RESET=$'\033[0m' +else + C_RED=''; C_GREEN=''; C_YELLOW=''; C_BLUE=''; C_BOLD=''; C_RESET='' +fi + +info() { echo "${C_BLUE}[INFO]${C_RESET} $*"; } +ok() { echo "${C_GREEN}[ OK ]${C_RESET} $*"; } +warn() { echo "${C_YELLOW}[WARN]${C_RESET} $*"; } +error() { echo "${C_RED}[FAIL]${C_RESET} $*" >&2; } + +# ---- 参数解析 ---- +ASSUME_YES=0 +INSTALL_DIR="${HOME}/.local/bin" +for arg in "$@"; do + case "${arg}" in + --yes|-y) ASSUME_YES=1 ;; + --help|-h) + # 头部注释覆盖标题/用法/行为 4 步,对应源文件第 2-13 行 + sed -n '2,13p' "$0" | sed 's/^# \?//' + exit 0 + ;; + *) INSTALL_DIR="${arg}" ;; + esac +done + +DEST_BIN="${INSTALL_DIR}/ghost" +AUTOSTART_FILE="${XDG_CONFIG_HOME:-${HOME}/.config}/autostart/ghost.desktop" +CONFIG_DIR="${XDG_CONFIG_HOME:-${HOME}/.config}/ghost" + +confirm() { + [[ "${ASSUME_YES}" -eq 1 ]] && return 0 + local prompt="$1" + local ans="" + echo -n "${prompt} [y/N] " + read -r ans || true # EOF on stdin: ans stays empty, 返回 no + [[ "${ans}" =~ ^[Yy]$ ]] +} + +echo "${C_BOLD}YAMA Ghost Linux 卸载${C_RESET}" +echo " 二进制: ${DEST_BIN}" +echo " 自启动: ${AUTOSTART_FILE}" +echo " 配置: ${CONFIG_DIR}" +echo "" + +if ! confirm "确认卸载?"; then + info "已取消" + exit 0 +fi + +# ---- 1. 停止进程 ---- +if pgrep -x ghost > /dev/null; then + info "停止运行中的 ghost 进程" + pkill -x ghost || true + sleep 1 + if pgrep -x ghost > /dev/null; then + warn "进程未优雅退出,强制 kill" + pkill -9 -x ghost || true + sleep 1 + fi + ok "ghost 进程已停止" +else + info "没有运行中的 ghost 进程" +fi + +# ---- 2. 删除 Autostart 文件 ---- +if [[ -f "${AUTOSTART_FILE}" ]]; then + rm -f "${AUTOSTART_FILE}" + ok "已删除 ${AUTOSTART_FILE}" +else + info "Autostart 文件不存在(已卸载或未安装过)" +fi + +# ---- 3. 删除二进制 ---- +if [[ -f "${DEST_BIN}" ]]; then + if [[ -w "${DEST_BIN}" ]] || [[ -w "$(dirname "${DEST_BIN}")" ]]; then + rm -f "${DEST_BIN}" + ok "已删除 ${DEST_BIN}" + else + info "需要 sudo 才能删除 ${DEST_BIN}" + sudo rm -f "${DEST_BIN}" + ok "已删除 ${DEST_BIN}" + fi + # 如果安装目录是 ~/.local/bin 且现在空了,不删除(可能用户还有其它东西) +else + info "二进制不存在(已卸载或不在 ${INSTALL_DIR})" +fi + +# ---- 4. 用户配置目录(询问,不主动删)---- +if [[ -d "${CONFIG_DIR}" ]]; then + echo "" + warn "用户配置目录仍存在:${CONFIG_DIR}" + warn "其中可能包含 PID 文件、日志等。删除后无法恢复。" + if confirm " 一并删除配置目录?"; then + rm -rf "${CONFIG_DIR}" + ok "已删除 ${CONFIG_DIR}" + else + info "保留配置目录" + fi +fi + +echo "" +echo "${C_GREEN}${C_BOLD}✓ 卸载完成${C_RESET}"