How to Use su Inside a Shell Script for Automated Server Deployment


2 views

When automating deployment scripts, one common hurdle is executing privileged commands on remote servers. The naive approach of embedding su - in an SSH heredoc block fails because:

ssh user@server <

Here are three practical solutions for different scenarios:

1. Sudo Without Password (Preferred)

Configure passwordless sudo for specific commands:

# In /etc/sudoers on the remote server
deploy-user ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart httpd

# In your script:
ssh user@server "sudo systemctl restart httpd"

2. SSH Direct Root Login (Less Secure)

If you must use root directly:

ssh root@server <<'EOF'
service mysql stop
# Other root commands
EOF

Note: Requires enabling root SSH access and using SSH keys.

3. Expect Script for Password Handling

For cases where you must provide a password programmatically:

#!/usr/bin/expect -f
spawn ssh user@server
expect "password:"
send "yourpassword\r"
expect "$ "
send "sudo su -\r"
expect "password:"
send "yourpassword\r"
# Continue with privileged commands

Here's a complete deployment script using sudo:

#!/bin/bash
REMOTE_USER="deployer"
SERVER="prod.example.com"
SERVICE="app-server"

# Build and upload
zip -r deploy.zip .
scp deploy.zip ${REMOTE_USER}@${SERVER}:/tmp/

# Remote commands
ssh -t ${REMOTE_USER}@${SERVER} <
  • Always prefer SSH keys over passwords
  • Limit sudo privileges to specific commands
  • Use dedicated deployment users rather than root
  • Consider tools like Ansible for more complex deployments

When automating deployment processes, we often need to execute privileged commands on remote servers. The typical approach using SSH works fine for regular user commands, but becomes tricky when root access is required.

Your initial attempt probably looks something like this:

ssh user@server <

The problem is that su expects interactive password input, which doesn't work well in automated scripts.

Option 1: Using sudo with NOPASSWD

The most secure approach is to configure sudo on the target server:

# On the server, edit sudoers:
visudo
# Add this line:
deploy_user ALL=(ALL) NOPASSWD: /usr/sbin/service server_instance *

Then your script becomes:

ssh user@server "sudo service server_instance stop"

Option 2: SSH Key-Based Root Access

If you must use root directly (not recommended), you can:

# Generate a key pair if you don't have one
ssh-keygen -t rsa -b 4096

# Copy the public key to root's authorized_keys
ssh-copy-id root@server

Then execute commands directly as root:

ssh root@server "service server_instance stop"

Option 3: Expect Script (Last Resort)

If you absolutely must use su with password, consider expect (though this is insecure):

#!/usr/bin/expect -f
spawn ssh user@server
expect "password:"
send "user_password\r"
expect "$ "
send "su -\r"
expect "Password:"
send "root_password\r"
expect "# "
send "service server_instance stop\r"
expect "# "
send "exit\r"
  • Always prefer sudo over direct root access
  • Limit sudo permissions to only necessary commands
  • Use SSH keys instead of passwords
  • Consider tools like Ansible for more complex deployments

Here's a production-ready approach using sudo and SSH keys:

#!/bin/bash
SERVER="user@server"
SERVICE="server_instance"

# Build and package locally
./build.sh
scp package.zip $SERVER:/tmp/

# Execute remote commands
ssh $SERVER << 'ENDSSH'
  sudo mkdir -p /opt/deployments
  sudo unzip /tmp/package.zip -d /opt/deployments/
  sudo chown -R appuser:appgroup /opt/deployments
  sudo service $SERVICE restart
ENDSSH