Compare commits

...

16 Commits

Author SHA1 Message Date
OpenGnSys Support Team cf9577a40e fs: call wipefs partition before formatting partition
call wipefs before formatting partition, to remove any labels and stale data.

call wipefs if formatting fails, to leave partition in consistent state.

moreover, remove unnecessary exception handling on get_partition_device().
2025-02-14 15:48:53 +01:00
OpenGnSys Support Team 6503d0ffe7 live: call wipefs if partition or format fails
wipe disk if partition or format fails to leave the disk in consistent state.
2025-02-14 15:48:13 +01:00
Alejandro Sirgo Rica 0ca16bc46c live: remove unused variable in cache_delete()
Remove unused variable delted_images in the function cache_delete()
2025-02-14 13:15:27 +01:00
Alejandro Sirgo Rica bd190f8d44 live: send refresh payload in image restore
Send the refresh payload after a completed image restore operation.

The fields sent to ogServer are not enough to update the status of
the client and the OS installed in a partition does not appear in
the database.
2025-02-13 12:40:53 +01:00
Alejandro Sirgo Rica e0ba9cc98c uefi: log efibootmgr errors as warnings
Log as warning when efibootmgr fails to update the NVRAM.

Raise an exception when the command is not available or when
there are not enough permissions to execute. Provide contextual
information in the error message.
2025-02-13 09:06:38 +01:00
Alejandro Sirgo Rica ccdcb7bfc7 uefi: add missing f-string prefix in _find_bootentry() error
Add missing f-string prefix for proper string interpolation in
the error message of _find_bootentry()
2025-02-13 09:06:25 +01:00
Alejandro Sirgo Rica 72406a7d89 live: configure OS on first disk only
Call configure_os only for first disk, any other disks are only
supported to restore data images by now.

Call os_probe only for disk 1 in refresh.
2025-02-11 11:06:29 +01:00
Alejandro Sirgo Rica 0f519ecfeb grub: move disabled boot entries at the end of boot order
The GRUB entry is always set as the second boot option, assuming
PXE is first. This is not always true, as disabled entries before
PXE IPv4 can make it the first valid but not the first defined
entry in the boot order.

Move every disabled boot entry at the end of boot order.
2025-02-05 09:42:38 +01:00
Alejandro Sirgo Rica c260534534 live: remove unneeded string formatting in image_create
Replace f-string wf'{image_path}' with the variable image_path.
2025-02-03 11:27:18 +01:00
Alejandro Sirgo Rica f67f3c598a rest: register image/update as a valid request
Accept both image/create and image/update requests from ogServer.
2025-01-31 14:31:17 +01:00
OpenGnSys Support Team 574822907d grub: skip OS guess if partition cannot be mounted
otherwise, _get_os_entries() fails when it finds a swap partition:

  (2025-01-23 17:44:30) ogClient: [ERROR] - Error generating /mnt/nvme0n1p4/EFI/grub/Boot/grub.cfg: Unable to mount /dev/nvme0n1p3 into /mnt/nvme0n1p3
2025-01-24 15:15:57 +01:00
Alejandro Sirgo Rica 30e0e1dca3 src: run session check thread if the event socket is available
Disable session check thread if the event socket is not in an
initialized state.
2025-01-24 15:15:57 +01:00
OpenGnSys Support Team 59d642f6b5 ogclient: rename event handler
no functional changes intended.
2025-01-15 11:41:41 +01:00
Alejandro Sirgo Rica 24568356bc winreg: move disk id functions into disk.py
Move uuid_to_bytes, get_disk_id_bytes and get_part_id_bytes from
winreg.py to the more fitting location disk.py
2025-01-07 15:56:55 +01:00
Alejandro Sirgo Rica 40e4545bb7 live: fix image restore backtrace
import the missing function is_hibernation_enabled() into
ogOperations.py to prevent a backtrace in each restore operation.
2025-01-07 15:02:10 +01:00
Alejandro Sirgo Rica d29b601f17 uefi: remove dependency with probe.py
Reduce interdependency between imports by checking the correct OS for
copy_windows_efi_bootloader() from the code invoking the operation.

