Files
ugv/sudo-ssh.sh
2026-06-12 09:47:17 +08:00

426 lines
11 KiB
Bash
Raw 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
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
}
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"
need_cmd apt-get
run_root apt-get update
run_root apt-get install -y openssh-server
}
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 "$@"