VMRack
Home
Products
Solutions
Pricing
Support
Referral Program
Help Documentation
VPS
Billing Overview
Expiration and Renewal
Quick Start Guide
Connect to Instance
Instance Management
Storage Management
Network and Security
Violation Handling
Resource Transfer
Private IP Address
FAQ
CDN
Billing Overview
Quick Start
Console Guide
FAQ
Object Storage
Billing Overview
Quick Start
Console Guide
FAQ
Transcoder
Billing Overview
Quick Start Guide
Task Management
Workflow Management
Preset Management
Queue Management
FAQ
Bare Metal Server
Billing Overview
Expiration and Renewal
Quick Start Guide
Connect to Instance
Instance Management
Network and Security
IP Change Guide
Violation Handling
Resource Transfer
FAQ
  1. Help Documentation
  2. /
  3. Bare Metal Server
  4. /
  5. IP Change Guide

IP Change Guide

1. Use Case

This guide applies to bare metal servers where IPv4 addresses need to be added, modified, or removed from the operating system. In such cases, you need to log in to the server and run the IP address change script we provide.

2. Instructions

2.1 Prepare the Script

Select the appropriate script based on the server's operating system and save the script content to a file, for example:

change-ip.sh

You can create and edit the script file using the following command:

vi change-ip.sh

Copy the corresponding script content into the file and save it. After saving the file, grant the script execute permission:

chmod +x change-ip.sh

2.2 Configure the Target IP Addresses

Open the script file and locate the NEW_IPS configuration item:

NEW_IPS=()

Enter the IPv4 addresses you want to configure into NEW_IPS. The IP addresses can be copied directly from the IP Management page using the one-click copy function.

You can also leave NEW_IPS empty and specify all IP addresses through command-line arguments instead. See Section 4 for details.

Separate multiple IP addresses with spaces. For example:

NEW_IPS=("192.168.1.20" "192.168.1.30")

After the script is executed, the server will be configured with the following two IPv4 addresses:

  • 192.168.1.20

  • 192.168.1.30

2.3 Run the Script

After configuring the target IP addresses, run the script with sudo:

sudo ./change-ip.sh

The script will apply the IP address changes according to the configured target IP addresses.

2.4 Specify IP Addresses Using Command-Line Arguments

In addition to configuring NEW_IPS in the script, you can also pass IP addresses as command-line arguments when running the script.

For example:

sudo ./change-ip.sh "192.168.1.10" "192.168.1.11"

The IP addresses specified through command-line arguments will be merged with and deduplicated against the IP addresses configured in NEW_IPS, and the resulting list will then be applied.

For example, if the script contains:

NEW_IPS=("192.168.1.20" "192.168.1.30")

and you run:

sudo ./change-ip.sh "192.168.1.10" "192.168.1.11"

the final set of IP addresses processed by the script will be:

  • 192.168.1.10

  • 192.168.1.11

  • 192.168.1.20

  • 192.168.1.30

If the same IP address is specified both in NEW_IPS and as a command-line argument, it will only be retained once.

2.5 Workflow

The complete workflow is as follows:

Select the script for the corresponding operating system
        ↓
Create the change-ip.sh file
        ↓
Copy the script content into the file
        ↓
Grant execute permission
        ↓
Configure NEW_IPS
        ↓
Run the script
        ↓
Complete the IPv4 address change

3.Script Content

3.1 Scripts for CentOS 7.9

change-ip.sh file:

#!/usr/bin/env bash
set -o pipefail

# IPs configured here are merged with command-line IPs.
# Example: NEW_IPS=("192.169.21.7" "192.169.21.8")
NEW_IPS=()

CONFIG_DIR="/etc/sysconfig/network-scripts"
INTERFACE=""
CONFIG_FILE=""
BACKUP_FILE=""
PREFIX=""
changes_started=0
success=0

declare -a OLD_CIDRS=()
declare -a NEW_IPS_RESULT=()

log_info() {
    printf '[INFO] %s\n' "$*"
}

log_warn() {
    printf '[WARN] %s\n' "$*" >&2
}