Break circular dependency where:
probe.py imports from winreg.py
winreg.py imports from uefi.py
uefi.py imports from probe.py
2025-01-07 15:02:04 +01:00
8 changed files with 116 additions and 77 deletions

View File

@ -27,7 +27,7 @@ from src.utils.postinstall import configure_os
from src.utils.net import *
from src.utils.menu import generate_menu
from src.utils.fs import *
from src.utils.probe import os_probe, get_cache_dev_path
from src.utils.probe import *
from src.utils.disk import *
from src.utils.cache import *
from src.utils.tiptorrent import *
@ -82,8 +82,10 @@ class OgLiveOperations:
code = int(pa.parttype, base=16)
if mount_mkdir(pa.padev, target):
probe_result = os_probe(target)
part_setup['os'] = probe_result
part_setup['os'] = ''
if part_setup['disk'] == '1':
probe_result = os_probe(target)
part_setup['os'] = probe_result
total, used, free = shutil.disk_usage(target)
part_setup['used_size'] = used
@ -526,7 +528,12 @@ class OgLiveOperations:
raise OgError(f'Invalid disk number {disk}, {len(get_disks())} disks available.')
diskname = get_disks()[disk-1]
self._partition(diskname, table_type, partlist)
try:
self._partition(diskname, table_type, partlist)
except Exception as e:
ret = subprocess.run(['wipefs', '-af', f'/dev/{diskname}'])
logging.warning(f'wipefs on /dev/{diskname} after failure for consistency, reports {ret.returncode}')
raise
ret = subprocess.run(['partprobe', f'/dev/{diskname}'])
logging.info(f'first partprobe /dev/{diskname} reports {ret.returncode}')
@ -594,19 +601,14 @@ class OgLiveOperations:
extend_filesystem(disk, partition)
configure_os(disk, partition)
if disk == 1:
configure_os(disk, partition)
self.refresh(ogRest)
result = self.refresh(ogRest)
logging.info('Image restore command OK')
json_dict = {
'disk': request.getDisk(),
'partition': request.getPartition(),
'image_id': request.getId(),
'cache': self._get_cache_contents(),
}
return json_dict
return result
def image_create(self, request, ogRest):
disk = int(request.getDisk())
@ -655,6 +657,7 @@ class OgLiveOperations:
raise OgError(f'Cannot mount {ro_mountpoint} as readonly')
try:
is_windows = get_os_family(ro_mountpoint) == OSFamily.WINDOWS
if is_hibernation_enabled(ro_mountpoint):
raise OgError(f'Target system in {padev} has hibernation enabled')
finally:
@ -666,10 +669,12 @@ class OgLiveOperations:
if os.access(f'/opt/opengnsys/images', os.R_OK | os.W_OK) == False:
raise OgError('Cannot access /opt/opengnsys/images in read and write mode, check permissions')
if os.access(f'{image_path}', os.R_OK) == True:
if os.access(image_path, os.R_OK) == True:
logging.info(f'image file {image_path} already exists, updating.')
copy_windows_efi_bootloader(disk, partition)
if is_windows and is_uefi_supported():
copy_windows_efi_bootloader(disk, partition)
if ogReduceFs(disk, partition) == -1:
raise OgError(f'Failed to shrink {fstype} filesystem in {padev}')
@ -741,7 +746,6 @@ class OgLiveOperations:
def cache_delete(self, request, ogRest):
images = request.getImages()
deleted_images = []
logging.info(f'Request to remove files from cache')

View File

