Skip to content

Commit 74cbb73

Browse files
authored
restore: carry a VNC password into the relaunch; drop the one-implementation Actions interfaces (#38)
* cut: drop the one-implementation Actions interfaces cmd/vm and cmd/image each declared an Actions interface with a single Handler behind it and a compile-time assertion to match; Command takes the Handler. * restore: carry a VNC password into the relaunch of a running VM A running VM whose display is password-gated could only be restored by stopping it first, since the password is never persisted. restore now takes --vnc-password like start does and hands it to the relaunch; a password-gated display without the flag is still refused before any disk is touched. docs/snapshots.md states the rule and the window for VMs launched before the password-set bit existed.
1 parent 0cee700 commit 74cbb73

7 files changed

Lines changed: 21 additions & 33 deletions

File tree

cmd/image/commands.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,8 @@ import (
66
"github.com/cocoonstack/cocoon/cmd/cliutil"
77
)
88

9-
// Actions is the image-subcommand surface, backed by cocoon's cloudimg store.
10-
type Actions interface {
11-
Pull(cmd *cobra.Command, args []string) error
12-
List(cmd *cobra.Command, args []string) error
13-
Inspect(cmd *cobra.Command, args []string) error
14-
RM(cmd *cobra.Command, args []string) error
15-
}
16-
179
// Command builds the `image` subcommand tree against the given handler.
18-
func Command(h Actions) *cobra.Command {
10+
func Command(h *Handler) *cobra.Command {
1911
imageCmd := &cobra.Command{Use: "image", Short: "Manage macOS disk images (reuses cocoon's cloudimg store)"} // --state-dir is a root persistent flag
2012

2113
pull := &cobra.Command{

cmd/image/handler.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ import (
1616

1717
const maxPullAttempts = 3
1818

19-
var _ Actions = (*Handler)(nil)
20-
21-
// Handler implements Actions on cocoon's cloudimg store.
19+
// Handler is the image command surface on cocoon's cloudimg store.
2220
type Handler struct{}
2321

2422
// NewHandler returns a Handler backed by the cloudimg store.

cmd/vm/commands.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,8 @@ import (
66
"github.com/cocoonstack/cocoon/cmd/cliutil"
77
)
88

9-
// Actions mirrors cocoon's cmd/vm Actions, re-declared rather than imported because cocoon's commands.go drags in the Linux-only CH/netlink backend.
10-
type Actions interface {
11-
Create(cmd *cobra.Command, args []string) error
12-
Run(cmd *cobra.Command, args []string) error
13-
Start(cmd *cobra.Command, args []string) error
14-
Stop(cmd *cobra.Command, args []string) error
15-
List(cmd *cobra.Command, args []string) error
16-
Inspect(cmd *cobra.Command, args []string) error
17-
Console(cmd *cobra.Command, args []string) error
18-
RM(cmd *cobra.Command, args []string) error
19-
Snapshot(cmd *cobra.Command, args []string) error
20-
Restore(cmd *cobra.Command, args []string) error
21-
Clone(cmd *cobra.Command, args []string) error
22-
}
23-
249
// Command builds the `vm` subcommand tree against the given handler.
25-
func Command(h Actions) *cobra.Command {
10+
func Command(h *Handler) *cobra.Command {
2611
vmCmd := &cobra.Command{Use: "vm", Short: "Manage macOS VMs"} // --state-dir is a root persistent flag
2712

2813
createCmd := &cobra.Command{
@@ -104,6 +89,7 @@ func Command(h Actions) *cobra.Command {
10489
}
10590
restoreCmd.Flags().String("tag", "", "snapshot tag to restore (default: newest)")
10691
restoreCmd.Flags().Bool("force", false, "stop the VM, restore, then relaunch if it was running")
92+
restoreCmd.Flags().String("vnc-password", "", "VNC password for the relaunch of a running VM whose display is password-gated (≤8 chars)")
10793

10894
cloneCmd := &cobra.Command{
10995
Use: "clone SRC",

cmd/vm/handler.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ const (
1717
netCNI = "cni"
1818
)
1919

20-
var _ Actions = (*Handler)(nil)
21-
22-
// Handler implements the vm Actions: per-VM CoW overlays on a golden macOS qcow2, booted by qemu-system-x86_64 on an x86 Linux/KVM host.
20+
// Handler is the vm command surface: per-VM CoW overlays on a golden macOS qcow2, booted by qemu-system-x86_64 on an x86 Linux/KVM host.
2321
type Handler struct{}
2422

2523
// NewHandler returns a Handler ready to serve the vm subcommands.

cmd/vm/lifecycle_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func TestRestoreRefusesRunningPasswordedVNC(t *testing.T) {
6565
cmd := newLifecycleTestCommand(t, stateDir)
6666
cmd.Flags().String("tag", "", "")
6767
cmd.Flags().Bool("force", true, "")
68+
cmd.Flags().String("vnc-password", "", "")
6869

6970
err = NewHandler().Restore(cmd, []string{"macos-demo"})
7071
if err == nil || !strings.Contains(err.Error(), "password-gated VNC") {

cmd/vm/snapshot.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,14 @@ func (h *Handler) Restore(cmd *cobra.Command, args []string) error {
6767
if force, _ := cmd.Flags().GetBool("force"); !force {
6868
return fmt.Errorf("vm %q is running; stop it first or pass --force to stop+restore", r.Name)
6969
}
70-
if r.VNCDisp >= 0 && (r.VNCPassSet || r.Netns != "") {
71-
return fmt.Errorf("vm %q serves a password-gated VNC display and a relaunch cannot carry the password; stop it, restore, then start --vnc %d --vnc-password", r.Name, r.VNCDisp)
70+
vncPass, _ := cmd.Flags().GetString("vnc-password")
71+
if err := validateVNCPassword(vncPass); err != nil {
72+
return err
73+
}
74+
if vncPass == "" && r.VNCDisp >= 0 && (r.VNCPassSet || r.Netns != "") {
75+
return fmt.Errorf("vm %q serves a password-gated VNC display; pass --vnc-password to relaunch it, or stop, restore, then start --vnc %d --vnc-password", r.Name, r.VNCDisp)
7276
}
77+
r.VNCPass = vncPass
7378
terminate(ctx, r, stopGracePeriod)
7479
r.PID = 0
7580
if err := saveRec(dir, r); err != nil {

docs/snapshots.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ cocoon-macos vm snapshot m1 --tag clean
88
cocoon-macos vm restore m1 --tag clean # --force to stop+restore+relaunch a running VM
99
```
1010

11+
`--force` on a running VM keeps its VNC display across the relaunch. A display
12+
that was started with `--vnc-password` (every CNI display, optionally others)
13+
needs the password again: pass `--vnc-password` to `restore`, or stop, restore
14+
and `start --vnc N --vnc-password`; without it the restore is refused before
15+
any disk is touched. VMs launched by a cocoon-macos older than this rule with a
16+
passworded non-CNI display are not recognised as password-gated until their
17+
next stop or start.
18+
1119
Snapshots are **offline qcow2-internal** (`qemu-img snapshot`, VM stopped) and capture **disk state
1220
only**. Live RAM snapshot is intentionally impossible here: `-cpu +invtsc` makes the macOS guest
1321
non-migratable, so resume-from-RAM can't work. This is by design (see [Roadmap](roadmap.md)).

0 commit comments

Comments
 (0)