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 have no specific OS requirements, we recommend using standard images — from them you can install the OS automatically or manually.

  1. Create an ISO image.
  2. Upload the image to S3 storage.
  3. Get a link to the object in 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 archive with the image, install the OS without software RAID and without a swap partition. We recommend automatically installing the OS on your dedicated server; then some steps of the instructions will already be completed.

  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, the output will contain a line with the ii status, for example ii sssd 2.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. Delete the 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 run:

    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:

    File /etc/cloud/cloud.cfg
    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 saving changes:

    :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 saving changes:

    :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:

    File boot_order_sort.sh
    #!/bin/bash

    # Retrieve the output of efibootmgr
    efi_output=$(LC_ALL=C efibootmgr 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

    # Do not delete entries if the current boot entry cannot be identified.
    if [[ -z "$current_os" ]] || ! printf '%s\n' "$boot_entries" | grep -qE "^${current_os}(\*|[[:space:]])"; then
    echo "[ERROR] Missing current boot entry; no changes made" >&2
    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)
    # Compare labels only: different disks have different device paths/GUIDs.
    description=$(printf '%s\n' "$entry" | sed -E \
    -e 's/^[0-9A-F]{4}\*?[[:space:]]+//' \
    -e 's/\t.*//' \
    -e 's/[[:space:]]+(HD|PciRoot|Pci|VenHw|VenMsg|Acpi|USB|MAC|BBS|FvFile|File|Fv|NVMe)\(.*//' \
    -e 's/[[:space:]]+$//')
    # 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 [[ ${#boot_dict[@]} -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
    if efibootmgr -b "$boot_number" -B; then
    removed_entries+=("$boot_number")
    echo "[INFO] Removed Boot Entry: $boot_number"
    else
    echo "[ERROR] Failed to remove Boot$boot_number" >&2
    exit 1
    fi
    fi
    done
    fi

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

    # Show the final EFI boot entries for verification
    final_output=$(LC_ALL=C efibootmgr) || exit 1
    printf '%s\n' "$final_output"
    actual_order=$(printf '%s\n' "$final_output" | sed -n 's/^BootOrder: //p')
    if [[ "$boot_order_sorted_str" != None && "$actual_order" != "$boot_order_sorted_str" ]]; then
    echo "[ERROR] BootOrder verification failed" >&2
    exit 1
    fi

    7.3. Exit the vi text editor saving changes:

    :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, when installing the OS, 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 passed 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, modify or add the PermitRootLogin yes parameter.

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

    8.4. Exit the vi text editor saving changes:

    :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 saving changes:

    :wq
  10. Optional: install and configure Fail2ban — a service for blocking IP addresses 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 saving changes:

    :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 systemd-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 exclude 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 saving changes:

    :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 archive with the image to S3 storage, follow the Upload object instructions.

If, when creating the ISO image of VMware ESXi, you prepared the setup.tpl file, upload it to storage along with the image.

To get a link to an object in S3 storage, follow the Get a link to an object instructions.

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 the link to the image that you uploaded in step 2 to S3 storage.

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

  8. Click Create image.