@ -52,8 +52,9 @@ class ogClient:
self.ogrest = ogRest(self.CONFIG)
self.seq = None
self.session_check_thread = threading.Thread(target=self._session_check_loop, daemon=True)
self.session_check_thread.start()
if self.event_sock:
self.session_check_thread = threading.Thread(target=self._session_check_loop, daemon=True)
self.session_check_thread.start()
def _session_check_loop(self):
while True:
@ -80,7 +81,7 @@ class ogClient:
def get_state(self):
return self.state
def send_event_hint(self, message):
def handle_session_event(self, message):
try:
event, action, user = message.split(" ")
logging.debug('Sending event: %s, %s, %s', event, action, user)
@ -210,6 +211,6 @@ class ogClient:
self.connect2()
elif state == State.RECEIVING and event_sock in readable:
message = event_sock.recv(4096).decode('utf-8').rstrip()
self.send_event_hint(message)
self.handle_session_event(message)
else:
raise OgError(f'Invalid ogClient run state: {str(state)}.')

View File

@ -343,7 +343,7 @@ class ogRest():
self.process_imagerestore(client, request)
elif ("stop" in URI):
self.process_stop(client)
elif ("image/create" in URI):
elif ("image/create" in URI or "image/update" in URI):
self.process_imagecreate(client, request)
elif ("cache/delete" in URI):
self.process_cache_delete(client, request)

View File

@ -11,6 +11,7 @@ import logging
import shlex
import subprocess
import json
from uuid import UUID
from src.log import OgError
import fdisk
@ -190,3 +191,38 @@ def get_partition_start_offset(disk, partition):
raise OgError(f'Error while trying to parse sfdisk: {e}') from e
return start_offset
def uuid_to_bytes(uuid):
uuid = uuid.replace('-', '')
group0 = f'{uuid[6:8]}{uuid[4:6]}{uuid[2:4]}{uuid[0:2]}'
group1 = f'{uuid[10:12]}{uuid[8:10]}'
group2 = f'{uuid[14:16]}{uuid[12:14]}'
group3 = uuid[16:20]
group4 = uuid[20:32]
res = f'{group0}-{group1}-{group2}-{group3}-{group4}'
return UUID(res).bytes
def get_disk_id_bytes(disk):
from src.utils.uefi import is_uefi_supported
disk_id = get_disk_id(disk)
if is_uefi_supported():
return uuid_to_bytes(disk_id)
return bytes.fromhex(disk_id)[::-1]
def get_part_id_bytes(disk, partition):
from src.utils.uefi import is_uefi_supported
if is_uefi_supported():
part_id = get_partition_id(disk, partition)
return uuid_to_bytes(part_id)
partition_start_offset = get_partition_start_offset(disk, partition)
sector_size = get_sector_size(disk)
byte_offset = partition_start_offset * sector_size
byte_offset = "{0:016x}".format(byte_offset)
return bytes.fromhex(byte_offset)[::-1]

View File

@ -153,13 +153,19 @@ def mkfs(fs, disk, partition, label=None):
if fs not in fsdict:
raise OgError(f'mkfs failed, unsupported target filesystem {fs}')
try:
partdev = get_partition_device(disk, partition)
except ValueError as e:
raise OgError(f'mkfs aborted: {e}') from e
partdev = get_partition_device(disk, partition)
return fsdict[fs](partdev, label)
ret = subprocess.run(['wipefs', '-af', f'{partdev}'])
if ret.returncode != 0:
logging.warning(f'wipefs on {partdev}, fails with {ret.returncode}')
err = fsdict[fs](partdev, label)
if err != 0:
ret = subprocess.run(['wipefs', '-af', f'{partdev}'])
if ret.returncode != 0:
logging.warning(f'wipefs on {partdev} for consistency, fails with {ret.returncode}')
return err
def mkfs_ext4(partdev, label=None):
err = -1

View File

