6713df2186
Complete DR implementation (TASK2.md Phases 1-4): - Hub infra-backup push/pull endpoints (controller.yaml, disk layout, stacks) - Fresh-deployment detection pulls config from Hub, auto-mounts drives by UUID - Full-page restore UI with drive status, app table, sequential restore - docker-setup.sh shows DR instructions when customer_id is configured New files: disk_layout.go, restore_scan.go, restore_app_linux.go, restore_drives_linux.go, infra_backup.go, infra_pull.go, handler_restore.go, restore.html Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
93 lines
2.6 KiB
Go
93 lines
2.6 KiB
Go
package report
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"os"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
|
)
|
|
|
|
// InfraBackup is the payload pushed to the Hub for disaster recovery.
|
|
type InfraBackup struct {
|
|
CustomerID string `json:"customer_id"`
|
|
Domain string `json:"domain"`
|
|
ControllerVersion string `json:"controller_version"`
|
|
Timestamp string `json:"timestamp"`
|
|
|
|
ControllerConfigB64 string `json:"controller_config_b64"`
|
|
SettingsJSONB64 string `json:"settings_json_b64,omitempty"`
|
|
|
|
DiskLayout backup.DiskLayout `json:"disk_layout"`
|
|
DeployedStacks []InfraStack `json:"deployed_stacks"`
|
|
|
|
ResticPassword string `json:"restic_password,omitempty"`
|
|
CrossDrivePassword string `json:"cross_drive_password,omitempty"`
|
|
}
|
|
|
|
// InfraStack identifies a deployed app for disaster recovery.
|
|
type InfraStack struct {
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"display_name"`
|
|
HDDPath string `json:"hdd_path,omitempty"`
|
|
NeedsHDD bool `json:"needs_hdd"`
|
|
}
|
|
|
|
// BuildInfraBackup collects all infrastructure state for Hub backup.
|
|
func BuildInfraBackup(
|
|
customerID, domain, version string,
|
|
controllerYAMLPath string,
|
|
settingsPath string,
|
|
resticPasswordFile string,
|
|
systemDataPath string,
|
|
sett *settings.Settings,
|
|
stackProvider backup.StackDataProvider,
|
|
) (*InfraBackup, error) {
|
|
ib := &InfraBackup{
|
|
CustomerID: customerID,
|
|
Domain: domain,
|
|
ControllerVersion: version,
|
|
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
|
|
// Read and encode controller.yaml
|
|
if data, err := os.ReadFile(controllerYAMLPath); err == nil {
|
|
ib.ControllerConfigB64 = base64.StdEncoding.EncodeToString(data)
|
|
}
|
|
|
|
// Read and encode settings.json
|
|
if data, err := os.ReadFile(settingsPath); err == nil {
|
|
ib.SettingsJSONB64 = base64.StdEncoding.EncodeToString(data)
|
|
}
|
|
|
|
// Read primary restic password
|
|
if data, err := os.ReadFile(resticPasswordFile); err == nil {
|
|
ib.ResticPassword = base64.StdEncoding.EncodeToString(data)
|
|
}
|
|
|
|
// Read cross-drive restic password
|
|
if pw := sett.GetCrossDriveResticPassword(); pw != "" {
|
|
ib.CrossDrivePassword = pw
|
|
}
|
|
|
|
// Collect disk layout from fstab + blkid
|
|
ib.DiskLayout = collectDiskLayout(systemDataPath)
|
|
|
|
// Collect deployed stacks
|
|
deployed := stackProvider.ListDeployedStacks()
|
|
for _, s := range deployed {
|
|
ib.DeployedStacks = append(ib.DeployedStacks, InfraStack{
|
|
Name: s.Name,
|
|
DisplayName: s.DisplayName,
|
|
HDDPath: stackProvider.GetStackHDDPath(s.Name),
|
|
NeedsHDD: s.NeedsHDD,
|
|
})
|
|
}
|
|
if ib.DeployedStacks == nil {
|
|
ib.DeployedStacks = []InfraStack{}
|
|
}
|
|
|
|
return ib, nil
|
|
}
|