From be250cba11144eb5f0a4a4254bbd4ca33239336a Mon Sep 17 00:00:00 2001 From: CMGS Date: Fri, 25 Sep 2026 17:18:04 +0800 Subject: [PATCH 1/4] fix: an unparsable COCOON_MACOS_LOG_LEVEL prints its error before the exit SetupLog fails before the global logger exists, so Fatalf went through a disabled logger and the CLI exited 1 with no output. --- cmd/root.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 483ac4a..2eb3199 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -23,10 +23,11 @@ import ( // Execute runs the cocoon-macos CLI. func Execute() { ctx := context.Background() - logger := log.WithFunc("cmd.Execute") if err := setupLog(ctx); err != nil { - logger.Fatalf(ctx, err, "setup log") + fmt.Fprintf(os.Stderr, "setup log: %v\n", err) + os.Exit(1) } + logger := log.WithFunc("cmd.Execute") // run() owns the signal context + its deferred cleanup, so os.Exit never strands a pending defer if err := run(ctx); err != nil { logger.Error(ctx, err, "command failed") From 2c970c83f62883b7c19739c360d5cf82f9a9fc3d Mon Sep 17 00:00:00 2001 From: CMGS Date: Fri, 25 Sep 2026 17:18:24 +0800 Subject: [PATCH 2/4] review: setupLog drops the stdout swap, since core v0.1.5 writes the log to stderr itself --- cmd/root.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 2eb3199..6cec057 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -74,12 +74,8 @@ func resolvePaths(cmd *cobra.Command, _ []string) error { return nil } -// setupLog swaps stderr in because SetupLog binds whatever os.Stdout is at call time. func setupLog(ctx context.Context) error { level := cmp.Or(os.Getenv("COCOON_MACOS_LOG_LEVEL"), "info") - origStdout := os.Stdout - os.Stdout = os.Stderr - defer func() { os.Stdout = origStdout }() return log.SetupLog(ctx, &types.ServerLogConfig{Level: level, UseJSON: !stderrIsTerminal()}, "") } From 505ed8a7f0a0199b6fcde673c79bfa8159e7ac1d Mon Sep 17 00:00:00 2001 From: CMGS Date: Sat, 26 Sep 2026 17:31:55 +0800 Subject: [PATCH 3/4] fix: the user-mode SSH forward listens on loopback only, as the docs and vm inspect say hostfwd=tcp::N-:22 bound every host interface, so a --ssh-port VM with the image's fixed login was reachable from the network. VNC already binds 127.0.0.1. --- qemu/launch.go | 2 +- qemu/launch_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qemu/launch.go b/qemu/launch.go index c734ffe..e7c9c29 100644 --- a/qemu/launch.go +++ b/qemu/launch.go @@ -90,7 +90,7 @@ func (s Spec) Args() []string { case s.Tap != "": a = append(a, "-netdev", "tap,id=net0,ifname="+escapeOptValue(s.Tap)+",script=no,downscript=no") case s.SSHPort > 0: - a = append(a, "-netdev", fmt.Sprintf("user,id=net0,hostfwd=tcp::%d-:22", s.SSHPort)) + a = append(a, "-netdev", fmt.Sprintf("user,id=net0,hostfwd=tcp:127.0.0.1:%d-:22", s.SSHPort)) default: a = append(a, "-netdev", "user,id=net0") } diff --git a/qemu/launch_test.go b/qemu/launch_test.go index e4ffd5b..bc3ada4 100644 --- a/qemu/launch_test.go +++ b/qemu/launch_test.go @@ -15,7 +15,7 @@ func TestArgsNetModes(t *testing.T) { want string }{ {"user-mode without ssh-port is bare SLIRP", func(_ *Spec) {}, "user,id=net0"}, - {"user-mode with ssh-port adds hostfwd", func(s *Spec) { s.SSHPort = 2222 }, "user,id=net0,hostfwd=tcp::2222-:22"}, + {"user-mode with ssh-port forwards a loopback port", func(s *Spec) { s.SSHPort = 2222 }, "user,id=net0,hostfwd=tcp:127.0.0.1:2222-:22"}, {"tap attaches to the pre-created TAP", func(s *Spec) { s.Tap = "tap0" }, "tap,id=net0,ifname=tap0,script=no,downscript=no"}, } for _, tt := range tests { From 16a2b6b4c7c85c42cb92d5af16ac4f64cce0671d Mon Sep 17 00:00:00 2001 From: CMGS Date: Sat, 26 Sep 2026 17:31:55 +0800 Subject: [PATCH 4/4] docs: point the GPU note at known-issues, name who derives the image variables, list the e2e REAL-tier tools --- docs/e2e.md | 2 +- docs/image-pipeline.md | 4 ++-- docs/networking.md | 2 +- docs/roadmap.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/e2e.md b/docs/e2e.md index 7993a3d..37f433a 100644 --- a/docs/e2e.md +++ b/docs/e2e.md @@ -36,7 +36,7 @@ Args: `--real` (run `[DUMMY]` then `[REAL]`), `--real-only` (skip straight to `[ (skip the root/bridge `--net` auto-create rows), `cleanup` (tear down and exit). Env vars beyond `CM_BIN` / `CM_HOME` / `CM_OPENCORE` / `CM_OVMF_CODE` / `CM_OVMF_VARS`: `CM_BRIDGE` (test bridge for `--net tap|bridge`, default `cmsmoke0`), `CM_TAHOE_REF` (ghcr ref for `[REAL]`), `CM_SSH_PORT` (host -port forwarded to guest `:22` in `[REAL]`, default `2299`). The `[REAL]` tier also needs `sshpass`. +port forwarded to guest `:22` in `[REAL]`, default `2299`). The `[REAL]` tier also needs `qemu-system-x86_64`, `socat` and `sshpass`. The `[DUMMY]` tier is the behavioral gate today's CI structurally can't give (CI is pure `go test` / `vet` / build — nothing opens `/dev/kvm` or mutates netlink); it belongs on a privileged self-hosted diff --git a/docs/image-pipeline.md b/docs/image-pipeline.md index 34da1c6..4a35e91 100644 --- a/docs/image-pipeline.md +++ b/docs/image-pipeline.md @@ -10,8 +10,8 @@ separately on a KVM testbed, keeping image and Go CI apart. ## `macos` — which OS -`macos` selects the OS to build and where it lands on ghcr (the build script derives -`MACOS_SHORTNAME` / `GHCR_REPO` / `GHCR_TAG` from it): +`macos` selects the OS to build and where it lands on ghcr (the workflow derives +`MACOS_SHORTNAME` / `GHCR_REPO` / `GHCR_TAG` from it, and the build script reads them): | `macos` | fetch-macOS shortname | ghcr repo:tag | |---------|-----------------------|---------------| diff --git a/docs/networking.md b/docs/networking.md index b160446..5fdac38 100644 --- a/docs/networking.md +++ b/docs/networking.md @@ -100,4 +100,4 @@ VNC shows a blank white/black screen with just the cursor even though the guest WindowServer is up). It is *not* a GPU/driver problem — a mouse move repaints it. The golden image's first-boot daemon runs `pmset -a displaysleep 0 sleep 0 disablesleep 1` system-wide (covering the pre-login loginwindow and disabling system sleep too) to keep the framebuffer painted; older images -need a `setup`-stage rebuild. See also the GPU/video note in [Boot, Firmware & GUI](vm.md). +need a `setup`-stage rebuild. See also the GPU/video note in [Known Issues](known-issues.md). diff --git a/docs/roadmap.md b/docs/roadmap.md index 7cff5c9..78815d2 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -6,7 +6,7 @@ yet wire to QEMU; port it the same way as networking (Linux-tagged, reusing cocoon types): - **Hardware passthrough (VFIO)** — cocoon's `extend/vfio` (`vfio.Spec`/`Attacher`, BDF/sysfs) and `cmd/vm device attach`. Maps to QEMU `-device vfio-pci,host=` for GPU/NIC passthrough — the real fix - for hardware-accelerated video (see [Boot, Firmware & GUI](vm.md)). + for hardware-accelerated video (see [Known Issues](known-issues.md)). - **GUI boot-to-desktop** — finish the Setup-Assistant mouse/OCR click-through so `:26` boots straight to `cocoon`'s desktop with auto-login (see [Boot, Firmware & GUI](vm.md)).