Skip to content

Commit 71c7fea

Browse files
committed
initial commit
0 parents  commit 71c7fea

File tree

10 files changed

+509
-0
lines changed

10 files changed

+509
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/

board/adb.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package board
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/arduino/go-paths-helper"
8+
9+
"github.com/arduino/remoteocd/feedback"
10+
)
11+
12+
var _ Boarder = (*ADBCmd)(nil)
13+
14+
type ADBCmd struct {
15+
Serial string
16+
ADBPath string
17+
}
18+
19+
func (a *ADBCmd) Run(ctx context.Context, args ...string) error {
20+
args = escapeArgs(args)
21+
22+
adbArgs := make([]string, 0, len(args)+4)
23+
adbArgs = append(adbArgs, a.ADBPath, "-s", a.Serial, "shell")
24+
adbArgs = append(adbArgs, args...)
25+
26+
cmd, err := paths.NewProcess(nil, adbArgs...)
27+
if err != nil {
28+
return err
29+
}
30+
31+
cmd.RedirectStderrTo(feedback.GetStdout())
32+
cmd.RedirectStdoutTo(feedback.GetStdout())
33+
34+
return cmd.RunWithinContext(ctx)
35+
}
36+
37+
func (a *ADBCmd) CopyTo(ctx context.Context, src, dst string) error {
38+
p, err := paths.NewProcess(nil, a.ADBPath, "-s", a.Serial, "push", src, dst)
39+
if err != nil {
40+
return err
41+
}
42+
out, err := p.RunAndCaptureCombinedOutput(ctx)
43+
if err != nil {
44+
return fmt.Errorf("copy files error: %w: %s", err, out)
45+
}
46+
return nil
47+
}
48+
49+
func (a *ADBCmd) MkDirAll(ctx context.Context, path string) error {
50+
p, err := paths.NewProcess(nil, a.ADBPath, "-s", a.Serial, "shell", "mkdir", "-p", path)
51+
if err != nil {
52+
return err
53+
}
54+
out, err := p.RunAndCaptureCombinedOutput(ctx)
55+
if err != nil {
56+
return fmt.Errorf("makedir error: %w: %s", err, out)
57+
}
58+
return nil
59+
}

board/board.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package board
2+
3+
import (
4+
"context"
5+
"os"
6+
"slices"
7+
"strconv"
8+
"strings"
9+
"sync"
10+
)
11+
12+
type Boarder interface {
13+
Run(ctx context.Context, args ...string) error
14+
CopyTo(ctx context.Context, src, dst string) error
15+
MkDirAll(ctx context.Context, path string) error
16+
}
17+
18+
var OnBoard = sync.OnceValue(func() bool {
19+
var boardNames = []string{"UNO Q\n", "Imola\n", "Inc. Robotics RB1\n"}
20+
buf, err := os.ReadFile("/sys/class/dmi/id/product_name")
21+
if err == nil && slices.Contains(boardNames, string(buf)) {
22+
return true
23+
}
24+
return false
25+
})()
26+
27+
// escapeArgs escapes arguments that contain spaces by wrapping them in quotes.
28+
// This should allow argument forwarding on a remote shells.
29+
func escapeArgs(args []string) []string {
30+
for i, arg := range args {
31+
if strings.Contains(arg, " ") {
32+
args[i] = strconv.Quote(arg)
33+
}
34+
}
35+
return args
36+
}

board/local.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package board
2+
3+
import (
4+
"context"
5+
6+
"github.com/arduino/go-paths-helper"
7+
8+
"github.com/arduino/remoteocd/feedback"
9+
)
10+
11+
var _ Boarder = (*LocalCmd)(nil)
12+
13+
type LocalCmd struct{}
14+
15+
func (l *LocalCmd) Run(ctx context.Context, args ...string) error {
16+
cmd, err := paths.NewProcess(nil, args...)
17+
if err != nil {
18+
return err
19+
}
20+
21+
cmd.RedirectStderrTo(feedback.GetStdout())
22+
cmd.RedirectStdoutTo(feedback.GetStdout())
23+
24+
return cmd.RunWithinContext(ctx)
25+
}
26+
27+
func (l *LocalCmd) CopyTo(_ context.Context, src, dst string) error {
28+
return paths.New(src).CopyTo(paths.New(dst))
29+
}
30+
31+
func (l *LocalCmd) MkDirAll(_ context.Context, path string) error {
32+
return paths.New(path).MkdirAll()
33+
}

