Skip to content

Commit

Permalink
incus-osd: Add support for sysupdate
Browse files Browse the repository at this point in the history
Signed-off-by: Stéphane Graber <[email protected]>
  • Loading branch information
stgraber committed Jan 16, 2025
1 parent 14ed94c commit be31c0a
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 15 deletions.
95 changes: 81 additions & 14 deletions incus-osd/cmd/incus-osd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ import (
)

var (
ghOrganization = "lxc"
ghRepository = "incus-os"
osExtensions = []string{"debug.raw.gz", "incus.raw.gz"}
osExtensionsPath = "/var/lib/extensions"
ghOrganization = "lxc"
ghRepository = "incus-os"

incusExtensions = []string{"debug.raw.gz", "incus.raw.gz"}
)

func main() {
err := run()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
_, _ = fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}
}

Expand All @@ -39,6 +39,12 @@ func run() error {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
slog.SetDefault(logger)

// Get current release.
release, err := systemd.GetCurrentRelease(ctx)
if err != nil {
return err
}

// Check kernel keyring.
slog.Info("Getting trusted system keys")
keys, err := keyring.GetKeys(ctx, keyring.PlatformKeyring)
Expand All @@ -61,34 +67,95 @@ func run() error {
slog.Info("Platform keyring entry", "name", key.Description, "key", key.Fingerprint)
}

slog.Info("Starting up", "mode", mode, "app", "incus")
slog.Info("Starting up", "mode", mode, "app", "incus", "release", release)

// Fetch the system extensions.
// Fetch the Github release.
gh := github.NewClient(nil)

release, _, err := gh.Repositories.GetLatestRelease(ctx, ghOrganization, ghRepository)
ghRelease, _, err := gh.Repositories.GetLatestRelease(ctx, ghOrganization, ghRepository)
if err != nil {
return err
}

slog.Info(fmt.Sprintf("Found latest %s/%s release", ghOrganization, ghRepository), "tag", release.GetTagName())
slog.Info(fmt.Sprintf("Found latest %s/%s release", ghOrganization, ghRepository), "tag", ghRelease.GetTagName())

assets, _, err := gh.Repositories.ListReleaseAssets(ctx, ghOrganization, ghRepository, ghRelease.GetID(), nil)
if err != nil {
return err
}

assets, _, err := gh.Repositories.ListReleaseAssets(ctx, ghOrganization, ghRepository, release.GetID(), nil)
// Download OS updates.
err = os.MkdirAll(systemd.SystemUpdatesPath, 0o700)
if err != nil {
return err
}

err = os.MkdirAll(osExtensionsPath, 0700)
if release != ghRelease.GetName() {
for _, asset := range assets {
// Skip system extensions.
if !strings.HasPrefix(asset.GetName(), "IncusOS_") {
continue
}

fields := strings.SplitN(asset.GetName(), ".", 2)
if len(fields) != 2 {
continue
}

// Skip the full image.
if fields[1] == "raw.gz" {
continue
}

slog.Info("Downloading OS update", "file", asset.GetName(), "url", asset.GetBrowserDownloadURL())

rc, _, err := gh.Repositories.DownloadReleaseAsset(ctx, ghOrganization, ghRepository, asset.GetID(), http.DefaultClient)
if err != nil {
return err
}

defer rc.Close()

body, err := gzip.NewReader(rc)
if err != nil {
return err
}

defer body.Close()

fd, err := os.Create(filepath.Join(systemd.SystemUpdatesPath, strings.TrimSuffix(asset.GetName(), ".gz")))
if err != nil {
return err
}

defer fd.Close()

_, err = io.Copy(fd, body)
if err != nil {
return err
}
}

err = systemd.ApplySystemUpdate(ctx, ghRelease.GetName(), true)
if err != nil {
return err
}

return nil
}

// Download system extensions.
err = os.MkdirAll(systemd.SystemExtensionsPath, 0o700)
if err != nil {
return err
}

for _, asset := range assets {
if !slices.Contains(osExtensions, asset.GetName()) {
if !slices.Contains(incusExtensions, asset.GetName()) {
continue
}

slog.Info("Downloading OS extension", "file", asset.GetName(), "url", asset.GetBrowserDownloadURL())
slog.Info("Downloading system extension", "file", asset.GetName(), "url", asset.GetBrowserDownloadURL())

rc, _, err := gh.Repositories.DownloadReleaseAsset(ctx, ghOrganization, ghRepository, asset.GetID(), http.DefaultClient)
if err != nil {
Expand All @@ -104,7 +171,7 @@ func run() error {

defer body.Close()

fd, err := os.Create(filepath.Join(osExtensionsPath, strings.TrimSuffix(asset.GetName(), ".gz")))
fd, err := os.Create(filepath.Join(systemd.SystemExtensionsPath, strings.TrimSuffix(asset.GetName(), ".gz")))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion incus-osd/internal/keyring/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type Key struct {
}

// GetKeys returns a list of keys in the requested keyring.
func GetKeys(ctx context.Context, keyring string) ([]Key, error) {
func GetKeys(_ context.Context, keyring string) ([]Key, error) {
keys := []Key{}

// Read the key list.
Expand Down
6 changes: 6 additions & 0 deletions incus-osd/internal/systemd/paths.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package systemd

var (
SystemExtensionsPath = "/var/lib/extensions"
SystemUpdatesPath = "/var/lib/updates"
)
12 changes: 12 additions & 0 deletions incus-osd/internal/systemd/systemctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ import (
"github.com/lxc/incus/v6/shared/subprocess"
)

func StartUnit(ctx context.Context, units ...string) error {
args := []string{"start"}
args = append(args, units...)

_, err := subprocess.RunCommandContext(ctx, "systemctl", args...)
if err != nil {
return err
}

return nil
}

func EnableUnit(ctx context.Context, now bool, units ...string) error {
args := []string{"enable"}

Expand Down
62 changes: 62 additions & 0 deletions incus-osd/internal/systemd/sysupdate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package systemd

import (
"bufio"
"context"
"errors"
"os"
"strings"

"github.com/lxc/incus/v6/shared/subprocess"
)

var ErrReleaseNotFound = errors.New("couldn't determine current OS release")

// GetCurrentRelease returns the current IMAGE_VERSION from the os-release file.
func GetCurrentRelease(_ context.Context) (string, error) {
// Open the os-release file.
fd, err := os.Open("/lib/os-release")
if err != nil {
return "", err
}

defer fd.Close()

// Prepare reader.
fdScan := bufio.NewScanner(fd)
for fdScan.Scan() {
line := fdScan.Text()
fields := strings.SplitN(line, "=", 2)

if len(fields) != 2 {
continue
}

if fields[0] == "IMAGE_VERSION" {
return strings.Trim(fields[1], "\""), nil
}
}

return "", ErrReleaseNotFound
}

func ApplySystemUpdate(ctx context.Context, version string, reboot bool) error {
// WORKAROUND: Start the boot.mount unit so /boot autofs is active before we create a new mount namespace.
err := StartUnit(ctx, "boot.mount")
if err != nil {
return err
}

// WORKAROUND: Needed until systemd-sysupdate can be run with system extensions applied.
cmd := "mount /dev/mapper/usr /usr && /usr/lib/systemd/systemd-sysupdate update " + version
if reboot {
cmd += "&& /usr/lib/systemd/systemd-sysupdate reboot"
}

_, err = subprocess.RunCommandContext(ctx, "unshare", "-m", "--", "sh", "-c", cmd)
if err != nil {
return err
}

return nil
}

0 comments on commit be31c0a

Please sign in to comment.