├── .gitignore ├── .travis.yml ├── DEPRECATION_WARNING ├── Dockerfile ├── LICENSE ├── MAINTAINERS ├── Makefile ├── README.md ├── VERSION ├── cmds.go ├── config.go ├── docs ├── images │ ├── bad_host.png │ ├── cool_view.png │ ├── good_host.png │ ├── kitematic.png │ ├── newsite_view.png │ ├── windows-boot2docker-cmd.png │ ├── windows-boot2docker-powershell.png │ ├── windows-boot2docker-start.png │ └── windows-installer.png ├── mac.md └── windows.md ├── download.sh ├── driver ├── config.go ├── dhcp.go ├── driver.go ├── nic.go ├── pfrule.go └── storage.go ├── dummy └── machine.go ├── main.go ├── release.sh ├── todo.md ├── util.go └── virtualbox ├── README.md ├── dhcp.go ├── dhcp_test.go ├── disk.go ├── doc.go ├── extra.go ├── hostonlynet.go ├── hostonlynet_test.go ├── machine.go ├── machine_test.go ├── natnet.go ├── natnet_test.go ├── util.go ├── vbm.go └── vbm_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | # OS or Editor folders 3 | .DS_Store 4 | Thumbs.db 5 | .cache 6 | .project 7 | .settings 8 | .tmproj 9 | *.esproj 10 | nbproject 11 | *.sublime-project 12 | *.sublime-workspace 13 | *.swp 14 | boot2docker-cli -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.4.1 5 | 6 | # let us have pretty experimental Docker-based Travis workers 7 | sudo: false 8 | 9 | env: 10 | - GIMME_OS=linux GIMME_ARCH=amd64 11 | - GIMME_OS=darwin GIMME_ARCH=amd64 12 | - GIMME_OS=windows GIMME_ARCH=amd64 13 | 14 | install: 15 | - env | sort 16 | - go get -d -v ./... 17 | 18 | script: 19 | # TODO - some gofmt magic 20 | - go build -v ./... 21 | # TODO - go test -v ./... # (we first need to skip all the meaty vbox tests when "VBoxManage" isn't available, or mock it) 22 | -------------------------------------------------------------------------------- /DEPRECATION_WARNING: -------------------------------------------------------------------------------- 1 | 2 | WARNING: The 'boot2docker' command line interface (not to be confused with 3 | 'boot2docker' the operating system) is officially deprecated. 4 | 5 | Please switch to Docker Machine (https://docs.docker.com/machine/) ASAP. 6 | 7 | Docker Toolbox (https://docker.com/toolbox) is the recommended install method. 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile to cross compile boot2docker-cli 2 | 3 | FROM golang:1.4-cross 4 | 5 | WORKDIR /go/src/github.com/boot2docker/boot2docker-cli 6 | 7 | # Download (but not install) dependencies 8 | RUN go get -v github.com/BurntSushi/toml 9 | RUN go get -v github.com/ogier/pflag 10 | 11 | ADD . /go/src/github.com/boot2docker/boot2docker-cli 12 | 13 | CMD ["make", "all"] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2014 Docker, Inc. 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Steeve Morin (@steeve) 2 | Glenn Lewis (@gmlewis) 3 | Michael Crosby (@crosbymichael) 4 | # Riobard Zhan (@riobard) # inactive while traveling 5 | Sven Dowideit (@SvenDowideit) 6 | Tianon Gravi (@tianon) 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := $(shell cat VERSION) 2 | GITSHA1 := $(shell git rev-parse --short HEAD) 3 | GOARCH := amd64 4 | GOFLAGS := -ldflags "-X main.Version $(VERSION) -X main.GitSHA $(GITSHA1)" 5 | PREFIX := boot2docker 6 | DOCKER_IMAGE := boot2docker-golang 7 | DOCKER_CONTAINER := boot2docker-cli-build 8 | DOCKER_SRC_PATH := /go/src/github.com/boot2docker/boot2docker-cli 9 | 10 | 11 | default: dockerbuild 12 | @true # stop from matching "%" later 13 | 14 | 15 | # Build binaries in Docker container. 16 | dockerbuild: clean 17 | docker build -t "$(DOCKER_IMAGE)" . 18 | docker run --name "$(DOCKER_CONTAINER)" "$(DOCKER_IMAGE)" 19 | docker cp "$(DOCKER_CONTAINER)":"$(DOCKER_SRC_PATH)"/$(PREFIX)-$(VERSION)-darwin-$(GOARCH) . 20 | docker cp "$(DOCKER_CONTAINER)":"$(DOCKER_SRC_PATH)"/$(PREFIX)-$(VERSION)-linux-$(GOARCH) . 21 | docker cp "$(DOCKER_CONTAINER)":"$(DOCKER_SRC_PATH)"/$(PREFIX)-$(VERSION)-windows-$(GOARCH).exe . 22 | docker rm "$(DOCKER_CONTAINER)" 23 | 24 | 25 | # Remove built binaries and Docker container. Silent errors if container not found. 26 | clean: 27 | rm -f $(PREFIX)* 28 | docker rm "$(DOCKER_CONTAINER)" 2>/dev/null || true 29 | 30 | 31 | all: darwin linux windows 32 | @true # stop "all" from matching "%" later 33 | 34 | 35 | # Native Go build per OS/ARCH combo. 36 | %: 37 | GOOS=$@ GOARCH=$(GOARCH) go build $(GOFLAGS) -o $(PREFIX)-$(VERSION)-$@-$(GOARCH)$(if $(filter windows, $@),.exe) 38 | 39 | 40 | # This binary will be installed at $GOBIN or $GOPATH/bin. Requires proper 41 | # $GOPATH setup AND the location of the source directory in $GOPATH. 42 | goinstall: 43 | go install $(GOFLAGS) 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | 3 | This project is officially deprecated in favor of [Docker 4 | Machine](https://docs.docker.com/machine/). The code and documentation here 5 | only exist as a reference for users who have not yet switched over (but please 6 | do soon). The recommended way to install Machine is with the [Docker 7 | Toolbox](https://docker.com/toolbox). 8 | 9 | # boot2docker command line management tool 10 | 11 | This tool downloads the boot2docker ISO image, creates a VirtualBox virtual 12 | machine, sets up two networks for that virtual machine (one NAT to allow the VM 13 | and containers to access the internet, the other host-only to allow container 14 | port mapping to work securely), and then provides the user a simple way to 15 | login via SSH. 16 | 17 | On Windows, [MSYS SSH](http://www.mingw.org/) provides a first class way to 18 | connect to the boot2docker VM using `boot2docker.exe ssh`. 19 | 20 | > **Note:** Docker now has an [IANA registered IP Port: 2376]( http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker) 21 | > , so the use of port 4243 is deprecated. This also means that new Boot2Docker 22 | > ISO releases and management tool are not compatible. 23 | 24 | ## Installation 25 | 26 | ### Official Installers 27 | 28 | Signed installers are available for [Mac OS X](http://github.com/boot2docker/osx-installer/releases) and [Windows](http://github.com/boot2docker/windows-installer/releases). 29 | 30 | Refer to the installation instructions for [Mac OS X](http://docs.docker.io/installation/mac/) and [Windows](http://docs.docker.io/installation/windows/). 31 | 32 | ### Manual Installation 33 | 34 | ### Pre-compiled binaries 35 | 36 | You can download binary releases at https://github.com/boot2docker/boot2docker-cli/releases 37 | 38 | ### Docker container build 39 | 40 | The most reliable way to cross-compile boot2docker for all 3 platforms, is to 41 | use the Dockerfile (and Docker) 42 | 43 | ```sh 44 | git clone https://github.com/boot2docker/boot2docker-cli 45 | cd boot2docker-cli 46 | make 47 | ``` 48 | 49 | Built binaries will be available in the current directory. 50 | 51 | This assumes you have an accessible Docker daemon (local or remote with `DOCKER_HOST` set) 52 | and `make` installed. 53 | 54 | 55 | ### Install from source 56 | 57 | You need to have the [Go compiler (v1.4 or higher)](http://golang.org) installed, and `$GOPATH` 58 | [properly setup](http://golang.org/doc/code.html#GOPATH). Then run 59 | 60 | go get github.com/boot2docker/boot2docker-cli 61 | 62 | The binary will be available at `$GOPATH/bin/boot2docker-cli`. However the 63 | binary built this way will have missing version information when you run 64 | 65 | $ boot2docker version 66 | 67 | You can solve the issue by using `make goinstall` 68 | 69 | ```sh 70 | cd $GOPATH/src/github.com/boot2docker/boot2docker-cli 71 | make goinstall 72 | ``` 73 | 74 | ### Cross compiling 75 | 76 | You can cross compile to OS X, Windows, and Linux. For that you need to first 77 | [make your Go compiler ready for cross compiling to the target 78 | platforms](http://stackoverflow.com/questions/12168873/cross-compile-go-on-osx). 79 | 80 | Please make sure you build with golang v1.4 or later - it is required for 81 | `boot2docker download` to work on OS X. 82 | 83 | We provide a Makefile to make the process a bit easier. 84 | 85 | ```sh 86 | make darwin # build for OS X/amd64 87 | make linux # build for Linux/amd64 88 | make windows # build for Windows/amd64 89 | make all # build for all three above 90 | make clean # clean up the built binaries 91 | ``` 92 | 93 | Built binaries will be available in the current directory. 94 | 95 | 96 | ## Usage 97 | 98 | To initialize a new boot2docker VM, run 99 | 100 | $ boot2docker init 101 | 102 | Then you can start the VM by 103 | 104 | $ boot2docker up 105 | 106 | To stop the VM, run 107 | 108 | $ boot2docker down 109 | 110 | And finally if you don't need the VM anymore, run 111 | 112 | $ boot2docker delete 113 | 114 | to remove it completely. 115 | 116 | You can also run commands on the remote boot2docker virtual machine: 117 | 118 | $ boot2docker ssh ip addr show eth1 |sed -nEe 's/^[ \t]*inet[ \t]*([0-9.]+)\/.*$/\1/p' 119 | 192.168.59.103 120 | # this example is equivalent to the built in command: 121 | $ boot2docker ip 122 | 192.168.59.103 123 | 124 | In this case, the command tells you the host only interface IP address of the 125 | boot2docker vm, which you can then use to access ports you map from your containers. 126 | 127 | ## Configuration 128 | 129 | The `boot2docker` binary reads configuration from `$BOOT2DOCKER_PROFILE` if set, or 130 | `$BOOT2DOCKER_DIR/profile` or `$HOME/.boot2docker/profile` or (on Windows) 131 | `%USERPROFILE%/.boot2docker/profile`. `boot2docker config` will 132 | tell you where it is looking for the file, and will also output the settings that 133 | are in use, so you can initialise a default file to customize using 134 | `boot2docker config > ~/.boot2docker/profile`. 135 | 136 | Currently you can configure the following options (undefined options take 137 | default values): 138 | 139 | ```ini 140 | # Comments must be on their own lines; inline comments are not supported. 141 | 142 | # path to VirtualBox management utility 143 | VBM = "VBoxManage" 144 | 145 | # path to SSH client utility 146 | SSH = "ssh" 147 | SSHGen = "ssh-keygen" 148 | SSHKey = "/Users/sven/.ssh/id_boot2docker" 149 | 150 | # name of boot2docker virtual machine 151 | VM = "boot2docker-vm" 152 | 153 | # URL pointing either to a Github "/releases" API endpoint to automatically 154 | # retrieve the `boot2docker.iso` asset from the latest released version of a 155 | # repo, or directly to an ISO image 156 | ISOURL = "https://api.github.com/repos/boot2docker/boot2docker/releases" 157 | #ISOURL = "https://github.com/boot2docker/boot2docker/releases/download/v1.0.0/boot2docker.iso" 158 | #ISOURL = "https://internal.corp.org/b2d.iso" 159 | 160 | # path to boot2docker ISO image 161 | ISO = "/Users/sven/.boot2docker/boot2docker.iso" 162 | 163 | # VM disk image size in MB 164 | DiskSize = 20000 165 | 166 | # VM memory size in MB 167 | Memory = 2048 168 | 169 | # Number of CPUs 170 | CPUs = 1 171 | 172 | # host port forwarding to port 22 in the VM 173 | SSHPort = 2022 174 | 175 | # host port forwarding to port 2376 in the VM 176 | DockerPort = 2376 177 | 178 | # host-only network host IP 179 | HostIP = "192.168.59.3" 180 | 181 | # host only network network mask 182 | NetMask = [255, 255, 255, 0] 183 | 184 | # host-only network DHCP server IP 185 | DHCPIP = "192.168.59.99" 186 | 187 | # host-only network DHCP server enabled 188 | DHCPEnabled = true 189 | 190 | # host-only network IP range lower bound 191 | LowerIP = "192.168.59.103" 192 | 193 | # host-only network IP range upper bound 194 | UpperIP = "192.168.59.254" 195 | ``` 196 | 197 | You can override the configurations using matching command-line flags. Type 198 | `boot2docker -h` for more information. The configuration file options are 199 | the same as the command-line flags with long names. 200 | 201 | ## Upgrade 202 | 203 | You can use boot2docker-cli to upgrade: 204 | 205 | 1. The ISO you are using in the VM (and consequently the Docker daemon version) 206 | 2. The Docker client binary on your host system 207 | 3. The boot2docker-cli binary itself 208 | 209 | To do so, run the `boot2docker upgrade` command. 210 | 211 | ```console 212 | $ boot2docker upgrade 213 | Backing up existing docker binary... 214 | Downloading new docker client binary... 215 | Success: downloaded https://get.docker.com/builds/Darwin/x86_64/docker-latest 216 | to /usr/local/bin/docker 217 | The old version is backed up to ~/.boot2docker. 218 | Backing up existing boot2docker binary... 219 | Downloading new boot2docker client binary... 220 | Success: downloaded https://github.com/boot2docker/boot2docker-cli/releases/download/v1.4.0/boot2docker-v1.4.0-darwin-amd64 221 | to /usr/local/bin/boot2docker 222 | The old version is backed up to ~/.boot2docker. 223 | Latest release for boot2docker/boot2docker is v1.4.0 224 | Downloading boot2docker ISO image... 225 | Success: downloaded https://github.com/boot2docker/boot2docker/releases/download/v1.4.0/boot2docker.iso 226 | to /Users/youruser/.boot2docker/boot2docker.iso 227 | Waiting for VM and Docker daemon to start... 228 | .................ooo 229 | Started. 230 | ``` 231 | 232 | This will back up your current `docker` and `boot2docker` binaries to 233 | `~/.boot2docker` and download the latest ISO, `docker` binary and `boot2docker` 234 | binary in place of the old versions. 235 | 236 | 237 | ## Contribution 238 | 239 | [![Build Status](https://travis-ci.org/boot2docker/boot2docker-cli.svg?branch=master)](https://travis-ci.org/boot2docker/boot2docker-cli) 240 | 241 | We are implementing the same process as [Docker merge 242 | approval](https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md#merge-approval), 243 | so all commits need to be done via pull requests, and will need three or more 244 | LGTMs (Looks Good To Me) before merging. 245 | 246 | To submit pull request, please make sure to follow the [Go Style 247 | Guide](https://code.google.com/p/go-wiki/wiki/Style). In particular, you MUST 248 | run `gofmt` before committing. We suggest you run `go tool vet -all .` as well. 249 | 250 | Please rebase the upstream in your fork in order to keep the commit history 251 | tidy. 252 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | v1.8.0 2 | -------------------------------------------------------------------------------- /cmds.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "regexp" 11 | "runtime" 12 | "strings" 13 | "time" 14 | 15 | "github.com/boot2docker/boot2docker-cli/driver" 16 | _ "github.com/boot2docker/boot2docker-cli/dummy" 17 | _ "github.com/boot2docker/boot2docker-cli/virtualbox" 18 | ) 19 | 20 | func vmNotRunningError(vmName string) error { 21 | return fmt.Errorf("VM %q is not running. (Did you run `boot2docker up`?)", vmName) 22 | } 23 | 24 | // Initialize the boot2docker VM from scratch. 25 | func cmdInit() error { 26 | B2D.Init = false 27 | _, err := driver.GetMachine(&B2D) 28 | if err == nil { 29 | fmt.Printf("Virtual machine %s already exists\n", B2D.VM) 30 | return nil 31 | } 32 | 33 | if _, err := os.Stat(B2D.ISO); err != nil { 34 | if !os.IsNotExist(err) { 35 | return fmt.Errorf("Failed to open ISO image %q: %s", B2D.ISO, err) 36 | } 37 | 38 | if err := cmdDownload(); err != nil { 39 | return err 40 | } 41 | } 42 | 43 | if _, err := os.Stat(B2D.SSHKey); err != nil { 44 | if !os.IsNotExist(err) { 45 | return fmt.Errorf("Something wrong with SSH Key file %q: %s", B2D.SSHKey, err) 46 | } 47 | 48 | cmd := exec.Command(B2D.SSHGen, "-t", "rsa", "-N", "", "-f", B2D.SSHKey) 49 | cmd.Stdin = os.Stdin 50 | cmd.Stdout = os.Stdout 51 | cmd.Stderr = os.Stderr 52 | if B2D.Verbose { 53 | cmd.Stderr = os.Stderr 54 | fmt.Printf("executing: %v %v\n", cmd.Path, strings.Join(cmd.Args, " ")) 55 | } 56 | 57 | if err := cmd.Run(); err != nil { 58 | return fmt.Errorf("Error generating new SSH Key into %s: %s", B2D.SSHKey, err) 59 | } 60 | } 61 | //TODO: print a ~/.ssh/config entry for our b2d connection that the user can c&p 62 | 63 | B2D.Init = true 64 | _, err = driver.GetMachine(&B2D) 65 | if err != nil { 66 | return fmt.Errorf("Failed to initialize machine %q: %s", B2D.VM, err) 67 | } 68 | fmt.Printf("Initialization of virtual machine %q complete.\n", B2D.VM) 69 | fmt.Printf("Use `boot2docker up` to start it.\n") 70 | return nil 71 | } 72 | 73 | // Bring up the VM from all possible states. 74 | func cmdUp() error { 75 | m, err := driver.GetMachine(&B2D) 76 | if err != nil { 77 | return fmt.Errorf("Failed to get machine %q: %s", B2D.VM, err) 78 | } 79 | if err := m.Start(); err != nil { 80 | return fmt.Errorf("Failed to start machine %q: %s", B2D.VM, err) 81 | } 82 | 83 | if err := m.Refresh(); err != nil { 84 | return fmt.Errorf("Failed to start machine %q: %s", B2D.VM, err) 85 | } 86 | if m.GetState() != driver.Running { 87 | return fmt.Errorf("Failed to start machine %q (run again with -v for details)", B2D.VM) 88 | } 89 | 90 | fmt.Println("Waiting for VM and Docker daemon to start...") 91 | //give the VM a little time to start, so we don't kill the Serial Pipe/Socket 92 | time.Sleep(time.Duration(B2D.Waittime) * time.Millisecond) 93 | natSSH := fmt.Sprintf("localhost:%d", m.GetSSHPort()) 94 | IP := "" 95 | for i := 1; i < B2D.Retries; i++ { 96 | print(".") 97 | if B2D.Serial && runtime.GOOS != "windows" { 98 | if IP, err = RequestIPFromSerialPort(m.GetSerialFile()); err == nil { 99 | break 100 | } 101 | } 102 | if err := read(natSSH, 1, time.Duration(B2D.Waittime)*time.Millisecond); err == nil { 103 | if IP, err = RequestIPFromSSH(m); err == nil { 104 | break 105 | } 106 | } 107 | } 108 | if B2D.Verbose { 109 | fmt.Printf("VM Host-only IP address: %s", IP) 110 | fmt.Printf("\nWaiting for Docker daemon to start...\n") 111 | } 112 | 113 | time.Sleep(time.Duration(B2D.Waittime) * time.Millisecond) 114 | socket := "" 115 | for i := 1; i < B2D.Retries; i++ { 116 | print("o") 117 | if socket, err = RequestSocketFromSSH(m); err == nil { 118 | break 119 | } 120 | if B2D.Verbose { 121 | fmt.Printf("Error requesting socket: %s\n", err) 122 | } 123 | time.Sleep(600 * time.Millisecond) 124 | } 125 | fmt.Printf("\nStarted.\n") 126 | 127 | if socket == "" { 128 | // lets try one more time 129 | time.Sleep(time.Duration(B2D.Waittime) * time.Millisecond) 130 | fmt.Printf(" Trying to get Docker socket one more time\n") 131 | 132 | if socket, err = RequestSocketFromSSH(m); err != nil { 133 | fmt.Printf("Error requesting socket: %s\n", err) 134 | } 135 | } 136 | // Copying the certs here - someone might have have written a Windows API client. 137 | certPath, err := RequestCertsUsingSSH(m) 138 | if err != nil { 139 | // These errors are not fatal 140 | fmt.Fprintf(os.Stderr, "Warning: error copying certificates: %s\n", err) 141 | } 142 | 143 | if socket == "" { 144 | fmt.Fprintf(os.Stderr, "Auto detection of the VM's Docker socket failed.\n") 145 | fmt.Fprintf(os.Stderr, "Please run `boot2docker -v up` to diagnose.\n") 146 | } else { 147 | // Check if $DOCKER_* ENV vars are properly configured. 148 | if !checkEnvironment(socket, certPath) { 149 | fmt.Printf("\nTo connect the Docker client to the Docker daemon, please set:\n") 150 | printExport(socket, certPath) 151 | fmt.Printf("\nOr run: `eval \"$(boot2docker shellinit)\"`\n") 152 | } else { 153 | fmt.Printf("Your environment variables are already set correctly.\n") 154 | } 155 | } 156 | fmt.Printf("\n") 157 | return nil 158 | } 159 | 160 | // Give the user the exact command to run to set the env. 161 | func cmdShellInit() error { 162 | m, err := driver.GetMachine(&B2D) 163 | if err != nil { 164 | return fmt.Errorf("Failed to get machine %q: %s", B2D.VM, err) 165 | } 166 | 167 | if m.GetState() != driver.Running { 168 | return vmNotRunningError(B2D.VM) 169 | } 170 | 171 | socket, err := RequestSocketFromSSH(m) 172 | if err != nil { 173 | return fmt.Errorf("Error requesting socket: %s\n", err) 174 | } 175 | 176 | certPath, err := RequestCertsUsingSSH(m) 177 | if err != nil { 178 | // These errors are not fatal 179 | fmt.Fprintf(os.Stderr, "Warning: error copying certificates: %s\n", err) 180 | } 181 | 182 | // Check if $DOCKER_* ENV vars are properly configured. 183 | if !checkEnvironment(socket, certPath) { 184 | printExport(socket, certPath) 185 | } else { 186 | fmt.Fprintf(os.Stderr, "Your environment variables are already set correctly.\n") 187 | } 188 | 189 | return nil 190 | } 191 | 192 | func checkEnvironment(socket, certPath string) bool { 193 | for name, value := range exports(socket, certPath) { 194 | value = strings.Trim(value, `"'`) // remove surrounding quotes 195 | if os.Getenv(name) != value { 196 | return false 197 | } 198 | } 199 | 200 | return true 201 | } 202 | 203 | func printExport(socket, certPath string) { 204 | values := exports(socket, certPath) 205 | if runtime.GOOS == "windows" && !isUnixShellOnWindows() { 206 | printExportWindows(values) 207 | } else { 208 | printExportUnix(values) 209 | } 210 | } 211 | 212 | func isUnixShellOnWindows() bool { 213 | return os.Getenv("TERM") != "" 214 | } 215 | 216 | func printExportUnix(values map[string]string) { 217 | for name, value := range values { 218 | switch filepath.Base(os.Getenv("SHELL")) { 219 | case "fish": 220 | if value == "" { 221 | fmt.Printf(" set -e %s\n", name) 222 | } else { 223 | fmt.Printf(" set -x %s %s\n", name, value) 224 | } 225 | default: // default command to export variables POSIX shells, like bash, zsh, etc. 226 | if value == "" { 227 | fmt.Printf(" unset %s\n", name) 228 | } else { 229 | fmt.Printf(" export %s=%s\n", name, value) 230 | } 231 | } 232 | } 233 | } 234 | 235 | func printExportWindows(values map[string]string) { 236 | // Print cmd.exe instructions to stderr 237 | fmt.Fprintln(os.Stderr, "If you are running inside Windows Command Prompt (cmd.exe), copy and paste the") 238 | fmt.Fprintln(os.Stderr, "following commands to your terminal to set the environment variables:") 239 | for name, value := range values { 240 | if value == "" { 241 | fmt.Fprintf(os.Stderr, " set %s=\n", name) 242 | } else { 243 | fmt.Fprintf(os.Stderr, " set %s=%s\n", name, value) 244 | } 245 | } 246 | fmt.Fprintln(os.Stderr, "") 247 | // Print powershell instructions to stderr 248 | fmt.Fprintln(os.Stderr, "If you are running inside PowerShell, copy or paste the following commands") 249 | fmt.Fprintln(os.Stderr, `to your shell or run "boot2docker shellinit | Invoke-Expression" to set the`) 250 | fmt.Fprintln(os.Stderr, "environment variables:") 251 | 252 | // Print powershell exports to stdout 253 | for name, value := range values { 254 | if value == "" { 255 | fmt.Printf(" Remove-Item Env:\\%s\n", name) 256 | } else { 257 | fmt.Printf(" $Env:%s = \"%s\"\n", name, value) 258 | } 259 | } 260 | } 261 | 262 | func exports(socket, certPath string) map[string]string { 263 | out := make(map[string]string) 264 | 265 | out["DOCKER_HOST"] = socket 266 | out["DOCKER_CERT_PATH"] = certPath 267 | 268 | if runtime.GOOS == "windows" && isUnixShellOnWindows() { 269 | // Surround Windows-style paths with single quotes in exported path otherwise 270 | // bash swallows the backslashes in export statements like: 271 | // export DOCKER_CERT_PATH=C:\Users\ahmet\.boot2docker\certs\boot2docker-vm 272 | out["DOCKER_CERT_PATH"] = fmt.Sprintf("'%s'", out["DOCKER_CERT_PATH"]) 273 | } 274 | 275 | if certPath == "" { 276 | out["DOCKER_TLS_VERIFY"] = "" 277 | } else { 278 | out["DOCKER_TLS_VERIFY"] = "1" 279 | } 280 | 281 | //if a http_proxy is set, we need to make sure the boot2docker ip 282 | //is added to the NO_PROXY environment variable 283 | if os.Getenv("http_proxy") != "" || os.Getenv("HTTP_PROXY") != "" { 284 | //get the ip from socket/DOCKER_HOST 285 | re := regexp.MustCompile("tcp://([^:]+):") 286 | if matches := re.FindStringSubmatch(socket); len(matches) == 2 { 287 | ip := matches[1] 288 | name := "no_proxy" 289 | val := os.Getenv("no_proxy") 290 | if val == "" { 291 | name = "NO_PROXY" 292 | val = os.Getenv("NO_PROXY") 293 | } 294 | 295 | switch { 296 | case val == "": 297 | out[name] = ip 298 | case strings.Contains(val, ip): 299 | out[name] = val 300 | default: 301 | out[name] = fmt.Sprintf("%s,%s", val, ip) 302 | } 303 | } 304 | } 305 | return out 306 | } 307 | 308 | // Tell the user the config (and later let them set it?) 309 | func cmdConfig() error { 310 | dir, err := cfgDir(".boot2docker") 311 | if err != nil { 312 | return fmt.Errorf("Error working out Profile file location: %s\n", err) 313 | } 314 | filename := cfgFilename(dir) 315 | fmt.Printf("# boot2docker profile filename: %s\n", filename) 316 | fmt.Println(printConfig()) 317 | return nil 318 | } 319 | 320 | // Suspend and save the current state of VM on disk. 321 | func cmdSave() error { 322 | m, err := driver.GetMachine(&B2D) 323 | if err != nil { 324 | return fmt.Errorf("Failed to get machine %q: %s\n", B2D.VM, err) 325 | } 326 | if err := m.Save(); err != nil { 327 | return fmt.Errorf("Failed to save machine %q: %s\n", B2D.VM, err) 328 | } 329 | return nil 330 | } 331 | 332 | // Gracefully stop the VM by sending ACPI shutdown signal. 333 | func cmdStop() error { 334 | m, err := driver.GetMachine(&B2D) 335 | if err != nil { 336 | return fmt.Errorf("Failed to get machine %q: %s", B2D.VM, err) 337 | } 338 | if err := m.Stop(); err != nil { 339 | return fmt.Errorf("Failed to stop machine %q: %s", B2D.VM, err) 340 | } 341 | return nil 342 | } 343 | 344 | // Forcefully power off the VM (equivalent to unplug power). Might corrupt disk 345 | // image. 346 | func cmdPoweroff() error { 347 | m, err := driver.GetMachine(&B2D) 348 | if err != nil { 349 | return fmt.Errorf("Failed to get machine %q: %s", B2D.VM, err) 350 | } 351 | if err := m.Poweroff(); err != nil { 352 | return fmt.Errorf("Failed to poweroff machine %q: %s", B2D.VM, err) 353 | } 354 | return nil 355 | } 356 | 357 | // Upgrade the boot2docker ISO - preserving server state 358 | func cmdUpgrade() error { 359 | if err := upgradeBoot2DockerBinary(); err != nil { 360 | return fmt.Errorf("Error upgrading boot2docker binary: %s", err) 361 | } 362 | m, err := driver.GetMachine(&B2D) 363 | if err == nil { 364 | if m.GetState() == driver.Running || m.GetState() == driver.Saved || m.GetState() == driver.Paused { 365 | // Windows won't let us move the ISO aside while it's in use 366 | if err = cmdStop(); err == nil { 367 | if err = cmdDownload(); err == nil { 368 | err = cmdUp() 369 | } 370 | } 371 | return err 372 | } 373 | } 374 | return cmdDownload() 375 | } 376 | 377 | func upgradeBoot2DockerBinary() error { 378 | var ( 379 | goos, arch, ext string 380 | ) 381 | latestVersion, err := getLatestReleaseName("https://api.github.com/repos/boot2docker/boot2docker-cli/releases") 382 | if err != nil { 383 | return fmt.Errorf("Error attempting to get the latest boot2docker-cli release: %s", err) 384 | } 385 | baseUrl := "https://github.com/boot2docker/boot2docker-cli/releases/download" 386 | 387 | ext = "" 388 | 389 | switch runtime.GOARCH { 390 | case "amd64": 391 | arch = "amd64" 392 | default: 393 | return fmt.Errorf("Architecture not supported") 394 | } 395 | 396 | switch runtime.GOOS { 397 | case "darwin", "linux": 398 | goos = runtime.GOOS 399 | case "windows": 400 | goos = "windows" 401 | arch = "amd64" 402 | ext = ".exe" 403 | default: 404 | return fmt.Errorf("Operating system not supported") 405 | } 406 | binaryUrl := fmt.Sprintf("%s/%s/boot2docker-%s-%s-%s%s", baseUrl, latestVersion, latestVersion, goos, arch, ext) 407 | currentBoot2DockerVersion := Version 408 | if err := attemptUpgrade(binaryUrl, "boot2docker", latestVersion, currentBoot2DockerVersion); err != nil { 409 | return fmt.Errorf("Error attempting upgrade: %s", err) 410 | } 411 | return nil 412 | } 413 | 414 | func attemptUpgrade(binaryUrl, binaryName, latestVersion, localVersion string) error { 415 | if (latestVersion != localVersion && !strings.Contains(latestVersion, "rc")) || B2D.ForceUpgradeDownload { 416 | if err := backupAndDownload(binaryUrl, binaryName, localVersion); err != nil { 417 | return fmt.Errorf("Error attempting backup and download of Docker client binary: %s", err) 418 | } 419 | } else { 420 | fmt.Printf("%s is up to date (%s), skipping upgrade...\n", binaryName, localVersion) 421 | } 422 | return nil 423 | } 424 | 425 | func backupAndDownload(binaryUrl, binaryName, localVersion string) error { 426 | binaryPath, err := exec.LookPath(binaryName) 427 | if err != nil { 428 | return fmt.Errorf("Error attempting to locate local binary: %s", err) 429 | } 430 | path := strings.TrimSpace(string(binaryPath)) 431 | 432 | fmt.Println("Backing up existing", binaryName, "binary...") 433 | if err := backupBinary(binaryName, localVersion, path); err != nil { 434 | return fmt.Errorf("Error backing up docker client: %s", err) 435 | } 436 | 437 | fmt.Println("Downloading new", binaryName, "client binary...") 438 | if err := download(path, binaryUrl); err != nil { 439 | return fmt.Errorf("Error attempting to download new client binary: %s", err) 440 | } 441 | if err := os.Chmod(path, 0755); err != nil { 442 | return err 443 | } 444 | fmt.Printf("Success: downloaded %s\n\tto %s\n\tThe old version is backed up to ~/.boot2docker.\n", binaryUrl, path) 445 | return nil 446 | } 447 | 448 | func backupBinary(binaryName, localVersion, path string) error { 449 | dir, err := cfgDir(".boot2docker") 450 | if err != nil { 451 | return fmt.Errorf("Error getting boot2docker config dir: %s", err) 452 | } 453 | buf, err := ioutil.ReadFile(path) 454 | if err != nil { 455 | return fmt.Errorf("Error opening binary for reading at %s: %s", path, err) 456 | } 457 | backupName := fmt.Sprintf("%s-%s", binaryName, localVersion) 458 | if err := ioutil.WriteFile(filepath.Join(dir, backupName), buf, 0755); err != nil { 459 | return fmt.Errorf("Error creating backup file: %s", err) 460 | } 461 | return nil 462 | } 463 | 464 | // Gracefully stop and then start the VM. 465 | func cmdRestart() error { 466 | m, err := driver.GetMachine(&B2D) 467 | if err != nil { 468 | return fmt.Errorf("Failed to get machine %q: %s", B2D.VM, err) 469 | } 470 | if err := m.Restart(); err != nil { 471 | return fmt.Errorf("Failed to restart machine %q: %s", B2D.VM, err) 472 | } 473 | return nil 474 | } 475 | 476 | // Forcefully reset (equivalent to cold boot) the VM. Might corrupt disk image. 477 | func cmdReset() error { 478 | m, err := driver.GetMachine(&B2D) 479 | if err != nil { 480 | return fmt.Errorf("Failed to get machine %q: %s", B2D.VM, err) 481 | } 482 | if err := m.Reset(); err != nil { 483 | return fmt.Errorf("Failed to reset machine %q: %s", B2D.VM, err) 484 | } 485 | return nil 486 | } 487 | 488 | // Delete the VM and associated disk image. 489 | func cmdDelete() error { 490 | m, err := driver.GetMachine(&B2D) 491 | if err != nil { 492 | if err == driver.ErrMachineNotExist { 493 | return fmt.Errorf("Machine %q does not exist.", B2D.VM) 494 | } 495 | return fmt.Errorf("Failed to get machine %q: %s", B2D.VM, err) 496 | } 497 | if err := m.Delete(); err != nil { 498 | return fmt.Errorf("Failed to delete machine %q: %s", B2D.VM, err) 499 | } 500 | return nil 501 | } 502 | 503 | // Show detailed info of the VM. 504 | func cmdInfo() error { 505 | m, err := driver.GetMachine(&B2D) 506 | if err != nil { 507 | return fmt.Errorf("Failed to get machine %q: %s", B2D.VM, err) 508 | } 509 | b, err := json.MarshalIndent(m, "", "\t") 510 | if err != nil { 511 | return fmt.Errorf("Failed to encode machine %q info: %s", B2D.VM, err) 512 | } 513 | 514 | os.Stdout.Write(b) 515 | 516 | return nil 517 | } 518 | 519 | // Show the current state of the VM. 520 | func cmdStatus() error { 521 | m, err := driver.GetMachine(&B2D) 522 | if err != nil { 523 | return fmt.Errorf("Failed to get machine %q: %s", B2D.VM, err) 524 | } 525 | fmt.Println(m.GetState()) 526 | return nil 527 | } 528 | 529 | // Call the external SSH command to login into boot2docker VM. 530 | func cmdSSH() error { 531 | m, err := driver.GetMachine(&B2D) 532 | if err != nil { 533 | return fmt.Errorf("Failed to get machine %q: %s", B2D.VM, err) 534 | } 535 | 536 | if m.GetState() != driver.Running { 537 | return vmNotRunningError(B2D.VM) 538 | } 539 | 540 | // find the ssh cmd string and then pass any remaining strings to ssh 541 | // TODO: it's a shame to repeat the same code as in config.go, but I 542 | // didn't find a way to share the unsharable without more rework 543 | i := 1 544 | for i < len(os.Args) && os.Args[i-1] != "ssh" { 545 | i++ 546 | } 547 | 548 | if err := cmdInteractive(m, os.Args[i:]...); err != nil { 549 | return fmt.Errorf("%s", err) 550 | } 551 | return nil 552 | } 553 | 554 | func cmdIP() error { 555 | m, err := driver.GetMachine(&B2D) 556 | if err != nil { 557 | return fmt.Errorf("Failed to get machine %q: %s", B2D.VM, err) 558 | } 559 | 560 | if m.GetState() != driver.Running { 561 | return vmNotRunningError(B2D.VM) 562 | } 563 | 564 | IP := "" 565 | if B2D.Serial { 566 | if runtime.GOOS != "windows" { 567 | if IP, err = RequestIPFromSerialPort(m.GetSerialFile()); err != nil { 568 | if B2D.Verbose { 569 | fmt.Printf("Error getting IP via Serial: %s\n", err) 570 | } 571 | } 572 | } 573 | } 574 | 575 | if IP == "" { 576 | if IP, err = RequestIPFromSSH(m); err != nil { 577 | if B2D.Verbose { 578 | fmt.Printf("Error getting IP via SSH: %s\n", err) 579 | } 580 | } 581 | } 582 | if IP != "" { 583 | fmt.Println(IP) 584 | } else { 585 | fmt.Fprintf(os.Stderr, "\nFailed to get VM Host only IP address.\n") 586 | fmt.Fprintf(os.Stderr, "\tWas the VM initialized using boot2docker?\n") 587 | } 588 | return nil 589 | } 590 | 591 | // Download the boot2docker ISO image. 592 | func cmdDownload() error { 593 | url := B2D.ISOURL 594 | 595 | // match github (enterprise) release urls: 596 | // https://api.github.com/repos/../../relases or 597 | // https://some.github.enterprise/api/v3/repos/../../relases 598 | re := regexp.MustCompile("https://([^/]+)(/api/v3)?/repos/([^/]+)/([^/]+)/releases") 599 | if matches := re.FindStringSubmatch(url); len(matches) == 5 { 600 | tag, err := getLatestReleaseName(url) 601 | if err != nil { 602 | return fmt.Errorf("Failed to get latest release: %s", err) 603 | } 604 | host := matches[1] 605 | org := matches[3] 606 | repo := matches[4] 607 | if host == "api.github.com" { 608 | host = "github.com" 609 | } 610 | fmt.Printf("Latest release for %s/%s/%s is %s\n", host, org, repo, tag) 611 | url = fmt.Sprintf("https://%s/%s/%s/releases/download/%s/boot2docker.iso", host, org, repo, tag) 612 | } 613 | 614 | fmt.Println("Downloading boot2docker ISO image...") 615 | if err := download(B2D.ISO, url); err != nil { 616 | return fmt.Errorf("Failed to download ISO image: %s", err) 617 | } 618 | fmt.Printf("Success: downloaded %s\n\tto %s\n", url, B2D.ISO) 619 | return nil 620 | } 621 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net" 7 | "os" 8 | "path/filepath" 9 | "regexp" 10 | "runtime" 11 | 12 | toml "github.com/BurntSushi/toml" 13 | "github.com/boot2docker/boot2docker-cli/driver" 14 | flag "github.com/ogier/pflag" 15 | ) 16 | 17 | var ( 18 | // Pattern to parse a key=value line in config profile. 19 | reFlagLine = regexp.MustCompile(`^\s*(\w+)\s*=\s*([^#;]+)`) 20 | B2D = driver.MachineConfig{} 21 | ) 22 | 23 | func homeDir() (string, error) { 24 | dir := "" 25 | if runtime.GOOS == "windows" { 26 | dir = os.Getenv("USERPROFILE") 27 | } else { 28 | dir = os.Getenv("HOME") 29 | } 30 | if _, err := os.Stat(dir); err != nil { 31 | return "", err 32 | } 33 | 34 | return dir, nil 35 | } 36 | 37 | func cfgDir(name string) (string, error) { 38 | if name == ".boot2docker" { 39 | if b2dDir := os.Getenv("BOOT2DOCKER_DIR"); b2dDir != "" { 40 | return b2dDir, nil 41 | } 42 | } 43 | 44 | dir, err := homeDir() 45 | if err != nil { 46 | return "", err 47 | } 48 | dir = filepath.Join(dir, name) 49 | if err := os.MkdirAll(dir, 0755); err != nil { 50 | return "", err 51 | } 52 | return dir, nil 53 | } 54 | 55 | func cfgFilename(dir string) string { 56 | filename := os.Getenv("BOOT2DOCKER_PROFILE") 57 | if filename == "" { 58 | filename = filepath.Join(dir, "profile") 59 | } 60 | return filename 61 | } 62 | 63 | // Write configuration set by the combination of profile and flags 64 | // Should result in a format that can be piped into a profile file 65 | func printConfig() string { 66 | var buf bytes.Buffer 67 | e := toml.NewEncoder(&buf) 68 | err := e.Encode(B2D) 69 | if err != nil { 70 | return "" 71 | } 72 | return buf.String() 73 | } 74 | 75 | // Read configuration from both profile and flags. Flags override profile. 76 | func config() (*flag.FlagSet, error) { 77 | dir, err := cfgDir(".boot2docker") 78 | if err != nil { 79 | return nil, fmt.Errorf("failed to get boot2docker directory: %s", err) 80 | } 81 | 82 | flags := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) 83 | flags.Usage = func() { usageLong(flags) } 84 | 85 | driver.ConfigFlags(&B2D, flags) 86 | 87 | // Add the generic flags 88 | 89 | flags.StringVar(&B2D.VM, "vm", "boot2docker-vm", "virtual machine name.") 90 | // removed for now, requires re-parsing a new config file which is too messy 91 | //flags.StringVarP(&B2D.Dir, "dir", "d", dir, "boot2docker config directory.") 92 | B2D.Dir = dir 93 | flags.StringVar(&B2D.ISOURL, "iso-url", "https://api.github.com/repos/boot2docker/boot2docker/releases", "source URL to provision the boot2docker ISO image.") 94 | flags.StringVar(&B2D.ISO, "iso", filepath.Join(dir, "boot2docker.iso"), "path to boot2docker ISO image.") 95 | 96 | // clobber (overwrite client binary) by default on OSX. it's more likely that 97 | // users have installed through package manager on Linux, and if so, they should 98 | // upgrade that way. 99 | flags.BoolVar(&B2D.Clobber, "clobber", (runtime.GOOS == "darwin"), "overwrite Docker client binary on boot2docker upgrade") 100 | 101 | flags.BoolVar(&B2D.ForceUpgradeDownload, "force-upgrade-download", false, "always download on boot2docker upgrade, never skip") 102 | 103 | // Sven disabled this, as it is broken - if I user with a fresh computer downloads 104 | // just the boot2docker-cli, and then runs `boot2docker --init ip`, we create a vm 105 | // which cannot run, because it fails to have have the boot2docker.iso and the ssh keys 106 | B2D.Init = false 107 | //flags.BoolVarP(&B2D.Init, "init", "i", false, "auto initialize vm instance.") 108 | 109 | flags.BoolVarP(&B2D.Verbose, "verbose", "v", false, "display verbose command invocations.") 110 | flags.StringVar(&B2D.Driver, "driver", "virtualbox", "hypervisor driver.") 111 | flags.StringVar(&B2D.SSH, "ssh", "ssh", "path to SSH client utility.") 112 | flags.StringVar(&B2D.SSHGen, "ssh-keygen", "ssh-keygen", "path to ssh-keygen utility.") 113 | 114 | sshdir, _ := cfgDir(".ssh") 115 | flags.StringVar(&B2D.SSHKey, "sshkey", filepath.Join(sshdir, "id_boot2docker"), "path to SSH key to use.") 116 | flags.UintVarP(&B2D.DiskSize, "disksize", "s", 20000, "boot2docker disk image size (in MB).") 117 | flags.UintVarP(&B2D.Memory, "memory", "m", 2048, "virtual machine memory size (in MB).") 118 | flags.UintVarP(&B2D.CPUs, "cpus", "c", uint(runtime.NumCPU()), "number of CPUs for boot2docker.") 119 | flags.Uint16Var(&B2D.SSHPort, "sshport", 2022, "host SSH port (forward to port 22 in VM).") 120 | flags.Uint16Var(&B2D.DockerPort, "dockerport", 0, "host Docker port (forward to port 2376 in VM). (deprecated - use with care)") 121 | flags.IPVar(&B2D.HostIP, "hostip", net.ParseIP("192.168.59.3"), "VirtualBox host-only network IP address.") 122 | flags.IPMaskVar(&B2D.NetMask, "netmask", flag.ParseIPv4Mask("255.255.255.0"), "VirtualBox host-only network mask.") 123 | flags.BoolVar(&B2D.DHCPEnabled, "dhcp", true, "enable VirtualBox host-only network DHCP.") 124 | flags.IPVar(&B2D.DHCPIP, "dhcpip", net.ParseIP("192.168.59.99"), "VirtualBox host-only network DHCP server address.") 125 | flags.IPVar(&B2D.LowerIP, "lowerip", net.ParseIP("192.168.59.103"), "VirtualBox host-only network DHCP lower bound.") 126 | flags.IPVar(&B2D.UpperIP, "upperip", net.ParseIP("192.168.59.254"), "VirtualBox host-only network DHCP upper bound.") 127 | 128 | flags.IntVar(&B2D.Waittime, "waittime", 300, "Time in milliseconds to wait between port knocking retries during 'start'") 129 | flags.IntVar(&B2D.Retries, "retries", 75, "number of port knocking retries during 'start'") 130 | 131 | if runtime.GOOS != "windows" { 132 | //SerialFile ~~ filepath.Join(dir, B2D.vm+".sock") 133 | flags.StringVar(&B2D.SerialFile, "serialfile", "", "path to the serial socket/pipe.") 134 | flags.BoolVar(&B2D.Serial, "serial", false, "try serial console to get IP address (experimental)") 135 | } else { 136 | B2D.Serial = false 137 | } 138 | 139 | // Set the defaults 140 | if err := flags.Parse([]string{}); err != nil { 141 | return nil, err 142 | } 143 | // Over-ride from the profile file 144 | filename := cfgFilename(B2D.Dir) 145 | if _, err := os.Lstat(filename); err == nil { 146 | if _, err := toml.DecodeFile(filename, &B2D); err != nil { 147 | return nil, err 148 | } 149 | } 150 | 151 | if B2D.SerialFile == "" { 152 | if runtime.GOOS == "windows" { 153 | //SerialFile ~~ filepath.Join(dir, B2D.vm+".sock") 154 | B2D.SerialFile = `\\.\pipe\` + B2D.VM 155 | } else { 156 | B2D.SerialFile = filepath.Join(dir, B2D.VM+".sock") 157 | } 158 | } 159 | // for cmd==ssh only: 160 | // only pass the params up to and including the `ssh` command - after that, 161 | // there might be other -flags that are destined for the ssh cmd 162 | sshIdx := 1 163 | for sshIdx < len(os.Args) && os.Args[sshIdx-1] != "ssh" { 164 | sshIdx++ 165 | } 166 | // Command-line overrides profile config. 167 | if err := flags.Parse(os.Args[1:sshIdx]); err != nil { 168 | return nil, err 169 | } 170 | 171 | leftovers := flags.Args() 172 | 173 | if B2D.Verbose || (len(leftovers) > 0 && leftovers[0] == "version") { 174 | fmt.Printf("Boot2Docker-cli version: %s\nGit commit: %s\n", Version, GitSHA) 175 | } 176 | 177 | return flags, nil 178 | } 179 | 180 | func usageShort() { 181 | binName := filepath.Base(os.Args[0]) 182 | fmt.Fprintf(os.Stderr, "Usage: %s [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|shellinit|delete|download|upgrade|version} []\n", binName) 183 | } 184 | 185 | func usageLong(flags *flag.FlagSet) { 186 | // NOTE: the help message uses spaces, not tabs for indentation! 187 | fmt.Fprintf(os.Stderr, `Usage: %s [] [] 188 | 189 | Boot2Docker management utility. 190 | 191 | Commands: 192 | init Create a new Boot2Docker VM. 193 | up|start|boot Start VM from any states. 194 | ssh [ssh-command] Login to VM via SSH. 195 | save|suspend Suspend VM and save state to disk. 196 | down|stop|halt Gracefully shutdown the VM. 197 | restart Gracefully reboot the VM. 198 | poweroff Forcefully power off the VM (may corrupt disk image). 199 | reset Forcefully power cycle the VM (may corrupt disk image). 200 | delete|destroy Delete Boot2Docker VM and its disk image. 201 | config|cfg Show selected profile file settings. 202 | info Display detailed information of VM. 203 | ip Display the IP address of the VM's Host-only network. 204 | shellinit Display the shell commands to set up the Docker client. 205 | status Display current state of VM. 206 | download Download Boot2Docker ISO image. 207 | upgrade Upgrade the Boot2Docker ISO image (restart if running). 208 | version Display version information. 209 | 210 | Options: 211 | `, os.Args[0]) 212 | flags.PrintDefaults() 213 | } 214 | -------------------------------------------------------------------------------- /docs/images/bad_host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boot2docker/boot2docker-cli/8a3999640ae7be3493c80a02203eba8c381d2d5c/docs/images/bad_host.png -------------------------------------------------------------------------------- /docs/images/cool_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boot2docker/boot2docker-cli/8a3999640ae7be3493c80a02203eba8c381d2d5c/docs/images/cool_view.png -------------------------------------------------------------------------------- /docs/images/good_host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boot2docker/boot2docker-cli/8a3999640ae7be3493c80a02203eba8c381d2d5c/docs/images/good_host.png -------------------------------------------------------------------------------- /docs/images/kitematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boot2docker/boot2docker-cli/8a3999640ae7be3493c80a02203eba8c381d2d5c/docs/images/kitematic.png -------------------------------------------------------------------------------- /docs/images/newsite_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boot2docker/boot2docker-cli/8a3999640ae7be3493c80a02203eba8c381d2d5c/docs/images/newsite_view.png -------------------------------------------------------------------------------- /docs/images/windows-boot2docker-cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boot2docker/boot2docker-cli/8a3999640ae7be3493c80a02203eba8c381d2d5c/docs/images/windows-boot2docker-cmd.png -------------------------------------------------------------------------------- /docs/images/windows-boot2docker-powershell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boot2docker/boot2docker-cli/8a3999640ae7be3493c80a02203eba8c381d2d5c/docs/images/windows-boot2docker-powershell.png -------------------------------------------------------------------------------- /docs/images/windows-boot2docker-start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boot2docker/boot2docker-cli/8a3999640ae7be3493c80a02203eba8c381d2d5c/docs/images/windows-boot2docker-start.png -------------------------------------------------------------------------------- /docs/images/windows-installer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boot2docker/boot2docker-cli/8a3999640ae7be3493c80a02203eba8c381d2d5c/docs/images/windows-installer.png -------------------------------------------------------------------------------- /docs/mac.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | # Mac OS X 12 | 13 | You can install Docker using Boot2Docker to run `docker` commands at your command-line. 14 | Choose this installation if you are familiar with the command-line or plan to 15 | contribute to the Docker project on GitHub. 16 | 17 | [Download Kitematic](https://kitematic.com/download) 19 | 20 | Alternatively, you may want to try Kitematic, an application that lets you set up Docker and 22 | run containers using a graphical user interface (GUI). 23 | 24 | ## Command-line Docker with Boot2Docker 25 | 26 | Because the Docker daemon uses Linux-specific kernel features, you can't run 27 | Docker natively in OS X. Instead, you must install the Boot2Docker application. 28 | The application includes a VirtualBox Virtual Machine (VM), Docker itself, and the 29 | Boot2Docker management tool. 30 | 31 | The Boot2Docker management tool is a lightweight Linux virtual machine made 32 | specifically to run the Docker daemon on Mac OS X. The VirtualBox VM runs 33 | completely from RAM, is a small ~24MB download, and boots in approximately 5s. 34 | 35 | **Requirements** 36 | 37 | Your Mac must be running OS X 10.6 "Snow Leopard" or newer to run Boot2Docker. 38 | 39 | ### Learn the key concepts before installing 40 | 41 | In a Docker installation on Linux, your machine is both the localhost and the 42 | Docker host. In networking, localhost means your computer. The Docker host is 43 | the machine on which the containers run. 44 | 45 | On a typical Linux installation, the Docker client, the Docker daemon, and any 46 | containers run directly on your localhost. This means you can address ports on a 47 | Docker container using standard localhost addressing such as `localhost:8000` or 48 | `0.0.0.0:8376`. 49 | 50 | ![Linux Architecture Diagram](/installation/images/linux_docker_host.svg) 51 | 52 | In an OS X installation, the `docker` daemon is running inside a Linux virtual 53 | machine provided by Boot2Docker. 54 | 55 | ![OSX Architecture Diagram](/installation/images/mac_docker_host.svg) 56 | 57 | In OS X, the Docker host address is the address of the Linux VM. 58 | When you start the `boot2docker` process, the VM is assigned an IP address. Under 59 | `boot2docker` ports on a container map to ports on the VM. To see this in 60 | practice, work through the exercises on this page. 61 | 62 | 63 | ### Installation 64 | 65 | 1. Go to the [boot2docker/osx-installer ]( 66 | https://github.com/boot2docker/osx-installer/releases/latest) release page. 67 | 68 | 4. Download Boot2Docker by clicking `Boot2Docker-x.x.x.pkg` in the "Downloads" 69 | section. 70 | 71 | 3. Install Boot2Docker by double-clicking the package. 72 | 73 | The installer places Boot2Docker and VirtualBox in your "Applications" folder. 74 | 75 | The installation places the `docker` and `boot2docker` binaries in your 76 | `/usr/local/bin` directory. 77 | 78 | 79 | ## Start the Boot2Docker Application 80 | 81 | To run a Docker container, you first start the `boot2docker` VM and then issue 82 | `docker` commands to create, load, and manage containers. You can launch 83 | `boot2docker` from your Applications folder or from the command line. 84 | 85 | > **NOTE**: Boot2Docker is designed as a development tool. You should not use 86 | > it in production environments. 87 | 88 | ### From the Applications folder 89 | 90 | When you launch the "Boot2Docker" application from your "Applications" folder, the 91 | application: 92 | 93 | * opens a terminal window 94 | 95 | * creates a $HOME/.boot2docker directory 96 | 97 | * creates a VirtualBox ISO and certs 98 | 99 | * starts a VirtualBox VM running the `docker` daemon 100 | 101 | Once the launch completes, you can run `docker` commands. A good way to verify 102 | your setup succeeded is to run the `hello-world` container. 103 | 104 | $ docker run hello-world 105 | Unable to find image 'hello-world:latest' locally 106 | 511136ea3c5a: Pull complete 107 | 31cbccb51277: Pull complete 108 | e45a5af57b00: Pull complete 109 | hello-world:latest: The image you are pulling has been verified. 110 | Important: image verification is a tech preview feature and should not be 111 | relied on to provide security. 112 | Status: Downloaded newer image for hello-world:latest 113 | Hello from Docker. 114 | This message shows that your installation appears to be working correctly. 115 | 116 | To generate this message, Docker took the following steps: 117 | 1. The Docker client contacted the Docker daemon. 118 | 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. 119 | (Assuming it was not already locally available.) 120 | 3. The Docker daemon created a new container from that image which runs the 121 | executable that produces the output you are currently reading. 122 | 4. The Docker daemon streamed that output to the Docker client, which sent it 123 | to your terminal. 124 | 125 | To try something more ambitious, you can run an Ubuntu container with: 126 | $ docker run -it ubuntu bash 127 | 128 | For more examples and ideas, visit: 129 | http://docs.docker.com/userguide/ 130 | 131 | 132 | A more typical way to start and stop `boot2docker` is using the command line. 133 | 134 | ### From your command line 135 | 136 | Initialize and run `boot2docker` from the command line, do the following: 137 | 138 | 1. Create a new Boot2Docker VM. 139 | 140 | $ boot2docker init 141 | 142 | This creates a new virtual machine. You only need to run this command once. 143 | 144 | 2. Start the `boot2docker` VM. 145 | 146 | $ boot2docker start 147 | 148 | 3. Display the environment variables for the Docker client. 149 | 150 | $ boot2docker shellinit 151 | Writing /Users/mary/.boot2docker/certs/boot2docker-vm/ca.pem 152 | Writing /Users/mary/.boot2docker/certs/boot2docker-vm/cert.pem 153 | Writing /Users/mary/.boot2docker/certs/boot2docker-vm/key.pem 154 | export DOCKER_HOST=tcp://192.168.59.103:2376 155 | export DOCKER_CERT_PATH=/Users/mary/.boot2docker/certs/boot2docker-vm 156 | export DOCKER_TLS_VERIFY=1 157 | 158 | The specific paths and address on your machine will be different. 159 | 160 | 4. To set the environment variables in your shell do the following: 161 | 162 | $ eval "$(boot2docker shellinit)" 163 | 164 | You can also set them manually by using the `export` commands `boot2docker` 165 | returns. 166 | 167 | 5. Run the `hello-world` container to verify your setup. 168 | 169 | $ docker run hello-world 170 | 171 | 172 | ## Basic Boot2Docker exercises 173 | 174 | At this point, you should have `boot2docker` running and the `docker` client 175 | environment initialized. To verify this, run the following commands: 176 | 177 | $ boot2docker status 178 | $ docker version 179 | 180 | Work through this section to try some practical container tasks using `boot2docker` VM. 181 | 182 | ### Access container ports 183 | 184 | 1. Start an NGINX container on the DOCKER_HOST. 185 | 186 | $ docker run -d -P --name web nginx 187 | 188 | Normally, the `docker run` commands starts a container, runs it, and then 189 | exits. The `-d` flag keeps the container running in the background 190 | after the `docker run` command completes. The `-P` flag publishes exposed ports from the 191 | container to your local host; this lets you access them from your Mac. 192 | 193 | 2. Display your running container with `docker ps` command 194 | 195 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 196 | 5fb65ff765e9 nginx:latest "nginx -g 'daemon of 3 minutes ago Up 3 minutes 0.0.0.0:49156->443/tcp, 0.0.0.0:49157->80/tcp web 197 | 198 | At this point, you can see `nginx` is running as a daemon. 199 | 200 | 3. View just the container's ports. 201 | 202 | $ docker port web 203 | 443/tcp -> 0.0.0.0:49156 204 | 80/tcp -> 0.0.0.0:49157 205 | 206 | This tells you that the `web` container's port `80` is mapped to port 207 | `49157` on your Docker host. 208 | 209 | 4. Enter the `http://localhost:49157` address (`localhost` is `0.0.0.0`) in your browser: 210 | 211 | ![Bad Address](/installation/images/bad_host.png) 212 | 213 | This didn't work. The reason it doesn't work is your `DOCKER_HOST` address is 214 | not the localhost address (0.0.0.0) but is instead the address of the 215 | `boot2docker` VM. 216 | 217 | 5. Get the address of the `boot2docker` VM. 218 | 219 | $ boot2docker ip 220 | 192.168.59.103 221 | 222 | 6. Enter the `http://192.168.59.103:49157` address in your browser: 223 | 224 | ![Correct Addressing](/installation/images/good_host.png) 225 | 226 | Success! 227 | 228 | 7. To stop and then remove your running `nginx` container, do the following: 229 | 230 | $ docker stop web 231 | $ docker rm web 232 | 233 | ### Mount a volume on the container 234 | 235 | When you start `boot2docker`, it automatically shares your `/Users` directory 236 | with the VM. You can use this share point to mount directories onto your container. 237 | The next exercise demonstrates how to do this. 238 | 239 | 1. Change to your user `$HOME` directory. 240 | 241 | $ cd $HOME 242 | 243 | 2. Make a new `site` directory. 244 | 245 | $ mkdir site 246 | 247 | 3. Change into the `site` directory. 248 | 249 | $ cd site 250 | 251 | 4. Create a new `index.html` file. 252 | 253 | $ echo "my new site" > index.html 254 | 255 | 5. Start a new `nginx` container and replace the `html` folder with your `site` directory. 256 | 257 | $ docker run -d -P -v $HOME/site:/usr/share/nginx/html --name mysite nginx 258 | 259 | 6. Get the `mysite` container's port. 260 | 261 | $ docker port mysite 262 | 80/tcp -> 0.0.0.0:49166 263 | 443/tcp -> 0.0.0.0:49165 264 | 265 | 7. Open the site in a browser: 266 | 267 | ![My site page](/installation/images/newsite_view.png) 268 | 269 | 8. Try adding a page to your `$HOME/site` in real time. 270 | 271 | $ echo "This is cool" > cool.html 272 | 273 | 9. Open the new page in the browser. 274 | 275 | ![Cool page](/installation/images/cool_view.png) 276 | 277 | 9. Stop and then remove your running `mysite` container. 278 | 279 | $ docker stop mysite 280 | $ docker rm mysite 281 | 282 | ## Upgrade Boot2Docker 283 | 284 | If you running Boot2Docker 1.4.1 or greater, you can upgrade Boot2Docker from 285 | the command line. If you are running an older version, you should use the 286 | package provided by the `boot2docker` repository. 287 | 288 | ### From the command line 289 | 290 | To upgrade from 1.4.1 or greater, you can do this: 291 | 292 | 1. Open a terminal on your local machine. 293 | 294 | 2. Stop the `boot2docker` application. 295 | 296 | $ boot2docker stop 297 | 298 | 3. Run the upgrade command. 299 | 300 | $ boot2docker upgrade 301 | 302 | 303 | ### Use the installer 304 | 305 | To upgrade any version of Boot2Docker, do this: 306 | 307 | 1. Open a terminal on your local machine. 308 | 309 | 2. Stop the `boot2docker` application. 310 | 311 | $ boot2docker stop 312 | 313 | 3. Go to the [boot2docker/osx-installer ]( 314 | https://github.com/boot2docker/osx-installer/releases/latest) release page. 315 | 316 | 4. Download Boot2Docker by clicking `Boot2Docker-x.x.x.pkg` in the "Downloads" 317 | section. 318 | 319 | 2. Install Boot2Docker by double-clicking the package. 320 | 321 | The installer places Boot2Docker in your "Applications" folder. 322 | 323 | 324 | ## Uninstallation 325 | 326 | 1. Go to the [boot2docker/osx-installer ]( 327 | https://github.com/boot2docker/osx-installer/releases/latest) release page. 328 | 329 | 2. Download the source code by clicking `Source code (zip)` or 330 | `Source code (tar.gz)` in the "Downloads" section. 331 | 332 | 3. Extract the source code. 333 | 334 | 4. Open a terminal on your local machine. 335 | 336 | 5. Change to the directory where you extracted the source code: 337 | 338 | $ cd 339 | 340 | 6. Make sure the uninstall.sh script is executable: 341 | 342 | $ chmod +x uninstall.sh 343 | 344 | 7. Run the uninstall.sh script: 345 | 346 | $ ./uninstall.sh 347 | 348 | 349 | ## Learning more and acknowledgement 350 | 351 | Use `boot2docker help` to list the full command line reference. For more 352 | information about using SSH or SCP to access the Boot2Docker VM, see the README 353 | at [Boot2Docker repository](https://github.com/boot2docker/boot2docker). 354 | 355 | Thanks to Chris Jones whose [blog](http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide) 356 | inspired me to redo this page. 357 | 358 | Continue with the [Docker User Guide](/userguide). 359 | -------------------------------------------------------------------------------- /docs/windows.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | # Windows 12 | > **Note:** 13 | > Docker has been tested on Windows 7 and 8.1; it may also run on older versions. 14 | > Your processor needs to support hardware virtualization. 15 | 16 | The Docker Engine uses Linux-specific kernel features, so to run it on Windows 17 | we need to use a lightweight virtual machine (VM). You use the **Windows Docker 18 | Client** to control the virtualized Docker Engine to build, run, and manage 19 | Docker containers. 20 | 21 | To make this process easier, we've designed a helper application called 22 | [Boot2Docker](https://github.com/boot2docker/boot2docker) creates a Linux virtual 23 | machine on Windows to run Docker on a Linux operating system. 24 | 25 | Although you will be using Windows Docker client, the docker engine hosting the 26 | containers will still be running on Linux. Until the Docker engine for Windows 27 | is developed, you can launch only Linux containers from your Windows machine. 28 | 29 | ![Windows Architecture Diagram](/installation/images/win_docker_host.svg) 30 | 31 | ## Demonstration 32 | 33 | 34 | 35 | ## Installation 36 | 37 | 1. Download the latest release of the 38 | [Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest). 39 | 2. Run the installer, which will install Docker Client for Windows, VirtualBox, 40 | Git for Windows (MSYS-git), the boot2docker Linux ISO, and the Boot2Docker 41 | management tool. 42 | ![](/installation/images/windows-installer.png) 43 | 3. Run the **Boot2Docker Start** shortcut from your Desktop or “Program Files → 44 | Boot2Docker for Windows”. 45 | The Start script will ask you to enter an ssh key passphrase - the simplest 46 | (but least secure) is to just hit [Enter]. 47 | 48 | 4. The **Boot2Docker Start** will start a unix shell already configured to manage 49 | Docker running inside the virtual machine. Run `docker version` to see 50 | if it is working correctly: 51 | 52 | ![](/installation/images/windows-boot2docker-start.png) 53 | 54 | ## Running Docker 55 | 56 | > **Note:** if you are using a remote Docker daemon, such as Boot2Docker, 57 | > then _do not_ type the `sudo` before the `docker` commands shown in the 58 | > documentation's examples. 59 | 60 | **Boot2Docker Start** will automatically start a shell with environment variables 61 | correctly set so you can start using Docker right away: 62 | 63 | Let's try the `hello-world` example image. Run 64 | 65 | $ docker run hello-world 66 | 67 | This should download the very small `hello-world` image and print a 68 | `Hello from Docker.` message. 69 | 70 | ## Using Docker from Windows Command Line Prompt (cmd.exe) 71 | 72 | Launch a Windows Command Line Prompt (cmd.exe). 73 | 74 | Boot2Docker command requires `ssh.exe` to be in the PATH, therefore we need to 75 | include `bin` folder of the Git installation (which has ssh.exe) to the `%PATH%` 76 | environment variable by running: 77 | 78 | set PATH=%PATH%;"c:\Program Files (x86)\Git\bin" 79 | 80 | and then we can run the `boot2docker start` command to start the Boot2Docker VM. 81 | (Run `boot2docker init` command if you get an error saying machine does not 82 | exist.) Then copy the instructions for cmd.exe to set the environment variables 83 | to your console window and you are ready to run docker commands such as 84 | `docker ps`: 85 | 86 | ![](/installation/images/windows-boot2docker-cmd.png) 87 | 88 | ## Using Docker from PowerShell 89 | 90 | Launch a PowerShell window, then you need to add `ssh.exe` to your PATH: 91 | 92 | $Env:Path = "${Env:Path};c:\Program Files (x86)\Git\bin" 93 | 94 | and after running `boot2docker start` command it will print PowerShell commands 95 | to set the environment variables to connect Docker running inside VM. Run these 96 | commands and you are ready to run docker commands such as `docker ps`: 97 | 98 | ![](/installation/images/windows-boot2docker-powershell.png) 99 | 100 | > NOTE: You can alternatively run `boot2docker shellinit | Invoke-Expression` 101 | > command to set the environment variables instead of copying and pasting on 102 | > PowerShell. 103 | 104 | # Further Details 105 | 106 | The Boot2Docker management tool provides several commands: 107 | 108 | $ boot2docker 109 | Usage: boot2docker.exe [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|shellinit|delete|download|upgrade|version} [] 110 | 111 | ## Upgrading 112 | 113 | 1. Download the latest release of the [Docker for Windows Installer]( 114 | https://github.com/boot2docker/windows-installer/releases/latest) 115 | 116 | 2. Run the installer, which will update the Boot2Docker management tool. 117 | 118 | 3. To upgrade your existing virtual machine, open a terminal and run: 119 | 120 | boot2docker stop 121 | boot2docker download 122 | boot2docker start 123 | 124 | ## Container port redirection 125 | 126 | If you are curious, the username for the boot2docker default user is `docker` 127 | and the password is `tcuser`. 128 | 129 | The latest version of `boot2docker` sets up a host only network adaptor which 130 | provides access to the container's ports. 131 | 132 | If you run a container with an exposed port: 133 | 134 | docker run --rm -i -t -p 80:80 nginx 135 | 136 | Then you should be able to access that nginx server using the IP address reported 137 | to you using: 138 | 139 | boot2docker ip 140 | 141 | Typically, it is 192.168.59.103, but it could get changed by Virtualbox's DHCP 142 | implementation. 143 | 144 | For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io) 145 | 146 | ## Login with PUTTY instead of using the CMD 147 | 148 | Boot2Docker generates and uses the public/private key pair in your `%USERPROFILE%\.ssh` 149 | directory so to log in you need to use the private key from this same directory. 150 | 151 | The private key needs to be converted into the format PuTTY uses. 152 | 153 | You can do this with 154 | [puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html): 155 | 156 | - Open `puttygen.exe` and load ("File"->"Load" menu) the private key from 157 | `%USERPROFILE%\.ssh\id_boot2docker` 158 | - then click: "Save Private Key". 159 | - Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`. 160 | 161 | ## Uninstallation 162 | 163 | You can uninstall Boot2Docker using Window's standard process for removing programs. 164 | This process does not remove the `docker-install.exe` file. You must delete that file 165 | yourself. 166 | 167 | ## References 168 | 169 | If you have Docker hosts running and if you don't wish to do a 170 | Boot2Docker installation, you can install the docker.exe using 171 | unofficial Windows package manager Chocolately. For information 172 | on how to do this, see [Docker package on Chocolatey](http://chocolatey.org/packages/docker). 173 | -------------------------------------------------------------------------------- /download.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # Set version to latest unless set by user 6 | if [ -z "$VERSION" ]; then 7 | VERSION="1.1.2" 8 | fi 9 | EXTENSION="" 10 | 11 | echo "Downloading version ${VERSION}..." 12 | 13 | # OS information (contains e.g. Darwin x86_64) 14 | UNAME=`uname -a` 15 | # Determine platform 16 | if [[ $UNAME == *"Darwin"* ]]; then 17 | PLATFORM="darwin" 18 | elif [[ ($UNAME == *MINGW*) || ($UNAME == *Cygwin*) ]]; then 19 | PLATFORM="windows" 20 | EXTENSION=".exe" 21 | UNAME="${PROCESSOR_ARCHITEW6432}" 22 | else 23 | PLATFORM="linux" 24 | fi 25 | # Determine architecture 26 | if [[ ($UNAME == *x86_64*) || ($UNAME == *amd64*) || ($UNAME == *AMD64*) ]] 27 | then 28 | ARCH="amd64" 29 | else 30 | echo "Currently, there are no 32bit binaries provided." 31 | echo "You will need to go get / go install github.com/boot2docker/boot2docker-cli." 32 | exit 1 33 | fi 34 | 35 | # Download binary 36 | URL="https://github.com/boot2docker/boot2docker-cli/releases/download/v${VERSION}/boot2docker-v${VERSION}-${PLATFORM}-${ARCH}${EXTENSION}" 37 | echo "Downloading $URL" 38 | curl -L -o "boot2docker${EXTENSION}" "$URL" 39 | 40 | # Make binary executable 41 | chmod +x "boot2docker${EXTENSION}" 42 | 43 | echo "Done." 44 | -------------------------------------------------------------------------------- /driver/config.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | flag "github.com/ogier/pflag" 8 | ) 9 | 10 | // Machine config. 11 | type MachineConfig struct { 12 | // Gereral flags. 13 | Init bool 14 | Verbose bool 15 | Driver string 16 | 17 | // basic config 18 | Clobber bool 19 | ForceUpgradeDownload bool 20 | SSH string // SSH client executable 21 | SSHGen string // SSH keygen executable 22 | SSHKey string // SSH key to send to the vm 23 | VM string // virtual machine name 24 | Dir string // boot2docker directory 25 | ISOURL string // Source URL to retrieve the ISO from 26 | ISO string // boot2docker ISO image path 27 | DiskSize uint // VM disk image size (MB) 28 | Memory uint // VM memory size (MB) 29 | CPUs uint // Number of CPUs 30 | 31 | // NAT network: port forwarding 32 | SSHPort uint16 // host SSH port (forward to port 22 in VM) 33 | DockerPort uint16 // host Docker port (forward to port 2376 in VM) 34 | 35 | // host-only network 36 | HostIP net.IP 37 | DHCPIP net.IP 38 | NetMask net.IPMask 39 | LowerIP net.IP 40 | UpperIP net.IP 41 | DHCPEnabled bool 42 | 43 | // Serial console pipe/socket 44 | Serial bool 45 | SerialFile string 46 | 47 | // boot2docker init retry settings 48 | Waittime int 49 | Retries int 50 | 51 | DriverCfg map[string]interface{} 52 | } 53 | 54 | type ConfigFunc func(B2D *MachineConfig, flags *flag.FlagSet) error 55 | 56 | var configs map[string]ConfigFunc // optional map of driver ConfigFunc 57 | 58 | func init() { 59 | configs = make(map[string]ConfigFunc) 60 | } 61 | 62 | // optional - allows a driver to add its own commandline parameters 63 | func RegisterConfig(driver string, configFunc ConfigFunc) error { 64 | if _, exists := configs[driver]; exists { 65 | return fmt.Errorf("Driver already registered %s", driver) 66 | } 67 | configs[driver] = configFunc 68 | 69 | return nil 70 | } 71 | 72 | func ConfigFlags(B2D *MachineConfig, flags *flag.FlagSet) error { 73 | for _, configFunc := range configs { 74 | if err := configFunc(B2D, flags); err != nil { 75 | return err 76 | } 77 | } 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /driver/dhcp.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import "net" 4 | 5 | // DHCP server info. 6 | type DHCP struct { 7 | NetworkName string 8 | IPv4 net.IPNet 9 | LowerIP net.IP 10 | UpperIP net.IP 11 | Enabled bool 12 | } 13 | -------------------------------------------------------------------------------- /driver/driver.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | type InitFunc func(i *MachineConfig) (Machine, error) 9 | 10 | type MachineState string 11 | 12 | const ( 13 | // Known ports 14 | SSHPort = 22 15 | DockerPort = 2376 16 | 17 | // VM states 18 | Poweroff = MachineState("poweroff") 19 | Running = MachineState("running") 20 | Paused = MachineState("paused") 21 | Saved = MachineState("saved") 22 | Aborted = MachineState("aborted") 23 | ) 24 | 25 | // Machine represents a virtual machine instance 26 | type Machine interface { 27 | Start() error 28 | Save() error 29 | Pause() error 30 | Stop() error 31 | Refresh() error 32 | Poweroff() error 33 | Restart() error 34 | Reset() error 35 | Delete() error 36 | Modify() error 37 | AddNATPF(n int, name string, rule PFRule) error 38 | DelNATPF(n int, name string) error 39 | SetNIC(n int, nic NIC) error 40 | AddStorageCtl(name string, ctl StorageController) error 41 | DelStorageCtl(name string) error 42 | AttachStorage(ctlName string, medium StorageMedium) error 43 | GetState() MachineState 44 | GetName() string 45 | GetSerialFile() string 46 | GetDockerPort() uint 47 | GetSSHPort() uint 48 | } 49 | 50 | var ( 51 | // All registred machines 52 | machines map[string]InitFunc 53 | 54 | ErrNotSupported = errors.New("driver not supported") 55 | ErrMachineNotExist = errors.New("machine does not exist (Did you run `boot2docker init`?)") 56 | ErrMachineExist = errors.New("machine already exists") 57 | ErrPrerequisites = errors.New("prerequisites for machine not satisfied (hypervisor installed?)") 58 | ) 59 | 60 | func init() { 61 | machines = make(map[string]InitFunc) 62 | } 63 | 64 | func Register(driver string, initFunc InitFunc) error { 65 | if _, exists := machines[driver]; exists { 66 | return fmt.Errorf("Driver already registered %s", driver) 67 | } 68 | machines[driver] = initFunc 69 | 70 | return nil 71 | } 72 | 73 | func GetMachine(mc *MachineConfig) (Machine, error) { 74 | if initFunc, exists := machines[mc.Driver]; exists { 75 | return initFunc(mc) 76 | } 77 | return nil, ErrNotSupported 78 | } 79 | -------------------------------------------------------------------------------- /driver/nic.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | // NIC represents a virtualized network interface card. 4 | type NIC struct { 5 | Network NICNetwork 6 | Hardware NICHardware 7 | HostonlyAdapter string 8 | } 9 | 10 | // NICNetwork represents the type of NIC networks. 11 | type NICNetwork string 12 | 13 | const ( 14 | NICNetAbsent = NICNetwork("none") 15 | NICNetDisconnected = NICNetwork("null") 16 | NICNetNAT = NICNetwork("nat") 17 | NICNetBridged = NICNetwork("bridged") 18 | NICNetInternal = NICNetwork("intnet") 19 | NICNetHostonly = NICNetwork("hostonly") 20 | NICNetGeneric = NICNetwork("generic") 21 | ) 22 | 23 | // NICHardware represents the type of NIC hardware. 24 | type NICHardware string 25 | 26 | const ( 27 | AMDPCNetPCIII = NICHardware("Am79C970A") 28 | AMDPCNetFASTIII = NICHardware("Am79C973") 29 | IntelPro1000MTDesktop = NICHardware("82540EM") 30 | IntelPro1000TServer = NICHardware("82543GC") 31 | IntelPro1000MTServer = NICHardware("82545EM") 32 | VirtIO = NICHardware("virtio") 33 | ) 34 | -------------------------------------------------------------------------------- /driver/pfrule.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | // PFRule represents a port forwarding rule. 9 | type PFRule struct { 10 | Proto PFProto 11 | HostIP net.IP // can be nil to match any host interface 12 | HostPort uint16 13 | GuestIP net.IP // can be nil if guest IP is leased from built-in DHCP 14 | GuestPort uint16 15 | } 16 | 17 | // PFProto represents the protocol of a port forwarding rule. 18 | type PFProto string 19 | 20 | const ( 21 | PFTCP = PFProto("tcp") 22 | PFUDP = PFProto("udp") 23 | ) 24 | 25 | // String returns a human-friendly representation of the port forwarding rule. 26 | func (r PFRule) String() string { 27 | hostip := "" 28 | if r.HostIP != nil { 29 | hostip = r.HostIP.String() 30 | } 31 | guestip := "" 32 | if r.GuestIP != nil { 33 | guestip = r.GuestIP.String() 34 | } 35 | return fmt.Sprintf("%s://%s:%d --> %s:%d", 36 | r.Proto, hostip, r.HostPort, 37 | guestip, r.GuestPort) 38 | } 39 | 40 | // Format returns the string needed as a command-line argument to VBoxManage. 41 | func (r PFRule) Format() string { 42 | hostip := "" 43 | if r.HostIP != nil { 44 | hostip = r.HostIP.String() 45 | } 46 | guestip := "" 47 | if r.GuestIP != nil { 48 | guestip = r.GuestIP.String() 49 | } 50 | return fmt.Sprintf("%s,%s,%d,%s,%d", r.Proto, hostip, r.HostPort, guestip, r.GuestPort) 51 | } 52 | -------------------------------------------------------------------------------- /driver/storage.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | // StorageController represents a virtualized storage controller. 4 | type StorageController struct { 5 | SysBus SystemBus 6 | Ports uint // SATA port count 1--30 7 | Chipset StorageControllerChipset 8 | HostIOCache bool 9 | Bootable bool 10 | } 11 | 12 | // SystemBus represents the system bus of a storage controller. 13 | type SystemBus string 14 | 15 | const ( 16 | SysBusIDE = SystemBus("ide") 17 | SysBusSATA = SystemBus("sata") 18 | SysBusSCSI = SystemBus("scsi") 19 | SysBusFloppy = SystemBus("floppy") 20 | ) 21 | 22 | // StorageControllerChipset represents the hardware of a storage controller. 23 | type StorageControllerChipset string 24 | 25 | const ( 26 | CtrlLSILogic = StorageControllerChipset("LSILogic") 27 | CtrlLSILogicSAS = StorageControllerChipset("LSILogicSAS") 28 | CtrlBusLogic = StorageControllerChipset("BusLogic") 29 | CtrlIntelAHCI = StorageControllerChipset("IntelAHCI") 30 | CtrlPIIX3 = StorageControllerChipset("PIIX3") 31 | CtrlPIIX4 = StorageControllerChipset("PIIX4") 32 | CtrlICH6 = StorageControllerChipset("ICH6") 33 | CtrlI82078 = StorageControllerChipset("I82078") 34 | ) 35 | 36 | // StorageMedium represents the storage medium attached to a storage controller. 37 | type StorageMedium struct { 38 | Port uint 39 | Device uint 40 | DriveType DriveType 41 | Medium string // none|emptydrive|||iscsi 42 | } 43 | 44 | // DriveType represents the hardware type of a drive. 45 | type DriveType string 46 | 47 | const ( 48 | DriveDVD = DriveType("dvddrive") 49 | DriveHDD = DriveType("hdd") 50 | DriveFDD = DriveType("fdd") 51 | ) 52 | -------------------------------------------------------------------------------- /dummy/machine.go: -------------------------------------------------------------------------------- 1 | package dummy 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/boot2docker/boot2docker-cli/driver" 8 | flag "github.com/ogier/pflag" 9 | ) 10 | 11 | type DriverCfg struct { 12 | DummyParam string // Example string for dummy driver 13 | hiddenParam string 14 | } 15 | 16 | var ( 17 | verbose bool // Verbose mode (Local copy of B2D.Verbose). 18 | cfg DriverCfg 19 | ) 20 | 21 | func init() { 22 | if err := driver.Register("dummy", InitFunc); err != nil { 23 | fmt.Fprintf(os.Stderr, "Failed to initialize driver. Error : %s", err.Error()) 24 | os.Exit(1) 25 | } 26 | if err := driver.RegisterConfig("dummy", ConfigFlags); err != nil { 27 | fmt.Fprintf(os.Stderr, "Failed to initialize driver config. Error : %s", err.Error()) 28 | os.Exit(1) 29 | } 30 | } 31 | 32 | // Initialize the Machine. 33 | func InitFunc(i *driver.MachineConfig) (driver.Machine, error) { 34 | verbose = i.Verbose 35 | 36 | fmt.Printf("Init dummy %s\n", i.VM) 37 | return &Machine{Name: i.VM, State: driver.Poweroff}, nil 38 | } 39 | 40 | // Add cmdline params for this driver 41 | func ConfigFlags(B2D *driver.MachineConfig, flags *flag.FlagSet) error { 42 | //B2D.DriverCfg["dummy"] = cfg 43 | flags.StringVar(&cfg.DummyParam, "no-dummy", "", "Example parameter for the dummy driver.") 44 | 45 | return nil 46 | } 47 | 48 | // Machine information. 49 | type Machine struct { 50 | Name string 51 | UUID string 52 | State driver.MachineState 53 | CPUs uint 54 | Memory uint // main memory (in MB) 55 | VRAM uint // video memory (in MB) 56 | CfgFile string 57 | BaseFolder string 58 | OSType string 59 | BootOrder []string // max 4 slots, each in {none|floppy|dvd|disk|net} 60 | DockerPort uint 61 | SSHPort uint 62 | SerialFile string 63 | } 64 | 65 | // Refresh reloads the machine information. 66 | func (m *Machine) Refresh() error { 67 | fmt.Printf("Refresh %s: %s\n", m.Name, m.State) 68 | return nil 69 | } 70 | 71 | // Start starts the machine. 72 | func (m *Machine) Start() error { 73 | m.State = driver.Running 74 | fmt.Printf("Start %s: %s\n", m.Name, m.State) 75 | return nil 76 | } 77 | 78 | // Suspend suspends the machine and saves its state to disk. 79 | func (m *Machine) Save() error { 80 | m.State = driver.Saved 81 | fmt.Printf("Save %s: %s\n", m.Name, m.State) 82 | return nil 83 | } 84 | 85 | // Pause pauses the execution of the machine. 86 | func (m *Machine) Pause() error { 87 | m.State = driver.Paused 88 | fmt.Printf("Pause %s: %s\n", m.Name, m.State) 89 | return nil 90 | } 91 | 92 | // Stop gracefully stops the machine. 93 | func (m *Machine) Stop() error { 94 | m.State = driver.Poweroff 95 | fmt.Printf("Stop %s: %s\n", m.Name, m.State) 96 | return nil 97 | } 98 | 99 | // Poweroff forcefully stops the machine. State is lost and might corrupt the disk image. 100 | func (m *Machine) Poweroff() error { 101 | m.State = driver.Poweroff 102 | fmt.Printf("Poweroff %s: %s\n", m.Name, m.State) 103 | return nil 104 | } 105 | 106 | // Restart gracefully restarts the machine. 107 | func (m *Machine) Restart() error { 108 | m.State = driver.Running 109 | fmt.Printf("Restart %s: %s\n", m.Name, m.State) 110 | return nil 111 | } 112 | 113 | // Reset forcefully restarts the machine. State is lost and might corrupt the disk image. 114 | func (m *Machine) Reset() error { 115 | m.State = driver.Running 116 | fmt.Printf("Reset %s: %s\n", m.Name, m.State) 117 | return nil 118 | } 119 | 120 | // Get current name 121 | func (m *Machine) GetName() string { 122 | return m.Name 123 | } 124 | 125 | // Get current state 126 | func (m *Machine) GetState() driver.MachineState { 127 | return m.State 128 | } 129 | 130 | // Get serial file 131 | func (m *Machine) GetSerialFile() string { 132 | return m.SerialFile 133 | } 134 | 135 | // Get Docker port 136 | func (m *Machine) GetDockerPort() uint { 137 | return m.DockerPort 138 | } 139 | 140 | // Get SSH port 141 | func (m *Machine) GetSSHPort() uint { 142 | return m.SSHPort 143 | } 144 | 145 | // Delete deletes the machine and associated disk images. 146 | func (m *Machine) Delete() error { 147 | fmt.Printf("Delete %s: %s\n", m.Name, m.State) 148 | return nil 149 | } 150 | 151 | // Modify changes the settings of the machine. 152 | func (m *Machine) Modify() error { 153 | fmt.Printf("Modify %s: %s\n", m.Name, m.State) 154 | return m.Refresh() 155 | } 156 | 157 | // AddNATPF adds a NAT port forarding rule to the n-th NIC with the given name. 158 | func (m *Machine) AddNATPF(n int, name string, rule driver.PFRule) error { 159 | fmt.Println("Add NAT PF") 160 | return nil 161 | } 162 | 163 | // DelNATPF deletes the NAT port forwarding rule with the given name from the n-th NIC. 164 | func (m *Machine) DelNATPF(n int, name string) error { 165 | fmt.Println("Del NAT PF") 166 | return nil 167 | } 168 | 169 | // SetNIC set the n-th NIC. 170 | func (m *Machine) SetNIC(n int, nic driver.NIC) error { 171 | fmt.Println("Set NIC") 172 | return nil 173 | } 174 | 175 | // AddStorageCtl adds a storage controller with the given name. 176 | func (m *Machine) AddStorageCtl(name string, ctl driver.StorageController) error { 177 | fmt.Println("Add storage ctl") 178 | return nil 179 | } 180 | 181 | // DelStorageCtl deletes the storage controller with the given name. 182 | func (m *Machine) DelStorageCtl(name string) error { 183 | fmt.Println("Del storage ctl") 184 | return nil 185 | } 186 | 187 | // AttachStorage attaches a storage medium to the named storage controller. 188 | func (m *Machine) AttachStorage(ctlName string, medium driver.StorageMedium) error { 189 | fmt.Println("Attach storage") 190 | return nil 191 | } 192 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "os" 8 | ) 9 | 10 | // The following vars will be injected during the build process. 11 | var ( 12 | Version string 13 | GitSHA string 14 | ) 15 | 16 | const ( 17 | hardcodedWarning = ` 18 | WARNING: The 'boot2docker' command line interface is officially deprecated. 19 | 20 | Please switch to Docker Machine (https://docs.docker.com/machine/) ASAP. 21 | 22 | Docker Toolbox (https://docker.com/toolbox) is the recommended install method. 23 | ` 24 | warningURL = "https://raw.githubusercontent.com/boot2docker/boot2docker-cli/master/DEPRECATION_WARNING" 25 | ) 26 | 27 | type unknownCommandError struct { 28 | cmd string 29 | } 30 | 31 | func (e unknownCommandError) Error() string { 32 | return fmt.Sprintf("Unknown command: %s", e.cmd) 33 | } 34 | 35 | func main() { 36 | // os.Exit will terminate the program at the place of call without running 37 | // any deferred cleanup statements. It might cause unintended effects. To 38 | // be safe, we wrap the program in run() and only os.Exit() outside the 39 | // wrapper. Be careful not to indirectly trigger os.Exit() in the program, 40 | // notably via log.Fatal() and on flag.Parse() where the default behavior 41 | // is ExitOnError. 42 | if err := run(); err != nil { 43 | fmt.Fprintf(os.Stderr, "error in run: %v\n", err) 44 | if _, ok := err.(unknownCommandError); ok { 45 | usageShort() 46 | } 47 | os.Exit(1) 48 | } 49 | } 50 | 51 | // Run the program and return exit code. 52 | func run() error { 53 | flags, err := config() 54 | if err != nil { 55 | return fmt.Errorf("config error: %v\n", err) 56 | } 57 | 58 | switch cmd := flags.Arg(0); cmd { 59 | case "download": 60 | return cmdDownload() 61 | case "config", "cfg": 62 | return cmdConfig() 63 | case "init": 64 | printDeprecationWarning() 65 | return cmdInit() 66 | case "up", "start", "boot", "resume": 67 | printDeprecationWarning() 68 | return cmdUp() 69 | case "save", "suspend": 70 | return cmdSave() 71 | case "down", "halt", "stop": 72 | return cmdStop() 73 | case "poweroff": 74 | return cmdPoweroff() 75 | case "restart": 76 | return cmdRestart() 77 | case "reset": 78 | return cmdReset() 79 | case "delete", "destroy": 80 | return cmdDelete() 81 | case "info": 82 | return cmdInfo() 83 | case "shellinit", "socket": 84 | return cmdShellInit() 85 | case "status": 86 | return cmdStatus() 87 | case "ssh": 88 | return cmdSSH() 89 | case "ip": 90 | return cmdIP() 91 | case "upgrade": 92 | return cmdUpgrade() 93 | case "version": 94 | // Version is now printed by the call to config() 95 | return nil 96 | case "help": 97 | flags.Usage() 98 | return nil 99 | case "": 100 | usageShort() 101 | return nil 102 | default: 103 | return unknownCommandError{cmd: cmd} 104 | } 105 | } 106 | 107 | func printDeprecationWarning() { 108 | var ( 109 | warning string 110 | ) 111 | 112 | // Try to get the warning from the Github raw URL. If there's any 113 | // failure along the way, e.g. network, just fall back to the default 114 | // warning hardcoded in the source. 115 | resp, err := http.Get(warningURL) 116 | if err != nil || resp.StatusCode != http.StatusOK { 117 | warning = hardcodedWarning 118 | } else { 119 | defer resp.Body.Close() 120 | body, err := ioutil.ReadAll(resp.Body) 121 | if err != nil { 122 | warning = hardcodedWarning 123 | } else { 124 | warning = string(body) 125 | } 126 | } 127 | 128 | fmt.Fprintln(os.Stderr, warning) 129 | } 130 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # This is how I built the 0.9.2 boot2docker-cli release on my mac. 5 | 6 | # install https://storage.googleapis.com/golang/go1.2.2.darwin-amd64-osx10.8.pkg 7 | 8 | TMP=$(mktemp -d /tmp/b2d-cli.XXXXXX) 9 | echo Building in $TMP 10 | export GOPATH=$TMP 11 | export DOCKER_HOST=tcp://localhost:2375 12 | go get github.com/boot2docker/boot2docker-cli 13 | cd ${TMP}/src/github.com/boot2docker/boot2docker-cli 14 | 15 | 16 | make 17 | 18 | echo building OSX native 19 | make darwin 20 | 21 | pwd 22 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | ##ToDos to replace the shell script: 2 | 3 | ### Configuration 4 | - [X] modify standard values with enviroment variables 5 | - [ ] modify standard values with a config file `~/profiles` 6 | - [X] check if all required software exists e.g. virtualbox,.. 7 | 8 | ### Commands 9 | - [X] init Create a new boot2docker VM. 10 | - [X] up|start|boot Start the VM from any state. 11 | - [X] save|suspend Suspend the VM (saving running state to disk). 12 | - [X] down|stop|halt Gracefully shutdown the VM. 13 | - [X] restart Gracefully reboot the VM. 14 | - [X] poweroff Forcefully shutdown the VM (might cause disk corruption). 15 | - [X] reset Forcefully reboot the VM (might cause disk corruption). 16 | - [X] delete Delete the boot2docker VM and its disk image. 17 | - [X] download Download the boot2docker ISO image. 18 | - [X] info Display the detailed information of the VM 19 | - [X] status Display the current state of the VM. 20 | 21 | ### Build 22 | - [X] go get suport 23 | - [X] build with Dockerfile 24 | - [ ] Testcases 25 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "archive/tar" 5 | "bufio" 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "log" 12 | "net" 13 | "net/http" 14 | "os" 15 | "os/exec" 16 | "path/filepath" 17 | "regexp" 18 | "strings" 19 | "time" 20 | 21 | "github.com/boot2docker/boot2docker-cli/driver" 22 | ) 23 | 24 | var ( 25 | // We're looking to get e.g. "1.2.0" from "Docker version 1.2.0, build fa7b24f" 26 | versionRe = regexp.MustCompile(`(\d+\.?){3}`) 27 | ) 28 | 29 | // Try if addr tcp://addr is readable for n times at wait interval. 30 | func read(addr string, n int, wait time.Duration) error { 31 | var lastErr error 32 | for i := 0; i < n; i++ { 33 | if B2D.Verbose { 34 | fmt.Printf("Connecting to tcp://%v (attempt #%d)\n", addr, i) 35 | } 36 | conn, err := net.DialTimeout("tcp", addr, 1*time.Second) 37 | if err != nil { 38 | lastErr = err 39 | time.Sleep(wait) 40 | continue 41 | } 42 | defer conn.Close() 43 | conn.SetDeadline(time.Now().Add(1 * time.Second)) 44 | if _, err = conn.Read(make([]byte, 1)); err != nil { 45 | lastErr = err 46 | time.Sleep(wait) 47 | continue 48 | } 49 | return nil 50 | } 51 | return lastErr 52 | } 53 | 54 | // Check if an addr can be successfully connected. 55 | func ping(addr string) bool { 56 | conn, err := net.Dial("tcp", addr) 57 | if err != nil { 58 | return false 59 | } 60 | defer conn.Close() 61 | return true 62 | } 63 | 64 | // Download the url to the dest path. 65 | func download(dest, url string) error { 66 | rsp, err := http.Get(url) 67 | if err != nil { 68 | return err 69 | } 70 | defer rsp.Body.Close() 71 | 72 | // Create the dest dir. 73 | if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil { 74 | return err 75 | } 76 | 77 | f, err := os.Create(fmt.Sprintf("%s.download", dest)) 78 | if err != nil { 79 | return err 80 | } 81 | defer os.Remove(f.Name()) 82 | 83 | if _, err := io.Copy(f, rsp.Body); err != nil { 84 | // TODO: display download progress? 85 | return err 86 | } 87 | if err := f.Close(); err != nil { 88 | return err 89 | } 90 | 91 | if _, err := os.Stat(dest); err == nil { 92 | backup_dest := dest + ".bak" 93 | os.Remove(backup_dest) 94 | if err := os.Rename(dest, backup_dest); err != nil { 95 | return err 96 | } 97 | } 98 | if err := os.Rename(f.Name(), dest); err != nil { 99 | return err 100 | } 101 | return nil 102 | } 103 | 104 | // Get latest release tag name (e.g. "v0.6.0") from a repo on GitHub. 105 | func getLatestReleaseName(url string) (string, error) { 106 | rsp, err := http.Get(url) 107 | if err != nil { 108 | return "", err 109 | } 110 | defer rsp.Body.Close() 111 | 112 | var t []struct { 113 | // ".../tags" endpoints 114 | Name string `json:"name"` 115 | 116 | // ".../releases" endpoints 117 | TagName string `json:"tag_name"` 118 | Prerelease bool `json:"prerelease"` 119 | } 120 | body, err := ioutil.ReadAll(rsp.Body) 121 | if err != nil { 122 | return "", err 123 | } 124 | 125 | if err := json.Unmarshal(body, &t); err != nil { 126 | var e struct { 127 | Message string 128 | DocumentationUrl string 129 | } 130 | if err := json.Unmarshal(body, &e); err != nil { 131 | return "", fmt.Errorf("Error decoding %s\nbody: %s", err, body) 132 | } 133 | return "", fmt.Errorf("Error getting releases: %s\n see %s", e.Message, e.DocumentationUrl) 134 | } 135 | if len(t) == 0 { 136 | return "", fmt.Errorf("no releases found at %q", url) 137 | } 138 | 139 | // Looking up by tag instead of release. 140 | // Github API call for docker releases yields nothing, 141 | // so we use tags API call in this case. 142 | if strings.Contains(url, "tags") { 143 | return t[0].Name, nil 144 | } 145 | 146 | for _, rel := range t { 147 | if rel.Prerelease { 148 | // skip "pre-releases" (RCs, etc) entirely 149 | continue 150 | } 151 | return rel.TagName, nil 152 | } 153 | 154 | return "", fmt.Errorf("no non-prerelease releases found at %q", url) 155 | } 156 | 157 | func getLocalClientVersion() (string, error) { 158 | versionOutput, err := exec.Command("docker", "-v").Output() 159 | if err != nil { 160 | return "", err 161 | } 162 | versionNumber := versionRe.FindString(string(versionOutput)) 163 | 164 | return versionNumber, nil 165 | } 166 | 167 | func cmdInteractive(m driver.Machine, args ...string) error { 168 | cmd := getSSHCommand(m, args...) 169 | cmd.Stdin = os.Stdin 170 | cmd.Stdout = os.Stdout 171 | cmd.Stderr = os.Stderr 172 | return cmd.Run() 173 | } 174 | 175 | //swiped from dotcloud/docker/utils/utils.go 176 | func CopyFile(src, dst string) (int64, error) { 177 | if src == dst { 178 | return 0, nil 179 | } 180 | sf, err := os.Open(src) 181 | if err != nil { 182 | return 0, err 183 | } 184 | defer sf.Close() 185 | if err := os.Remove(dst); err != nil && !os.IsNotExist(err) { 186 | return 0, err 187 | } 188 | df, err := os.Create(dst) 189 | if err != nil { 190 | return 0, err 191 | } 192 | defer df.Close() 193 | return io.Copy(df, sf) 194 | } 195 | 196 | func reader(r io.Reader) { 197 | buf := make([]byte, 1024) 198 | for { 199 | _, err := io.ReadAtLeast(r, buf[:], 20) 200 | if err != nil { 201 | return 202 | } 203 | } 204 | } 205 | 206 | func getSSHCommand(m driver.Machine, args ...string) *exec.Cmd { 207 | 208 | DefaultSSHArgs := []string{ 209 | "-o", "IdentitiesOnly=yes", 210 | "-o", "StrictHostKeyChecking=no", 211 | "-o", "UserKnownHostsFile=/dev/null", 212 | "-o", "LogLevel=quiet", // suppress "Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts." 213 | "-p", fmt.Sprintf("%d", m.GetSSHPort()), 214 | "-i", B2D.SSHKey, 215 | "docker@localhost", 216 | } 217 | 218 | sshArgs := append(DefaultSSHArgs, args...) 219 | cmd := exec.Command(B2D.SSH, sshArgs...) 220 | if B2D.Verbose { 221 | cmd.Stderr = os.Stderr 222 | log.Printf("executing: %v %v", cmd.Path, strings.Join(cmd.Args, " ")) 223 | } 224 | 225 | return cmd 226 | } 227 | 228 | func RequestIPFromSSH(m driver.Machine) (string, error) { 229 | cmd := getSSHCommand(m, "ip addr show dev eth1") 230 | 231 | b, err := cmd.Output() 232 | if err != nil { 233 | return "", err 234 | } 235 | out := string(b) 236 | if B2D.Verbose { 237 | fmt.Printf("SSH returned: %s\nEND SSH\n", out) 238 | } 239 | // parse to find: inet 192.168.59.103/24 brd 192.168.59.255 scope global eth1 240 | lines := strings.Split(out, "\n") 241 | for _, line := range lines { 242 | vals := strings.Split(strings.TrimSpace(line), " ") 243 | if len(vals) >= 2 && vals[0] == "inet" { 244 | return vals[1][:strings.Index(vals[1], "/")], nil 245 | } 246 | } 247 | 248 | return "", fmt.Errorf("No IP address found %s", out) 249 | } 250 | 251 | func RequestSocketFromSSH(m driver.Machine) (string, error) { 252 | cmd := getSSHCommand(m, "grep tcp:// /proc/$(cat /var/run/docker.pid)/cmdline") 253 | 254 | b, err := cmd.Output() 255 | if err != nil { 256 | return "", err 257 | } 258 | out := string(b) 259 | if B2D.Verbose { 260 | fmt.Printf("SSH returned: %s\nEND SSH\n", out) 261 | } 262 | // Lets only use the first one - its possible to specify more than one... 263 | lines := strings.Split(out, "\n") 264 | tcpRE := regexp.MustCompile(`^(tcp://)(0.0.0.0)(:.*)`) 265 | if s := tcpRE.FindStringSubmatch(lines[0]); s != nil { 266 | IP, err := RequestIPFromSSH(m) 267 | if err != nil { 268 | return "", err 269 | } 270 | return s[1] + IP + s[3], nil 271 | } 272 | if !strings.HasPrefix(lines[0], "tcp://") { 273 | return "", fmt.Errorf("Error requesting Docker Socket: %s", lines[0]) 274 | } 275 | return lines[0], nil 276 | } 277 | 278 | // use the serial port socket to ask what the VM's host only IP is 279 | func RequestIPFromSerialPort(socket string) (string, error) { 280 | c, err := net.Dial("unix", socket) 281 | 282 | if err != nil { 283 | return "", err 284 | } 285 | defer c.Close() 286 | c.SetDeadline(time.Now().Add(time.Second)) 287 | 288 | line := "" 289 | _, err = c.Write([]byte("\r")) 290 | _, err = c.Write([]byte("docker\r")) 291 | 292 | IP := "" 293 | fullLog := "" 294 | 295 | for IP == "" { 296 | _, err := c.Write([]byte("ip addr show dev eth1\r")) 297 | if err != nil { 298 | return "", err 299 | } 300 | time.Sleep(1 * time.Second) 301 | buf := make([]byte, 1024) 302 | for { 303 | n, err := c.Read(buf[:]) 304 | if err != nil { 305 | return "", err 306 | } 307 | line = line + string(buf[0:n]) 308 | fullLog += string(buf[0:n]) 309 | if strings.Contains(line, "\n") { 310 | //go looking for the string we want, and chomp line to after the \n 311 | if i := strings.IndexAny(line, "\n"); i != -1 { 312 | // inet 10.180.1.3/16 brd 10.180.255.255 scope global wlan0 313 | inetRE := regexp.MustCompile(`^[\t ]*inet ([0-9.]*).*$`) 314 | if ip := inetRE.FindStringSubmatch(line[:i]); ip != nil { 315 | IP = ip[1] 316 | // clean up 317 | break 318 | } else { 319 | line = line[i+1:] 320 | } 321 | } 322 | } 323 | } 324 | 325 | } 326 | go reader(c) 327 | //give us time reader clean up 328 | time.Sleep(1 * time.Second) 329 | if IP == "" && B2D.Verbose { 330 | fmt.Printf(fullLog) 331 | } 332 | 333 | return IP, nil 334 | } 335 | 336 | // TODO: need to add or abstract to get a Serial coms version 337 | // RequestCertsUsingSSH requests certs using SSH. 338 | // The assumption is that if the certs are in b2d:/home/docker/.docker 339 | // then the daemon is using TLS. We can't assume that because there are 340 | // certs in the local host's user dir, that the server is using them, so 341 | // for now, make sure things are updated from the server. (for `docker shellinit`) 342 | func RequestCertsUsingSSH(m driver.Machine) (string, error) { 343 | cmd := getSSHCommand(m, "tar c /home/docker/.docker/*.pem") 344 | 345 | certDir := "" 346 | 347 | b, err := cmd.Output() 348 | if err == nil { 349 | dir, err := cfgDir(".boot2docker") 350 | if err != nil { 351 | return "", err 352 | } 353 | 354 | certDir = filepath.Join(dir, "certs", m.GetName()) 355 | 356 | // Open the tar archive for reading. 357 | r := bytes.NewReader(b) 358 | tr := tar.NewReader(r) 359 | 360 | // Iterate through the files in the archive. 361 | for { 362 | hdr, err := tr.Next() 363 | if err == io.EOF { 364 | // end of tar archive 365 | break 366 | } 367 | if err != nil { 368 | return "", err 369 | } 370 | filename := filepath.Base(hdr.Name) 371 | if err := os.MkdirAll(certDir, 0755); err != nil { 372 | return "", err 373 | } 374 | certFile := filepath.Join(certDir, filename) 375 | fmt.Fprintf(os.Stderr, "Writing %s\n", certFile) 376 | f, err := os.Create(certFile) 377 | if err != nil { 378 | return "", err 379 | } 380 | w := bufio.NewWriter(f) 381 | if _, err := io.Copy(w, tr); err != nil { 382 | return "", err 383 | } 384 | w.Flush() 385 | } 386 | } 387 | return certDir, nil 388 | } 389 | -------------------------------------------------------------------------------- /virtualbox/README.md: -------------------------------------------------------------------------------- 1 | # go-virtualbox 2 | 3 | This is a wrapper package for Golang to interact with VirtualBox. The API is 4 | experimental at the moment and you should expect frequent changes. 5 | 6 | API doc at http://godoc.org/github.com/riobard/go-virtualbox 7 | -------------------------------------------------------------------------------- /virtualbox/dhcp.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "bufio" 5 | "net" 6 | "strings" 7 | 8 | "github.com/boot2docker/boot2docker-cli/driver" 9 | ) 10 | 11 | func addDHCP(kind, name string, d driver.DHCP) error { 12 | command := "modify" 13 | 14 | // On some platforms (OSX), creating a hostonlyinterface adds a default dhcpserver 15 | // While on others (Windows?) it does not. 16 | dhcps, err := DHCPs() 17 | if err != nil { 18 | return err 19 | } 20 | 21 | if _, ok := dhcps[name]; !ok { 22 | command = "add" 23 | } 24 | 25 | args := []string{"dhcpserver", command, 26 | kind, name, 27 | "--ip", d.IPv4.IP.String(), 28 | "--netmask", net.IP(d.IPv4.Mask).String(), 29 | "--lowerip", d.LowerIP.String(), 30 | "--upperip", d.UpperIP.String(), 31 | } 32 | if d.Enabled { 33 | args = append(args, "--enable") 34 | } else { 35 | args = append(args, "--disable") 36 | } 37 | return vbm(args...) 38 | } 39 | 40 | // AddInternalDHCP adds a DHCP server to an internal network. 41 | func AddInternalDHCP(netname string, d driver.DHCP) error { 42 | return addDHCP("--netname", netname, d) 43 | } 44 | 45 | // AddHostonlyDHCP adds a DHCP server to a host-only network. 46 | func AddHostonlyDHCP(ifname string, d driver.DHCP) error { 47 | return addDHCP("--netname", "HostInterfaceNetworking-"+ifname, d) 48 | } 49 | 50 | // DHCPs gets all DHCP server settings in a map keyed by DHCP.NetworkName. 51 | func DHCPs() (map[string]*driver.DHCP, error) { 52 | out, err := vbmOut("list", "dhcpservers") 53 | if err != nil { 54 | return nil, err 55 | } 56 | s := bufio.NewScanner(strings.NewReader(out)) 57 | m := map[string]*driver.DHCP{} 58 | dhcp := &driver.DHCP{} 59 | for s.Scan() { 60 | line := s.Text() 61 | if line == "" { 62 | m[dhcp.NetworkName] = dhcp 63 | dhcp = &driver.DHCP{} 64 | continue 65 | } 66 | res := reColonLine.FindStringSubmatch(line) 67 | if res == nil { 68 | continue 69 | } 70 | switch key, val := res[1], res[2]; key { 71 | case "NetworkName": 72 | dhcp.NetworkName = val 73 | case "IP": 74 | dhcp.IPv4.IP = net.ParseIP(val) 75 | case "upperIPAddress": 76 | dhcp.UpperIP = net.ParseIP(val) 77 | case "lowerIPAddress": 78 | dhcp.LowerIP = net.ParseIP(val) 79 | case "NetworkMask": 80 | dhcp.IPv4.Mask = ParseIPv4Mask(val) 81 | case "Enabled": 82 | dhcp.Enabled = (val == "Yes") 83 | } 84 | } 85 | if err := s.Err(); err != nil { 86 | return nil, err 87 | } 88 | return m, nil 89 | } 90 | -------------------------------------------------------------------------------- /virtualbox/dhcp_test.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import "testing" 4 | 5 | func TestDHCPs(t *testing.T) { 6 | m, err := DHCPs() 7 | if err != nil { 8 | t.Fatal(err) 9 | } 10 | 11 | for _, dhcp := range m { 12 | t.Logf("%+v", dhcp) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /virtualbox/disk.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "os/exec" 8 | ) 9 | 10 | // MakeDiskImage makes a disk image at dest with the given size in MB. If r is 11 | // not nil, it will be read as a raw disk image to convert from. 12 | func MakeDiskImage(dest string, size uint, r io.Reader) error { 13 | // Convert a raw image from stdin to the dest VMDK image. 14 | sizeBytes := int64(size) << 20 // usually won't fit in 32-bit int (max 2GB) 15 | cmd := exec.Command(cfg.VBM, "convertfromraw", "stdin", dest, 16 | fmt.Sprintf("%d", sizeBytes), "--format", "VMDK") 17 | 18 | if verbose { 19 | cmd.Stdout = os.Stdout 20 | cmd.Stderr = os.Stderr 21 | } 22 | 23 | stdin, err := cmd.StdinPipe() 24 | if err != nil { 25 | return err 26 | } 27 | if err := cmd.Start(); err != nil { 28 | return err 29 | } 30 | 31 | n, err := io.Copy(stdin, r) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | // The total number of bytes written to stdin must match sizeBytes, or 37 | // VBoxManage.exe on Windows will fail. Fill remaining with zeros. 38 | if left := sizeBytes - n; left > 0 { 39 | if err := ZeroFill(stdin, left); err != nil { 40 | return err 41 | } 42 | } 43 | 44 | // cmd won't exit until the stdin is closed. 45 | if err := stdin.Close(); err != nil { 46 | return err 47 | } 48 | 49 | return cmd.Wait() 50 | } 51 | 52 | // ZeroFill writes n zero bytes into w. 53 | func ZeroFill(w io.Writer, n int64) error { 54 | const blocksize = 32 << 10 55 | zeros := make([]byte, blocksize) 56 | var k int 57 | var err error 58 | for n > 0 { 59 | if n > blocksize { 60 | k, err = w.Write(zeros) 61 | } else { 62 | k, err = w.Write(zeros[:n]) 63 | } 64 | if err != nil { 65 | return err 66 | } 67 | n -= int64(k) 68 | } 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /virtualbox/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package virtualbox implements wrappers to interact with VirtualBox. 3 | 4 | VirtualBox Machine State Transition 5 | 6 | A VirtualBox machine can be in one of the following states: 7 | 8 | poweroff: The VM is powered off and no previous running state saved. 9 | running: The VM is running. 10 | paused: The VM is paused, but its state is not saved to disk. If you quit VirtualBox, the state will be lost. 11 | saved: The VM is powered off, and the previous state is saved on disk. 12 | aborted: The VM process crashed. This should happen very rarely. 13 | 14 | VBoxManage supports the following transitions between states: 15 | 16 | startvm : poweroff|saved --> running 17 | controlvm pause: running --> paused 18 | controlvm resume: paused --> running 19 | controlvm savestate: running -> saved 20 | controlvm acpipowerbutton: running --> poweroff 21 | controlvm poweroff: running --> poweroff (unsafe) 22 | controlvm reset: running --> poweroff --> running (unsafe) 23 | 24 | Poweroff and reset are unsafe because they will lose state and might corrupt 25 | the disk image. 26 | 27 | To make things simpler, the following transitions are used instead: 28 | 29 | start: poweroff|saved|paused|aborted --> running 30 | stop: [paused|saved -->] running --> poweroff 31 | save: [paused -->] running --> saved 32 | restart: [paused|saved -->] running --> poweroff --> running 33 | poweroff: [paused|saved -->] running --> poweroff (unsafe) 34 | reset: [paused|saved -->] running --> poweroff --> running (unsafe) 35 | 36 | The takeaway is we try our best to transit the virtual machine into the state 37 | you want it to be, and you only need to watch out for the potentially unsafe 38 | poweroff and reset. 39 | 40 | */ 41 | package virtualbox 42 | -------------------------------------------------------------------------------- /virtualbox/extra.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | // SetExtra sets extra data. Name could be "global"|| 4 | func SetExtra(name, key, val string) error { 5 | return vbm("setextradata", name, key, val) 6 | } 7 | 8 | // DelExtraData deletes extra data. Name could be "global"|| 9 | func DelExtra(name, key string) error { 10 | return vbm("setextradata", name, key) 11 | } 12 | -------------------------------------------------------------------------------- /virtualbox/hostonlynet.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "regexp" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | var ( 14 | reHostonlyInterfaceCreated = regexp.MustCompile(`Interface '(.+)' was successfully created`) 15 | ) 16 | 17 | var ( 18 | ErrHostonlyInterfaceCreation = errors.New("failed to create hostonly interface") 19 | ) 20 | 21 | // Host-only network. 22 | type HostonlyNet struct { 23 | Name string 24 | GUID string 25 | DHCP bool 26 | IPv4 net.IPNet 27 | IPv6 net.IPNet 28 | HwAddr net.HardwareAddr 29 | Medium string 30 | Status string 31 | NetworkName string // referenced in DHCP.NetworkName 32 | } 33 | 34 | // CreateHostonlyNet creates a new host-only network. 35 | func CreateHostonlyNet() (*HostonlyNet, error) { 36 | out, err := vbmOut("hostonlyif", "create") 37 | if err != nil { 38 | return nil, err 39 | } 40 | res := reHostonlyInterfaceCreated.FindStringSubmatch(string(out)) 41 | if res == nil { 42 | return nil, ErrHostonlyInterfaceCreation 43 | } 44 | return &HostonlyNet{Name: res[1]}, nil 45 | } 46 | 47 | // Config changes the configuration of the host-only network. 48 | func (n *HostonlyNet) Config() error { 49 | if n.IPv4.IP != nil && n.IPv4.Mask != nil { 50 | if err := vbm("hostonlyif", "ipconfig", n.Name, "--ip", n.IPv4.IP.String(), "--netmask", net.IP(n.IPv4.Mask).String()); err != nil { 51 | return err 52 | } 53 | } 54 | 55 | if n.IPv6.IP != nil && n.IPv6.Mask != nil { 56 | prefixLen, _ := n.IPv6.Mask.Size() 57 | if err := vbm("hostonlyif", "ipconfig", n.Name, "--ipv6", n.IPv6.IP.String(), "--netmasklengthv6", fmt.Sprintf("%d", prefixLen)); err != nil { 58 | return err 59 | } 60 | } 61 | 62 | if n.DHCP { 63 | vbm("hostonlyif", "ipconfig", n.Name, "--dhcp") // not implemented as of VirtualBox 4.3 64 | } 65 | 66 | return nil 67 | } 68 | 69 | // HostonlyNets gets all host-only networks in a map keyed by HostonlyNet.NetworkName. 70 | func HostonlyNets() (map[string]*HostonlyNet, error) { 71 | out, err := vbmOut("list", "hostonlyifs") 72 | if err != nil { 73 | return nil, err 74 | } 75 | s := bufio.NewScanner(strings.NewReader(out)) 76 | m := map[string]*HostonlyNet{} 77 | n := &HostonlyNet{} 78 | for s.Scan() { 79 | line := s.Text() 80 | if line == "" { 81 | m[n.NetworkName] = n 82 | n = &HostonlyNet{} 83 | continue 84 | } 85 | res := reColonLine.FindStringSubmatch(line) 86 | if res == nil { 87 | continue 88 | } 89 | switch key, val := res[1], res[2]; key { 90 | case "Name": 91 | n.Name = val 92 | case "GUID": 93 | n.GUID = val 94 | case "DHCP": 95 | n.DHCP = (val != "Disabled") 96 | case "IPAddress": 97 | n.IPv4.IP = net.ParseIP(val) 98 | case "NetworkMask": 99 | n.IPv4.Mask = ParseIPv4Mask(val) 100 | case "IPV6Address": 101 | n.IPv6.IP = net.ParseIP(val) 102 | case "IPV6NetworkMaskPrefixLength": 103 | l, err := strconv.ParseUint(val, 10, 8) 104 | if err != nil { 105 | return nil, err 106 | } 107 | n.IPv6.Mask = net.CIDRMask(int(l), net.IPv6len*8) 108 | case "HardwareAddress": 109 | mac, err := net.ParseMAC(val) 110 | if err != nil { 111 | return nil, err 112 | } 113 | n.HwAddr = mac 114 | case "MediumType": 115 | n.Medium = val 116 | case "Status": 117 | n.Status = val 118 | case "VBoxNetworkName": 119 | n.NetworkName = val 120 | } 121 | } 122 | if err := s.Err(); err != nil { 123 | return nil, err 124 | } 125 | return m, nil 126 | } 127 | -------------------------------------------------------------------------------- /virtualbox/hostonlynet_test.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import "testing" 4 | 5 | func TestHostonlyNets(t *testing.T) { 6 | m, err := HostonlyNets() 7 | if err != nil { 8 | t.Fatal(err) 9 | } 10 | for _, n := range m { 11 | t.Logf("%+v", n) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /virtualbox/machine.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "archive/tar" 5 | "bufio" 6 | "bytes" 7 | "fmt" 8 | "io/ioutil" 9 | "net" 10 | "os" 11 | "path/filepath" 12 | "runtime" 13 | "strconv" 14 | "strings" 15 | "time" 16 | 17 | "github.com/boot2docker/boot2docker-cli/driver" 18 | flag "github.com/ogier/pflag" 19 | ) 20 | 21 | type Flag int 22 | 23 | // Flag names in lowercases to be consistent with VBoxManage options. 24 | const ( 25 | F_acpi Flag = 1 << iota 26 | F_ioapic 27 | F_rtcuseutc 28 | F_cpuhotplug 29 | F_pae 30 | F_longmode 31 | F_hpet 32 | F_hwvirtex 33 | F_triplefaultreset 34 | F_nestedpaging 35 | F_largepages 36 | F_vtxvpid 37 | F_vtxux 38 | F_accelerate3d 39 | ) 40 | 41 | type DriverCfg struct { 42 | VBM string // Path to VBoxManage utility. 43 | VMDK string // base VMDK to use as persistent disk. 44 | 45 | shares shareSlice 46 | 47 | // see also func ConfigFlags later in this file 48 | } 49 | 50 | var shareDefault string // set in ConfigFlags - this is what gets filled in for "shares" if it's empty 51 | 52 | var ( 53 | verbose bool // Verbose mode (Local copy of B2D.Verbose). 54 | cfg DriverCfg 55 | ) 56 | 57 | func init() { 58 | if err := driver.Register("virtualbox", InitFunc); err != nil { 59 | fmt.Fprintf(os.Stderr, "Failed to initialize driver. Error : %s", err.Error()) 60 | os.Exit(1) 61 | } 62 | if err := driver.RegisterConfig("virtualbox", ConfigFlags); err != nil { 63 | fmt.Fprintf(os.Stderr, "Failed to initialize driver config. Error : %s", err.Error()) 64 | os.Exit(1) 65 | } 66 | } 67 | 68 | // Initialize the Machine. 69 | func InitFunc(mc *driver.MachineConfig) (driver.Machine, error) { 70 | verbose = mc.Verbose 71 | 72 | m, err := GetMachine(mc.VM) 73 | if err != nil && mc.Init { 74 | return CreateMachine(mc) 75 | } 76 | return m, err 77 | } 78 | 79 | type shareSlice map[string]string 80 | 81 | const shareSliceSep = "=" 82 | 83 | func (s shareSlice) String() string { 84 | var ret []string 85 | for name, dir := range s { 86 | ret = append(ret, fmt.Sprintf("%s%s%s", dir, shareSliceSep, name)) 87 | } 88 | return fmt.Sprintf("[%s]", strings.Join(ret, " ")) 89 | } 90 | 91 | func (s *shareSlice) Set(shareDir string) error { 92 | var shareName string 93 | if i := strings.Index(shareDir, shareSliceSep); i >= 0 { 94 | shareName = shareDir[i+1:] 95 | shareDir = shareDir[:i] 96 | } 97 | if shareName == "" { 98 | // parts of the VBox internal code are buggy with share names that start with "/" 99 | shareName = strings.TrimLeft(shareDir, "/") 100 | // TODO do some basic Windows -> MSYS path conversion 101 | // ie, s!^([a-z]+):[/\\]+!\1/!; s!\\!/!g 102 | } 103 | if *s == nil { 104 | *s = shareSlice{} 105 | } 106 | (*s)[shareName] = shareDir 107 | return nil 108 | } 109 | 110 | // Add cmdline params for this driver 111 | func ConfigFlags(B2D *driver.MachineConfig, flags *flag.FlagSet) error { 112 | //B2D.DriverCfg["virtualbox"] = cfg 113 | 114 | flags.StringVar(&cfg.VMDK, "basevmdk", "", "Path to VMDK to use as base for persistent partition") 115 | 116 | cfg.VBM = "VBoxManage" 117 | if runtime.GOOS == "windows" { 118 | p := "C:\\Program Files\\Oracle\\VirtualBox" 119 | if t := os.Getenv("VBOX_INSTALL_PATH"); t != "" { 120 | p = t 121 | } else if t = os.Getenv("VBOX_MSI_INSTALL_PATH"); t != "" { 122 | p = t 123 | } 124 | cfg.VBM = filepath.Join(p, "VBoxManage.exe") 125 | } 126 | flags.StringVar(&cfg.VBM, "vbm", cfg.VBM, "path to VirtualBox management utility.") 127 | 128 | // TODO once boot2docker improves, replace this all with homeDir() from config.go so we only share the current user's HOME by default 129 | shareDefault = "disable" 130 | switch runtime.GOOS { 131 | case "darwin": 132 | shareDefault = "/Users" + shareSliceSep + "Users" 133 | case "windows": 134 | shareDefault = "C:\\Users" + shareSliceSep + "c/Users" 135 | } 136 | 137 | var defaultText string 138 | if shareDefault != "disable" { 139 | defaultText = "(defaults to '" + shareDefault + "' if no shares are specified; use 'disable' to explicitly prevent any shares from being created) " 140 | } 141 | flags.Var(&cfg.shares, "vbox-share", fmt.Sprintf("%sList of directories to share during 'up|start|boot' via VirtualBox Guest Additions, with optional labels", defaultText)) 142 | 143 | return nil 144 | } 145 | 146 | // Convert bool to "on"/"off" 147 | func bool2string(b bool) string { 148 | if b { 149 | return "on" 150 | } 151 | return "off" 152 | } 153 | 154 | // Test if flag is set. Return "on" or "off". 155 | func (f Flag) Get(o Flag) string { 156 | return bool2string(f&o == o) 157 | } 158 | 159 | // Machine information. 160 | type Machine struct { 161 | Name string 162 | UUID string 163 | Iso string 164 | State driver.MachineState 165 | CPUs uint 166 | Memory uint // main memory (in MB) 167 | VRAM uint // video memory (in MB) 168 | CfgFile string 169 | BaseFolder string 170 | OSType string 171 | Flag Flag 172 | BootOrder []string // max 4 slots, each in {none|floppy|dvd|disk|net} 173 | DockerPort uint 174 | SSHPort uint 175 | SerialFile string 176 | } 177 | 178 | // Refresh reloads the machine information. 179 | func (m *Machine) Refresh() error { 180 | id := m.Name 181 | if id == "" { 182 | id = m.UUID 183 | } 184 | mm, err := GetMachine(id) 185 | if err != nil { 186 | return err 187 | } 188 | *m = *mm 189 | return nil 190 | } 191 | 192 | // Start starts the machine. 193 | func (m *Machine) Start() error { 194 | switch m.State { 195 | case driver.Paused: 196 | return vbm("controlvm", m.Name, "resume") 197 | case driver.Poweroff, driver.Aborted: 198 | if err := m.setUpShares(); err != nil { 199 | return err 200 | } 201 | fallthrough 202 | case driver.Saved: 203 | return vbm("startvm", m.Name, "--type", "headless") 204 | } 205 | if err := m.Refresh(); err == nil { 206 | if m.State != driver.Running { 207 | return fmt.Errorf("Failed to start", m.Name) 208 | } 209 | } 210 | return nil 211 | } 212 | 213 | // Suspend suspends the machine and saves its state to disk. 214 | func (m *Machine) Save() error { 215 | switch m.State { 216 | case driver.Paused: 217 | if err := m.Start(); err != nil { 218 | return err 219 | } 220 | case driver.Poweroff, driver.Aborted, driver.Saved: 221 | return nil 222 | } 223 | return vbm("controlvm", m.Name, "savestate") 224 | } 225 | 226 | // Pause pauses the execution of the machine. 227 | func (m *Machine) Pause() error { 228 | switch m.State { 229 | case driver.Paused, driver.Poweroff, driver.Aborted, driver.Saved: 230 | return nil 231 | } 232 | return vbm("controlvm", m.Name, "pause") 233 | } 234 | 235 | // Stop gracefully stops the machine. 236 | func (m *Machine) Stop() error { 237 | switch m.State { 238 | case driver.Poweroff, driver.Aborted, driver.Saved: 239 | return nil 240 | case driver.Paused: 241 | if err := m.Start(); err != nil { 242 | return err 243 | } 244 | } 245 | 246 | // busy wait until the machine is stopped 247 | for i := 0; i < 10; i++ { 248 | if err := vbm("controlvm", m.Name, "acpipowerbutton"); err != nil { 249 | return err 250 | } 251 | time.Sleep(1 * time.Second) 252 | if err := m.Refresh(); err != nil { 253 | return err 254 | } 255 | if m.State == driver.Poweroff { 256 | return nil 257 | } 258 | } 259 | 260 | return fmt.Errorf("timed out waiting for VM to stop") 261 | } 262 | 263 | // Poweroff forcefully stops the machine. State is lost and might corrupt the disk image. 264 | func (m *Machine) Poweroff() error { 265 | switch m.State { 266 | case driver.Poweroff, driver.Aborted, driver.Saved: 267 | return nil 268 | } 269 | return vbm("controlvm", m.Name, "poweroff") 270 | } 271 | 272 | // Restart gracefully restarts the machine. 273 | func (m *Machine) Restart() error { 274 | switch m.State { 275 | case driver.Paused, driver.Saved: 276 | if err := m.Start(); err != nil { 277 | return err 278 | } 279 | } 280 | if err := m.Stop(); err != nil { 281 | return err 282 | } 283 | return m.Start() 284 | } 285 | 286 | // Reset forcefully restarts the machine. State is lost and might corrupt the disk image. 287 | func (m *Machine) Reset() error { 288 | switch m.State { 289 | case driver.Paused, driver.Saved: 290 | if err := m.Start(); err != nil { 291 | return err 292 | } 293 | } 294 | return vbm("controlvm", m.Name, "reset") 295 | } 296 | 297 | // Delete deletes the machine and associated disk images. 298 | func (m *Machine) Delete() error { 299 | if err := m.Poweroff(); err != nil { 300 | return err 301 | } 302 | return vbm("unregistervm", m.Name, "--delete") 303 | } 304 | 305 | // Get current state 306 | func (m *Machine) GetName() string { 307 | return m.Name 308 | } 309 | 310 | // Get current state 311 | func (m *Machine) GetState() driver.MachineState { 312 | return m.State 313 | } 314 | 315 | // Get serial file 316 | func (m *Machine) GetSerialFile() string { 317 | return m.SerialFile 318 | } 319 | 320 | // Get Docker port 321 | func (m *Machine) GetDockerPort() uint { 322 | return m.DockerPort 323 | } 324 | 325 | // Get SSH port 326 | func (m *Machine) GetSSHPort() uint { 327 | return m.SSHPort 328 | } 329 | 330 | // GetMachine finds a machine by its name or UUID. 331 | func GetMachine(id string) (*Machine, error) { 332 | stdout, stderr, err := vbmOutErr("showvminfo", id, "--machinereadable") 333 | if err != nil { 334 | if reMachineNotFound.FindString(stderr) != "" { 335 | return nil, driver.ErrMachineNotExist 336 | } 337 | return nil, err 338 | } 339 | s := bufio.NewScanner(strings.NewReader(stdout)) 340 | m := &Machine{} 341 | for s.Scan() { 342 | res := reVMInfoLine.FindStringSubmatch(s.Text()) 343 | if res == nil { 344 | continue 345 | } 346 | key := res[1] 347 | if key == "" { 348 | key = res[2] 349 | } 350 | val := res[3] 351 | if val == "" { 352 | val = res[4] 353 | } 354 | 355 | switch key { 356 | case "name": 357 | m.Name = val 358 | case "UUID": 359 | m.UUID = val 360 | case "SATA-0-0": 361 | m.Iso = val 362 | case "VMState": 363 | m.State = driver.MachineState(val) 364 | case "memory": 365 | n, err := strconv.ParseUint(val, 10, 32) 366 | if err != nil { 367 | return nil, err 368 | } 369 | m.Memory = uint(n) 370 | case "cpus": 371 | n, err := strconv.ParseUint(val, 10, 32) 372 | if err != nil { 373 | return nil, err 374 | } 375 | m.CPUs = uint(n) 376 | case "vram": 377 | n, err := strconv.ParseUint(val, 10, 32) 378 | if err != nil { 379 | return nil, err 380 | } 381 | m.VRAM = uint(n) 382 | case "CfgFile": 383 | m.CfgFile = val 384 | m.BaseFolder = filepath.Dir(val) 385 | case "uartmode1": 386 | // uartmode1="server,/home/sven/.boot2docker/boot2docker-vm.sock" 387 | vals := strings.Split(val, ",") 388 | if len(vals) >= 2 { 389 | m.SerialFile = vals[1] 390 | } 391 | default: 392 | if strings.HasPrefix(key, "Forwarding(") { 393 | // "Forwarding(\d*)" are ordered by the name inside the val, not fixed order. 394 | // Forwarding(0)="docker,tcp,127.0.0.1,5555,," 395 | // Forwarding(1)="ssh,tcp,127.0.0.1,2222,,22" 396 | vals := strings.Split(val, ",") 397 | n, err := strconv.ParseUint(vals[3], 10, 32) 398 | if err != nil { 399 | return nil, err 400 | } 401 | switch vals[0] { 402 | case "docker": 403 | m.DockerPort = uint(n) 404 | case "ssh": 405 | m.SSHPort = uint(n) 406 | } 407 | } 408 | } 409 | } 410 | if err := s.Err(); err != nil { 411 | return nil, err 412 | } 413 | return m, nil 414 | } 415 | 416 | // ListMachines lists all registered machines. 417 | func ListMachines() ([]string, error) { 418 | out, err := vbmOut("list", "vms") 419 | if err != nil { 420 | return nil, err 421 | } 422 | ms := []string{} 423 | s := bufio.NewScanner(strings.NewReader(out)) 424 | for s.Scan() { 425 | res := reVMNameUUID.FindStringSubmatch(s.Text()) 426 | if res == nil { 427 | continue 428 | } 429 | ms = append(ms, res[1]) 430 | } 431 | if err := s.Err(); err != nil { 432 | return nil, err 433 | } 434 | return ms, nil 435 | } 436 | 437 | // CreateMachine creates a new machine. If basefolder is empty, use default. 438 | func CreateMachine(mc *driver.MachineConfig) (*Machine, error) { 439 | if mc.VM == "" { 440 | return nil, fmt.Errorf("machine name is empty") 441 | } 442 | 443 | // Check if a machine with the given name already exists. 444 | machineNames, err := ListMachines() 445 | if err != nil { 446 | return nil, err 447 | } 448 | for _, m := range machineNames { 449 | if m == mc.VM { 450 | return nil, driver.ErrMachineExist 451 | } 452 | } 453 | 454 | // Create and register the machine. 455 | args := []string{"createvm", "--name", mc.VM, "--register"} 456 | if err := vbm(args...); err != nil { 457 | return nil, err 458 | } 459 | 460 | m, err := GetMachine(mc.VM) 461 | if err != nil { 462 | return nil, err 463 | } 464 | 465 | // Configure VM for Boot2docker 466 | SetExtra(mc.VM, "VBoxInternal/CPUM/EnableHVP", "1") 467 | m.OSType = "Linux26_64" 468 | if mc.CPUs > 0 { 469 | m.CPUs = mc.CPUs 470 | } else { 471 | m.CPUs = uint(runtime.NumCPU()) 472 | } 473 | if m.CPUs > 32 { 474 | m.CPUs = 32 475 | } 476 | m.Memory = mc.Memory 477 | m.SerialFile = mc.SerialFile 478 | 479 | m.Flag |= F_pae 480 | m.Flag |= F_longmode // important: use x86-64 processor 481 | m.Flag |= F_rtcuseutc 482 | m.Flag |= F_acpi 483 | m.Flag |= F_ioapic 484 | m.Flag |= F_hpet 485 | m.Flag |= F_hwvirtex 486 | m.Flag |= F_vtxvpid 487 | m.Flag |= F_largepages 488 | m.Flag |= F_nestedpaging 489 | 490 | // Set VM boot order 491 | m.BootOrder = []string{"dvd"} 492 | if err := m.Modify(); err != nil { 493 | return m, err 494 | } 495 | 496 | // Set NIC #1 to use NAT 497 | m.SetNIC(1, driver.NIC{Network: driver.NICNetNAT, Hardware: driver.VirtIO}) 498 | pfRules := map[string]driver.PFRule{ 499 | "ssh": {Proto: driver.PFTCP, HostIP: net.ParseIP("127.0.0.1"), HostPort: mc.SSHPort, GuestPort: driver.SSHPort}, 500 | } 501 | if mc.DockerPort > 0 { 502 | pfRules["docker"] = driver.PFRule{Proto: driver.PFTCP, HostIP: net.ParseIP("127.0.0.1"), HostPort: mc.DockerPort, GuestPort: driver.DockerPort} 503 | } 504 | 505 | for name, rule := range pfRules { 506 | if err := m.AddNATPF(1, name, rule); err != nil { 507 | return m, err 508 | } 509 | } 510 | 511 | hostIFName, err := getHostOnlyNetworkInterface(mc) 512 | if err != nil { 513 | return m, err 514 | } 515 | 516 | // Set NIC #2 to use host-only 517 | if err := m.SetNIC(2, driver.NIC{Network: driver.NICNetHostonly, Hardware: driver.VirtIO, HostonlyAdapter: hostIFName}); err != nil { 518 | return m, err 519 | } 520 | 521 | // Set VM storage 522 | if err := m.AddStorageCtl("SATA", driver.StorageController{SysBus: driver.SysBusSATA, HostIOCache: true, Bootable: true, Ports: 4}); err != nil { 523 | return m, err 524 | } 525 | 526 | // Attach ISO image 527 | if err := m.AttachStorage("SATA", driver.StorageMedium{Port: 0, Device: 0, DriveType: driver.DriveDVD, Medium: mc.ISO}); err != nil { 528 | return m, err 529 | } 530 | 531 | diskImg := filepath.Join(m.BaseFolder, fmt.Sprintf("%s.vmdk", mc.VM)) 532 | if _, err := os.Stat(diskImg); err != nil { 533 | if !os.IsNotExist(err) { 534 | return m, err 535 | } 536 | 537 | if cfg.VMDK != "" { 538 | if err := copyDiskImage(diskImg, cfg.VMDK); err != nil { 539 | return m, err 540 | } 541 | } else { 542 | magicString := "boot2docker, please format-me" 543 | 544 | buf := new(bytes.Buffer) 545 | tw := tar.NewWriter(buf) 546 | 547 | // magicString first so the automount script knows to format the disk 548 | file := &tar.Header{Name: magicString, Size: int64(len(magicString))} 549 | if err := tw.WriteHeader(file); err != nil { 550 | return m, err 551 | } 552 | if _, err := tw.Write([]byte(magicString)); err != nil { 553 | return m, err 554 | } 555 | // .ssh/key.pub => authorized_keys 556 | file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} 557 | if err := tw.WriteHeader(file); err != nil { 558 | return m, err 559 | } 560 | pubKey, err := ioutil.ReadFile(mc.SSHKey + ".pub") 561 | if err != nil { 562 | return m, err 563 | } 564 | file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} 565 | if err := tw.WriteHeader(file); err != nil { 566 | return m, err 567 | } 568 | if _, err := tw.Write([]byte(pubKey)); err != nil { 569 | return m, err 570 | } 571 | file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} 572 | if err := tw.WriteHeader(file); err != nil { 573 | return m, err 574 | } 575 | if _, err := tw.Write([]byte(pubKey)); err != nil { 576 | return m, err 577 | } 578 | if err := tw.Close(); err != nil { 579 | return m, err 580 | } 581 | 582 | if err := makeDiskImage(diskImg, mc.DiskSize, buf.Bytes()); err != nil { 583 | return m, err 584 | } 585 | if verbose { 586 | fmt.Println("Initializing disk with ssh keys") 587 | fmt.Printf("WRITING: %s\n-----\n", buf) 588 | } 589 | } 590 | } 591 | 592 | if err := m.AttachStorage("SATA", driver.StorageMedium{Port: 1, Device: 0, DriveType: driver.DriveHDD, Medium: diskImg}); err != nil { 593 | return m, err 594 | } 595 | 596 | return m, nil 597 | } 598 | 599 | func (m *Machine) setUpShares() error { 600 | // let VBoxService do nice magic automounting (when it's used) 601 | if err := vbm("guestproperty", "set", m.Name, "/VirtualBox/GuestAdd/SharedFolders/MountPrefix", "/"); err != nil { 602 | return err 603 | } 604 | if err := vbm("guestproperty", "set", m.Name, "/VirtualBox/GuestAdd/SharedFolders/MountDir", "/"); err != nil { 605 | return err 606 | } 607 | 608 | // set up some shared folders as appropriate 609 | if len(cfg.shares) == 0 { 610 | cfg.shares.Set(shareDefault) 611 | } 612 | for shareName, shareDir := range cfg.shares { 613 | if shareDir == "disable" { 614 | continue 615 | } 616 | if _, err := os.Stat(shareDir); err != nil { 617 | return err 618 | } 619 | 620 | // woo, shareDir exists! let's carry on! 621 | if err := vbm("sharedfolder", "add", m.Name, "--name", shareName, "--hostpath", shareDir, "--automount"); err != nil { 622 | return err 623 | } 624 | 625 | // enable symlinks 626 | if err := vbm("setextradata", m.Name, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/"+shareName, "1"); err != nil { 627 | return err 628 | } 629 | } 630 | return nil 631 | } 632 | 633 | // Modify changes the settings of the machine. 634 | func (m *Machine) Modify() error { 635 | args := []string{"modifyvm", m.Name, 636 | "--firmware", "bios", 637 | "--bioslogofadein", "off", 638 | "--bioslogofadeout", "off", 639 | "--bioslogodisplaytime", "0", 640 | "--biosbootmenu", "disabled", 641 | 642 | // the DNS Host Resolver doesn't support SRV records 643 | // the DNS proxy has performance issues 644 | // direct DNS pass-through doesn't support roaming laptops well 645 | // we can't win, so let's go direct and at least get performance 646 | "--natdnshostresolver1", "off", 647 | "--natdnsproxy1", "off", 648 | 649 | "--ostype", m.OSType, 650 | "--cpus", fmt.Sprintf("%d", m.CPUs), 651 | "--memory", fmt.Sprintf("%d", m.Memory), 652 | "--vram", fmt.Sprintf("%d", m.VRAM), 653 | 654 | "--acpi", m.Flag.Get(F_acpi), 655 | "--ioapic", m.Flag.Get(F_ioapic), 656 | "--rtcuseutc", m.Flag.Get(F_rtcuseutc), 657 | "--cpuhotplug", m.Flag.Get(F_cpuhotplug), 658 | "--pae", m.Flag.Get(F_pae), 659 | "--longmode", m.Flag.Get(F_longmode), 660 | "--hpet", m.Flag.Get(F_hpet), 661 | "--hwvirtex", m.Flag.Get(F_hwvirtex), 662 | "--triplefaultreset", m.Flag.Get(F_triplefaultreset), 663 | "--nestedpaging", m.Flag.Get(F_nestedpaging), 664 | "--largepages", m.Flag.Get(F_largepages), 665 | "--vtxvpid", m.Flag.Get(F_vtxvpid), 666 | "--vtxux", m.Flag.Get(F_vtxux), 667 | "--accelerate3d", m.Flag.Get(F_accelerate3d), 668 | } 669 | 670 | //if runtime.GOOS != "windows" { 671 | args = append(args, 672 | "--uart1", "0x3F8", "4", 673 | "--uartmode1", "server", m.SerialFile, 674 | ) 675 | //} 676 | 677 | for i, dev := range m.BootOrder { 678 | if i > 3 { 679 | break // Only four slots `--boot{1,2,3,4}`. Ignore the rest. 680 | } 681 | args = append(args, fmt.Sprintf("--boot%d", i+1), dev) 682 | } 683 | if err := vbm(args...); err != nil { 684 | return err 685 | } 686 | return m.Refresh() 687 | } 688 | 689 | // AddNATPF adds a NAT port forarding rule to the n-th NIC with the given name. 690 | func (m *Machine) AddNATPF(n int, name string, rule driver.PFRule) error { 691 | return vbm("modifyvm", m.Name, fmt.Sprintf("--natpf%d", n), 692 | fmt.Sprintf("%s,%s", name, rule.Format())) 693 | } 694 | 695 | // DelNATPF deletes the NAT port forwarding rule with the given name from the n-th NIC. 696 | func (m *Machine) DelNATPF(n int, name string) error { 697 | return vbm("controlvm", m.Name, fmt.Sprintf("natpf%d", n), "delete", name) 698 | } 699 | 700 | // SetNIC set the n-th NIC. 701 | func (m *Machine) SetNIC(n int, nic driver.NIC) error { 702 | args := []string{"modifyvm", m.Name, 703 | fmt.Sprintf("--nic%d", n), string(nic.Network), 704 | fmt.Sprintf("--nictype%d", n), string(nic.Hardware), 705 | fmt.Sprintf("--cableconnected%d", n), "on", 706 | } 707 | 708 | if nic.Network == "hostonly" { 709 | args = append(args, fmt.Sprintf("--hostonlyadapter%d", n), nic.HostonlyAdapter) 710 | } 711 | return vbm(args...) 712 | } 713 | 714 | // AddStorageCtl adds a storage controller with the given name. 715 | func (m *Machine) AddStorageCtl(name string, ctl driver.StorageController) error { 716 | args := []string{"storagectl", m.Name, "--name", name} 717 | if ctl.SysBus != "" { 718 | args = append(args, "--add", string(ctl.SysBus)) 719 | } 720 | if ctl.Ports > 0 { 721 | args = append(args, "--portcount", fmt.Sprintf("%d", ctl.Ports)) 722 | } 723 | if ctl.Chipset != "" { 724 | args = append(args, "--controller", string(ctl.Chipset)) 725 | } 726 | args = append(args, "--hostiocache", bool2string(ctl.HostIOCache)) 727 | args = append(args, "--bootable", bool2string(ctl.Bootable)) 728 | return vbm(args...) 729 | } 730 | 731 | // DelStorageCtl deletes the storage controller with the given name. 732 | func (m *Machine) DelStorageCtl(name string) error { 733 | return vbm("storagectl", m.Name, "--name", name, "--remove") 734 | } 735 | 736 | // AttachStorage attaches a storage medium to the named storage controller. 737 | func (m *Machine) AttachStorage(ctlName string, medium driver.StorageMedium) error { 738 | return vbm("storageattach", m.Name, "--storagectl", ctlName, 739 | "--port", fmt.Sprintf("%d", medium.Port), 740 | "--device", fmt.Sprintf("%d", medium.Device), 741 | "--type", string(medium.DriveType), 742 | "--medium", medium.Medium, 743 | ) 744 | } 745 | -------------------------------------------------------------------------------- /virtualbox/machine_test.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import "testing" 4 | 5 | func TestMachine(t *testing.T) { 6 | ms, err := ListMachines() 7 | if err != nil { 8 | t.Fatal(err) 9 | } 10 | for _, m := range ms { 11 | t.Logf("%+v", m) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /virtualbox/natnet.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "bufio" 5 | "net" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // A NATNet defines a NAT network. 11 | type NATNet struct { 12 | Name string 13 | IPv4 net.IPNet 14 | IPv6 net.IPNet 15 | DHCP bool 16 | Enabled bool 17 | } 18 | 19 | // NATNets gets all NAT networks in a map keyed by NATNet.Name. 20 | func NATNets() (map[string]NATNet, error) { 21 | out, err := vbmOut("list", "natnets") 22 | if err != nil { 23 | return nil, err 24 | } 25 | s := bufio.NewScanner(strings.NewReader(out)) 26 | m := map[string]NATNet{} 27 | n := NATNet{} 28 | for s.Scan() { 29 | line := s.Text() 30 | if line == "" { 31 | m[n.Name] = n 32 | n = NATNet{} 33 | continue 34 | } 35 | res := reColonLine.FindStringSubmatch(line) 36 | if res == nil { 37 | continue 38 | } 39 | switch key, val := res[1], res[2]; key { 40 | case "NetworkName": 41 | n.Name = val 42 | case "IP": 43 | n.IPv4.IP = net.ParseIP(val) 44 | case "Network": 45 | _, ipnet, err := net.ParseCIDR(val) 46 | if err != nil { 47 | return nil, err 48 | } 49 | n.IPv4.Mask = ipnet.Mask 50 | case "IPv6 Prefix": 51 | if val == "" { 52 | continue 53 | } 54 | l, err := strconv.ParseUint(val, 10, 7) 55 | if err != nil { 56 | return nil, err 57 | } 58 | n.IPv6.Mask = net.CIDRMask(int(l), net.IPv6len*8) 59 | case "DHCP Enabled": 60 | n.DHCP = (val == "Yes") 61 | case "Enabled": 62 | n.Enabled = (val == "Yes") 63 | } 64 | } 65 | if err := s.Err(); err != nil { 66 | return nil, err 67 | } 68 | return m, nil 69 | } 70 | -------------------------------------------------------------------------------- /virtualbox/natnet_test.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import "testing" 4 | 5 | func TestNATNets(t *testing.T) { 6 | m, err := NATNets() 7 | if err != nil { 8 | t.Fatal(err) 9 | } 10 | t.Logf("%+v", m) 11 | } 12 | -------------------------------------------------------------------------------- /virtualbox/util.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import "net" 4 | 5 | // ParseIPv4Mask parses IPv4 netmask written in IP form (e.g. 255.255.255.0). 6 | // This function should really belong to the net package. 7 | func ParseIPv4Mask(s string) net.IPMask { 8 | mask := net.ParseIP(s) 9 | if mask == nil { 10 | return nil 11 | } 12 | return net.IPv4Mask(mask[12], mask[13], mask[14], mask[15]) 13 | } 14 | -------------------------------------------------------------------------------- /virtualbox/vbm.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "log" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "regexp" 12 | "runtime" 13 | "strings" 14 | 15 | "github.com/boot2docker/boot2docker-cli/driver" 16 | ) 17 | 18 | func init() { 19 | if runtime.GOOS == "darwin" { 20 | // remove DYLD_LIBRARY_PATH and LD_LIBRARY_PATH as they break VBoxManage on OSX 21 | os.Unsetenv("DYLD_LIBRARY_PATH") 22 | os.Unsetenv("LD_LIBRARY_PATH") 23 | } 24 | } 25 | 26 | var ( 27 | reVMNameUUID = regexp.MustCompile(`"(.+)" {([0-9a-f-]+)}`) 28 | reVMInfoLine = regexp.MustCompile(`(?:"(.+)"|(.+))=(?:"(.*)"|(.*))`) 29 | reColonLine = regexp.MustCompile(`(.+):\s+(.*)`) 30 | reMachineNotFound = regexp.MustCompile(`Could not find a registered machine named '(.+)'`) 31 | ) 32 | 33 | var ( 34 | ErrVBMNotFound = errors.New("VBoxManage not found") 35 | ) 36 | 37 | func vbm(args ...string) error { 38 | cmd := exec.Command(cfg.VBM, args...) 39 | if verbose { 40 | cmd.Stdout = os.Stdout 41 | cmd.Stderr = os.Stderr 42 | log.Printf("executing: %v %v", cfg.VBM, strings.Join(args, " ")) 43 | } 44 | if err := cmd.Run(); err != nil { 45 | if ee, ok := err.(*exec.Error); ok && ee == exec.ErrNotFound { 46 | return ErrVBMNotFound 47 | } 48 | return err 49 | } 50 | return nil 51 | } 52 | 53 | func vbmOut(args ...string) (string, error) { 54 | cmd := exec.Command(cfg.VBM, args...) 55 | if verbose { 56 | cmd.Stderr = os.Stderr 57 | log.Printf("executing: %v %v", cfg.VBM, strings.Join(args, " ")) 58 | } 59 | 60 | b, err := cmd.Output() 61 | if err != nil { 62 | if ee, ok := err.(*exec.Error); ok && ee == exec.ErrNotFound { 63 | err = ErrVBMNotFound 64 | } 65 | } 66 | return string(b), err 67 | } 68 | 69 | func vbmOutErr(args ...string) (string, string, error) { 70 | cmd := exec.Command(cfg.VBM, args...) 71 | if verbose { 72 | log.Printf("executing: %v %v", cfg.VBM, strings.Join(args, " ")) 73 | } 74 | var stdout bytes.Buffer 75 | var stderr bytes.Buffer 76 | cmd.Stdout = &stdout 77 | cmd.Stderr = &stderr 78 | err := cmd.Run() 79 | if err != nil { 80 | if ee, ok := err.(*exec.Error); ok && ee == exec.ErrNotFound { 81 | err = ErrVBMNotFound 82 | } 83 | } 84 | return stdout.String(), stderr.String(), err 85 | } 86 | 87 | // Get or create the hostonly network interface 88 | func getHostOnlyNetworkInterface(mc *driver.MachineConfig) (string, error) { 89 | // Check if the interface/dhcp exists. 90 | nets, err := HostonlyNets() 91 | if err != nil { 92 | return "", err 93 | } 94 | 95 | dhcps, err := DHCPs() 96 | if err != nil { 97 | return "", err 98 | } 99 | 100 | for _, n := range nets { 101 | if dhcp, ok := dhcps[n.NetworkName]; ok { 102 | if dhcp.IPv4.IP.Equal(mc.DHCPIP) && 103 | dhcp.IPv4.Mask.String() == mc.NetMask.String() && 104 | dhcp.LowerIP.Equal(mc.LowerIP) && 105 | dhcp.UpperIP.Equal(mc.UpperIP) && 106 | dhcp.Enabled == mc.DHCPEnabled { 107 | return n.Name, nil 108 | } 109 | } 110 | } 111 | 112 | // No existing host-only interface found. Create a new one. 113 | hostonlyNet, err := CreateHostonlyNet() 114 | if err != nil { 115 | return "", err 116 | } 117 | hostonlyNet.IPv4.IP = mc.HostIP 118 | hostonlyNet.IPv4.Mask = mc.NetMask 119 | if err := hostonlyNet.Config(); err != nil { 120 | return "", err 121 | } 122 | 123 | // Create and add a DHCP server to the host-only network 124 | dhcp := driver.DHCP{} 125 | dhcp.IPv4.IP = mc.DHCPIP 126 | dhcp.IPv4.Mask = mc.NetMask 127 | dhcp.LowerIP = mc.LowerIP 128 | dhcp.UpperIP = mc.UpperIP 129 | dhcp.Enabled = true 130 | if err := AddHostonlyDHCP(hostonlyNet.Name, dhcp); err != nil { 131 | return "", err 132 | } 133 | return hostonlyNet.Name, nil 134 | } 135 | 136 | // Copy disk image from given source path to destination 137 | func copyDiskImage(dst, src string) (err error) { 138 | // Open source disk image 139 | srcImg, err := os.Open(src) 140 | if err != nil { 141 | return err 142 | } 143 | defer func() { 144 | if ee := srcImg.Close(); ee != nil { 145 | err = ee 146 | } 147 | }() 148 | dstImg, err := os.Create(dst) 149 | if err != nil { 150 | return err 151 | } 152 | defer func() { 153 | if ee := dstImg.Close(); ee != nil { 154 | err = ee 155 | } 156 | }() 157 | _, err = io.Copy(dstImg, srcImg) 158 | return err 159 | } 160 | 161 | // Make a boot2docker VM disk image with the given size (in MB). 162 | func makeDiskImage(dest string, size uint, initialBytes []byte) error { 163 | // Create the dest dir. 164 | if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil { 165 | return err 166 | } 167 | // Fill in the magic string so boot2docker VM will detect this and format 168 | // the disk upon first boot. 169 | raw := bytes.NewReader(initialBytes) 170 | return MakeDiskImage(dest, size, raw) 171 | } 172 | -------------------------------------------------------------------------------- /virtualbox/vbm_test.go: -------------------------------------------------------------------------------- 1 | package virtualbox 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func init() { 8 | verbose = true 9 | } 10 | 11 | func TestVBMOut(t *testing.T) { 12 | b, err := vbmOut("list", "vms") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | t.Logf("%s", b) 17 | } 18 | --------------------------------------------------------------------------------