board/ssh.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package board
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net"
7+
"os"
8+
"strings"
9+
10+
"golang.org/x/crypto/ssh"
11+
12+
"github.com/arduino/remoteocd/feedback"
13+
)
14+
15+
var _ Boarder = (*SSHCmd)(nil)
16+
17+
const (
18+
user = "arduino"
19+
sshPort = "22"
20+
)
21+
22+
type SSHCmd struct {
23+
client *ssh.Client
24+
}
25+
26+
func NewSSHCmd(password, address string) (*SSHCmd, error) {
27+
client, err := ssh.Dial("tcp", net.JoinHostPort(address, sshPort), &ssh.ClientConfig{
28+
User: user,
29+
Auth: []ssh.AuthMethod{
30+
ssh.Password(password),
31+
},
32+
// TODO: audit the security of this setting
33+
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // nolint:gosec
34+
})
35+
if err != nil {
36+
return nil, fmt.Errorf("failed to dial SSH: %w", err)
37+
}
38+
39+
return &SSHCmd{client: client}, nil
40+
}
41+
42+
func (s *SSHCmd) Run(ctx context.Context, args ...string) error {
43+
session, err := s.client.NewSession()
44+
if err != nil {
45+
return err
46+
}
47+
defer session.Close()
48+
49+
session.Stderr = feedback.GetStdout()
50+
session.Stdout = feedback.GetStdout()
51+
52+
args = escapeArgs(args)
53+
54+
return session.Run(strings.Join(args, " "))
55+
}
56+
57+
func (c *SSHCmd) CopyTo(ctx context.Context, src, dst string) error {
58+
session, err := c.client.NewSession()
59+
if err != nil {
60+
return err
61+
}
62+
defer session.Close()
63+
64+
f, err := os.Open(src)
65+
if err != nil {
66+
return fmt.Errorf("faild to open file: %w", err)
67+
}
68+
defer f.Close()
69+
70+
session.Stdin = f
71+
72+
if err := session.Run(fmt.Sprintf("cat > %s", dst)); err != nil {
73+
return fmt.Errorf("failed to write file: %w", err)
74+
}
75+
76+
return nil
77+
}
78+
79+
func (c *SSHCmd) MkDirAll(ctx context.Context, path string) error {
80+
session, err := c.client.NewSession()
81+
if err != nil {
82+
return err
83+
}
84+
defer session.Close()
85+
86+
if err := session.Run(fmt.Sprintf("mkdir -p %s", path)); err != nil {
87+
return fmt.Errorf("failt to make dir: %w", err)
88+
}
89+
return nil
90+
}

feedback/feedback.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package feedback
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
)
8+
9+
var verbose bool
10+
var quiet bool
11+
12+
func SetVerbose(v bool) {
13+
verbose = v
14+
}
15+
16+
func SetQuiet(q bool) {
17+
quiet = q
18+
}
19+
20+
func Printf(format string, a ...any) {
21+
if !quiet {
22+
fmt.Printf(format+"\n", a...)
23+
}
24+
}
25+
26+
func Logf(format string, a ...any) {
27+
if verbose && !quiet {
28+
fmt.Printf(format+"\n", a...)
29+
}
30+
}
31+
32+
func GetStdout() io.Writer {
33+
if quiet {
34+
return io.Discard
35+
}
36+
return os.Stdout
37+
}

flash.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"path"
7+
"strings"
8+
9+
"github.com/arduino/go-paths-helper"
10+
11+
"github.com/arduino/remoteocd/board"
12+
"github.com/arduino/remoteocd/feedback"
13+
)
14+
15+
const binaryDir = "/tmp/remoteocd/"
16+
const binaryName = "sketch.elf-zsk.bin"
17+
18+
func flash(ctx context.Context, cmder board.Boarder, binary *paths.Path, files []*paths.Path) error {
19+
err := cmder.MkDirAll(ctx, binaryDir)
20+
if err != nil {
21+
return err
22+
}
23+
24+
feedback.Logf("Pushing binary %q", binary)
25+
remoteBinary, err := pushBinary(ctx, cmder, binary)
26+
if err != nil {
27+
return err
28+
}
29+
30+
feedback.Logf("Pushing config files: %v", files)
31+
remoteFiles, err := pushFiles(ctx, cmder, files)
32+
if err != nil {
33+
return err
34+
}
35+
36+
args := makeOpenOCDCmd(remoteBinary, remoteFiles...)
37+
feedback.Logf("Running command: %s", strings.Join(args, " "))
38+
err = cmder.Run(ctx, args...)
39+
if err != nil {
40+
return fmt.Errorf("error running OpenOCD: %w", err)
41+
}
42+
43+
return nil
44+
}
45+
46+
func pushBinary(ctx context.Context, cmder board.Boarder, binary *paths.Path) (string, error) {
47+
destination := path.Join(binaryDir, binaryName)
48+
49+
if err := cmder.CopyTo(ctx, binary.String(), destination); err != nil {
50+
return "", err
51+
}
52+
53+
return destination, nil
54+
}
55+
56+
func pushFiles(ctx context.Context, cmder board.Boarder, files []*paths.Path) ([]string, error) {
57+
remoteFiles := make([]string, 0, len(files))
58+
for _, file := range files {
59+
destination := path.Join(binaryDir, file.Base())
60+
61+
if err := cmder.CopyTo(ctx, file.String(), destination); err != nil {
62+
return nil, err
63+
}
64+
65+
remoteFiles = append(remoteFiles, destination)
66+
}
67+
68+
return remoteFiles, nil
69+
}
70+
71+
const openOCDPath = "/opt/openocd"
72+
const openOCDBin = openOCDPath + "/bin/openocd"
73+
74+
func makeOpenOCDCmd(binary string, files ...string) []string {
75+
args := []string{
76+
openOCDBin, "-d2",
77+
"-s", openOCDPath,
78+
"-s", openOCDPath + "/share/openocd/scripts",
79+
"-f", "openocd_gpiod.cfg",
80+
"-c", "set filename " + binary,
81+
}
82+
for _, file := range files {
83+
args = append(args, "-f", file)
84+
}
85+
return args
86+
}

go.mod

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module github.com/arduino/remoteocd
2+
3+
go 1.25
4+
5+
require (
6+
github.com/arduino/go-paths-helper v1.14.0
7+
github.com/spf13/cobra v1.9.1
8+
go.bug.st/cleanup v1.0.0
9+
golang.org/x/crypto v0.42.0
10+
)
11+
12+
require (
13+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
14+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
15+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
16+
github.com/spf13/pflag v1.0.7 // indirect
17+
github.com/stretchr/testify v1.11.0 // indirect
18+
golang.org/x/sys v0.36.0 // indirect
19+
)

0 commit comments

Comments
 (0)