mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
feat: preserve volumes on failed install + migrate ext4 to btrfs
- COW snapshot (cp --reflink=always) of package volumes before install/update; restore on failure, remove on success - Automatic ext4→btrfs conversion via btrfs-convert during disk attach with e2fsck pre-check and post-conversion defrag - Probe package-data filesystem during setup.disk.list (on both disk and partition level) so the UI can warn about ext4 conversion - Setup wizard preserve-overwrite dialog shows ext4 warning with backup acknowledgment checkbox before allowing preserve
This commit is contained in:
@@ -1,12 +1,30 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiButton, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiDialogContext } from '@taiga-ui/core'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiCheckbox,
|
||||
TuiDialogContext,
|
||||
TuiNotification,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
|
||||
export interface PreserveOverwriteData {
|
||||
isExt4: boolean
|
||||
}
|
||||
|
||||
@Component({
|
||||
imports: [TuiButton, TuiHeader, TuiTitle, i18nPipe],
|
||||
imports: [
|
||||
FormsModule,
|
||||
TuiButton,
|
||||
TuiCheckbox,
|
||||
TuiHeader,
|
||||
TuiNotification,
|
||||
TuiTitle,
|
||||
i18nPipe,
|
||||
],
|
||||
template: `
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
@@ -24,6 +42,18 @@ import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
{{ 'to discard' | i18n }}
|
||||
</li>
|
||||
</ul>
|
||||
@if (context.data.isExt4) {
|
||||
<p tuiNotification appearance="warning" size="m">
|
||||
{{
|
||||
'This drive uses ext4 and will be automatically converted to btrfs. A backup is strongly recommended before proceeding.'
|
||||
| i18n
|
||||
}}
|
||||
</p>
|
||||
<label>
|
||||
<input tuiCheckbox type="checkbox" [(ngModel)]="backupAck" />
|
||||
{{ 'I have a backup of my data' | i18n }}
|
||||
</label>
|
||||
}
|
||||
<footer>
|
||||
<button
|
||||
tuiButton
|
||||
@@ -36,6 +66,7 @@ import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
tuiButton
|
||||
appearance=""
|
||||
[style.background]="'var(--tui-status-positive)'"
|
||||
[disabled]="context.data.isExt4 && !backupAck"
|
||||
(click)="context.completeWith(true)"
|
||||
>
|
||||
{{ 'Preserve' | i18n }}
|
||||
@@ -44,7 +75,9 @@ import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
`,
|
||||
})
|
||||
export class PreserveOverwriteDialog {
|
||||
protected readonly context = injectContext<TuiDialogContext<boolean>>()
|
||||
protected readonly context =
|
||||
injectContext<TuiDialogContext<boolean, PreserveOverwriteData>>()
|
||||
protected backupAck = false
|
||||
}
|
||||
|
||||
export const PRESERVE_OVERWRITE = new PolymorpheusComponent(
|
||||
|
||||
@@ -292,8 +292,18 @@ export default class DrivesPage {
|
||||
|
||||
private showPreserveOverwriteDialog() {
|
||||
let selectionMade = false
|
||||
const drive = this.selectedDataDrive
|
||||
const filesystem =
|
||||
drive?.filesystem ||
|
||||
drive?.partitions.find(p => p.guid)?.filesystem ||
|
||||
null
|
||||
const isExt4 = filesystem === 'ext2'
|
||||
|
||||
this.dialogs.openComponent<boolean>(PRESERVE_OVERWRITE).subscribe({
|
||||
this.dialogs
|
||||
.openComponent<boolean>(PRESERVE_OVERWRITE, {
|
||||
data: { isExt4 },
|
||||
})
|
||||
.subscribe({
|
||||
next: preserve => {
|
||||
selectionMade = true
|
||||
this.preserveData = preserve
|
||||
|
||||
@@ -203,6 +203,7 @@ const MOCK_DISKS: DiskInfo[] = [
|
||||
partitions: [],
|
||||
capacity: 0,
|
||||
guid: null,
|
||||
filesystem: null,
|
||||
},
|
||||
// 10 GiB - too small for OS and data; also tests both vendor+model null
|
||||
{
|
||||
@@ -217,10 +218,12 @@ const MOCK_DISKS: DiskInfo[] = [
|
||||
used: null,
|
||||
startOs: {},
|
||||
guid: null,
|
||||
filesystem: null,
|
||||
},
|
||||
],
|
||||
capacity: 10 * GiB,
|
||||
guid: null,
|
||||
filesystem: null,
|
||||
},
|
||||
// 18 GiB - exact OS boundary; tests vendor null with model present
|
||||
{
|
||||
@@ -235,10 +238,12 @@ const MOCK_DISKS: DiskInfo[] = [
|
||||
used: null,
|
||||
startOs: {},
|
||||
guid: null,
|
||||
filesystem: null,
|
||||
},
|
||||
],
|
||||
capacity: 18 * GiB,
|
||||
guid: null,
|
||||
filesystem: null,
|
||||
},
|
||||
// 20 GiB - exact data boundary; tests vendor present with model null
|
||||
{
|
||||
@@ -253,10 +258,12 @@ const MOCK_DISKS: DiskInfo[] = [
|
||||
used: null,
|
||||
startOs: {},
|
||||
guid: null,
|
||||
filesystem: null,
|
||||
},
|
||||
],
|
||||
capacity: 20 * GiB,
|
||||
guid: null,
|
||||
filesystem: null,
|
||||
},
|
||||
// 30 GiB - OK for OS or data alone, too small for both (< 38 GiB)
|
||||
{
|
||||
@@ -271,10 +278,12 @@ const MOCK_DISKS: DiskInfo[] = [
|
||||
used: null,
|
||||
startOs: {},
|
||||
guid: null,
|
||||
filesystem: null,
|
||||
},
|
||||
],
|
||||
capacity: 30 * GiB,
|
||||
guid: null,
|
||||
filesystem: null,
|
||||
},
|
||||
// 30 GiB with existing StartOS data - tests preserve/overwrite + capacity constraint
|
||||
{
|
||||
@@ -298,10 +307,12 @@ const MOCK_DISKS: DiskInfo[] = [
|
||||
},
|
||||
},
|
||||
guid: 'small-existing-guid',
|
||||
filesystem: 'ext2',
|
||||
},
|
||||
],
|
||||
capacity: 30 * GiB,
|
||||
guid: 'small-existing-guid',
|
||||
filesystem: 'ext2',
|
||||
},
|
||||
// 500 GB - large, always OK
|
||||
{
|
||||
@@ -316,10 +327,12 @@ const MOCK_DISKS: DiskInfo[] = [
|
||||
used: null,
|
||||
startOs: {},
|
||||
guid: null,
|
||||
filesystem: null,
|
||||
},
|
||||
],
|
||||
capacity: 500000000000,
|
||||
guid: null,
|
||||
filesystem: null,
|
||||
},
|
||||
// 1 TB with existing StartOS data
|
||||
{
|
||||
@@ -343,10 +356,12 @@ const MOCK_DISKS: DiskInfo[] = [
|
||||
},
|
||||
},
|
||||
guid: 'existing-guid',
|
||||
filesystem: 'btrfs',
|
||||
},
|
||||
],
|
||||
capacity: 1000000000000,
|
||||
guid: 'existing-guid',
|
||||
filesystem: 'btrfs',
|
||||
},
|
||||
// 2 TB
|
||||
{
|
||||
@@ -370,10 +385,12 @@ const MOCK_DISKS: DiskInfo[] = [
|
||||
},
|
||||
},
|
||||
guid: null,
|
||||
filesystem: null,
|
||||
},
|
||||
],
|
||||
capacity: 2000000000000,
|
||||
guid: null,
|
||||
filesystem: null,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -723,4 +723,6 @@ export default {
|
||||
800: 'Geben Sie bei Aufforderung Ihr StartOS-Passwort ein',
|
||||
801: 'Ihr System hat Secure Boot aktiviert, was erfordert, dass alle Kernel-Module mit einem vertrauenswürdigen Schlüssel signiert sind. Einige Hardware-Treiber \u2014 wie die für NVIDIA-GPUs \u2014 sind nicht mit dem Standard-Distributionsschlüssel signiert. Die Registrierung des StartOS-Signaturschlüssels ermöglicht es Ihrer Firmware, diesen Modulen zu vertrauen, damit Ihre Hardware vollständig genutzt werden kann.',
|
||||
802: 'Die Übersetzungen auf Betriebssystemebene sind bereits aktiv. Ein Neustart ist erforderlich, damit die Übersetzungen auf Dienstebene wirksam werden.',
|
||||
803: 'Dieses Laufwerk verwendet ext4 und wird automatisch in btrfs konvertiert. Ein Backup wird dringend empfohlen, bevor Sie fortfahren.',
|
||||
804: 'Ich habe ein Backup meiner Daten',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -724,4 +724,6 @@ export const ENGLISH: Record<string, number> = {
|
||||
'When prompted, enter your StartOS password': 800,
|
||||
'Your system has Secure Boot enabled, which requires all kernel modules to be signed with a trusted key. Some hardware drivers \u2014 such as those for NVIDIA GPUs \u2014 are not signed by the default distribution key. Enrolling the StartOS signing key allows your firmware to trust these modules so your hardware can be fully utilized.': 801,
|
||||
'OS-level translations are already in effect. A restart is required for service-level translations to take effect.': 802,
|
||||
'This drive uses ext4 and will be automatically converted to btrfs. A backup is strongly recommended before proceeding.': 803,
|
||||
'I have a backup of my data': 804,
|
||||
}
|
||||
|
||||
@@ -723,4 +723,6 @@ export default {
|
||||
800: 'Cuando se le solicite, ingrese su contraseña de StartOS',
|
||||
801: 'Su sistema tiene Secure Boot habilitado, lo que requiere que todos los módulos del kernel estén firmados con una clave de confianza. Algunos controladores de hardware \u2014 como los de las GPU NVIDIA \u2014 no están firmados con la clave de distribución predeterminada. Registrar la clave de firma de StartOS permite que su firmware confíe en estos módulos para que su hardware pueda utilizarse completamente.',
|
||||
802: 'Las traducciones a nivel del sistema operativo ya están en vigor. Se requiere un reinicio para que las traducciones a nivel de servicio surtan efecto.',
|
||||
803: 'Esta unidad usa ext4 y se convertirá automáticamente a btrfs. Se recomienda encarecidamente hacer una copia de seguridad antes de continuar.',
|
||||
804: 'Tengo una copia de seguridad de mis datos',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -723,4 +723,6 @@ export default {
|
||||
800: 'Lorsque vous y êtes invité, entrez votre mot de passe StartOS',
|
||||
801: "Votre système a Secure Boot activé, ce qui exige que tous les modules du noyau soient signés avec une clé de confiance. Certains pilotes matériels \u2014 comme ceux des GPU NVIDIA \u2014 ne sont pas signés par la clé de distribution par défaut. L'enregistrement de la clé de signature StartOS permet à votre firmware de faire confiance à ces modules afin que votre matériel puisse être pleinement utilisé.",
|
||||
802: "Les traductions au niveau du système d'exploitation sont déjà en vigueur. Un redémarrage est nécessaire pour que les traductions au niveau des services prennent effet.",
|
||||
803: "Ce disque utilise ext4 et sera automatiquement converti en btrfs. Il est fortement recommandé de faire une sauvegarde avant de continuer.",
|
||||
804: "J'ai une sauvegarde de mes données",
|
||||
} satisfies i18n
|
||||
|
||||
@@ -723,4 +723,6 @@ export default {
|
||||
800: 'Po wyświetleniu monitu wprowadź swoje hasło StartOS',
|
||||
801: 'Twój system ma włączony Secure Boot, co wymaga, aby wszystkie moduły jądra były podpisane zaufanym kluczem. Niektóre sterowniki sprzętowe \u2014 takie jak te dla GPU NVIDIA \u2014 nie są podpisane domyślnym kluczem dystrybucji. Zarejestrowanie klucza podpisu StartOS pozwala firmware ufać tym modułom, aby sprzęt mógł być w pełni wykorzystany.',
|
||||
802: 'Tłumaczenia na poziomie systemu operacyjnego są już aktywne. Wymagane jest ponowne uruchomienie, aby tłumaczenia na poziomie usług zaczęły obowiązywać.',
|
||||
803: 'Ten dysk używa ext4 i zostanie automatycznie skonwertowany na btrfs. Zdecydowanie zaleca się wykonanie kopii zapasowej przed kontynuowaniem.',
|
||||
804: 'Mam kopię zapasową moich danych',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface DiskInfo {
|
||||
partitions: PartitionInfo[]
|
||||
capacity: number
|
||||
guid: string | null
|
||||
filesystem: string | null
|
||||
}
|
||||
|
||||
export interface PartitionInfo {
|
||||
@@ -16,6 +17,7 @@ export interface PartitionInfo {
|
||||
used: number | null
|
||||
startOs: Record<string, StartOSDiskInfo>
|
||||
guid: string | null
|
||||
filesystem: string | null
|
||||
}
|
||||
|
||||
export type StartOSDiskInfo = {
|
||||
|
||||
Reference in New Issue
Block a user