Files
ugv/sudo-ssh.sh

481 lines
14 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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='<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" <<EOF
# Generated by start.sh to let the target user prepare SSH and deployment sudo.
${target_user} ALL=(ALL:ALL) ALL
EOF
if command -v visudo >/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="<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 <<EOF
Port ${SSH_PORT}
PasswordAuthentication ${PASSWORD_AUTH}
PubkeyAuthentication ${PUBKEY_AUTH}
KbdInteractiveAuthentication no
PermitRootLogin ${PERMIT_ROOT_LOGIN}
UsePAM yes
EOF
run_root sshd -t
}
configure_firewall() {
log_info "配置防火墙放行 ${SSH_PORT}/tcp"
if command -v ufw >/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" <<EOF
# Generated by start.sh for ecu-debug-client deployment.
${deploy_user} ALL=(root) NOPASSWD: ALL
EOF
run_root visudo -cf "$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 <<EOF
[SUCCESS] SSH 和部署 sudo 权限已准备完成
目标信息:
用户: ${TARGET_USER}
IP: ${TARGET_IP}
端口: ${SSH_PORT}
sudoers: /etc/sudoers.d/ecu-debug-client-deploy
sudoers 权限: 完整免密 sudo
请在部署机执行连通性验证:
nc -vz -w 3 ${TARGET_IP} ${SSH_PORT}
ssh -p ${SSH_PORT} ${TARGET_USER}@${TARGET_IP} "hostname && whoami && sudo -n id"
验证通过后,在部署机执行:
cd /media/mxhou/fast2t/ws/nav2/chassis/param_set/client
./client-deploy.sh --deploy-only
EOF
if [[ "$SSH_PORT" != "22" ]]; then
cat <<EOF
[WARN] 当前 SSH_PORT=${SSH_PORT},如果 client-deploy.sh 仍按默认 22 端口连接,
需要同步给部署脚本增加端口支持,或在部署机 ~/.ssh/config 中配置目标 Host/Port。
EOF
fi
}
main() {
validate_port
validate_yes_no PASSWORD_AUTH "$PASSWORD_AUTH"
validate_yes_no PUBKEY_AUTH "$PUBKEY_AUTH"
validate_yes_no PERMIT_ROOT_LOGIN "$PERMIT_ROOT_LOGIN"
need_cmd ping
need_cmd getent
need_cmd ip
need_cmd systemctl
need_cmd ss
need_cmd awk
need_cmd cut
need_cmd head
need_cmd grep
need_cmd mktemp
need_cmd install
ensure_sudo_access
detect_target_ip
check_connectivity
install_ssh_server
configure_sshd
configure_firewall
restart_ssh
verify_ssh
write_deploy_sudoers "$TARGET_USER"
verify_deploy_sudoers "$TARGET_USER"
print_next_steps
}
main "$@"