autorenew

Batch Upload and Update on Ubuntu

1. Background

The project requires batch uploading .deb packages and updating them, but the servers don’t allow root user login - only regular users who need to switch. After researching, I decided to use expect to solve this problem.

2. Dependency Installation

Since the project servers cannot connect to the external network, I had to manually download deb packages and install them.

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

3. Expect Script

#!/usr/bin/expect -f

# Set timeout
set timeout -1

# Get remote server info, SSH password, root password, and script path from command line arguments
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 login to remote server
spawn ssh "$user@$host"
expect {
    # If first connection, need to confirm host fingerprint
    "yes/no" { send "yes\r"; exp_continue }
    "password:" { send "$ssh_password\r" }
}

# After successful login, switch to root user
expect "$ "
send "su -\r"
expect "Password:"
send "$root_password\r"

# After successful switch, execute remote script
expect "# "
send "bash $script_path $package_version \r"

# Wait for script execution to complete
expect "# "

# Exit root user
send "exit\r"

# Exit SSH session
expect "$ "
send "exit\r"

# End expect session
expect eof

4. Batch Upload and Update Script

#!/bin/bash

PACKAGE_VERSION=$1
# Host list file
HOSTS_FILE="hosts.txt"
# Local file path to upload
LOCAL_FILE="back_install_dir"
# Remote path
REMOTE_PATH="/home/a123"
# SSH username
USER="a123"
PASSWORD="a123"
# root user password
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"
    # Use scp to upload file to remote host
    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

    # Use expect locally to login to remote server and execute script
    ./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"

Considering that updating each device one by one is too time-consuming, I added multi-threading here. The hosts.txt file contains the IP addresses of servers to be updated, as shown below.

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

Finally, here’s the install_back_end.sh update script:

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