Skip to main content

Prepare an ISO image for OS autoinstall on a dedicated server

You can prepare your own OS image that will be compatible with the OS autoinstall mechanism on a dedicated server in Selectel.

If you do not have specific OS requirements, we recommend using standard images — you can install the OS via autoinstall or manually.

  1. Create an ISO image.
  2. Upload the image to S3 storage.
  3. Get a link to the object in the storage.
  4. Add the ISO image to the control panel.

1. Create an ISO image

The size of the OS image archive must be at least 3 GB less than the RAM size of the dedicated server. For example, if the server RAM size is 8 GB, the image archive size must not exceed 5 GB.

We recommend preparing the system as the root user.

  1. To prepare the image archive, install the OS without software RAID and without a swap partition. We recommend installing the OS via autoinstall on your dedicated server, as some instruction steps will already be performed.

  2. Check the status of the sssd package and remove it if it is installed:

    2.1. Check the status of the sssd: package:

    dpkg -l sssd

    If the package is installed, a line with the status ii will appear in the response, for example iisssd2.9.1-....

    2.2. If the sssd package is installed, remove it:

    sudo apt purge -y sssd
    sudo apt autoremove -y
  3. Install the required packages:

    sudo apt install -y \
    openssh-server \
    cloud-init \
    grub2 \
    grub-efi-amd64-bin \
    mdadm \
    nftables
  4. Update the OS kernel to the latest stable version:

    linux-image-generic-hwe-24.04
    linux-headers-generic-hwe-24.04
  5. Update the system packages:

    apt update && apt upgrade -y
  6. Prepare the cloud-init service for use in the image:

    6.1. Remove files and directories that contain the current cloud-init: configuration:

    rm -rf /etc/cloud/cloud.cfg.d /etc/cloud/cloud-init.disabled

    6.2. Delete data from the previous cloud-init: execution:

    cloud-init clean --logs

    6.3. Create the /etc/cloud/cloud.cfg.d: directory:

    mkdir -p /etc/cloud/cloud.cfg.d

    6.4. Create the /etc/cloud/cloud.cfg file using the vi: text editor:

    vi /etc/cloud/cloud.cfg

    6.5. Add the cloud-init: configuration to the file:

    The /etc/cloud/cloud.cfg file
    datasource_list:
    - NoCloud
    - None

    users:
    - default

    #disable_root: 1
    #ssh_pwauth: 0

    mount_default_fields: [~, ~, 'auto', 'defaults,nofail,x-systemd.requires=cloud-init.service', '0', '2']
    resize_rootfs_tmp: /dev
    ssh_deletekeys: 1
    ssh_genkeytypes: ['rsa', 'ecdsa', 'ed25519']
    syslog_fix_perms: ~
    disable_vmware_customization: false

    cloud_init_modules:
    - disk_setup
    - growpart
    - resizefs
    - set_hostname
    - update_hostname
    - update_etc_hosts
    - users-groups
    - ssh
    - apt-configure
    - bootcmd
    - ca_certs
    - rsyslog
    - write_files

    cloud_config_modules:
    - mounts
    - locale
    - set-passwords
    - timezone
    - runcmd
    - snap
    - ssh_import_id
    - ubuntu_drivers
    - ntp

    cloud_final_modules:
    - package-update-upgrade-install
    - scripts-per-once
    - scripts-per-boot
    - scripts-per-instance
    - scripts-user
    - ssh-authkey-fingerprints
    - keys-to-console
    - final-message
    - power-state-change
    - lxd
    - puppet
    - chef
    - mcollective
    - salt_minion
    - phone_home

    system_info:
    default_user:
    name: cloud-user
    lock_passwd: true
    gecos: Cloud User
    groups: [adm, systemd-journal]
    sudo: ["ALL=(ALL) NOPASSWD:ALL"]
    shell: /bin/bash
    distro: ubuntu
    paths:
    cloud_dir: /var/lib/cloud
    templates_dir: /etc/cloud/templates
    ssh_svcname: sshd

    vendor_data:
    enabled: True

    # vim:syntax=yaml

    6.6. Exit the vi text editor with your changes saved:

    :wq

    6.7. Create the cloud-init-local.service file using the vi: text editor:

    sudo vi /usr/lib/systemd/system/cloud-init-local.service

    6.8. Add the configuration to the file:

    [Unit]
    Description=Initial cloud-init job (pre-networking)
    DefaultDependencies=no
    Wants=network-pre.target
    After=hv_kvp_daemon.service
    After=systemd-remount-fs.service
    Before=NetworkManager.service
    Before=network-pre.target
    Before=shutdown.target
    Before=sysinit.target
    Conflicts=shutdown.target
    RequiresMountsFor=/var/lib/cloud
    ConditionPathExists=!/etc/cloud/cloud-init.disabled
    ConditionKernelCommandLine=!cloud-init=disabled
    ConditionEnvironment=!KERNEL_CMDLINE=cloud-init=disabled

    [Service]
    Type=oneshot
    ExecStartPre=sleep 30
    ExecStart=/usr/bin/cloud-init init --local
    RemainAfterExit=yes
    TimeoutSec=0

    # Output needs to appear in instance console output
    StandardOutput=journal+console

    [Install]
    WantedBy=cloud-init.target

    6.9. Exit the vi text editor with your changes saved:

    :wq
  7. Add and configure the per-boot script:

    7.1. Create the boot_order_sort.sh script file using the vi: text editor:

    sudo vi /var/lib/cloud/scripts/per-boot/boot_order_sort.sh

    7.2. Add the script text to the file:

    The boot_order_sort.sh file
    #!/bin/bash

    # Retrieve the output of efibootmgr
    efi_output=$(efibootmgr -D 2>&1)

    # Check if the system supports EFI
    if [[ $? -ne 0 ]]; then
    echo "[WARNING] System does not support EFI, boot order will not be changed."
    exit 0
    fi

    # Extract the current boot entry (BootCurrent)
    current_os=$(echo "$efi_output" | grep -oP '(?<=BootCurrent: )[0-9A-F]{4}')
    # Extract the boot order (BootOrder)
    boot_order=$(echo "$efi_output" | grep -oP '(?<=BootOrder: )[0-9A-F,]+')

    # Declare an associative array to store BootXXXX values and their descriptions
    declare -A boot_dict
    # boot_order_sorted will contain the sorted boot order
    # removed_entries will contain boot numbers that are not in the sorted list (and are removed)
    boot_order_sorted=()
    removed_entries=()

    # Parse all BootXXXX entries with their descriptions (remove 'Boot' prefix and *)
    boot_entries=$(echo "$efi_output" | grep -oP '(?<=Boot)[0-9A-F]{4}\\*?.*')

    if [[ -z "$boot_entries" ]]; then
    echo "[ERROR] No boot entries found in EFI output."
    exit 1
    fi

    # Debug output
    echo "[INFO] Current Boot: ${current_os:-None}"
    echo "[INFO] Boot Order: ${boot_order:-None}"

    # Loop through and process each boot entry
    while read -r entry; do
    # Extract the four-digit boot number (excluding 'Boot' and '*')
    boot_number=$(echo "$entry" | grep -oP '^[0-9A-F]{4}')
    # Extract the description (everything after the boot number)
    description=$(echo "$entry" | sed 's/^[0-9A-F]\{4\}\\* //')
    # Ensure the boot_number is not empty and add to the dictionary
    if [[ -n "$boot_number" ]]; then
    boot_dict["$boot_number"]="$description"
    fi
    # Add entries containing "IP4" or "PXE", but not containing "HTTP", to the sorted list
    if [[ ("$description" =~ "IP4" || "$description" =~ "PXE" || "$description" =~ "IPv4" || "$description" =~ "NIC") && ! "$description" =~ "HTTP" ]]; then
    boot_order_sorted+=("$boot_number")
    fi
    echo "$boot_number: $description"
    done <<< "$boot_entries"

    # Add the current OS to the main list if it exists
    if [[ -n "$current_os" ]]; then
    boot_order_sorted+=("$current_os")
    fi

    # Find all entries with the same description as current_os and add them to the sorted list
    if [[ -n "$current_os" ]]; then
    current_description="${boot_dict[$current_os]}"
    for boot_number in "${!boot_dict[@]}"; do
    # If description matches the current OS but boot number is not the same, add it to the sorted list
    if [[ "$boot_number" != "$current_os" && "${boot_dict[$boot_number]}" == "$current_description" ]]; then
    boot_order_sorted+=("$boot_number")
    fi
    done
    fi

    # Find all boot numbers that are not in the main sorted list and remove them
    if [[ $ -eq 0 ]]; then
    echo "[INFO] No boot entries to process."
    else
    for boot_number in "${!boot_dict[@]}"; do
    if [[ ! " ${boot_order_sorted[*]} " =~ " $boot_number " ]]; then
    # Remove the boot entry from the system
    efibootmgr -b "$boot_number" -B > /dev/null 2>&1
    removed_entries+=("$boot_number") # Add removed boot number to the removed entries list
    echo "[INFO] Removed Boot Entry: $boot_number"
    fi
    done
    fi

    # Convert the main boot order list to a comma-separated string
    if [[ $ -eq 0 ]]; then
    boot_order_sorted_str="None"
    else
    boot_order_sorted_str=$(IFS=,; echo "${boot_order_sorted[*]}")
    efibootmgr -o "$boot_order_sorted_str" > /dev/null 2>&1
    echo "[INFO] Updated boot order: $boot_order_sorted_str"
    fi

    # Show the final EFI boot entries for verification
    efibootmgr -D

    7.3. Exit the vi text editor with your changes saved:

    :wq

    7.4. Set permissions for the boot_order_sort.sh: file:

    sudo chmod 700 /var/lib/cloud/scripts/per-boot/boot_order_sort.sh
  8. Optional: configure SSH. By default, during OS installation, the cloud-init service sets a password only for the root user. You can use your own authentication scheme, for example, disable password login and login as the root user by creating a separate user with SSH key authorization. In this case, the root password provided during installation will be set but will not be used.

    8.1. Open the /etc/ssh/sshd_config configuration file using the vi: text editor:

    sudo vi /etc/ssh/sshd_config

    8.2. If you want to allow SSH login as the root user, change or add the PermitRootLogin yes. parameter.

    8.3. If you want to allow password authentication, change or add the PasswordAuthentication yes. parameter.

    8.4. Exit the vi text editor with your changes saved:

    :wq
  9. Optional: configure grub:

    9.1. Open the /etc/default/grub configuration file using the vi: text editor:

    sudo vi /etc/default/grub

    9.2. Change or add the parameters:

    GRUB_DEFAULT=0
    GRUB_TIMEOUT=5
    GRUB_DISTRIBUTOR=Ubuntu
    GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet nomodeset"
    GRUB_CMDLINE_LINUX="rd.auto=1 net.ifnames=0 biosdevname=0"
    GRUB_PRELOAD_MODULES="part_gpt part_msdos"
    GRUB_TIMEOUT_STYLE=menu
    GRUB_TERMINAL_INPUT=console
    GRUB_GFXMODE=auto
    GRUB_GFXPAYLOAD_LINUX=keep
    GRUB_DISABLE_RECOVERY=true
    GRUB_DISABLE_OS_PROBER=false

    9.3. Exit the vi text editor with your changes saved:

    :wq
  10. Optional: install and configure Fail2ban — a service that blocks an IP address in case of failed authorization attempts:

    10.1. Install Fail2ban:

    fail2ban

    10.2. Open the /etc/fail2ban/jail.d/service.conf configuration file using the vi: text editor:

    sudo vi /etc/fail2ban/jail.d/service.conf

    10.3. Change or add the parameters:

    [sshd]
    enabled = true
    port = ssh
    filter = sshd
    action = nftables[name=sshd, port=ssh, protocol=tcp]
    logpath = /var/log/auth.log
    maxretry = 10
    findtime = 3600
    bantime = 86400

    10.4. Exit the vi text editor with your changes saved:

    :wq
  11. Optional: make additional changes to the system, such as installing extra software, packages, or performing other necessary settings for your image. Ensure that the changes made do not contradict the previous instruction steps.

  12. Disable sleep and hibernation mode:

    systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
  13. Create a symbolic link for mdamd:

    ln -s /usr/sbin/mdadm /usr/bin/mdadm
  14. Enable the system-resolved: service:

    systemctl enable --now systemd-resolved
    ln -s /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
  15. Remove unused packages:

    apt autoremove -y
  16. Apply the changes; to do this, reboot the system:

    systemctl reboot
  17. Reset the unique system identifier so that each instance of the installed system receives its own identifier:

    truncate -s 0 /etc/machine-id /var/lib/dbus/machine-id
  18. Prepare a file with a list of directories that should not be included in the OS image archive:

    18.1. Create the exclude.txt file using the vi: text editor:

    sudo vi /root/exclude.txt

    18.2. Add to the exclude.txt file the list of directories to be excluded from the archive:

    /tmp/*
    /proc/*
    /sys/*
    /dev/*
    /run/*
    /mnt/*
    /media/*
    /var/lib/lxcfs
    /var/spool/postfix
    /lost+found

    18.3. Exit the vi text editor with your changes saved:

    :wq
  19. Create a file system archive:

    tar -czp -f /mnt/base.tar.gz --exclude-from=/root/exclude.txt /

2. Upload the image to S3 storage

To upload the image archive to S3 storage, use the Upload Object instruction.

If while creating the ISO image for VMware ESXi you prepared the setul.tpl file, upload it to the storage along with the image.

To get a link to an object in S3 storage, use the Get a Link to an Object instruction.

4. Add the image to the control panel

  1. In the control panel in the top menu, click Products and select Dedicated Servers.

  2. Go to the Images section.

  3. Click Add Image.

  4. Select the OS family.

  5. Enter the image name.

  6. Add a link to the image you uploaded to S3 storage in step 2.

  7. If in step 6 you added the path to the image in S3 as the link, enter the Access key and Secret key.

  8. Click Create Image.