package main import ( "encoding/xml" "fmt" "os" "os/exec" "path/filepath" "strings" "syscall" ) const serviceLabel = "run.fc.byre" const plistTemplate = ` Label %s ProgramArguments %s serve EnvironmentVariables PATH %s RunAtLoad KeepAlive StandardOutPath %s StandardErrorPath %s ` func installCmd() { requireChecks() exe, err := os.Executable() if err != nil { fatal("find executable", "err", err) } exe, err = filepath.EvalSymlinks(exe) if err != nil { fatal("resolve executable", "err", err) } home, err := os.UserHomeDir() if err != nil { fatal("home dir", "err", err) } plistPath := filepath.Join(home, "Library", "LaunchAgents", serviceLabel+".plist") logPath := filepath.Join(home, "Library", "Logs", "byre.log") plist := fmt.Sprintf(plistTemplate, xmlText(serviceLabel), xmlText(exe), xmlText(filepath.Dir(exe)+":"+os.Getenv("PATH")), xmlText(logPath), xmlText(logPath)) err = os.MkdirAll(filepath.Dir(plistPath), 0o755) if err != nil { fatal("create launch agents dir", "err", err) } err = os.WriteFile(plistPath, []byte(plist), 0o644) if err != nil { fatal("write launch agent", "err", err) } fmt.Println("wrote", plistPath) fmt.Println("byre's loopback address is", loopbackURL) domain := fmt.Sprintf("gui/%d", os.Getuid()) service := domain + "/" + serviceLabel if exec.Command("launchctl", "print", service).Run() != nil { run("launchctl", "bootstrap", domain, plistPath) fmt.Println("started", service, "running", exe) return } // bootout kills every process the service started, which includes this one when an agent runs it. reload := exec.Command("/bin/sh", "-c", `launchctl bootout "$1"; launchctl bootstrap "$2" "$3"`, "sh", service, domain, plistPath) reload.SysProcAttr = &syscall.SysProcAttr{Setsid: true} err = reload.Start() if err != nil { fatal("restart service", "err", err) } fmt.Println("restarting", service, "on", exe) } func run(name string, args ...string) { cmd := exec.Command(name, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { fatal("run "+name, "args", args, "err", err) } } func xmlText(s string) string { var b strings.Builder xml.EscapeText(&b, []byte(s)) return b.String() }