diff --git a/cmds.go b/cmds.go index 8157dd8..50c4461 100644 --- a/cmds.go +++ b/cmds.go @@ -8,7 +8,6 @@ import ( "io/ioutil" "net" "os" - //"os/exec" "path/filepath" "runtime" "strings" @@ -80,6 +79,7 @@ func cmdInit() int { m.OSType = "Linux26_64" m.CPUs = uint(runtime.NumCPU()) m.Memory = B2D.Memory + m.SerialFile = B2D.SerialFile m.Flag |= vbx.F_pae m.Flag |= vbx.F_longmode // important: use x86-64 processor @@ -232,17 +232,40 @@ func cmdUp() int { return 1 } - logf("Waiting for SSH server to start...") - addr := fmt.Sprintf("localhost:%d", m.SSHPort) - const n = 10 - // Try to connect to the SSH 10 times at 3 sec interval before giving up. - if err := read(addr, n, 3*time.Second); err != nil { - logf("Failed to connect to SSH port at %s after %d attempts. Last error: %v", addr, n, err) + if err := m.Refresh(); err != nil { + logf("Failed to start machine %q: %s", B2D.VM, err) + return 1 + } + if m.State != vbx.Running { + logf("Failed to start machine %q (run again with -v for details)", B2D.VM) return 1 } + logf("Waiting for VM to be started...") + //give the VM a little time to start, so we don't kill the Serial Pipe/Socket + time.Sleep(2) + natSSH := fmt.Sprintf("localhost:%d", B2D.SSHPort) + IP := "" + for i := 1; i < 30; i++ { + if B2D.Serial && runtime.GOOS != "windows" { + if IP = RequestIPFromSerialPort(m.SerialFile); IP != "" { + break + } + } + if err := read(natSSH, 1, 1*time.Second); err == nil { + IP = RequestIPFromSSH(m) + break + } + + print(".") + } + print("\n") + logf("Started.") + if IP == "" { + logf("Auto detection of the VM's IP address.") + } switch runtime.GOOS { case "windows": logf("Docker client does not run on Windows for now. Please use") @@ -250,9 +273,9 @@ func cmdUp() int { logf("to SSH into the VM instead.") default: // Check if $DOCKER_HOST ENV var is properly configured. - if os.Getenv("DOCKER_HOST") != fmt.Sprintf("tcp://localhost:%d", m.DockerPort) { + if os.Getenv("DOCKER_HOST") != fmt.Sprintf("tcp://%s:%d", IP, m.DockerPort) { logf("To connect the Docker client to the Docker daemon, please set:") - logf(" export DOCKER_HOST=tcp://localhost:%d", m.DockerPort) + logf(" export DOCKER_HOST=tcp://%s:%d", IP, m.DockerPort) } } return 0 @@ -433,6 +456,33 @@ func cmdIP() int { return 1 } + IP := "" + if B2D.Serial { + for i := 1; i < 20; i++ { + if runtime.GOOS != "windows" { + if IP = RequestIPFromSerialPort(m.SerialFile); IP != "" { + break + } + } + } + } + + if IP == "" { + IP = RequestIPFromSSH(m) + } + if IP != "" { + errf("\nThe VM's Host only interface IP address is: ") + fmt.Printf("%s", IP) + errf("\n\n") + } else { + errf("\nFailed to get VM Host only IP address.\n") + errf("\tWas the VM initilized using boot2docker?\n") + } + return 0 +} + +func RequestIPFromSSH(m *vbx.Machine) string { + // fall back to using the NAT port forwarded ssh out, err := cmd(B2D.SSH, //"-vvv", //TODO: add if its boot2docker -v "-o", "StrictHostKeyChecking=no", @@ -442,25 +492,21 @@ func cmdIP() int { "docker@localhost", "ip addr show dev eth1", ) + IP := "" if err != nil { logf("%s", err) - return 1 - } - // parse to find: inet 192.168.59.103/24 brd 192.168.59.255 scope global eth1 - lines := strings.Split(out, "\n") - for _, line := range lines { - vals := strings.Split(strings.TrimSpace(line), " ") - if vals[0] == "inet" { - ip := vals[1][:strings.Index(vals[1], "/")] - errf("\nThe VM's Host only interface IP address is: ") - fmt.Printf("%s", ip) - errf("\n\n") - return 0 + } else { + // parse to find: inet 192.168.59.103/24 brd 192.168.59.255 scope global eth1 + lines := strings.Split(out, "\n") + for _, line := range lines { + vals := strings.Split(strings.TrimSpace(line), " ") + if len(vals) >= 2 && vals[0] == "inet" { + IP = vals[1][:strings.Index(vals[1], "/")] + break + } } } - errf("\nFailed to get VM Host only IP address.\n") - errf("\tWas the VM initilized using boot2docker?\n") - return 0 + return IP } // Download the boot2docker ISO image. diff --git a/config.go b/config.go index 77196fc..298a5a5 100644 --- a/config.go +++ b/config.go @@ -45,6 +45,10 @@ var B2D struct { LowerIP net.IP UpperIP net.IP DHCPEnabled bool + + // Serial console pipe/socket + Serial bool + SerialFile string } var ( @@ -138,6 +142,14 @@ func config() (*flag.FlagSet, error) { flags.IPVar(&B2D.LowerIP, "lowerip", net.ParseIP("192.168.59.103"), "VirtualBox host-only network DHCP lower bound.") flags.IPVar(&B2D.UpperIP, "upperip", net.ParseIP("192.168.59.254"), "VirtualBox host-only network DHCP upper bound.") + if runtime.GOOS != "windows" { + //SerialFile ~~ filepath.Join(dir, B2D.vm+".sock") + flags.StringVar(&B2D.SerialFile, "serialfile", "", "path to the serial socket/pipe.") + flags.BoolVar(&B2D.Serial, "serial", false, "try serial console to get IP address (experimental)") + } else { + B2D.Serial = false + } + // Set the defaults if err := flags.Parse([]string{}); err != nil { return nil, err @@ -164,6 +176,16 @@ func config() (*flag.FlagSet, error) { vbx.Verbose = B2D.Verbose vbx.VBM = B2D.VBM + + if B2D.SerialFile == "" { + if runtime.GOOS == "windows" { + //SerialFile ~~ filepath.Join(dir, B2D.vm+".sock") + B2D.SerialFile = `\\.\pipe\` + B2D.VM + } else { + B2D.SerialFile = filepath.Join(dir, B2D.VM+".sock") + } + } + return flags, nil } diff --git a/util.go b/util.go index e37cbca..5b1ebde 100644 --- a/util.go +++ b/util.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strings" "time" ) @@ -167,3 +168,72 @@ func CopyFile(src, dst string) (int64, error) { defer df.Close() return io.Copy(df, sf) } + +func reader(r io.Reader) { + buf := make([]byte, 1024) + for { + _, err := io.ReadAtLeast(r, buf[:], 20) + if err != nil { + return + } + } +} + +// use the serial port socket to ask what the VM's host only IP is +func RequestIPFromSerialPort(socket string) string { + c, err := net.Dial("unix", socket) + + if err != nil { + return "" + } + defer c.Close() + c.SetDeadline(time.Now().Add(time.Second)) + + line := "" + _, err = c.Write([]byte("\r")) + _, err = c.Write([]byte("docker\r")) + + IP := "" + fullLog := "" + + for IP == "" { + _, err := c.Write([]byte("ip addr show dev eth1\r")) + if err != nil { + println(err) + break + } + time.Sleep(1 * time.Second) + buf := make([]byte, 1024) + for { + n, err := c.Read(buf[:]) + if err != nil { + return IP + } + line = line + string(buf[0:n]) + fullLog += string(buf[0:n]) + if strings.Contains(line, "\n") { + //go looking for the string we want, and chomp line to after the \n + if i := strings.IndexAny(line, "\n"); i != -1 { + // inet 10.180.1.3/16 brd 10.180.255.255 scope global wlan0 + inet := regexp.MustCompile(`^[\t ]*inet ([0-9.]*).*$`) + if ip := inet.FindStringSubmatch(line[:i]); ip != nil { + IP = ip[1] + // clean up + break + } else { + line = line[i+1:] + } + } + } + } + + } + go reader(c) + //give us time reader clean up + time.Sleep(1) + if IP == "" && B2D.Verbose { + logf(fullLog) + } + + return IP +} diff --git a/virtualbox/machine.go b/virtualbox/machine.go index 6e106ac..b5b42db 100644 --- a/virtualbox/machine.go +++ b/virtualbox/machine.go @@ -68,6 +68,7 @@ type Machine struct { BootOrder []string // max 4 slots, each in {none|floppy|dvd|disk|net} DockerPort uint SSHPort uint + SerialFile string } // Refresh reloads the machine information. @@ -92,6 +93,11 @@ func (m *Machine) Start() error { case Poweroff, Saved, Aborted: return vbm("startvm", m.Name, "--type", "headless") } + if err := m.Refresh(); err == nil { + if m.State != Running { + return fmt.Errorf("Failed to start", m.Name) + } + } return nil } @@ -251,6 +257,12 @@ func GetMachine(id string) (*Machine, error) { return nil, err } m.SSHPort = uint(n) + case "uartmode1": + // uartmode1="server,/home/sven/.boot2docker/boot2docker-vm.sock" + vals := strings.Split(val, ",") + if len(vals) >= 2 { + m.SerialFile = vals[1] + } } } if err := s.Err(); err != nil { @@ -345,6 +357,13 @@ func (m *Machine) Modify() error { "--accelerate3d", m.Flag.Get(F_accelerate3d), } + //if runtime.GOOS != "windows" { + args = append(args, + "--uart1", "0x3F8", "4", + "--uartmode1", "server", m.SerialFile, + ) + //} + for i, dev := range m.BootOrder { if i > 3 { break // Only four slots `--boot{1,2,3,4}`. Ignore the rest.