一、背景

项目中需要批量上传.deb包并更新,但是需要上传的服务器并未开放 root 用户,只能登录普通用户再进行切换,我查阅资料后,决定借助 expect 来解决这一问题。

二、依赖安装

因为项目服务器无法连接外网,所以只能手动下载deb包并安装。

1
2
3
4
dpkg -i libtcl8.6_8.6.10+dfsg-1_amd64.deb
dpkg -i tcl8.6_8.6.10+dfsg-1_amd64.deb
dpkg -i tcl-expect_5.45.4-2build1_amd64.deb
dpkg -i expect_5.45.4-2build1_amd64.deb

三、expect脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/expect -f

# 设置超时时间
set timeout -1

# 从命令行参数中获取远程服务器信息、SSH 密码、root 密码和要执行的脚本路径
set host [lindex $argv 0]
set user [lindex $argv 1]
set ssh_password [lindex $argv 2]
set root_password [lindex $argv 3]
set script_path [lindex $argv 4]
set package_version [lindex $argv 5]

# SSH 登录到远程服务器
spawn ssh "$user@$host"
expect {
# 如果是首次连接,需要确认主机指纹
"yes/no" { send "yes\r"; exp_continue }
"password:" { send "$ssh_password\r" }
}

# 成功登录后,切换到 root 用户
expect "$ "
send "su -\r"
expect "Password:"
send "$root_password\r"

# 切换成功后执行远程脚本
expect "# "
send "bash $script_path $package_version \r"

# 等待脚本执行完成
expect "# "

# 退出 root 用户
send "exit\r"

# 退出 SSH 会话
expect "$ "
send "exit\r"

# 结束 expect 会话
expect eof

四、批量上传及更新脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/bin/bash

PACKAGE_VERSION=$1
# 主机列表文件
HOSTS_FILE="hosts.txt"
# 要上传的本地文件路径
LOCAL_FILE="back_install_dir"
# 远程路径
REMOTE_PATH="/home/a123"
# SSH 用户名
USER="a123"
PASSWORD="a123"
# root 用户密码
ROOT_PASSWORD="1"
REMOTE_SCRIPT="/home/a123/back_install_dir/install_back_end.sh"
THREADS=14

update_host() {
HOST=$1
LOCAL_FILE=$2
REMOTE_PATH=$3
USER=$4
PASSWORD=$5
ROOT_PASSWORD=$6
REMOTE_SCRIPT=$7
PACKAGE_VERSION=$8
echo "Connecting to $HOST..."
echo "upload $LOCAL_FILE to $USER @ $PASSWORD : $REMOTE_PATH"
# 使用 scp 上传文件到远程主机
sshpass -p "$PASSWORD" scp -r -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$LOCAL_FILE" "$USER@$HOST:$REMOTE_PATH"
if [ $? -eq 0 ]; then
echo "$HOST: Upload successful"
else
echo "$HOST: Upload failed"
return
fi

# 在本地使用 expect 登录远程服务器并执行脚本
./local_expect_script.exp "$HOST" "$USER" "$PASSWORD" "$ROOT_PASSWORD" "$REMOTE_SCRIPT" "$PACKAGE_VERSION"
if [ $? -eq 0 ]; then
echo "$HOST: script executed successful"
else
echo "$HOST: script executed failed"
return
fi
}

export -f update_host
cat "$HOSTS_FILE" | xargs -n 1 -P $THREADS -I {} bash -c 'update_host "$@"' _ {} "$LOCAL_FILE" "$REMOTE_PATH" "$USER" "$PASSWORD" "$ROOT_PASSWORD" "$REMOTE_SCRIPT" "$PACKAGE_VERSION"

考虑到逐一对每台设备更新太耗时,这里加入了多线程。hosts.txt里存放的是需要更新的服务器IP地址,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
10.64.200.167
10.64.200.170
10.64.200.177
10.64.200.175
10.64.200.176
10.64.200.172
10.64.200.168
10.64.200.166
10.64.200.169
10.64.200.174
10.64.200.171
10.64.200.152
10.64.200.102
10.64.200.173

最后是 install_back_end.sh 更新脚本:

1
2
3
4
5
6
PACKAGE_VERSION=$1

dpkg -i /home/a123/back_install_dir/box-station_${PACKAGE_VERSION}.deb
systemctl daemon-reload
systemctl restart box-station.service
systemctl enable box-station.service