#!/bin/bash # 一键准备目标 Linux 设备的 SSH 和部署 sudo 权限。 # # 作用: # 1. 先验证/补齐 TARGET_USER 的 sudo 权限;没有 sudo 时尝试用 root 权限自动补齐。 # 2. 判断目标设备是否已联网,避免 apt 安装时才失败。 # 3. 清理并修复 apt 依赖状态,安装 openssh-server 相关依赖。 # 4. 配置 SSH 端口、密码登录、key 登录和 root 登录策略。 # 5. 自动放行 UFW/firewalld 中的 SSH 端口。 # 6. 验证 ssh 服务 active、sshd 配置合法、端口已监听。 # 7. 写入 /etc/sudoers.d/ecu-debug-client-deploy,供 client-deploy.sh 远程部署使用。 # 8. 验证 TARGET_USER 可以无密码执行部署 sudo 命令。 # # 前置要求: # - 本脚本需要在目标 Linux 设备本地执行,不依赖目标设备已开启 SSH。 # - 目标系统为 Ubuntu/Debian 或兼容 apt 的发行版。 # - 当前用户需要具备 sudo 权限;如果没有,需要当前 shell 已是 root,或知道 root 密码。 # - 目标设备需要能访问 apt 软件源;如果只通 ZeroTier/LAN 但不通公网,请先修复 DNS/网关/代理。 #deve # 常用用法: # 默认配置当前用户、22 端口、完整免密 sudo: # ./tools/ssh-sudo-zerotier/start.sh # # 指定目标用户名和 IP: # TARGET_USER=leador TARGET_IP=192.168.191.37 ./tools/ssh-sudo-zerotier/start.sh # # 当前用户没有 sudo,但知道 root 密码: # ROOT_PASSWORD='' TARGET_USER=leador TARGET_IP=192.168.191.37 ./tools/ssh-sudo-zerotier/start.sh # # 使用非 22 端口: # SSH_PORT=2222 TARGET_USER=leador TARGET_IP=192.168.191.37 ./tools/ssh-sudo-zerotier/start.sh # # 环境变量: # TARGET_USER 后续部署机 SSH 登录和部署 sudo 的目标用户,默认取当前用户。 # TARGET_IP 后续部署机访问目标设备的 IP,默认自动探测本机 IPv4。 # SSH_PORT SSH 监听端口,默认 22。 # PASSWORD_AUTH 是否允许密码登录:yes/no,默认 yes。 # PUBKEY_AUTH 是否允许 SSH key 登录:yes/no,默认 yes。 # PERMIT_ROOT_LOGIN 是否允许 root SSH 登录,默认 no。 # CONNECTIVITY_HOSTS 联网检测地址列表,默认 "223.5.5.5 1.1.1.1 8.8.8.8"。 # ROOT_PASSWORD 当前用户无 sudo 时可选传入 root 密码;为空时 su 会交互询问。 # # 执行后,在部署机验证并部署: # nc -vz -w 3 "$TARGET_IP" "$SSH_PORT" # ssh -p "$SSH_PORT" "$TARGET_USER@$TARGET_IP" "hostname && whoami && sudo -n id" # ./client-deploy.sh --deploy-only set -euo pipefail TARGET_USER=${TARGET_USER:-leador} TARGET_IP=${TARGET_IP:-192.168.191.37} SSH_PORT=${SSH_PORT:-22} PASSWORD_AUTH=${PASSWORD_AUTH:-yes} PUBKEY_AUTH=${PUBKEY_AUTH:-yes} PERMIT_ROOT_LOGIN=${PERMIT_ROOT_LOGIN:-no} CONNECTIVITY_HOSTS=${CONNECTIVITY_HOSTS:-"223.5.5.5 1.1.1.1 8.8.8.8"} ROOT_PASSWORD=${ROOT_PASSWORD:-} log_info() { echo "[INFO] $*" } log_warn() { echo "[WARN] $*" >&2 } log_error() { echo "[ERROR] $*" >&2 } need_cmd() { local name="$1" if ! command -v "$name" >/dev/null 2>&1; then log_error "缺少命令: $name" exit 1 fi } validate_port() { if ! [[ "$SSH_PORT" =~ ^[0-9]+$ ]] || ((SSH_PORT < 1 || SSH_PORT > 65535)); then log_error "SSH_PORT 必须是 1-65535 之间的数字,当前值: $SSH_PORT" exit 1 fi } validate_yes_no() { local name="$1" local value="$2" if [[ "$value" != "yes" && "$value" != "no" ]]; then log_error "$name 只能是 yes 或 no,当前值: $value" exit 1 fi } run_root() { if [[ "$(id -u)" -eq 0 ]]; then "$@" else sudo "$@" fi } write_sudo_bootstrap_script() { local script_path="$1" cat > "$script_path" <<'BOOTSTRAP_SUDO' #!/bin/bash set -euo pipefail target_user="$1" if ! id "$target_user" >/dev/null 2>&1; then echo "[ERROR] 用户不存在: $target_user" >&2 exit 1 fi if ! command -v sudo >/dev/null 2>&1; then if command -v apt-get >/dev/null 2>&1; then apt-get update apt-get install -y sudo else echo "[ERROR] 系统未安装 sudo,且找不到 apt-get 自动安装" >&2 exit 1 fi fi if command -v groupadd >/dev/null 2>&1 && ! getent group sudo >/dev/null 2>&1; then groupadd sudo fi if command -v usermod >/dev/null 2>&1 && getent group sudo >/dev/null 2>&1; then usermod -aG sudo "$target_user" fi mkdir -p /etc/sudoers.d tmp_file=$(mktemp /tmp/start-bootstrap-sudoers.XXXXXX) trap 'rm -f "$tmp_file"' EXIT cat > "$tmp_file" </dev/null 2>&1; then visudo -cf "$tmp_file" >/dev/null fi install -o root -g root -m 0440 "$tmp_file" "/etc/sudoers.d/90-${target_user}-sudo" BOOTSTRAP_SUDO chmod 700 "$script_path" } bootstrap_sudo_with_root() { local target_user="$1" log_info "当前 shell 是 root,正在给用户 ${target_user} 添加 sudo 权限" local bootstrap_script="" bootstrap_script=$(mktemp /tmp/start-bootstrap-sudo.XXXXXX) write_sudo_bootstrap_script "$bootstrap_script" local status=0 set +e "$bootstrap_script" "$target_user" status=$? set -e rm -f "$bootstrap_script" return "$status" } bootstrap_sudo_with_su() { local target_user="$1" need_cmd su log_warn "当前用户没有可用 sudo 权限,尝试通过 root 用户为 ${target_user} 添加 sudo 权限" local bootstrap_script="" bootstrap_script=$(mktemp /tmp/start-bootstrap-sudo.XXXXXX) write_sudo_bootstrap_script "$bootstrap_script" local status=0 set +e if [[ -n "$ROOT_PASSWORD" ]]; then printf '%s\n' "$ROOT_PASSWORD" | su root -c "bash '$bootstrap_script' '$target_user'" status=$? else su root -c "bash '$bootstrap_script' '$target_user'" status=$? fi set -e rm -f "$bootstrap_script" return "$status" } ensure_sudo_access() { log_info "检查 ${TARGET_USER} 的 sudo 权限" if [[ "$(id -u)" -eq 0 ]]; then bootstrap_sudo_with_root "$TARGET_USER" return fi if [[ "$(id -un)" == "$TARGET_USER" ]] && command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then log_info "当前用户已有免密 sudo 权限" return fi if [[ "$(id -un)" == "$TARGET_USER" ]] && [[ -t 0 ]] && command -v sudo >/dev/null 2>&1 && sudo -v; then log_info "当前用户已有 sudo 权限" return fi bootstrap_sudo_with_su "$TARGET_USER" if [[ "$(id -un)" == "$TARGET_USER" ]] && command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then log_info "sudo 权限已补齐" return fi if [[ "$(id -un)" == "$TARGET_USER" ]] && [[ -t 0 ]] && command -v sudo >/dev/null 2>&1 && sudo -v; then log_info "sudo 权限已补齐" return fi if [[ "$(id -un)" != "$TARGET_USER" ]]; then log_info "已为 ${TARGET_USER} 写入 sudo 授权,后续 root 操作继续由当前用户执行" return fi log_error "已尝试添加 sudo 权限,但 ${TARGET_USER} 仍无法执行 sudo" log_error "如果刚加入 sudo 组仍不生效,请重新登录;如果 root 被禁用,请先用具备管理员权限的账号处理" exit 1 } detect_target_ip() { if [[ -n "$TARGET_IP" ]]; then return fi local detected="" detected=$(ip -o -4 addr show scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | head -n 1 || true) if [[ -n "$detected" ]]; then TARGET_IP="$detected" else TARGET_IP="" fi } check_connectivity() { log_info "检查目标设备联网状态" local ok=0 local host="" for host in $CONNECTIVITY_HOSTS; do if ping -c 1 -W 2 "$host" >/dev/null 2>&1; then log_info "网络连通: ping $host 成功" ok=1 break fi done if [[ "$ok" -ne 1 ]]; then log_error "目标设备无法 ping 通联网检测地址: $CONNECTIVITY_HOSTS" log_error "请先修复网络、DNS、网关、代理或 ZeroTier/LAN 路由,再重新执行本脚本" exit 1 fi if ! getent hosts archive.ubuntu.com >/dev/null 2>&1 && ! getent hosts mirrors.aliyun.com >/dev/null 2>&1; then log_warn "DNS 解析 apt 源域名失败。ping IP 虽然成功,但 apt update 可能失败" fi } repair_apt_dependencies() { log_info "清理并修复 apt 依赖状态" need_cmd apt-get run_root apt-get clean run_root bash -c 'rm -rf /var/lib/apt/lists/*' run_root apt-get update run_root apt-get --fix-broken install -y run_root apt-get install -y libtalloc2 libtdb1 libtevent0 openssh-client openssh-server } install_ssh_server() { if command -v sshd >/dev/null 2>&1; then log_info "openssh-server 已存在,跳过安装" return fi if command -v dpkg >/dev/null 2>&1 && dpkg -s openssh-server >/dev/null 2>&1; then log_info "openssh-server 已安装,跳过安装" return fi log_info "安装 openssh-server" repair_apt_dependencies } configure_sshd() { log_info "写入 SSH 配置: port=${SSH_PORT}, password=${PASSWORD_AUTH}, pubkey=${PUBKEY_AUTH}, root=${PERMIT_ROOT_LOGIN}" run_root mkdir -p /etc/ssh/sshd_config.d run_root tee /etc/ssh/sshd_config.d/10-remote-login.conf >/dev/null </dev/null 2>&1; then local ufw_status="" ufw_status=$(run_root ufw status | head -n 1 || true) if [[ "$ufw_status" == *active* || "$ufw_status" == *活动* ]]; then run_root ufw allow "${SSH_PORT}/tcp" run_root ufw reload log_info "UFW 已放行 ${SSH_PORT}/tcp" else log_info "UFW 未启用,跳过 UFW 放行" fi fi if command -v firewall-cmd >/dev/null 2>&1; then if run_root firewall-cmd --state >/dev/null 2>&1; then run_root firewall-cmd --add-port="${SSH_PORT}/tcp" --permanent run_root firewall-cmd --reload log_info "firewalld 已放行 ${SSH_PORT}/tcp" else log_info "firewalld 未运行,跳过 firewalld 放行" fi fi } restart_ssh() { log_info "启动并重启 SSH 服务" run_root systemctl enable --now ssh run_root systemctl restart ssh } verify_ssh() { log_info "验证 SSH 服务状态" run_root sshd -t systemctl is-active --quiet ssh if ss -lntp 2>/dev/null | grep -qE ":${SSH_PORT}[[:space:]]"; then log_info "SSH 端口已监听: ${SSH_PORT}" else log_error "未发现 SSH 端口监听: ${SSH_PORT}" log_error "最近 SSH 日志如下:" run_root journalctl -u ssh -n 80 --no-pager || true exit 1 fi log_info "当前 sshd 关键配置:" run_root sshd -T | grep -E '^(port|passwordauthentication|pubkeyauthentication|permitrootlogin|usepam) ' } resolve_cmd_path() { local name="$1" local path="" path=$(type -P "$name" 2>/dev/null || true) if [[ -z "$path" ]]; then log_error "目标设备缺少必要命令: $name" exit 1 fi printf '%s' "$path" } write_deploy_sudoers() { local deploy_user="$1" local sudoers_tmp="" log_info "写入完整免密 sudo 规则: user=${deploy_user}" resolve_cmd_path install >/dev/null resolve_cmd_path mkdir >/dev/null resolve_cmd_path visudo >/dev/null sudoers_tmp=$(mktemp /tmp/ecu-debug-client-deploy-sudoers.XXXXXX) cat > "$sudoers_tmp" </dev/null run_root mkdir -p /etc/sudoers.d run_root install -o root -g root -m 0440 "$sudoers_tmp" /etc/sudoers.d/ecu-debug-client-deploy run_root visudo -cf /etc/sudoers.d/ecu-debug-client-deploy >/dev/null rm -f "$sudoers_tmp" } verify_deploy_sudoers() { local deploy_user="$1" local systemctl_path="" systemctl_path=$(resolve_cmd_path systemctl) log_info "验证 ${deploy_user} 可以无密码执行部署 sudo 命令" if [[ "$(id -u)" -ne 0 && "$(id -un)" == "$deploy_user" ]]; then sudo -k sudo -n "$systemctl_path" daemon-reload return fi need_cmd su run_root su -s /bin/bash -c "sudo -k; sudo -n '$systemctl_path' daemon-reload" "$deploy_user" } print_next_steps() { cat <