log_error() {
    printf '[ERROR] %s\n' "$*" >&2
}

die() {
    log_error "$*"
    exit 1
}

usage() {
    cat <<EOF
Usage:
    $0 <IP1> [IP2] [IP3] ...

Examples:
    $0 192.169.21.7
    $0 192.169.21.7 192.169.21.8
EOF
}

valid_ipv4() {
    local ip="$1"
    local octet
    local -a octets

    [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] ||
        return 1

    IFS=. read -r -a octets <<< "$ip"

    for octet in "${octets[@]}"; do
        (( 10#$octet <= 255 )) || return 1
    done
}

add_unique_ip() {
    local ip="$1"
    local existing

    for existing in "${NEW_IPS_RESULT[@]}"; do
        [ "$existing" = "$ip" ] && return 0
    done

    NEW_IPS_RESULT+=("$ip")
}

detect_interface() {
    INTERFACE="$(
        ip -4 route get 1.1.1.1 2>/dev/null |
            awk '{
                for (i = 1; i <= NF; i++) {
                    if ($i == "dev") {
                        print $(i + 1)
                        exit
                    }
                }
            }'
    )"

    [ -n "$INTERFACE" ] ||
        INTERFACE="$(
            ip -4 route show default 2>/dev/null |
                awk 'NR == 1 {print $5}'
        )"

    [ -n "$INTERFACE" ] ||
        die "Unable to determine the default IPv4 interface"

    [ "$INTERFACE" != "lo" ] ||
        die "The detected interface is loopback"
}

get_config_value() {
    local key="$1"

    awk -F= -v key="$key" '
        $0 !~ /^[[:space:]]*#/ && $1 == key {
            value = substr($0, index($0, "=") + 1)
            print value
            exit
        }
    ' "$CONFIG_FILE" |
        sed \
            -e 's/^[[:space:]]*//' \
            -e 's/[[:space:]]*$//' \
            -e 's/^"//' \
            -e 's/"$//' \
            -e "s/^'//" \
            -e "s/'$//"
}

get_current_addresses() {
    OLD_CIDRS=()

    while IFS= read -r cidr; do
        [ -n "$cidr" ] && OLD_CIDRS+=("$cidr")
    done < <(
        ip -4 -o addr show dev "$INTERFACE" scope global 2>/dev/null |
            awk '{print $4}'
    )
}

