|
9 | 9 | "net" |
10 | 10 | "os" |
11 | 11 | "os/signal" |
| 12 | + "os/user" |
| 13 | + "path/filepath" |
12 | 14 | "strconv" |
13 | 15 | "syscall" |
14 | 16 | "time" |
@@ -183,15 +185,39 @@ func main() { |
183 | 185 |
|
184 | 186 | logger.Verbosef("Interface %s created\n", interfaceName) |
185 | 187 |
|
| 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 |
186 | 211 | dockerHost, err := dcontext.CurrentDockerHost() |
187 | 212 | 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) |
190 | 218 | } |
191 | 219 |
|
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()) |
195 | 221 | if err != nil { |
196 | 222 | logger.Errorf("Failed to create Docker client: %v", err) |
197 | 223 | os.Exit(ExitSetupFailed) |
@@ -361,3 +387,24 @@ func setupVm( |
361 | 387 |
|
362 | 388 | return nil |
363 | 389 | } |
| 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