Shell Script Automation: How to Modify MySQL bind-address in my.cnf for Remote Connections


3 views

When provisioning MySQL servers in automated environments like Vagrant, we often need to modify the bind-address parameter to allow remote connections. The default configuration (typically 127.0.0.1) only allows local connections, which is problematic for development environments.

While tools like mysqladmin are great for runtime configuration, they don't persist changes across restarts. The most reliable method is to modify my.cnf directly. Here's why other approaches fall short:

  • Appending to the file risks having multiple conflicting bind-address directives
  • Environment variables only affect the current session
  • SET GLOBAL commands don't persist after restart

Here's a production-tested shell script snippet that safely modifies the MySQL configuration:

#!/bin/bash

MYSQL_CONF="/etc/mysql/my.cnf"
BACKUP_CONF="/etc/mysql/my.cnf.bak"

# Create backup
sudo cp "$MYSQL_CONF" "$BACKUP_CONF"

# Handle three possible scenarios:
# 1. bind-address exists and needs modification
# 2. bind-address doesn't exist and needs adding
# 3. File might use [mysqld] section with indentation

# First, comment out any existing bind-address
sudo sed -i '/^bind-address/s/^/#/' "$MYSQL_CONF"

# Then add our new binding after the [mysqld] section
sudo sed -i '/^$$mysqld$$/a bind-address = 0.0.0.0' "$MYSQL_CONF"

# Restart MySQL to apply changes
sudo systemctl restart mysql

For more complex configurations, Perl provides better pattern matching:

#!/usr/bin/perl

use strict;
use warnings;

my $file = '/etc/mysql/my.cnf';
my @content;

{
    local $/;
    open(my $fh, '<', $file) or die "Can't open $file: $!";
    @content = split(/\n/, <$fh>);
    close($fh);
}

my $found_mysqld = 0;
foreach my $line (@content) {
    if ($line =~ /^$$mysqld$$/) {
        $found_mysqld = 1;
        next;
    }
    if ($found_mysqld && $line =~ /^bind-address/) {
        $line = "# $line";  # Comment out existing
    }
}

if ($found_mysqld) {
    # Add our new binding after [mysqld]
    splice(@content, $found_mysqld + 1, 0, 'bind-address = 0.0.0.0');
    
    open(my $fh, '>', $file) or die "Can't write to $file: $!";
    print $fh join("\n", @content);
    close($fh);
    
    system('sudo systemctl restart mysql');
} else {
    die "[mysqld] section not found in $file";
}

Modern MySQL installations often split configuration across multiple files. Here's how to handle this scenario:

#!/bin/bash

# Search for active configuration files
MYSQL_CONF_FILES=$(find /etc/mysql -name '*.cnf')

for conf_file in $MYSQL_CONF_FILES; do
    # Skip if file doesn't contain [mysqld] section
    if ! grep -q '^$$mysqld$$' "$conf_file"; then
        continue
    fi
    
    # Process each relevant config file
    sudo sed -i '/^$$mysqld$$/a bind-address = 0.0.0.0' "$conf_file"
    sudo sed -i '/^bind-address/s/^/#/' "$conf_file"
done

sudo systemctl restart mysql

Always verify your changes:

# Check running configuration
mysql -e "SHOW VARIABLES LIKE 'bind_address';"

# Verify listening ports
sudo netstat -tulnp | grep mysql

# Test remote connection (from another machine)
mysql -h [your-server-ip] -u root -p

When automating MySQL server configuration in provisioning scripts (like for Vagrant boxes), programmatically modifying the bind-address parameter presents unique challenges. Unlike runtime parameters, this setting requires file modification and service restart.

Common but problematic methods include:

# Risky: Appending without checking existing values
echo "bind-address = 0.0.0.0" >> /etc/mysql/my.cnf

# Dangerous: Blind overwrites
sed -i 's/bind-address.*/bind-address = 0.0.0.0/' /etc/mysql/my.cnf

These can cause configuration conflicts or file corruption.

Here's a robust method that handles various my.cnf formats:

#!/bin/bash
CONF_FILE="/etc/mysql/my.cnf"
TEMP_FILE=$(mktemp)

# Process the configuration
{
  # Preserve existing [mysqld] section with modified bind-address
  sed -n '/^$$mysqld$$/,/^\[/p' "$CONF_FILE" | \
    sed '/^bind-address/d;/^$$mysqld$$/a bind-address = 0.0.0.0'
  
  # Include non-[mysqld] sections verbatim
  sed '/^\[mysqld\]/,/^\[/d' "$CONF_FILE"
} > "$TEMP_FILE"

# Validate before applying
if mysql --defaults-file="$TEMP_FILE" --connect-expired-password -e "SELECT 1" &>/dev/null; then
  sudo mv "$TEMP_FILE" "$CONF_FILE"
  sudo systemctl restart mysql
else
  echo "Configuration test failed" >&2
  rm "$TEMP_FILE"
  exit 1
fi

For different environments:

# Ubuntu/Debian with includedir
find /etc/mysql/conf.d/ -name '*.cnf' -exec sed -i '/bind-address/d' {} +
echo "[mysqld]\nbind-address = 0.0.0.0" | sudo tee /etc/mysql/conf.d/bind.cnf

# RHEL/CentOS with override directory
if [ -d /etc/my.cnf.d/ ]; then
  echo "!includedir /etc/my.cnf.d" | sudo tee -a /etc/my.cnf
  echo "[mysqld]\nbind-address = 0.0.0.0" | sudo tee /etc/my.cnf.d/bind.cnf
fi

When opening MySQL to remote connections:

# Automate firewall rules
sudo ufw allow 3306/tcp comment 'MySQL remote access'

# Set up restricted user
mysql -e "CREATE USER 'remote_user'@'%' IDENTIFIED BY 'complex-password'"
mysql -e "GRANT SELECT,INSERT ON db.* TO 'remote_user'@'%'"
mysql -e "FLUSH PRIVILEGES"

Include these checks in your provisioning script:

# Verify binding
ss -tulnp | grep 3306
# Expected: tcp LISTEN 0 70 0.0.0.0:3306

# Test remote connection (from host)
mysql -h [VM_IP] -u remote_user -p -e "SELECT @@bind_address"