get_prefix() {
    local cidr
    local config_prefix

    for cidr in "${OLD_CIDRS[@]}"; do
        PREFIX="${cidr##*/}"
        break
    done

    if [ -z "$PREFIX" ]; then
        config_prefix="$(get_config_value "PREFIX0")"

        [ -n "$config_prefix" ] ||
            config_prefix="$(get_config_value "PREFIX")"

        PREFIX="$config_prefix"
    fi

    [[ "$PREFIX" =~ ^[0-9]+$ ]] &&
        (( 10#$PREFIX <= 32 )) ||
        die "Unable to determine a valid IPv4 prefix"

    PREFIX=$((10#$PREFIX))
}

create_backup() {
    local backup_dir

    backup_dir="$(mktemp -d "/root/change-ip-backup-${INTERFACE}.XXXXXX")" ||
        die "Failed to create backup directory"

    chmod 700 "$backup_dir"
    BACKUP_FILE="$backup_dir/$(basename "$CONFIG_FILE")"

    cp -p "$CONFIG_FILE" "$BACKUP_FILE" ||
        die "Failed to create backup: $BACKUP_FILE"

    log_info "Backup created: $BACKUP_FILE"
}

update_config() {
    local temp_file
    local i

    temp_file="$(mktemp "${CONFIG_FILE}.tmp.XXXXXX")" ||
        die "Failed to create temporary configuration file"

    awk '
        $0 !~ /^[[:space:]]*IPADDR[0-9]*=/ &&
        $0 !~ /^[[:space:]]*PREFIX[0-9]*=/ &&
        $0 !~ /^[[:space:]]*NETMASK[0-9]*=/ &&
        $0 !~ /^[[:space:]]*DEVTIMEOUT=/ {
            print
        }
    ' "$CONFIG_FILE" > "$temp_file" || {
        rm -f "$temp_file"
        die "Failed to process configuration file"
    }

    for ((i = 0; i < ${#NEW_IPS_RESULT[@]}; i++)); do
        printf 'IPADDR%d=%s\n' "$i" "${NEW_IPS_RESULT[$i]}" >> "$temp_file"
        printf 'PREFIX%d=%s\n' "$i" "$PREFIX" >> "$temp_file"
    done

    # Keep DEVTIMEOUT as the final line of the ifcfg file.
    printf 'DEVTIMEOUT=60\n' >> "$temp_file"

    chmod --reference="$CONFIG_FILE" "$temp_file" 2>/dev/null || true
    chown --reference="$CONFIG_FILE" "$temp_file" 2>/dev/null || true

    mv -f "$temp_file" "$CONFIG_FILE" ||
        die "Failed to update $CONFIG_FILE"

    log_info "Configuration file updated"
}

remove_old_runtime_addresses() {
    local old_cidr
    local old_ip
    local new_ip
    local keep

    for old_cidr in "${OLD_CIDRS[@]}"; do
        old_ip="${old_cidr%%/*}"
        keep=false

        for new_ip in "${NEW_IPS_RESULT[@]}"; do
            if [ "$old_ip" = "$new_ip" ]; then
                keep=true
                break
            fi
        done

        [ "$keep" = true ] && continue

        if ip -4 addr show dev "$INTERFACE" | grep -qw "$old_cidr"; then
            log_info "Removing old runtime address: $old_cidr"

            ip -4 addr del "$old_cidr" dev "$INTERFACE" ||
                die "Failed to remove old runtime address: $old_cidr"
        fi
    done
}

verify_runtime() {
    local actual
    local expected

    actual="$(
        ip -4 -o addr show dev "$INTERFACE" scope global |
            awk '{print $4}' |
            sort
    )"

    expected="$(
        for ip in "${NEW_IPS_RESULT[@]}"; do
            printf '%s/%s\n' "$ip" "$PREFIX"
        done | sort
    )"

    if [ "$actual" != "$expected" ]; then
        log_error "Runtime IPv4 addresses do not match the requested list"
        log_error "Actual: ${actual//$'\n'/, }"
        log_error "Expected: ${expected//$'\n'/, }"
        return 1
    fi

    return 0
}

restore_backup() {
    [ -n "$BACKUP_FILE" ] && [ -f "$BACKUP_FILE" ] || return 1

    log_warn "Restoring the original configuration..."

    cp -p "$BACKUP_FILE" "$CONFIG_FILE" ||
        return 1

    ifdown "$INTERFACE" >/dev/null 2>&1 || true

    if ! ifup "$INTERFACE" >/dev/null 2>&1; then
        log_error "Configuration was restored, but interface activation failed"
        return 1
    fi

    log_info "Original configuration restored"
}

cleanup() {
    local status=$?

    trap - EXIT

    if [ "$changes_started" -eq 1 ] && [ "$success" -eq 0 ]; then
        restore_backup ||
            log_error "Automatic rollback failed; use the console to recover the network"
    fi

    exit "$status"
}

main() {
    local bootproto
    local configured_ip
    local -a configured_ips

    [ "$(id -u)" -eq 0 ] ||
        die "Run this script as root or via sudo"

    command -v ip >/dev/null 2>&1 ||
        die "The ip command is not available"

    command -v ifup >/dev/null 2>&1 ||
        die "The ifup command is not available"

    command -v ifdown >/dev/null 2>&1 ||
        die "The ifdown command is not available"

    command -v awk >/dev/null 2>&1 ||
        die "The awk command is not available"

    command -v sed >/dev/null 2>&1 ||
        die "The sed command is not available"

    command -v grep >/dev/null 2>&1 ||
        die "The grep command is not available"

    [ -d "$CONFIG_DIR" ] ||
        die "$CONFIG_DIR does not exist"

    if systemctl is-active --quiet NetworkManager; then
        die "NetworkManager is active. Use the nmcli script instead"
    fi

    configured_ips=("${NEW_IPS[@]}")
    NEW_IPS_RESULT=()

    for configured_ip in "${configured_ips[@]}" "$@"; do
        case "$configured_ip" in
            -h|--help)
                usage
                exit 0
                ;;
        esac

        valid_ipv4 "$configured_ip" ||
            die "Invalid IPv4 address: $configured_ip"

        add_unique_ip "$configured_ip"
    done

    [ "${#NEW_IPS_RESULT[@]}" -gt 0 ] ||
        die "At least one IPv4 address is required"

    detect_interface

    CONFIG_FILE="$CONFIG_DIR/ifcfg-$INTERFACE"
    [ -f "$CONFIG_FILE" ] ||
        die "Configuration file not found: $CONFIG_FILE"

    bootproto="$(get_config_value "BOOTPROTO")"

    case "$bootproto" in
        ""|none|static)
            ;;
        *)
            die "BOOTPROTO=$bootproto is not supported; static IPv4 is required"
            ;;
    esac

    get_current_addresses
    get_prefix

    log_info "Target interface: $INTERFACE"
    log_info "Configuration file: $CONFIG_FILE"
    log_info "Current IPv4 addresses: ${OLD_CIDRS[*]:-none}"
    log_info "New IPv4 addresses:"

    for configured_ip in "${NEW_IPS_RESULT[@]}"; do
        log_info "  $configured_ip/$PREFIX"
    done

    log_info "DEVTIMEOUT: 60"
    log_warn "ifdown/ifup will interrupt the current SSH session"

    create_backup
    changes_started=1

    update_config

    log_info "Reactivating interface..."

    ifdown "$INTERFACE" >/dev/null 2>&1 || true

    ifup "$INTERFACE" ||
        die "Failed to activate interface"

    remove_old_runtime_addresses

    verify_runtime ||
        die "Runtime IPv4 verification failed"

    success=1

    log_info "IPv4 address update completed successfully"
    log_info "Active IPv4 addresses:"

    ip -4 -o addr show dev "$INTERFACE" scope global |
        awk '{print "  " $4}'

    log_info "Backup retained at: $BACKUP_FILE"
}

trap cleanup EXIT
main "$@"

3.2 Scripts for CentOS 8.3, Rocky 9.2, and Rocky 9.4

change-ip.sh file:

#!/usr/bin/env bash

set -o pipefail

# IP addresses can be specified here or passed through the command line.
# Example: NEW_IPS=("192.168.1.20" "192.168.1.30")
NEW_IPS=()

selected_uuid=""
default_interface=""
old_address_spec=""
modified=0
success=0

die() {
    echo "Error: $*" >&2
    exit 1
}

trim() {
    local value="$1"
    value="${value#"${value%%[![:space:]]*}"}"
    value="${value%"${value##*[![:space:]]}"}"
    printf '%s' "$value"
}

valid_ipv4() {
    local ip="$1"
    local octet
    local -a octets

    [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] ||
        return 1

    IFS=. read -r -a octets <<< "$ip"

    for octet in "${octets[@]}"; do
        (( 10#$octet <= 255 )) || return 1
    done
}

add_unique_ip() {
    local ip="$1"
    local existing_ip

    for existing_ip in "${NEW_IPS[@]}"; do
        [ "$existing_ip" = "$ip" ] && return 0
    done

    NEW_IPS+=("$ip")
}

detect_default_interface() {
    ip -4 route get 1.1.1.1 2>/dev/null |
        awk '{
            for (i = 1; i <= NF; i++) {
                if ($i == "dev") {
                    print $(i + 1)
                    exit
                }
            }
        }'
}

normalize_addresses() {
    local addresses="$1"
    local address
    local -a normalized=()

    while IFS= read -r address; do
        address="$(trim "$address")"

        [ -n "$address" ] || continue
        [ "$address" = "--" ] && continue
        [[ "$address" == */* ]] || continue

        normalized+=("$address")
    done < <(printf '%s\n' "$addresses" | tr ',' '\n')

    (IFS=,; printf '%s' "${normalized[*]}")
}

rollback() {
    [ "$modified" -eq 1 ] || return 0
    [ -n "$selected_uuid" ] || return 0
    [ -n "$old_address_spec" ] || return 0

    echo "Attempting to restore the original IPv4 address configuration..." >&2

    nmcli connection modify \
        uuid "$selected_uuid" \
        ipv4.addresses "$old_address_spec" >/dev/null 2>&1 || true

    nmcli connection up \
        uuid "$selected_uuid" \
        ifname "$default_interface" >/dev/null 2>&1 || true
}

cleanup() {
    local status=$?

    if [ "$modified" -eq 1 ] && [ "$success" -eq 0 ]; then
        rollback
    fi

    exit "$status"
}

trap cleanup EXIT

if [ "$(id -u)" -ne 0 ]; then
    die "Please run this script as root or via sudo"
fi

command -v systemctl >/dev/null 2>&1 ||
    die "systemctl not found"

command -v nmcli >/dev/null 2>&1 ||
    die "nmcli not found. Please make sure NetworkManager is installed"

command -v ip >/dev/null 2>&1 ||
    die "ip command not found"

systemctl enable --now NetworkManager ||
    die "Failed to start NetworkManager or enable it at boot"

systemctl is-active --quiet NetworkManager ||
    die "NetworkManager is not running"

# Merge, validate, and deduplicate IP addresses specified in the script and on the command line.
configured_ips=("${NEW_IPS[@]}")
NEW_IPS=()

for ip in "${configured_ips[@]}" "$@"; do
    valid_ipv4 "$ip" ||
        die "Invalid IPv4 address: $ip"

    add_unique_ip "$ip"
done

[ "${#NEW_IPS[@]}" -gt 0 ] ||
    die "No IPv4 address specified. Please fill in NEW_IPS in the script or pass IP addresses through the command line"

default_interface="$(detect_default_interface)"

[ -n "$default_interface" ] ||
    die "Unable to determine the network interface used by the current default IPv4 route"

[ "$default_interface" != "lo" ] ||
    die "A loopback interface was detected. No usable default IPv4 route is available"

ip link show dev "$default_interface" >/dev/null 2>&1 ||
    die "Default route interface does not exist: $default_interface"

selected_uuid="$(
    nmcli -t -f UUID,DEVICE connection show --active 2>/dev/null |
        awk -F: -v device="$default_interface" '$2 == device { print $1; exit }'
)"

[ -n "$selected_uuid" ] ||
    die "Interface $default_interface is not managed by NetworkManager or has no active connection"

connection_type="$(
    nmcli -g connection.type connection show uuid "$selected_uuid" 2>/dev/null
)"

[ "$connection_type" = "802-3-ethernet" ] ||
    die "Target connection is not a standard Ethernet connection: $connection_type; bond, bridge, and VLAN connections are not supported by this script"

ipv4_method="$(
    nmcli -g ipv4.method connection show uuid "$selected_uuid" 2>/dev/null
)"

[ "$ipv4_method" = "manual" ] ||
    die "The IPv4 method of the target connection is not manual: $ipv4_method"

old_addresses="$(
    nmcli -g ipv4.addresses connection show uuid "$selected_uuid" 2>/dev/null
)" || die "Unable to read the IPv4 addresses of the target connection"

old_address_spec="$(normalize_addresses "$old_addresses")"

[ -n "$old_address_spec" ] ||
    die "The target connection has no usable IPv4 CIDR addresses"

first_prefix=""

while IFS= read -r address; do
    address="$(trim "$address")"

    [[ "$address" == */* ]] || continue

    first_prefix="${address##*/}"
    break
done < <(printf '%s\n' "$old_address_spec" | tr ',' '\n')

[[ "$first_prefix" =~ ^[0-9]+$ ]] &&
    (( 10#$first_prefix <= 32 )) ||
    die "Unable to obtain a valid prefix from the original IPv4 address"

new_addresses=()

for ip in "${NEW_IPS[@]}"; do
    new_addresses+=("${ip}/${first_prefix}")
done

address_spec="$(IFS=,; printf '%s' "${new_addresses[*]}")"

echo "Target interface: $default_interface"
echo "Target connection UUID: $selected_uuid"
echo "Original IPv4 addresses: $old_address_spec"
echo "New IPv4 addresses: $address_spec"
echo "Warning: The connection will be reactivated, and the current SSH session may be interrupted."

nmcli connection modify \
    uuid "$selected_uuid" \
    ipv4.addresses "$address_spec" ||
    die "Failed to modify IPv4 addresses"

modified=1

nmcli connection down uuid "$selected_uuid" >/dev/null 2>&1 || true

nmcli connection up \
    uuid "$selected_uuid" \
    ifname "$default_interface" ||
    die "Failed to reactivate the NetworkManager connection"

success=1

echo "IPv4 addresses updated and the connection was successfully reactivated"

3.3 Scripts for Ubuntu 20, Ubuntu 22, and Debian 12

change-ip.sh file:

#!/usr/bin/env bash
set -o pipefail

# IPs defined here are merged with command-line IPs.
# Example: NEW_IPS=("192.169.21.45" "192.169.21.46")
NEW_IPS=()

backup_dir=""
netplan_file=""
cloud_init_marker_created=0
modified=0
success=0

die() {
    echo "Error: $*" >&2
    exit 1
}

valid_ipv4() {
    local candidate="$1"
    local octet
    local -a octets

    [[ "$candidate" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] ||
        return 1

    IFS=. read -r -a octets <<< "$candidate"
    for octet in "${octets[@]}"; do
        (( 10#$octet <= 255 )) || return 1
    done
}

add_unique_ip() {
    local candidate="$1"
    local existing

    for existing in "${new_ips[@]}"; do
        [ "$existing" = "$candidate" ] && return 0
    done

    new_ips+=("$candidate")
}

detect_default_interface() {
    ip -4 route get 1.1.1.1 2>/dev/null |
        awk '{
            for (i = 1; i <= NF; i++) {
                if ($i == "dev") {
                    print $(i + 1)
                    exit
                }
            }
        }'
}

rollback() {
    [ "$modified" -eq 1 ] || return 0
    [ -n "$backup_dir" ] || return 0
    [ -d "$backup_dir/netplan" ] || return 0

    echo "Restoring previous Netplan configuration..." >&2
    set +e

    find /etc/netplan -maxdepth 1 -type f \
        \( -name '*.yaml' -o -name '*.yml' \) -delete

    cp -a "$backup_dir/netplan/." /etc/netplan/

    if [ "$cloud_init_marker_created" -eq 1 ]; then
        rm -f /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
    fi

    netplan generate
    netplan apply
}

cleanup() {
    local status=$?

    trap - EXIT

    if [ "$modified" -eq 1 ] && [ "$success" -eq 0 ]; then
        rollback
    fi

    exit "$status"
}

trap cleanup EXIT

[ "$(id -u)" -eq 0 ] ||
    die "Run this script as root or with sudo"

command -v netplan >/dev/null 2>&1 ||
    die "netplan is not installed"

command -v ip >/dev/null 2>&1 ||
    die "ip command is not installed"

command -v python3 >/dev/null 2>&1 ||
    die "python3 is not installed"

python3 -c 'import yaml' >/dev/null 2>&1 ||
    die "python3-yaml is required: sudo apt-get install -y python3-yaml"

[ -d /etc/netplan ] ||
    die "/etc/netplan does not exist"

interface="$(detect_default_interface)"
[ -n "$interface" ] ||
    die "Cannot detect the default IPv4 route interface"

[ "$interface" != "lo" ] ||
    die "The detected interface is loopback"

ip link show dev "$interface" >/dev/null 2>&1 ||
    die "Interface does not exist: $interface"

configured_ips=("${NEW_IPS[@]}")
new_ips=()

for candidate in "${configured_ips[@]}" "$@"; do
    valid_ipv4 "$candidate" ||
        die "Invalid IPv4 address: $candidate"
    add_unique_ip "$candidate"
done

[ "${#new_ips[@]}" -gt 0 ] ||
    die "Specify at least one IPv4 address"

prefix="$(
    ip -4 -o addr show dev "$interface" scope global |
        awk 'NR == 1 { split($4, fields, "/"); print fields[2] }'
)"

[[ "$prefix" =~ ^([0-9]|[12][0-9]|3[0-2])$ ]] ||
    die "Cannot determine a valid IPv4 prefix from $interface"

address_spec=""
for candidate in "${new_ips[@]}"; do
    address_spec+="${candidate}/${prefix},"
done
address_spec="${address_spec%,}"

netplan_file="$(
    python3 - /etc/netplan "$interface" <<'PY'
import pathlib
import sys
import yaml

root = pathlib.Path(sys.argv[1])
interface = sys.argv[2]
matches = []

for path in sorted(list(root.glob("*.yaml")) + list(root.glob("*.yml"))):
    with path.open(encoding="utf-8") as source:
        document = yaml.safe_load(source) or {}

    ethernets = document.get("network", {}).get("ethernets", {})
    if isinstance(ethernets, dict) and interface in ethernets:
        matches.append(str(path))

if len(matches) != 1:
    print(
        f"Expected exactly one Netplan file defining ethernets.{interface}; found {len(matches)}",
        file=sys.stderr,
    )
    sys.exit(1)

print(matches[0])
PY
)"

backup_dir="$(mktemp -d /root/netplan-backup.XXXXXX)"
chmod 700 "$backup_dir"
cp -a /etc/netplan "$backup_dir/netplan"

if [ "$(basename "$netplan_file")" = "50-cloud-init.yaml" ] &&
   [ ! -e /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg ]; then
    printf '%s\n' 'network: {config: disabled}' \
        > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
    chmod 600 /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
    cloud_init_marker_created=1
fi

echo "Interface: $interface"
echo "Netplan source: $netplan_file"
echo "IPv4 addresses: $address_spec"
echo "Backup: $backup_dir"
echo "Applying configuration directly; the current SSH session may disconnect."

python3 - "$netplan_file" "$interface" "$address_spec" <<'PY'
import os
import pathlib
import stat
import sys
import tempfile
import yaml

path = pathlib.Path(sys.argv[1])
interface = sys.argv[2]
addresses = [address for address in sys.argv[3].split(",") if address]

with path.open(encoding="utf-8") as source:
    document = yaml.safe_load(source) or {}

profile = document["network"]["ethernets"][interface]
profile["addresses"] = addresses

original_mode = stat.S_IMODE(path.stat().st_mode)
fd, temporary_name = tempfile.mkstemp(
    dir=path.parent,
    prefix=f".{path.name}.",
    suffix=".tmp",
)

with os.fdopen(fd, "w", encoding="utf-8") as target:
    yaml.safe_dump(
        document,
        target,
        allow_unicode=True,
        default_flow_style=False,
        sort_keys=False,
    )

os.chmod(temporary_name, original_mode)
os.replace(temporary_name, path)
PY

chmod 600 "$netplan_file"
modified=1

netplan generate ||
    die "Generated Netplan configuration is invalid"

netplan apply ||
    die "Failed to apply Netplan configuration"

success=1
echo "IPv4 configuration applied successfully."

VMRack
  • Products
  • VPS Hosting
  • VPS Hosting
    Unmetered
  • Bare Metal
  • GPU Rental
  • CDN
    Public Beta
  • Custom CDN
  • Object Storage
    Public Beta
  • Transcoder
    Public Beta
  • Solutions
  • Bring Your Own IP (BYOIP)
  • Customized Server Solutions
  • Colocation Services
  • Resources
  • Pricing
  • Help Documentation
  • Articles
  • Developer Center
  • Referral Program
  • Contact
  • Company
  • About Us
  • Terms of Service
  • User Agreement
  • Privacy Policy
  • Service Level Agreement