Skip to content

Commit 98a9b44

Browse files
committed
[test] leverage launchd tool to implement autostart feature on macos
1 parent e40ebde commit 98a9b44

File tree

4 files changed

+156
-16
lines changed

4 files changed

+156
-16
lines changed

config/autostart.go

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//
2+
// This program is free software: you can redistribute it and/or modify
3+
// it under the terms of the GNU Affero General Public License as published
4+
// by the Free Software Foundation, either version 3 of the License, or
5+
// (at your option) any later version.
6+
//
7+
// This program is distributed in the hope that it will be useful,
8+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
// GNU Affero General Public License for more details.
11+
//
12+
// You should have received a copy of the GNU Affero General Public License
13+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
14+
15+
package config
16+
17+
import (
18+
"fmt"
19+
"os"
20+
"os/exec"
21+
22+
"github.com/arduino/go-paths-helper"
23+
log "github.com/sirupsen/logrus"
24+
)
25+
26+
// getLaunchdAgentPath will return the path of the launchd agent default path
27+
func getLaunchdAgentPath() *paths.Path {
28+
return GetDefaultHomeDir().Join("Library", "LaunchAgents", "ArduinoCreateAgent.plist")
29+
}
30+
31+
// WritePlistFile function will write the required plist file to $HOME/Library/LaunchAgents/ArduinoCreateAgent.plist
32+
// it will return nil in case of success,
33+
// it will error if the file is already there or in any other case
34+
func WritePlistFile() error {
35+
src, err := os.Executable()
36+
if err != nil {
37+
return err
38+
}
39+
40+
launchdAgentPath := getLaunchdAgentPath()
41+
42+
// For now this file comes from the installbuilder autogenerated one
43+
launchdAgentDefinition := `<?xml version="1.0" encoding="UTF-8"?>
44+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
45+
<plist version="1.0">
46+
<dict>
47+
<key>KeepAlive</key>
48+
<false/>
49+
<key>Label</key>
50+
<string>ArduinoCreateAgent</string>
51+
<key>ProgramArguments</key>
52+
<array>
53+
<string>` + src + `</string>
54+
</array>
55+
<key>RunAtLoad</key>
56+
<true/>
57+
58+
<key>AbandonProcessGroup</key>
59+
<true/>
60+
</dict>
61+
</plist>
62+
`
63+
if launchdAgentPath.Exist() {
64+
// TODO check for differences in files(?)
65+
// we already have an existing launchd plist file, so we don't have to do anything
66+
return fmt.Errorf("the autostart file %s already exists", launchdAgentPath)
67+
}
68+
// we need to create a new one
69+
return launchdAgentPath.WriteFile([]byte(launchdAgentDefinition))
70+
}
71+
72+
// LoadLaunchdAgent will use launchctl to load the agent, will return an error if something goes wrong
73+
func LoadLaunchdAgent() error {
74+
oscmd := exec.Command("launchctl", "load", getLaunchdAgentPath().String())
75+
err := oscmd.Run()
76+
return err
77+
}
78+
79+
// UnloadLaunchdAgent will use launchctl to load the agent, will return an error if something goes wrong
80+
func UnloadLaunchdAgent() error {
81+
oscmd := exec.Command("launchctl", "unload", getLaunchdAgentPath().String())
82+
err := oscmd.Run()
83+
return err
84+
}
85+
86+
// RemovePlistFile function will remove the plist file from $HOME/Library/LaunchAgents/ArduinoCreateAgent.plist and return an error
87+
// it will not do anything if the file is not there
88+
func RemovePlistFile() error {
89+
launchdAgentPath := getLaunchdAgentPath()
90+
if launchdAgentPath.Exist() {
91+
log.Infof("removing: %s", launchdAgentPath)
92+
return launchdAgentPath.Remove()
93+
}
94+
return nil
95+
}
96+
97+
// TODO win/linux ?
98+
// TODO read the doc about launchd https://www.launchd.info/

config/config.go

