Skip to content

Commit 50d17a4

Browse files
authored
Merge pull request #81 from chipmk/fix/docker-context-as-root
fix: resolve docker context when running as root via launchd
2 parents e898a49 + 7f44b85 commit 50d17a4

File tree

2 files changed

+55
-7
lines changed

2 files changed

+55
-7
lines changed

CONTRIBUTING.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ Before releasing, verify:
5757
1. `make build` succeeds
5858
2. `sudo ./docker-mac-net-connect` starts and creates the tunnel
5959
3. `./scripts/e2e-test.sh` passes
60-
4. Stop and restart Docker Desktop - verify the server reconnects automatically
61-
5. For Homebrew releases: `brew upgrade chipmk/tap/docker-mac-net-connect` and `sudo brew services restart chipmk/tap/docker-mac-net-connect`
60+
4. Test as root (simulates launchd): `sudo -i $(pwd)/docker-mac-net-connect`
61+
5. Stop and restart Docker Desktop - verify the server reconnects automatically
62+
6. For Homebrew releases: `brew upgrade chipmk/tap/docker-mac-net-connect` and `sudo brew services restart chipmk/tap/docker-mac-net-connect`
6263

6364
## Releases
6465

main.go

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"net"
1010
"os"
1111
"os/signal"
12+
"os/user"
13+
"path/filepath"
1214
"strconv"
1315
"syscall"
1416
"time"
@@ -183,15 +185,39 @@ func main() {
183185

184186
logger.Verbosef("Interface %s created\n", interfaceName)
185187

188+
// When running as root (e.g. via launchd), the docker config lives under
189+
// the console user's home directory. Set DOCKER_CONFIG so the context
190+
// resolver can find it.
191+
if os.Getenv("DOCKER_CONFIG") == "" {
192+
consoleUser, err := getConsoleUser()
193+
if err != nil {
194+
logger.Verbosef("Failed to get console user: %v\n", err)
195+
} else {
196+
u, err := user.Lookup(consoleUser)
197+
if err != nil {
198+
logger.Verbosef("Failed to lookup user %s: %v\n", consoleUser, err)
199+
} else {
200+
dockerConfig := filepath.Join(u.HomeDir, ".docker")
201+
if err := os.Setenv("DOCKER_CONFIG", dockerConfig); err != nil {
202+
logger.Verbosef("Failed to set DOCKER_CONFIG: %v\n", err)
203+
} else {
204+
logger.Verbosef("Set DOCKER_CONFIG to %s (console user: %s)\n", dockerConfig, consoleUser)
205+
}
206+
}
207+
}
208+
}
209+
210+
var hostOpt client.Opt
186211
dockerHost, err := dcontext.CurrentDockerHost()
187212
if err != nil {
188-
logger.Errorf("Failed to resolve Docker host from context: %v", err)
189-
os.Exit(ExitSetupFailed)
213+
logger.Verbosef("Failed to resolve Docker host from context: %v, falling back to env/default\n", err)
214+
hostOpt = client.FromEnv
215+
} else {
216+
logger.Verbosef("Using Docker host: %s\n", dockerHost)
217+
hostOpt = client.WithHost(dockerHost)
190218
}
191219

192-
logger.Verbosef("Using Docker host: %s\n", dockerHost)
193-
194-
cli, err := client.NewClientWithOpts(client.WithHost(dockerHost), client.WithAPIVersionNegotiation())
220+
cli, err := client.NewClientWithOpts(hostOpt, client.WithAPIVersionNegotiation())
195221
if err != nil {
196222
logger.Errorf("Failed to create Docker client: %v", err)
197223
os.Exit(ExitSetupFailed)
@@ -361,3 +387,24 @@ func setupVm(
361387

362388
return nil
363389
}
390+
391+
// getConsoleUser returns the username of the currently logged-in GUI user
392+
// by checking the owner of /dev/console.
393+
func getConsoleUser() (string, error) {
394+
info, err := os.Stat("/dev/console")
395+
if err != nil {
396+
return "", fmt.Errorf("stat /dev/console: %w", err)
397+
}
398+
stat, ok := info.Sys().(*syscall.Stat_t)
399+
if !ok {
400+
return "", fmt.Errorf("unexpected stat type for /dev/console")
401+
}
402+
u, err := user.LookupId(strconv.FormatUint(uint64(stat.Uid), 10))
403+
if err != nil {
404+
return "", fmt.Errorf("lookup uid %d: %w", stat.Uid, err)
405+
}
406+
if u.Username == "root" {
407+
return "", fmt.Errorf("no console user logged in")
408+
}
409+
return u.Username, nil
410+
}

0 commit comments

Comments
 (0)