Skip to content

Commit 527103e

Browse files
Flash local Debian image with the arduino-flasher-cli (#693)
1 parent c166b77 commit 527103e

File tree

3 files changed

+112
-68
lines changed

3 files changed

+112
-68
lines changed

arduino-flasher-cli/flash.go

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -48,56 +48,81 @@ func checkDriversInstalled() {
4848
}
4949

5050
func runFlashCommand(ctx context.Context, args []string, forceYes bool) {
51-
version := args[0]
52-
53-
updateURL := os.Getenv("UPDATE_URL")
54-
if updateURL == "" {
55-
// TODO: change to prod
56-
updateURL = "https://downloads.oniudra.cc"
57-
}
58-
59-
parsedURL, err := url.Parse(updateURL)
51+
imagePath, err := paths.New(args[0]).Abs()
6052
if err != nil {
61-
feedback.Fatal(i18n.Tr("invalid UPDATE_URL:", err), feedback.ErrBadArgument)
53+
feedback.Fatal(i18n.Tr("could not find image absolute path: %v", err), feedback.ErrBadArgument)
6254
}
6355

64-
headers := map[string]string{}
65-
clientID := os.Getenv("CF_ACCESS_CLIENT_ID")
66-
clientSecret := os.Getenv("CF_ACCESS_CLIENT_SECRET")
67-
if clientID != "" && clientSecret != "" {
68-
headers["CF-Access-Client-Id"] = clientID
69-
headers["CF-Access-Client-Secret"] = clientSecret
70-
}
56+
if !imagePath.Exist() {
57+
version := args[0]
7158

72-
var client *updater.Client
73-
if len(headers) == 2 {
74-
client = updater.NewClient(parsedURL, "debian-im/Stable", updater.WithHeaders(headers))
75-
} else {
76-
client = updater.NewClient(parsedURL, "debian-im/Stable")
77-
}
59+
updateURL := os.Getenv("UPDATE_URL")
60+
if updateURL == "" {
61+
// TODO: change to prod
62+
updateURL = "https://downloads.oniudra.cc"
63+
}
64+
65+
parsedURL, err := url.Parse(updateURL)
66+
if err != nil {
67+
feedback.Fatal(i18n.Tr("invalid UPDATE_URL:", err), feedback.ErrBadArgument)
68+
}
69+
70+
headers := map[string]string{}
71+
clientID := os.Getenv("CF_ACCESS_CLIENT_ID")
72+
clientSecret := os.Getenv("CF_ACCESS_CLIENT_SECRET")
73+
if clientID != "" && clientSecret != "" {
74+
headers["CF-Access-Client-Id"] = clientID
75+
headers["CF-Access-Client-Secret"] = clientSecret
76+
}
77+
78+
var client *updater.Client
79+
if len(headers) == 2 {
80+
client = updater.NewClient(parsedURL, "debian-im/Stable", updater.WithHeaders(headers))
81+
} else {
82+
client = updater.NewClient(parsedURL, "debian-im/Stable")
83+
}
7884

79-
downloadedImagePath, updatedVersion, err := updater.DownloadImage(client, version, func(target string) (bool, error) {
80-
feedback.Printf("Found Debian image version: %s", target)
81-
feedback.Printf("Do you want to download it and flash it on the board? (yes/no)")
85+
tempImagePath, err := updater.DownloadAndExtract(client, version, func(target string) (bool, error) {
86+
feedback.Printf("Found Debian image version: %s", target)
87+
feedback.Printf("Do you want to download it and flash it on the board? (yes/no)")
88+
89+
var yesInput string
90+
_, err := fmt.Scanf("%s\n", &yesInput)
91+
if err != nil {
92+
return false, err
93+
}
94+
yes := strings.ToLower(yesInput) == "yes" || strings.ToLower(yesInput) == "y"
95+
return yes, nil
96+
}, forceYes)
8297

83-
var yesInput string
84-
_, err := fmt.Scanf("%s\n", &yesInput)
8598
if err != nil {
86-
return false, err
99+
feedback.Fatal(i18n.Tr("could not download and extract the image: %v", err), feedback.ErrBadArgument)
87100
}
88-
yes := strings.ToLower(yesInput) == "yes" || strings.ToLower(yesInput) == "y"
89-
return yes, nil
90-
}, forceYes)
91101

92-
if err != nil {
93-
feedback.Fatal(i18n.Tr("error downloading the image: %v", err), feedback.ErrBadArgument)
94-
}
102+
defer tempImagePath.Parent().RemoveAll()
103+
104+
imagePath = tempImagePath
105+
} else if !imagePath.IsDir() {
106+
temp, err := paths.MkTempDir("", "debian-image-")
107+
if err != nil {
108+
feedback.Fatal(i18n.Tr("error creating a temporary directory to extract the archive: %v", err), feedback.ErrBadArgument)
109+
}
110+
defer temp.RemoveAll()
95111

96-
if downloadedImagePath != "" {
97-
defer paths.New(downloadedImagePath).RemoveAll()
112+
err = updater.ExtractImage(imagePath, temp)
113+
if err != nil {
114+
feedback.Fatal(i18n.Tr("error extracting the archive: %v", err), feedback.ErrBadArgument)
115+
}
98116

99-
if err := updater.FlashBoard(ctx, downloadedImagePath, updatedVersion); err != nil {
100-
feedback.Fatal(i18n.Tr("error flashing the board: %v", err), feedback.ErrBadArgument)
117+
tempContent, err := temp.ReadDir(paths.AndFilter(paths.FilterDirectories(), paths.FilterPrefixes("arduino-unoq-debian-image-")))
118+
if err != nil {
119+
feedback.Fatal(i18n.Tr("could not find Debian image directory: %v", err), feedback.ErrBadArgument)
101120
}
121+
122+
imagePath = tempContent[0]
123+
}
124+
125+
if err := updater.FlashBoard(ctx, imagePath.String()); err != nil {
126+
feedback.Fatal(i18n.Tr("error flashing the board: %v", err), feedback.ErrBadArgument)
102127
}
103128
}

arduino-flasher-cli/updater/download_image.go

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,28 @@ func (pt *PassThru) Read(p []byte) (int, error) {
5656
return n, nil
5757
}
5858

59-
func DownloadImage(client *Client, targetVersion string, upgradeConfirmCb DownloadConfirmCB, forceYes bool) (string, string, error) {
59+
func DownloadAndExtract(client *Client, targetVersion string, upgradeConfirmCb DownloadConfirmCB, forceYes bool) (*paths.Path, error) {
60+
tmpZip, version, err := DownloadImage(client, targetVersion, upgradeConfirmCb, forceYes)
61+
if err != nil {
62+
return nil, fmt.Errorf("error downloading the image: %v", err)
63+
}
64+
65+
err = ExtractImage(tmpZip, tmpZip.Parent())
66+
if err != nil {
67+
return nil, fmt.Errorf("error extracting the image: %v", err)
68+
}
69+
70+
imagePath := tmpZip.Parent().Join("arduino-unoq-debian-image-" + version)
71+
return imagePath, nil
72+
}
73+
74+
func DownloadImage(client *Client, targetVersion string, upgradeConfirmCb DownloadConfirmCB, forceYes bool) (*paths.Path, string, error) {
6075
var err error
6176

6277
slog.Info("Checking for Debian image releases")
6378
manifest, err := client.GetInfoManifest()
6479
if err != nil {
65-
return "", "", err
80+
return nil, "", err
6681
}
6782

6883
if targetVersion == "latest" {
@@ -72,11 +87,11 @@ func DownloadImage(client *Client, targetVersion string, upgradeConfirmCb Downlo
7287
if !forceYes {
7388
res, err := upgradeConfirmCb(targetVersion)
7489
if err != nil {
75-
return "", "", err
90+
return nil, "", err
7691
}
7792
if !res {
7893
slog.Info("Download not confirmed by user, exiting")
79-
return "", "", nil
94+
return nil, "", nil
8095
}
8196
}
8297

@@ -87,63 +102,60 @@ func DownloadImage(client *Client, targetVersion string, upgradeConfirmCb Downlo
87102
slog.Info("Downloading Debian image", "version", manifest.Latest.Version)
88103
download, size, err = client.FetchZip(manifest.Latest.Url)
89104
if err != nil {
90-
return "", "", fmt.Errorf("could not fetch Debian image: %w", err)
105+
return nil, "", fmt.Errorf("could not fetch Debian image: %w", err)
91106
}
92107
} else {
93108
// TODO: check the json for the specific version and download it
94-
return "", "", nil
109+
return nil, "", nil
95110
}
96111
defer download.Close()
97112

98113
// Download the zip
99114
temp, err := paths.MkTempDir("", "flasher-updater-")
100115
if err != nil {
101-
return "", "", fmt.Errorf("could not create temporary download directory: %w", err)
116+
return nil, "", fmt.Errorf("could not create temporary download directory: %w", err)
102117
}
103-
tmpZip := temp.Join("update.tar.xz")
104-
defer func() {
105-
if err := tmpZip.Remove(); err != nil {
106-
slog.Warn("Could not remove temp zip", "zip", tmpZip.String(), "error", err)
107-
}
108-
}()
109118

119+
tmpZip := temp.Join("update.tar.zst")
110120
tmpZipFile, err := tmpZip.Create()
111121
if err != nil {
112-
return "", "", err
122+
return nil, "", err
113123
}
114124
defer tmpZipFile.Close()
115125

116126
// Download and keep track of the progress
117127
src := &PassThru{Reader: download, length: size, progressCB: func(f float64) { feedback.Printf("Download progress: %.2f %%", f) }}
118128
md5 := md5.New()
119129
if _, err := io.Copy(io.MultiWriter(md5, tmpZipFile), src); err != nil {
120-
return "", "", err
130+
return nil, "", err
121131
}
122-
tmpZipFile.Close()
123132

124133
// Check the hash
125134
if md5Byte, err := hex.DecodeString(manifest.Latest.Md5sum); err != nil {
126-
return "", "", fmt.Errorf("could not convert md5 from hex to bytes: %w", err)
135+
return nil, "", fmt.Errorf("could not convert md5 from hex to bytes: %w", err)
127136
} else if s := md5.Sum(nil); !bytes.Equal(s, md5Byte) {
128-
return "", "", fmt.Errorf("bad hash: %x (expected %x)", s, md5Byte)
137+
return nil, "", fmt.Errorf("bad hash: %x (expected %x)", s, md5Byte)
129138
}
130139

140+
slog.Info("Download of Debian image completed", "path", temp)
141+
142+
return tmpZip, targetVersion, nil
143+
}
144+
145+
func ExtractImage(archive, temp *paths.Path) error {
131146
// Unzip the Debian image
132147
slog.Info("Unzipping Debian image", "tmpDir", temp)
133-
tmpZipFile, err = tmpZip.Open()
148+
tmpZipFile, err := archive.Open()
134149
if err != nil {
135-
return "", "", fmt.Errorf("could not open archive for unzip: %w", err)
150+
return fmt.Errorf("could not open archive: %w", err)
136151
}
137152
defer tmpZipFile.Close()
138153

139154
if err := extract.Archive(context.Background(), tmpZipFile, temp.String(), func(s string) string {
140155
feedback.Print(s)
141156
return s
142157
}); err != nil {
143-
return "", "", fmt.Errorf("extracting archive: %w", err)
158+
return fmt.Errorf("could not extract archive: %w", err)
144159
}
145-
146-
slog.Info("Download of Debian image completed", "path", temp)
147-
148-
return temp.String(), targetVersion, nil
160+
return nil
149161
}

arduino-flasher-cli/updater/flasher.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,23 @@ import (
1515
//go:embed assets
1616
var assets embed.FS
1717

18-
func FlashBoard(ctx context.Context, downloadedImagePath string, updatedVersion string) error {
18+
func FlashBoard(ctx context.Context, downloadedImagePath string) error {
1919
qdl, err := getQdlBytes()
2020
if err != nil {
2121
return err
2222
}
2323

24-
flashDir := paths.New(downloadedImagePath, "arduino-unoq-debian-image-"+updatedVersion, "flash_UnoQ")
25-
qdlPath := flashDir.Join("qdl")
24+
flashDir := paths.New(downloadedImagePath, "flash_UnoQ")
25+
26+
qdlDir, err := paths.MkTempDir("", "qdl-")
27+
if err != nil {
28+
return err
29+
}
30+
defer qdlDir.RemoveAll()
31+
32+
qdlPath := qdlDir.Join("qdl")
2633
if runtime.GOOS == "windows" {
27-
qdlPath = flashDir.Join("qdl.exe")
34+
qdlPath = qdlDir.Join("qdl.exe")
2835
}
2936

3037
err = qdlPath.WriteFile(qdl)

0 commit comments

Comments
 (0)