fix(cli): ask before macOS pops "Background Items Added" at init (#139)

This commit is contained in:
Snow Lee (Sungwon)
2026-08-10 09:56:47 +09:00
committed by GitHub
parent 27cc558ac7
commit d362b1dbf9
3 changed files with 99 additions and 3 deletions
+57
View File
@@ -0,0 +1,57 @@
package main
import (
"io"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
// TestInstallAutostartWarnsBeforeTheFirstMacOSWrite
//
// Writing ~/Library/LaunchAgents/*.plist is what makes macOS pop "Background
// Items Added". Nothing suppresses that notice, so the only thing keeping it
// from reading as malware is our own line arriving first — and it must arrive
// only when a write is actually about to happen, or it becomes noise every
// init prints and nobody reads.
func TestInstallAutostartWarnsBeforeTheFirstMacOSWrite(t *testing.T) {
if runtime.GOOS != "darwin" {
t.Skip("the notice exists only on macOS")
}
home := t.TempDir()
t.Setenv("HOME", home)
// Non-interactive: no prompt, just the heads-up. (The TTY branch needs a
// terminal survey can drive, which a test does not have.)
say := func() string {
t.Helper()
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
old := os.Stdout
os.Stdout = w
installAutostart(false)
os.Stdout = old
w.Close()
out, _ := io.ReadAll(r)
r.Close()
return string(out)
}
first := say()
if !strings.Contains(first, "Background Items Added") {
t.Errorf("first install said nothing about the macOS notice:\n%s", first)
}
plist := filepath.Join(home, "Library", "LaunchAgents", "ai.beardrive.daemon.plist")
if _, err := os.Stat(plist); err != nil {
t.Fatalf("no agent written: %v", err)
}
// Already registered: no second write, so no notice to explain.
if second := say(); strings.Contains(second, "Background Items Added") {
t.Errorf("re-running warned again about a notice that will not fire:\n%s", second)
}
}
+33 -3
View File
@@ -11,6 +11,7 @@ import (
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"time"
@@ -241,7 +242,7 @@ the folder was renamed or moved.`,
installAgentHooks(folder)
}
if !noAutostart {
installAutostart()
installAutostart(stdinIsTTY() && !yes)
}
return startSync(cmd.Context(), folder, proj, foreground, 3*time.Second, 10*time.Second)
}
@@ -387,7 +388,7 @@ the folder was renamed or moved.`,
installAgentHooks(folder)
}
if !noAutostart {
installAutostart()
installAutostart(interactive)
}
if len(scope) > 0 {
dirs := make([]string, len(scope))
@@ -493,7 +494,36 @@ func installAgentHooks(folder string) {
// Linux without systemd) or an unwritable config dir is not a reason to fail
// an init that otherwise worked — the folder syncs, it just won't come back by
// itself.
func installAutostart() {
//
// On macOS the write itself is what users see: the moment anything lands in
// ~/Library/LaunchAgents, Ventura+ pops "Background Items Added", naming a
// binary they just installed. Nothing suppresses that notice — SMAppService, a
// System Settings login item and a real crontab all trigger it (cron also
// wants Full Disk Access on top) — so the fix is to make it expected rather
// than to dodge it: say what is about to happen before it happens, and on a
// TTY ask first. Linux and Windows show nothing, so they are not asked.
func installAutostart(interactive bool) {
if runtime.GOOS == "darwin" && !autostart.Installed() {
const notice = `macOS will show a "Background Items Added" notice for it`
if interactive {
ok := true
// An interrupt reads as "no": autostart is a convenience, and the
// caller is one step from starting the daemon anyway.
//
// ponytail: a decline is not remembered, so re-running `bdrive init`
// interactively asks again. Persist it in settings.json if that ever
// nags — agents and scripts have no TTY and never see the prompt.
if err := survey.AskOne(&survey.Confirm{
Message: "Restart syncing at login? (" + notice + ")",
Default: true,
}, &ok); err != nil || !ok {
fmt.Println(" login: autostart skipped — `bdrive autostart install` enables it later")
return
}
} else {
fmt.Println(" login: registering sync to restart at login — " + notice)
}
}
res, err := autostart.Install()
if err != nil {
if !errors.Is(err, autostart.ErrUnsupported) {
@@ -110,6 +110,15 @@ Linux needs systemd as the init system. Without it — Alpine or another
runit/OpenRC distro, WSL1, a slim container — `bdrive autostart` says so rather
than writing a unit nothing would read.
On macOS, the moment that file is written you get a **"Background Items Added"**
notification, and `bdrive` appears in System Settings → General → Login Items.
That notice is macOS reporting the registration above — every way of starting
at login triggers it, including Apple's own `SMAppService` and a plain
`crontab` — so `bdrive init` asks first on a terminal and says what is about to
happen when it can't ask. Answer no, or run `bdrive autostart uninstall`, and
the item goes away; sync then resumes on the next `bdrive resume`, `bdrive
init`, or agent turn instead of at login.
On Windows you may see a console window flicker at logon: bdrive is a console
program and `resume` exits in milliseconds. Nothing is wrong.