@ -279,7 +279,7 @@ def _get_os_entries(esp_mountpoint):
continue
if not mount_mkdir(p.padev, mountpoint):
raise OgError(f'Unable to mount {p.padev} into {mountpoint}')
continue
try:
os_family = get_os_family(mountpoint)
@ -334,6 +334,7 @@ def get_disk_part_type(disk_num):
def _update_nvram(esp_disk, esp_part_number):
loader_path = '/EFI/grub/Boot/shimx64.efi'
bootlabel = 'grub'
egibootmgr_reorder_disabled_entries()
efibootmgr_delete_bootentry(bootlabel)
efibootmgr_create_bootentry(esp_disk, esp_part_number, loader_path, bootlabel, add_to_bootorder=False)
efibootmgr_set_entry_order(bootlabel, 1)

View File

@ -14,7 +14,6 @@ import subprocess
import shutil
from src.utils.disk import *
from src.utils.fs import *
from src.utils.probe import *
from src.log import OgError
import fdisk
@ -26,7 +25,7 @@ def _find_bootentry(entries, label):
if entry['description'] == label:
return entry
else:
raise OgError('Boot entry {label} not found')
raise OgError(f'Boot entry {label} not found')
def _strip_boot_prefix(entry):
@ -96,8 +95,13 @@ def efibootmgr_bootnext(description):
entry = _find_bootentry(boot_entries, description)
num = _strip_boot_prefix(entry) # efibootmgr output uses BootXXXX for each entry, remove the "Boot" prefix.
bootnext_cmd = bootnext_cmd.format(bootnum=num, efibootmgr=EFIBOOTMGR_BIN)
subprocess.run(shlex.split(bootnext_cmd), check=True,
stdout=subprocess.DEVNULL)
try:
subprocess.run(shlex.split(bootnext_cmd), check=True,
stdout=subprocess.DEVNULL)
except subprocess.CalledProcessError:
logging.warning("Failed to update BootNext. UEFI firmware might be buggy")
except OSError as e:
raise OgError(f"Unexpected error updating BootNext: {e}") from e
def efibootmgr_delete_bootentry(label):
@ -108,7 +112,12 @@ def efibootmgr_delete_bootentry(label):
if entry['description'] == label:
num = entry['name'][4:] # Remove "Boot" prefix to extract num
efibootmgr_cmd = efibootmgr_cmd.format(bootnum=num, efibootmgr=EFIBOOTMGR_BIN)
subprocess.run(shlex.split(efibootmgr_cmd), check=True)
try:
subprocess.run(shlex.split(efibootmgr_cmd), check=True)
except subprocess.CalledProcessError:
logging.warning(f"Failed to delete boot entry {label}. UEFI firmware might be buggy")
except OSError as e:
raise OgError(f"Unexpected error deleting boot entry {label}: {e}") from e
break
else:
logging.info(f'Cannot delete boot entry {label} because it was not found.')
@ -122,8 +131,10 @@ def efibootmgr_create_bootentry(disk, part, loader, label, add_to_bootorder=True
logging.info(f'{EFIBOOTMGR_BIN} command creating boot entry: {efibootmgr_cmd}')
try:
proc = subprocess.run(shlex.split(efibootmgr_cmd), check=True, text=True)
except subprocess.CalledProcessError:
logging.warning(f"Failed to add boot entry {label} to NVRAM. UEFI firmware might be buggy")
except OSError as e:
raise OgError(f'Unexpected error adding boot entry to nvram. UEFI firmware might be buggy') from e
raise OgError(f"Unexpected error adding boot entry {label}: {e}") from e
def efibootmgr_set_entry_order(label, position):
logging.info(f'Setting {label} entry to position {position} of boot order')
@ -141,8 +152,29 @@ def efibootmgr_set_entry_order(label, position):
try:
proc = subprocess.run([EFIBOOTMGR_BIN, "-o", ",".join(boot_order)], check=True, text=True)
except subprocess.CalledProcessError:
logging.warning("Failed to set boot order to NVRAM. UEFI firmware might be buggy")
except OSError as e:
raise OgError(f'Unexpected error setting boot order to NVRAM. UEFI firmware might be buggy') from e
raise OgError(f"Unexpected error updating boot order: {e}") from e
def egibootmgr_reorder_disabled_entries():
logging.info(f'Setting disabled entries at the end of boot order')
boot_info = run_efibootmgr_json(validate=False)
boot_entries = boot_info.get('vars', [])
boot_order = boot_info.get('BootOrder', [])
for entry in boot_entries:
entry_number = _strip_boot_prefix(entry)
if not entry['active'] and entry_number in boot_order:
boot_order.remove(entry_number)
boot_order.append(entry_number)
try:
proc = subprocess.run([EFIBOOTMGR_BIN, "-o", ",".join(boot_order)], check=True, text=True)
except subprocess.CalledProcessError:
logging.warning("Failed to update boot order. UEFI firmware might be buggy")
except OSError as e:
raise OgError(f"Unexpected error updating boot order: {e}") from e
def _find_efi_loader(loader_paths):
for efi_app in loader_paths:
@ -170,13 +202,7 @@ def copy_windows_efi_bootloader(disk, partition):
device = get_partition_device(disk, partition)
mountpoint = device.replace('dev', 'mnt')
if not mount_mkdir(device, mountpoint):
raise OgError(f'Cannot probe OS family. Unable to mount {device} into {mountpoint}')
os_family = get_os_family(mountpoint)
is_uefi = is_uefi_supported()
if not is_uefi or os_family != OSFamily.WINDOWS:
return
raise OgError(f'Unable to mount {device} into {mountpoint}')
bootlabel = f'Part-{disk:02d}-{partition:02d}'
esp, esp_disk, esp_part_number = get_efi_partition(disk, enforce_gpt=True)
@ -209,7 +235,7 @@ def restore_windows_efi_bootloader(disk, partition):
device = get_partition_device(disk, partition)
mountpoint = device.replace('dev', 'mnt')
if not mount_mkdir(device, mountpoint):
raise OgError(f'Cannot probe OS family. Unable to mount {device} into {mountpoint}')
raise OgError(f'Unable to mount {device} into {mountpoint}')
bootlabel = f'Part-{disk:02d}-{partition:02d}'
esp, esp_disk, esp_part_number = get_efi_partition(disk, enforce_gpt=True)

View File

@ -10,9 +10,7 @@ import libhivexmod
import hivex
from enum import Enum
from src.log import OgError
from uuid import UUID
from src.utils.disk import *
from src.utils.uefi import is_uefi_supported
WINDOWS_HIVES_PATH = '/Windows/System32/config'
@ -102,36 +100,3 @@ def get_node_child_from_path(hive, node, path):
child_node = get_node_child(hive, child_node, node_name)
return child_node
def uuid_to_bytes(uuid):
uuid = uuid.replace('-', '')
group0 = f'{uuid[6:8]}{uuid[4:6]}{uuid[2:4]}{uuid[0:2]}'
group1 = f'{uuid[10:12]}{uuid[8:10]}'
group2 = f'{uuid[14:16]}{uuid[12:14]}'
group3 = uuid[16:20]
group4 = uuid[20:32]
res = f'{group0}-{group1}-{group2}-{group3}-{group4}'
return UUID(res).bytes
def get_disk_id_bytes(disk):
disk_id = get_disk_id(disk)
if is_uefi_supported():
return uuid_to_bytes(disk_id)
return bytes.fromhex(disk_id)[::-1]
def get_part_id_bytes(disk, partition):
if is_uefi_supported():
part_id = get_partition_id(disk, partition)
return uuid_to_bytes(part_id)
partition_start_offset = get_partition_start_offset(disk, partition)
sector_size = get_sector_size(disk)
byte_offset = partition_start_offset * sector_size
byte_offset = "{0:016x}".format(byte_offset)
return bytes.fromhex(byte_offset)[::-1]