summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Estabrook <jestabro@vyos.io>2023-11-15 11:56:52 -0600
committerJohn Estabrook <jestabro@vyos.io>2023-12-16 20:37:10 -0600
commit623cc2935d3dfc1a0715f61cf3ec45fbb23d2787 (patch)
treec1ef5e425e365db9e324707bc22cfd663e50db3f
parenta1476c24fb549aaf2702f1c9e2383b3eb90bc6ee (diff)
downloadvyos-1x-623cc2935d3dfc1a0715f61cf3ec45fbb23d2787.tar.gz
vyos-1x-623cc2935d3dfc1a0715f61cf3ec45fbb23d2787.zip
image: T4516: add raid-1 install support
(cherry picked from commit e036f783bc85e4d2bad5f5cbfd688a03a352223e)
-rw-r--r--python/vyos/system/disk.py83
-rw-r--r--python/vyos/system/grub.py15
-rw-r--r--python/vyos/system/raid.py115
-rw-r--r--python/vyos/utils/io.py10
-rwxr-xr-xsrc/op_mode/image_installer.py295
5 files changed, 426 insertions, 92 deletions
diff --git a/python/vyos/system/disk.py b/python/vyos/system/disk.py
index 882b4eb39..49e6b5c5e 100644
--- a/python/vyos/system/disk.py
+++ b/python/vyos/system/disk.py
@@ -15,12 +15,20 @@
from json import loads as json_loads
from os import sync
+from dataclasses import dataclass
from psutil import disk_partitions
from vyos.utils.process import run, cmd
+@dataclass
+class DiskDetails:
+ """Disk details"""
+ name: str
+ partition: dict[str, str]
+
+
def disk_cleanup(drive_path: str) -> None:
"""Clean up disk partition table (MBR and GPT)
Zeroize primary and secondary headers - first and last 17408 bytes
@@ -67,6 +75,62 @@ def parttable_create(drive_path: str, root_size: int) -> None:
sync()
run(f'partprobe {drive_path}')
+ partitions: list[str] = partition_list(drive_path)
+
+ disk: DiskDetails = DiskDetails(
+ name = drive_path,
+ partition = {
+ 'efi': next(x for x in partitions if x.endswith('2')),
+ 'root': next(x for x in partitions if x.endswith('3'))
+ }
+ )
+
+ return disk
+
+
+def partition_list(drive_path: str) -> list[str]:
+ """Get a list of partitions on a drive
+
+ Args:
+ drive_path (str): path to a drive
+
+ Returns:
+ list[str]: a list of partition paths
+ """
+ lsblk: str = cmd(f'lsblk -Jp {drive_path}')
+ drive_info: dict = json_loads(lsblk)
+ device: list = drive_info.get('blockdevices')
+ children: list[str] = device[0].get('children', []) if device else []
+ partitions: list[str] = [child.get('name') for child in children]
+ return partitions
+
+
+def partition_parent(partition_path: str) -> str:
+ """Get a parent device for a partition
+
+ Args:
+ partition (str): path to a partition
+
+ Returns:
+ str: path to a parent device
+ """
+ parent: str = cmd(f'lsblk -ndpo pkname {partition_path}')
+ return parent
+
+
+def from_partition(partition_path: str) -> DiskDetails:
+ drive_path: str = partition_parent(partition_path)
+ partitions: list[str] = partition_list(drive_path)
+
+ disk: DiskDetails = DiskDetails(
+ name = drive_path,
+ partition = {
+ 'efi': next(x for x in partitions if x.endswith('2')),
+ 'root': next(x for x in partitions if x.endswith('3'))
+ }
+ )
+
+ return disk
def filesystem_create(partition: str, fstype: str) -> None:
"""Create a filesystem on a partition
@@ -138,25 +202,6 @@ def find_device(mountpoint: str) -> str:
return ''
-def raid_create(raid_name: str,
- raid_members: list[str],
- raid_level: str = 'raid1') -> None:
- """Create a RAID array
-
- Args:
- raid_name (str): a name of array (data, backup, test, etc.)
- raid_members (list[str]): a list of array members
- raid_level (str, optional): an array level. Defaults to 'raid1'.
- """
- raid_devices_num: int = len(raid_members)
- raid_members_str: str = ' '.join(raid_members)
- command: str = f'mdadm --create /dev/md/{raid_name} --metadata=1.2 \
- --raid-devices={raid_devices_num} --level={raid_level} \
- {raid_members_str}'
-
- run(command)
-
-
def disks_size() -> dict[str, int]:
"""Get a dictionary with physical disks and their sizes
diff --git a/python/vyos/system/grub.py b/python/vyos/system/grub.py
index 9ac205c03..0ac16af9a 100644
--- a/python/vyos/system/grub.py
+++ b/python/vyos/system/grub.py
@@ -19,7 +19,7 @@ from typing import Union
from uuid import uuid5, NAMESPACE_URL, UUID
from vyos.template import render
-from vyos.utils.process import run, cmd
+from vyos.utils.process import cmd
from vyos.system import disk
# Define variables
@@ -49,7 +49,7 @@ REGEX_GRUB_MODULES: str = r'^insmod (?P<module_name>.+)$'
REGEX_KERNEL_CMDLINE: str = r'^BOOT_IMAGE=/(?P<boot_type>boot|live)/((?P<image_version>.+)/)?vmlinuz.*$'
-def install(drive_path: str, boot_dir: str, efi_dir: str) -> None:
+def install(drive_path: str, boot_dir: str, efi_dir: str, id: str = 'VyOS') -> None:
"""Install GRUB for both BIOS and EFI modes (hybrid boot)
Args:
@@ -62,11 +62,11 @@ def install(drive_path: str, boot_dir: str, efi_dir: str) -> None:
{drive_path} --force',
f'grub-install --no-floppy --recheck --target=x86_64-efi \
--force-extra-removable --boot-directory={boot_dir} \
- --efi-directory={efi_dir} --bootloader-id="VyOS" \
+ --efi-directory={efi_dir} --bootloader-id="{id}" \
--no-uefi-secure-boot'
]
for command in commands:
- run(command)
+ cmd(command)
def gen_version_uuid(version_name: str) -> str:
@@ -294,7 +294,7 @@ def set_default(version_name: str, root_dir: str = '') -> None:
vars_write(vars_file, vars_current)
-def common_write(root_dir: str = '') -> None:
+def common_write(root_dir: str = '', grub_common: dict[str, str] = {}) -> None:
"""Write common GRUB configuration file (overwrite everything)
Args:
@@ -304,7 +304,7 @@ def common_write(root_dir: str = '') -> None:
if not root_dir:
root_dir = disk.find_persistence()
common_config = f'{root_dir}/{CFG_VYOS_COMMON}'
- render(common_config, TMPL_GRUB_COMMON, {})
+ render(common_config, TMPL_GRUB_COMMON, grub_common)
def create_structure(root_dir: str = '') -> None:
@@ -335,3 +335,6 @@ def set_console_type(console_type: str, root_dir: str = '') -> None:
vars_current: dict[str, str] = vars_read(vars_file)
vars_current['console_type'] = str(console_type)
vars_write(vars_file, vars_current)
+
+def set_raid(root_dir: str = '') -> None:
+ pass
diff --git a/python/vyos/system/raid.py b/python/vyos/system/raid.py
new file mode 100644
index 000000000..13b99fa69
--- /dev/null
+++ b/python/vyos/system/raid.py
@@ -0,0 +1,115 @@
+# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+"""RAID related functions"""
+
+from pathlib import Path
+from shutil import copy
+from dataclasses import dataclass
+
+from vyos.utils.process import cmd
+from vyos.system import disk
+
+
+@dataclass
+class RaidDetails:
+ """RAID type"""
+ name: str
+ level: str
+ members: list[str]
+ disks: list[disk.DiskDetails]
+
+
+def raid_create(raid_members: list[str],
+ raid_name: str = 'md0',
+ raid_level: str = 'raid1') -> None:
+ """Create a RAID array
+
+ Args:
+ raid_name (str): a name of array (data, backup, test, etc.)
+ raid_members (list[str]): a list of array members
+ raid_level (str, optional): an array level. Defaults to 'raid1'.
+ """
+ raid_devices_num: int = len(raid_members)
+ raid_members_str: str = ' '.join(raid_members)
+ if Path('/sys/firmware/efi').exists():
+ for part in raid_members:
+ drive: str = disk.partition_parent(part)
+ command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}'
+ cmd(command)
+ else:
+ for part in raid_members:
+ drive: str = disk.partition_parent(part)
+ command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}'
+ cmd(command)
+ for part in raid_members:
+ command: str = f'mdadm --zero-superblock {part}'
+ cmd(command)
+ command: str = f'mdadm --create /dev/{raid_name} -R --metadata=1.0 \
+ --raid-devices={raid_devices_num} --level={raid_level} \
+ {raid_members_str}'
+
+ cmd(command)
+
+ raid = RaidDetails(
+ name = f'/dev/{raid_name}',
+ level = raid_level,
+ members = raid_members,
+ disks = [disk.from_partition(m) for m in raid_members]
+ )
+
+ return raid
+
+def update_initramfs() -> None:
+ """Update initramfs"""
+ mdadm_script = '/etc/initramfs-tools/scripts/local-top/mdadm'
+ copy('/usr/share/initramfs-tools/scripts/local-block/mdadm', mdadm_script)
+ p = Path(mdadm_script)
+ p.write_text(p.read_text().replace('$((COUNT + 1))', '20'))
+ command: str = 'update-initramfs -u'
+ cmd(command)
+
+def update_default(target_dir: str) -> None:
+ """Update /etc/default/mdadm to start MD monitoring daemon at boot
+ """
+ source_mdadm_config = '/etc/default/mdadm'
+ target_mdadm_config = Path(target_dir).joinpath('/etc/default/mdadm')
+ target_mdadm_config_dir = Path(target_mdadm_config).parent
+ Path.mkdir(target_mdadm_config_dir, parents=True, exist_ok=True)
+ s = Path(source_mdadm_config).read_text().replace('START_DAEMON=false',
+ 'START_DAEMON=true')
+ Path(target_mdadm_config).write_text(s)
+
+def get_uuid(device: str) -> str:
+ """Get UUID of a device"""
+ command: str = f'tune2fs -l {device}'
+ l = cmd(command).splitlines()
+ uuid = next((x for x in l if x.startswith('Filesystem UUID')), '')
+ return uuid.split(':')[1].strip() if uuid else ''
+
+def get_uuids(raid_details: RaidDetails) -> tuple[str]:
+ """Get UUIDs of RAID members
+
+ Args:
+ raid_name (str): a name of array (data, backup, test, etc.)
+
+ Returns:
+ tuple[str]: root_disk uuid, root_md uuid
+ """
+ raid_name: str = raid_details.name
+ root_partition: str = raid_details.members[0]
+ uuid_root_disk: str = get_uuid(root_partition)
+ uuid_root_md: str = get_uuid(raid_name)
+ return uuid_root_disk, uuid_root_md
diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py
index e34a1ba32..74099b502 100644
--- a/python/vyos/utils/io.py
+++ b/python/vyos/utils/io.py
@@ -13,6 +13,8 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+from typing import Callable
+
def print_error(str='', end='\n'):
"""
Print `str` to stderr, terminated with `end`.
@@ -73,7 +75,8 @@ def is_dumb_terminal():
import os
return os.getenv('TERM') in ['vt100', 'dumb']
-def select_entry(l: list, list_msg: str = '', prompt_msg: str = '') -> str:
+def select_entry(l: list, list_msg: str = '', prompt_msg: str = '',
+ list_format: Callable = None,) -> str:
"""Select an entry from a list
Args:
@@ -87,7 +90,10 @@ def select_entry(l: list, list_msg: str = '', prompt_msg: str = '') -> str:
en = list(enumerate(l, 1))
print(list_msg)
for i, e in en:
- print(f'\t{i}: {e}')
+ if list_format:
+ print(f'\t{i}: {list_format(e)}')
+ else:
+ print(f'\t{i}: {e}')
select = ask_input(prompt_msg, numeric_only=True,
valid_responses=range(1, len(l)+1))
return next(filter(lambda x: x[0] == select, en))[1]
diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py
index 88d5eeb48..aa4cf301b 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -21,18 +21,20 @@ from argparse import ArgumentParser, Namespace
from pathlib import Path
from shutil import copy, chown, rmtree, copytree
from sys import exit
-from passlib.hosts import linux_context
+from time import sleep
+from typing import Union
from urllib.parse import urlparse
+from passlib.hosts import linux_context
from psutil import disk_partitions
from vyos.configtree import ConfigTree
from vyos.remote import download
-from vyos.system import disk, grub, image, compat, SYSTEM_CFG_VER
+from vyos.system import disk, grub, image, compat, raid, SYSTEM_CFG_VER
from vyos.template import render
-from vyos.utils.io import ask_input, ask_yes_no
+from vyos.utils.io import ask_input, ask_yes_no, select_entry
from vyos.utils.file import chmod_2775
-from vyos.utils.process import run
+from vyos.utils.process import cmd, run
# define text messages
MSG_ERR_NOT_LIVE: str = 'The system is already installed. Please use "add system image" instead.'
@@ -44,11 +46,15 @@ MSG_INFO_INSTALL_EXIT: str = 'Exiting from VyOS installation'
MSG_INFO_INSTALL_SUCCESS: str = 'The image installed successfully; please reboot now.'
MSG_INFO_INSTALL_DISKS_LIST: str = 'The following disks were found:'
MSG_INFO_INSTALL_DISK_SELECT: str = 'Which one should be used for installation?'
+MSG_INFO_INSTALL_RAID_CONFIGURE: str = 'Would you like to configure RAID-1 mirroring?'
+MSG_INFO_INSTALL_RAID_FOUND_DISKS: str = 'Would you like to configure RAID-1 mirroring on them?'
+MSG_INFO_INSTALL_RAID_CHOOSE_DISKS: str = 'Would you like to choose two disks for RAID-1 mirroring?'
MSG_INFO_INSTALL_DISK_CONFIRM: str = 'Installation will delete all data on the drive. Continue?'
+MSG_INFO_INSTALL_RAID_CONFIRM: str = 'Installation will delete all data on both drives. Continue?'
MSG_INFO_INSTALL_PARTITONING: str = 'Creating partition table...'
MSG_INPUT_CONFIG_FOUND: str = 'An active configuration was found. Would you like to copy it to the new image?'
MSG_INPUT_IMAGE_NAME: str = 'What would you like to name this image?'
-MSG_INPUT_IMAGE_DEFAULT: str = 'Would you like to set a new image as default one for boot?'
+MSG_INPUT_IMAGE_DEFAULT: str = 'Would you like to set the new image as the default one for boot?'
MSG_INPUT_PASSWORD: str = 'Please enter a password for the "vyos" user'
MSG_INPUT_ROOT_SIZE_ALL: str = 'Would you like to use all the free space on the drive?'
MSG_INPUT_ROOT_SIZE_SET: str = 'Please specify the size (in GB) of the root partition (min is 1.5 GB)?'
@@ -107,13 +113,14 @@ def gb_to_bytes(size: float) -> int:
return int(size * 1024**3)
-def find_disk() -> tuple[str, int]:
+def find_disks() -> dict[str, int]:
"""Find a target disk for installation
Returns:
- tuple[str, int]: disk name and size in bytes
+ dict[str, int]: a list of available disks by name and size
"""
# check for available disks
+ print('Probing disks')
disks_available: dict[str, int] = disk.disks_size()
for disk_name, disk_size in disks_available.copy().items():
if disk_size < CONST_MIN_DISK_SIZE:
@@ -122,17 +129,10 @@ def find_disk() -> tuple[str, int]:
print(MSG_ERR_NO_DISK)
exit(MSG_INFO_INSTALL_EXIT)
- # select one as a target
- print(MSG_INFO_INSTALL_DISKS_LIST)
- default_disk: str = list(disks_available)[0]
- for disk_name, disk_size in disks_available.items():
- disk_size_human: str = bytes_to_gb(disk_size)
- print(f'Drive: {disk_name} ({disk_size_human} GB)')
- disk_selected: str = ask_input(MSG_INFO_INSTALL_DISK_SELECT,
- default=default_disk,
- valid_responses=list(disks_available))
+ num_disks: int = len(disks_available)
+ print(f'{num_disks} disk(s) found')
- return disk_selected, disks_available[disk_selected]
+ return disks_available
def ask_root_size(available_space: int) -> int:
@@ -160,6 +160,126 @@ def ask_root_size(available_space: int) -> int:
return root_size_kbytes
+def create_partitions(target_disk: str, target_size: int,
+ prompt: bool = True) -> None:
+ """Create partitions on a target disk
+
+ Args:
+ target_disk (str): a target disk
+ target_size (int): size of disk in bytes
+ """
+ # define target rootfs size in KB (smallest unit acceptable by sgdisk)
+ available_size: int = (target_size - CONST_RESERVED_SPACE) // 1024
+ if prompt:
+ rootfs_size: int = ask_root_size(available_size)
+ else:
+ rootfs_size: int = available_size
+
+ print(MSG_INFO_INSTALL_PARTITONING)
+ disk.disk_cleanup(target_disk)
+ disk_details: disk.DiskDetails = disk.parttable_create(target_disk,
+ rootfs_size)
+
+ return disk_details
+
+
+def ask_single_disk(disks_available: dict[str, int]) -> str:
+ """Ask user to select a disk for installation
+
+ Args:
+ disks_available (dict[str, int]): a list of available disks
+ """
+ print(MSG_INFO_INSTALL_DISKS_LIST)
+ default_disk: str = list(disks_available)[0]
+ for disk_name, disk_size in disks_available.items():
+ disk_size_human: str = bytes_to_gb(disk_size)
+ print(f'Drive: {disk_name} ({disk_size_human} GB)')
+ disk_selected: str = ask_input(MSG_INFO_INSTALL_DISK_SELECT,
+ default=default_disk,
+ valid_responses=list(disks_available))
+
+ # create partitions
+ if not ask_yes_no(MSG_INFO_INSTALL_DISK_CONFIRM):
+ print(MSG_INFO_INSTALL_EXIT)
+ exit()
+
+ disk_details: disk.DiskDetails = create_partitions(disk_selected,
+ disks_available[disk_selected])
+
+ disk.filesystem_create(disk_details.partition['efi'], 'efi')
+ disk.filesystem_create(disk_details.partition['root'], 'ext4')
+
+ return disk_details
+
+
+def check_raid_install(disks_available: dict[str, int]) -> Union[str, None]:
+ """Ask user to select disks for RAID installation
+
+ Args:
+ disks_available (dict[str, int]): a list of available disks
+ """
+ if len(disks_available) < 2:
+ return None
+
+ if not ask_yes_no(MSG_INFO_INSTALL_RAID_CONFIGURE, default=True):
+ return None
+
+ def format_selection(disk_name: str) -> str:
+ return f'{disk_name}\t({bytes_to_gb(disks_available[disk_name])} GB)'
+
+ disk0, disk1 = list(disks_available)[0], list(disks_available)[1]
+ disks_selected: dict[str, int] = { disk0: disks_available[disk0],
+ disk1: disks_available[disk1] }
+
+ target_size: int = min(disks_selected[disk0], disks_selected[disk1])
+
+ print(MSG_INFO_INSTALL_DISKS_LIST)
+ for disk_name, disk_size in disks_selected.items():
+ disk_size_human: str = bytes_to_gb(disk_size)
+ print(f'\t{disk_name} ({disk_size_human} GB)')
+ if not ask_yes_no(MSG_INFO_INSTALL_RAID_FOUND_DISKS, default=True):
+ if not ask_yes_no(MSG_INFO_INSTALL_RAID_CHOOSE_DISKS, default=True):
+ return None
+ else:
+ disks_selected = {}
+ disk0 = select_entry(list(disks_available), 'Disks available:',
+ 'Select first disk:', format_selection)
+
+ disks_selected[disk0] = disks_available[disk0]
+ del disks_available[disk0]
+ disk1 = select_entry(list(disks_available), 'Remaining disks:',
+ 'Select second disk:', format_selection)
+ disks_selected[disk1] = disks_available[disk1]
+
+ target_size: int = min(disks_selected[disk0],
+ disks_selected[disk1])
+
+ # create partitions
+ if not ask_yes_no(MSG_INFO_INSTALL_RAID_CONFIRM):
+ print(MSG_INFO_INSTALL_EXIT)
+ exit()
+
+ disks: list[disk.DiskDetails] = []
+ for disk_selected in list(disks_selected):
+ print(f'Creating partitions on {disk_selected}')
+ disk_details = create_partitions(disk_selected, target_size,
+ prompt=False)
+ disk.filesystem_create(disk_details.partition['efi'], 'efi')
+
+ disks.append(disk_details)
+
+ print('Creating RAID array')
+ members = [disk.partition['root'] for disk in disks]
+ raid_details: raid.RaidDetails = raid.raid_create(members)
+ # raid init stuff
+ print('Updating initramfs')
+ raid.update_initramfs()
+ # end init
+ print('Creating filesystem on RAID array')
+ disk.filesystem_create(raid_details.name, 'ext4')
+
+ return raid_details
+
def prepare_tmp_disr() -> None:
"""Create temporary directories for installation
@@ -294,7 +414,7 @@ def image_fetch(image_path: str) -> Path:
if local_path.is_file():
return local_path
else:
- raise
+ raise FileNotFoundError
except Exception:
print(f'The image cannot be fetched from: {image_path}')
exit(1)
@@ -351,6 +471,27 @@ def cleanup(mounts: list[str] = [], remove_items: list[str] = []) -> None:
if Path(remove_item).is_dir():
rmtree(remove_item)
+def cleanup_raid(details: raid.RaidDetails) -> None:
+ efiparts = []
+ for raid_disk in details.disks:
+ efiparts.append(raid_disk.partition['efi'])
+ cleanup([details.name, *efiparts],
+ ['/mnt/installation'])
+
+
+def is_raid_install(install_object: Union[disk.DiskDetails, raid.RaidDetails]) -> bool:
+ """Check if installation target is a RAID array
+
+ Args:
+ install_object (Union[disk.DiskDetails, raid.RaidDetails]): a target disk
+
+ Returns:
+ bool: True if it is a RAID array
+ """
+ if isinstance(install_object, raid.RaidDetails):
+ return True
+ return False
+
def install_image() -> None:
"""Install an image to a disk
@@ -363,50 +504,44 @@ def install_image() -> None:
print(MSG_INFO_INSTALL_EXIT)
exit()
+ # configure image name
+ running_image_name: str = image.get_running_image()
+ while True:
+ image_name: str = ask_input(MSG_INPUT_IMAGE_NAME,
+ running_image_name)
+ if image.validate_name(image_name):
+ break
+ print(MSG_WARN_IMAGE_NAME_WRONG)
+
+ # ask for password
+ user_password: str = ask_input(MSG_INPUT_PASSWORD, default='vyos')
+
+ # ask for default console
+ console_type: str = ask_input(MSG_INPUT_CONSOLE_TYPE,
+ default='K',
+ valid_responses=['K', 'S', 'U'])
+ console_dict: dict[str, str] = {'K': 'tty', 'S': 'ttyS', 'U': 'ttyUSB'}
+
+ disks: dict[str, int] = find_disks()
+
+ install_target: Union[disk.DiskDetails, raid.RaidDetails, None] = None
try:
- # configure image name
- running_image_name: str = image.get_running_image()
- while True:
- image_name: str = ask_input(MSG_INPUT_IMAGE_NAME,
- running_image_name)
- if image.validate_name(image_name):
- break
- print(MSG_WARN_IMAGE_NAME_WRONG)
-
- # define target drive
- install_target, target_size = find_disk()
-
- # define target rootfs size in KB (smallest unit acceptable by sgdisk)
- availabe_size: int = (target_size - CONST_RESERVED_SPACE) // 1024
- rootfs_size: int = ask_root_size(availabe_size)
-
- # ask for password
- user_password: str = ask_input(MSG_INPUT_PASSWORD, default='vyos')
-
- # ask for default console
- console_type: str = ask_input(MSG_INPUT_CONSOLE_TYPE,
- default='K',
- valid_responses=['K', 'S', 'U'])
- console_dict: dict[str, str] = {'K': 'tty', 'S': 'ttyS', 'U': 'ttyUSB'}
-
- # create partitions
- if not ask_yes_no(MSG_INFO_INSTALL_DISK_CONFIRM):
- print(MSG_INFO_INSTALL_EXIT)
- exit()
- print(MSG_INFO_INSTALL_PARTITONING)
- disk.disk_cleanup(install_target)
- disk.parttable_create(install_target, rootfs_size)
- disk.filesystem_create(f'{install_target}2', 'efi')
- disk.filesystem_create(f'{install_target}3', 'ext4')
-
- # create directiroes for installation media
+ install_target = check_raid_install(disks)
+ if install_target is None:
+ install_target = ask_single_disk(disks)
+
+ # create directories for installation media
prepare_tmp_disr()
# mount target filesystem and create required dirs inside
print('Mounting new partitions')
- disk.partition_mount(f'{install_target}3', DIR_DST_ROOT)
- Path(f'{DIR_DST_ROOT}/boot/efi').mkdir(parents=True)
- disk.partition_mount(f'{install_target}2', f'{DIR_DST_ROOT}/boot/efi')
+ if is_raid_install(install_target):
+ disk.partition_mount(install_target.name, DIR_DST_ROOT)
+ Path(f'{DIR_DST_ROOT}/boot/efi').mkdir(parents=True)
+ else:
+ disk.partition_mount(install_target.partition['root'], DIR_DST_ROOT)
+ Path(f'{DIR_DST_ROOT}/boot/efi').mkdir(parents=True)
+ disk.partition_mount(install_target.partition['efi'], f'{DIR_DST_ROOT}/boot/efi')
# a config dir. It is the deepest one, so the comand will
# create all the rest in a single step
@@ -432,10 +567,10 @@ def install_image() -> None:
copy(FILE_ROOTFS_SRC,
f'{DIR_DST_ROOT}/boot/{image_name}/{image_name}.squashfs')
- # install GRUB
- print('Installing GRUB to the drive')
- grub.install(install_target, f'{DIR_DST_ROOT}/boot/',
- f'{DIR_DST_ROOT}/boot/efi')
+ if is_raid_install(install_target):
+ write_dir: str = f'{DIR_DST_ROOT}/boot/{image_name}/rw'
+ raid.update_default(write_dir)
+
setup_grub(DIR_DST_ROOT)
# add information about version
grub.create_structure()
@@ -443,9 +578,34 @@ def install_image() -> None:
grub.set_default(image_name, DIR_DST_ROOT)
grub.set_console_type(console_dict[console_type], DIR_DST_ROOT)
+ if is_raid_install(install_target):
+ # add RAID specific modules
+ grub.modules_write(f'{DIR_DST_ROOT}/{grub.CFG_VYOS_MODULES}',
+ ['part_msdos', 'part_gpt', 'diskfilter',
+ 'ext2','mdraid1x'])
+ # install GRUB
+ if is_raid_install(install_target):
+ print('Installing GRUB to the drives')
+ l = install_target.disks
+ for disk_target in l:
+ disk.partition_mount(disk_target.partition['efi'], f'{DIR_DST_ROOT}/boot/efi')
+ grub.install(disk_target.name, f'{DIR_DST_ROOT}/boot/',
+ f'{DIR_DST_ROOT}/boot/efi',
+ id=f'VyOS (RAID disk {l.index(disk_target) + 1})')
+ disk.partition_umount(disk_target.partition['efi'])
+ else:
+ print('Installing GRUB to the drive')
+ grub.install(install_target.name, f'{DIR_DST_ROOT}/boot/',
+ f'{DIR_DST_ROOT}/boot/efi')
+
# umount filesystems and remove temporary files
- cleanup([f'{install_target}2', f'{install_target}3'],
- ['/mnt/installation'])
+ if is_raid_install(install_target):
+ cleanup([install_target.name],
+ ['/mnt/installation'])
+ else:
+ cleanup([install_target.partition['efi'],
+ install_target.partition['root']],
+ ['/mnt/installation'])
# we are done
print(MSG_INFO_INSTALL_SUCCESS)
@@ -455,8 +615,13 @@ def install_image() -> None:
print(f'Unable to install VyOS: {err}')
# unmount filesystems and clenup
try:
- cleanup([f'{install_target}2', f'{install_target}3'],
- ['/mnt/installation'])
+ if install_target is not None:
+ if is_raid_install(install_target):
+ cleanup_raid(install_target)
+ else:
+ cleanup([install_target.partition['efi'],
+ install_target.partition['root']],
+ ['/mnt/installation'])
except Exception as err:
print(f'Cleanup failed: {err}')