diff --git a/snapd-installation-monitor/CMakeLists.txt b/snapd-installation-monitor/CMakeLists.txt index f9d20ab..74fdf6e 100644 --- a/snapd-installation-monitor/CMakeLists.txt +++ b/snapd-installation-monitor/CMakeLists.txt @@ -1,12 +1,17 @@ cmake_minimum_required(VERSION 3.5.0) project(snapd-installation-monitor) +# Enable CMake features set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) + +# Find required Qt6 components find_package(Qt6 COMPONENTS Widgets DBus REQUIRED) +# Set C++ standard set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# Add the main application executable add_executable(snapd-installation-monitor main.cpp) target_link_libraries(snapd-installation-monitor Qt6::Widgets Qt6::DBus) diff --git a/snapd-seed-glue/cleanup.go b/snapd-seed-glue/cleanup.go index 05d8786..c414848 100644 --- a/snapd-seed-glue/cleanup.go +++ b/snapd-seed-glue/cleanup.go @@ -10,11 +10,11 @@ import ( ) // cleanUpFiles removes partial, old, and orphaned snap and assertion files from the download and assertions directories. -func cleanUpFiles(snapsDir, assertionsDir, seedYaml string) { +func cleanUpFiles(snapsDir string, assertionsDir string) { verboseLog("Starting cleanup process...") // Load the seed.yaml data - seedData := loadSeedData(seedYaml) + seedData := loadSeedData() // Create a map of valid snap and assertion files based on seed.yaml validSnaps := make(map[string]bool) diff --git a/snapd-seed-glue/main.go b/snapd-seed-glue/main.go index b44b733..46773be 100644 --- a/snapd-seed-glue/main.go +++ b/snapd-seed-glue/main.go @@ -12,15 +12,6 @@ import ( "github.com/snapcore/snapd/store" ) -// Seed structure for seed.yaml -type seed struct { - Snaps []struct { - Name string `yaml:"name"` - Channel string `yaml:"channel"` - File string `yaml:"file"` - } `yaml:"snaps"` -} - var ( ctx = context.Background() storeClient *store.Store @@ -30,6 +21,7 @@ var ( processedSnaps = make(map[string]bool) snapSizeMap = make(map[string]float64) totalSnapSize float64 + seedYaml string ) type SnapInfo struct { @@ -61,14 +53,14 @@ func main() { // Define directories based on the seed directory snapsDir := filepath.Join(seedDirectory, "snaps") assertionsDir := filepath.Join(seedDirectory, "assertions") - seedYaml := filepath.Join(seedDirectory, "seed.yaml") + seedYaml = filepath.Join(seedDirectory, "seed.yaml") // Setup directories and seed.yaml initializeDirectories(snapsDir, assertionsDir) - initializeSeedYaml(seedYaml) + initializeSeedYaml() // Load existing snaps from seed.yaml - existingSnapsInYaml := loadExistingSnaps(seedYaml) + existingSnapsInYaml := loadExistingSnaps() // Populate currentSnaps based on existing snaps for snapName := range existingSnapsInYaml { @@ -133,7 +125,7 @@ func main() { cleanUpCurrentSnaps(assertionsDir, snapsDir) // Update seed.yaml with the current required snaps - if err := updateSeedYaml(snapsDir, seedYaml, currentSnaps); err != nil { + if err := updateSeedYaml(snapsDir, currentSnaps); err != nil { log.Fatalf("Failed to update seed.yaml: %v", err) } @@ -143,7 +135,7 @@ func main() { if err := validateSeed(seedYaml); err != nil { log.Fatalf("Seed validation failed: %v", err) } - cleanUpFiles(snapsDir, assertionsDir, seedYaml) + cleanUpFiles(snapsDir, assertionsDir) // Mark "Finalizing" as complete if progressTracker != nil { @@ -155,11 +147,21 @@ func main() { func collectSnapsToProcess(snapsDir, assertionsDir string) ([]SnapDetails, error) { var snapsToProcess []SnapDetails + versionID, err := getVersionID() + if err != nil { + return nil, err + } + + defaultChannel := "latest/stable/ubuntu-" + versionID + if err != nil { + return nil, err + } + fallbackChannel := "latest/stable" for snapEntry := range requiredSnaps { // Extract channel if specified, default to "stable" parts := strings.SplitN(snapEntry, "=", 2) - channel := "latest/stable/ubuntu-25.04" + channel := defaultChannel if len(parts) == 2 { channel = parts[1] } @@ -173,9 +175,10 @@ func collectSnapsToProcess(snapsDir, assertionsDir string) ([]SnapDetails, error // Append only those snaps that need updates for _, snapDetails := range snapList { - verboseLog("Processing snap: %s with result: %v", snapDetails.InstanceName, snapDetails.Result) + verboseLog("Processing snap: %s", snapDetails.InstanceName) if len(snapDetails.Result.Deltas) > 0 { for _, delta := range snapDetails.Result.Deltas { + verboseLog("Delta found for %s from %d to %d", snapDetails.InstanceName, delta.FromRevision, delta.ToRevision) snapSize := float64(delta.Size) snapSizeMap[snapDetails.Result.Info.SuggestedName] = snapSize totalSnapSize += snapSize diff --git a/snapd-seed-glue/process.go b/snapd-seed-glue/process.go index a91cb2b..ca6236c 100644 --- a/snapd-seed-glue/process.go +++ b/snapd-seed-glue/process.go @@ -32,6 +32,7 @@ func collectSnapDependencies(snapName, channel, fallbackChannel, snapsDir, asser var result *store.SnapActionResult var err error + workingChannel := "" // Fetch or refresh snap information if oldSnap == nil || oldSnap.SnapID == "" || oldSnap.Revision.N == 0 { @@ -41,18 +42,25 @@ func collectSnapDependencies(snapName, channel, fallbackChannel, snapsDir, asser result, err = fetchOrRefreshSnapInfo(snapName, nil, fallbackChannel) if err != nil { return nil, err + } else { + workingChannel = fallbackChannel } } else { return nil, err } + } else { + workingChannel = channel } } else { + verboseLog("Old snap info: %s %d", oldSnap.SnapID, oldSnap.Revision.N) result, err = fetchOrRefreshSnapInfo(snapName, oldSnap, channel) if err != nil { if strings.Contains(err.Error(), "snap has no updates available") { result, err = fetchOrRefreshSnapInfo(snapName, nil, channel) if err != nil { return nil, err + } else { + workingChannel = channel } } else if strings.Contains(err.Error(), "no snap revision available as specified") { result, err = fetchOrRefreshSnapInfo(snapName, oldSnap, fallbackChannel) @@ -61,14 +69,20 @@ func collectSnapDependencies(snapName, channel, fallbackChannel, snapsDir, asser result, err = fetchOrRefreshSnapInfo(snapName, nil, fallbackChannel) if err != nil { return nil, err + } else { + workingChannel = fallbackChannel } } else { return nil, err } + } else { + workingChannel = fallbackChannel } } else { return nil, err } + } else { + workingChannel = channel } } @@ -78,9 +92,10 @@ func collectSnapDependencies(snapName, channel, fallbackChannel, snapsDir, asser info := result.Info newSnap := &store.CurrentSnap{ - InstanceName: snapName, - SnapID: info.SnapID, - Revision: snap.Revision{N: info.Revision.N}, + InstanceName: snapName, + SnapID: info.SnapID, + Revision: snap.Revision{N: info.Revision.N}, + TrackingChannel: workingChannel, } snapInCurrentSnaps, oldRevision := isSnapInCurrentSnaps(snapName) if snapInCurrentSnaps { @@ -176,7 +191,7 @@ func fetchOrRefreshSnapInfo(snapName string, currentSnap *store.CurrentSnap, cha results, _, err := storeClient.SnapAction(ctx, includeSnap, actions, nil, nil, nil) if err != nil { verboseLog("SnapAction error for %s: %v", snapName, err) - if strings.Contains(err.Error(), "snap has no updates available") && currentSnap != nil { + if (strings.Contains(err.Error(), "snap has no updates available") || strings.Contains(err.Error(), "no snap revision available as specified")) && currentSnap != nil { return nil, err } return nil, fmt.Errorf("snap action failed for %s: %w", snapName, err) @@ -232,6 +247,12 @@ func findPreviousSnap(downloadDir, assertionsDir, snapName string) (string, *sto assertFilePath := filepath.Join(assertionsDir, strings.Replace(file.Name(), ".snap", ".assert", 1)) currentSnap = parseSnapInfo(assertFilePath, snapName) currentSnap.Revision.N = revision + trackingChannel, err := getChannelName(snapName) + if err != nil { + verboseLog("Failed to get existing channel name for %s", snapName) + continue + } + currentSnap.TrackingChannel = "latest/" + trackingChannel } } } diff --git a/snapd-seed-glue/seed.go b/snapd-seed-glue/seed.go index 1475c28..aa250d6 100644 --- a/snapd-seed-glue/seed.go +++ b/snapd-seed-glue/seed.go @@ -5,13 +5,42 @@ import ( "io/ioutil" "log" "os" + "strings" "gopkg.in/yaml.v3" "github.com/snapcore/snapd/store" ) +type seed struct { + Snaps []struct { + Name string `yaml:"name"` + Channel string `yaml:"channel"` + File string `yaml:"file"` + } `yaml:"snaps"` +} + +// getChannelName returns the channel name for a specific snap name +func getChannelName(snapName string) (string, error) { + file, err := ioutil.ReadFile(seedYaml) + if err != nil { + return "", fmt.Errorf("failed to read seed.yaml: %w", err) + } + + var seedData seed + if err := yaml.Unmarshal(file, &seedData); err != nil { + return "", fmt.Errorf("failed to parse seed.yaml: %w", err) + } + + for _, snap := range seedData.Snaps { + if snap.Name == snapName { + return snap.Channel, nil + } + } + return "", fmt.Errorf("snap %s not found in seed.yaml", snapName) +} + // initializeSeedYaml ensures that seed.yaml exists; if not, creates it. -func initializeSeedYaml(seedYaml string) { +func initializeSeedYaml() { if _, err := os.Stat(seedYaml); os.IsNotExist(err) { file, err := os.Create(seedYaml) if err != nil { @@ -23,7 +52,7 @@ func initializeSeedYaml(seedYaml string) { } // loadSeedData loads seed data from seed.yaml -func loadSeedData(seedYaml string) seed { +func loadSeedData() seed { file, err := ioutil.ReadFile(seedYaml) if err != nil { log.Fatalf("Failed to read seed.yaml: %v", err) @@ -38,7 +67,7 @@ func loadSeedData(seedYaml string) seed { } // loadExistingSnaps loads snaps from seed.yaml into a map -func loadExistingSnaps(seedYaml string) map[string]bool { +func loadExistingSnaps() map[string]bool { file, err := ioutil.ReadFile(seedYaml) if err != nil { log.Fatalf("Failed to read seed.yaml: %v", err) @@ -58,7 +87,7 @@ func loadExistingSnaps(seedYaml string) map[string]bool { } // updateSeedYaml updates the seed.yaml file with the current required snaps -func updateSeedYaml(snapsDir, seedYaml string, currentSnaps []*store.CurrentSnap) error { +func updateSeedYaml(snapsDir string, currentSnaps []*store.CurrentSnap) error { // Log the snaps to be written verboseLog("CurrentSnaps to be written to seed.yaml:") for _, snapInfo := range currentSnaps { @@ -92,7 +121,7 @@ func updateSeedYaml(snapsDir, seedYaml string, currentSnaps []*store.CurrentSnap File string `yaml:"file"` }{ Name: snapInfo.InstanceName, - Channel: "stable", // Assuming 'stable' channel; modify as needed + Channel: strings.Replace(snapInfo.TrackingChannel, "latest/", "", -1), File: snapFileName, } seedData.Snaps = append(seedData.Snaps, snapData) diff --git a/snapd-seed-glue/utils.go b/snapd-seed-glue/utils.go index f89f082..72502d1 100644 --- a/snapd-seed-glue/utils.go +++ b/snapd-seed-glue/utils.go @@ -148,3 +148,28 @@ func verifySnapIntegrity(filePath, expectedChecksum string) bool { } return checksumMatches } + +// Get the raw VERSION_ID from /etc/os-release to use for branch detection +func getVersionID() (string, error) { + file, err := os.Open("/etc/os-release") + if err != nil { + return "", fmt.Errorf("failed to open /etc/os-release: %w", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "VERSION_ID=") { + // Remove the prefix and any surrounding quotes + versionID := strings.Trim(strings.SplitN(line, "=", 2)[1], `"`) + return versionID, nil + } + } + + if err := scanner.Err(); err != nil { + return "", fmt.Errorf("error reading /etc/os-release: %w", err) + } + + return "", fmt.Errorf("VERSION_ID not found in /etc/os-release") +}