Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion internal/daemon/autostart_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ package daemon

import (
"fmt"
"html"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"text/template"
)

Expand Down Expand Up @@ -53,6 +55,10 @@ const plistTemplate = `<?xml version="1.0" encoding="UTF-8"?>
<dict>
<key>PATH</key>
<string>{{.EnvPath}}</string>
{{- range $key, $val := .ExtraEnv}}
<key>{{$key}}</key>
<string>{{xmlEscape $val}}</string>
{{- end}}
</dict>
</dict>
</plist>
Expand All @@ -66,6 +72,7 @@ type plistData struct {
Port int
LogFile string
EnvPath string
ExtraEnv map[string]string
}

// EnableAutostart creates the launchd plist and loads it.
Expand All @@ -89,16 +96,30 @@ func EnableAutostart(configPath string, port int) error {
envPath = "/usr/local/bin:/usr/bin:/bin"
}

extraEnv := make(map[string]string)
for _, e := range os.Environ() {
k, v, ok := strings.Cut(e, "=")
if !ok {
continue
}
if strings.HasPrefix(k, "ROUTATIC_PROXY_") {
extraEnv[k] = v
}
}

data := plistData{
Label: LaunchAgent,
BinaryPath: paths.BinaryPath,
ConfigPath: configPath,
Port: port,
LogFile: paths.LogFile,
EnvPath: envPath,
ExtraEnv: extraEnv,
}

tmpl, err := template.New("plist").Parse(plistTemplate)
tmpl, err := template.New("plist").Funcs(template.FuncMap{
"xmlEscape": html.EscapeString,
}).Parse(plistTemplate)
if err != nil {
return fmt.Errorf("cannot parse plist template: %w", err)
}
Expand Down
18 changes: 16 additions & 2 deletions internal/daemon/autostart_darwin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ func TestEnableDisableAutostart_Darwin(t *testing.T) {
// Setup temporary home directory
tempHome := t.TempDir()
t.Setenv("HOME", tempHome)
// Set a ROUTATIC_PROXY_* var with characters that need XML escaping.
t.Setenv("ROUTATIC_PROXY_API_KEY", "sk-test&key<value>")

configPath := "/tmp/mock-config.json"
port := 9999
Expand Down Expand Up @@ -40,8 +42,11 @@ func TestEnableDisableAutostart_Darwin(t *testing.T) {
if !strings.Contains(content, "<key>Label</key>\n <string>com.routatic.proxy</string>") {
t.Errorf("Plist missing correct Label string")
}
if !strings.Contains(content, "<string>serve</string>") {
t.Errorf("Plist missing serve command")
if !strings.Contains(content, "<string>start</string>") {
Comment thread
stefanoverna marked this conversation as resolved.
t.Errorf("Plist missing start command")
}
if !strings.Contains(content, "<string>--background</string>") {
t.Errorf("Plist missing --background flag")
}
if !strings.Contains(content, "<string>--config</string>\n <string>/tmp/mock-config.json</string>") {
t.Errorf("Plist missing config path arguments")
Expand All @@ -50,6 +55,15 @@ func TestEnableDisableAutostart_Darwin(t *testing.T) {
t.Errorf("Plist missing port arguments")
}

// Verify ROUTATIC_PROXY_* env var is present and XML-escaped.
if !strings.Contains(content, "<key>ROUTATIC_PROXY_API_KEY</key>") {
t.Errorf("Plist missing ROUTATIC_PROXY_API_KEY env var")
}
// The value should be XML-escaped: & -> &amp;, < -> &lt;, > -> &gt;
if !strings.Contains(content, "<string>sk-test&amp;key&lt;value&gt;</string>") {
t.Errorf("Plist missing XML-escaped API key value. Content:\n%s", content)
}

// Disable autostart
err = DisableAutostart()
if err != nil {
Expand Down
Loading