+16
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,22 @@ func GetDefaultConfigDir() *paths.Path {
9191
return agentConfigDir
9292
}
9393

94+
// GetDefaultHomeDir returns the full path to the user's home directory.
95+
func GetDefaultHomeDir() *paths.Path {
96+
// UserHomeDir returns the current user's home directory.
97+
98+
// On Unix, including macOS, it returns the $HOME environment variable.
99+
// On Windows, it returns %USERPROFILE%.
100+
// On Plan 9, it returns the $home environment variable.
101+
102+
homeDir, err := os.UserHomeDir()
103+
if err != nil {
104+
log.Panicf("Can't get user home dir: %s", err)
105+
}
106+
107+
return paths.New(homeDir)
108+
}
109+
94110
//go:embed config.ini
95111
var configContent []byte
96112

config/config.ini

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ appName = CreateAgent/Stable
66
updateUrl = https://downloads.arduino.cc/
77
origins = https://local.arduino.cc:8000
88
#httpProxy = http://your.proxy:port # Proxy server for HTTP requests
9-
crashreport = false # enable crashreport logging
9+
crashreport = false # enable crashreport logging
10+
autostartMacOS = true # the Arduino Create Agent is able to start automatically after login on macOS (launchd agent)

main.go

+40-15
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,22 @@ var (
6666

6767
// iniflags
6868
var (
69-
address = iniConf.String("address", "127.0.0.1", "The address where to listen. Defaults to localhost")
70-
appName = iniConf.String("appName", "", "")
71-
gcType = iniConf.String("gc", "std", "Type of garbage collection. std = Normal garbage collection allowing system to decide (this has been known to cause a stop the world in the middle of a CNC job which can cause lost responses from the CNC controller and thus stalled jobs. use max instead to solve.), off = let memory grow unbounded (you have to send in the gc command manually to garbage collect or you will run out of RAM eventually), max = Force garbage collection on each recv or send on a serial port (this minimizes stop the world events and thus lost serial responses, but increases CPU usage)")
72-
hostname = iniConf.String("hostname", "unknown-hostname", "Override the hostname we get from the OS")
73-
httpProxy = iniConf.String("httpProxy", "", "Proxy server for HTTP requests")
74-
httpsProxy = iniConf.String("httpsProxy", "", "Proxy server for HTTPS requests")
75-
indexURL = iniConf.String("indexURL", "https://downloads.arduino.cc/packages/package_staging_index.json", "The address from where to download the index json containing the location of upload tools")
76-
iniConf = flag.NewFlagSet("ini", flag.ContinueOnError)
77-
logDump = iniConf.String("log", "off", "off = (default)")
78-
origins = iniConf.String("origins", "", "Allowed origin list for CORS")
79-
regExpFilter = iniConf.String("regex", "usb|acm|com", "Regular expression to filter serial port list")
80-
signatureKey = iniConf.String("signatureKey", "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvc0yZr1yUSen7qmE3cxF\nIE12rCksDnqR+Hp7o0nGi9123eCSFcJ7CkIRC8F+8JMhgI3zNqn4cUEn47I3RKD1\nZChPUCMiJCvbLbloxfdJrUi7gcSgUXrlKQStOKF5Iz7xv1M4XOP3JtjXLGo3EnJ1\npFgdWTOyoSrA8/w1rck4c/ISXZSinVAggPxmLwVEAAln6Itj6giIZHKvA2fL2o8z\nCeK057Lu8X6u2CG8tRWSQzVoKIQw/PKK6CNXCAy8vo4EkXudRutnEYHEJlPkVgPn\n2qP06GI+I+9zKE37iqj0k1/wFaCVXHXIvn06YrmjQw6I0dDj/60Wvi500FuRVpn9\ntwIDAQAB\n-----END PUBLIC KEY-----", "Pem-encoded public key to verify signed commandlines")
81-
updateURL = iniConf.String("updateUrl", "", "")
82-
verbose = iniConf.Bool("v", true, "show debug logging")
83-
crashreport = iniConf.Bool("crashreport", false, "enable crashreport logging")
69+
address = iniConf.String("address", "127.0.0.1", "The address where to listen. Defaults to localhost")
70+
appName = iniConf.String("appName", "", "")
71+
gcType = iniConf.String("gc", "std", "Type of garbage collection. std = Normal garbage collection allowing system to decide (this has been known to cause a stop the world in the middle of a CNC job which can cause lost responses from the CNC controller and thus stalled jobs. use max instead to solve.), off = let memory grow unbounded (you have to send in the gc command manually to garbage collect or you will run out of RAM eventually), max = Force garbage collection on each recv or send on a serial port (this minimizes stop the world events and thus lost serial responses, but increases CPU usage)")
72+
hostname = iniConf.String("hostname", "unknown-hostname", "Override the hostname we get from the OS")
73+
httpProxy = iniConf.String("httpProxy", "", "Proxy server for HTTP requests")
74+
httpsProxy = iniConf.String("httpsProxy", "", "Proxy server for HTTPS requests")
75+
indexURL = iniConf.String("indexURL", "https://downloads.arduino.cc/packages/package_staging_index.json", "The address from where to download the index json containing the location of upload tools")
76+
iniConf = flag.NewFlagSet("ini", flag.ContinueOnError)
77+
logDump = iniConf.String("log", "off", "off = (default)")
78+
origins = iniConf.String("origins", "", "Allowed origin list for CORS")
79+
regExpFilter = iniConf.String("regex", "usb|acm|com", "Regular expression to filter serial port list")
80+
signatureKey = iniConf.String("signatureKey", "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvc0yZr1yUSen7qmE3cxF\nIE12rCksDnqR+Hp7o0nGi9123eCSFcJ7CkIRC8F+8JMhgI3zNqn4cUEn47I3RKD1\nZChPUCMiJCvbLbloxfdJrUi7gcSgUXrlKQStOKF5Iz7xv1M4XOP3JtjXLGo3EnJ1\npFgdWTOyoSrA8/w1rck4c/ISXZSinVAggPxmLwVEAAln6Itj6giIZHKvA2fL2o8z\nCeK057Lu8X6u2CG8tRWSQzVoKIQw/PKK6CNXCAy8vo4EkXudRutnEYHEJlPkVgPn\n2qP06GI+I+9zKE37iqj0k1/wFaCVXHXIvn06YrmjQw6I0dDj/60Wvi500FuRVpn9\ntwIDAQAB\n-----END PUBLIC KEY-----", "Pem-encoded public key to verify signed commandlines")
81+
updateURL = iniConf.String("updateUrl", "", "")
82+
verbose = iniConf.Bool("v", true, "show debug logging")
83+
crashreport = iniConf.Bool("crashreport", false, "enable crashreport logging")
84+
autostartMacOS = iniConf.Bool("autostartMacOS", true, "the Arduino Create Agent is able to start automatically after login on macOS (launchd agent)")
8485
)
8586

8687
var homeTemplate = template.Must(template.New("home").Parse(homeTemplateHTML))
@@ -327,6 +328,30 @@ func loop() {
327328
}
328329
}
329330

331+
// osx agent launchd autostart
332+
// TODO ignore setting if not on macos
333+
if *autostartMacOS {
334+
err := config.WritePlistFile()
335+
if err != nil {
336+
log.Info(err)
337+
} else {
338+
err = config.LoadLaunchdAgent()
339+
if err != nil {
340+
log.Error(err)
341+
}
342+
}
343+
} else {
344+
err := config.UnloadLaunchdAgent()
345+
if err != nil {
346+
log.Error(err)
347+
} else {
348+
err = config.RemovePlistFile()
349+
if err != nil {
350+
log.Error(err)
351+
}
352+
}
353+
}
354+
330355
// launch the hub routine which is the singleton for the websocket server
331356
go h.run()
332357
// launch our serial port routine

0 commit comments

Comments
 (0)