[release-branch.go1.9] all: merge master into release-branch.go1.9

0f5d61c4 imports: print dir of candidates in addition to import path
a237aba5 godoc: fix out-of-bounds panic when serving top-level files
4e70a1b2 godoc: add GoogleCN property to pages
fcc44a63 cmd/getgo: add a user-agent to download requests
3fd990c6 cmd/tip: fix the build
d07a458d cmd/tip: add a cert cache, clean up Kubernetes config, use update-deps
9badcbe4 cmd/getgo: prompt warning if an earlier installation exists
6fdd948b godoc: remove disabled admin code
f2b3bb00 cmd/getgo: fix builds
001b4ec8 cmd/getgo: have consistent messages
29518d98 cmd/getgo: display that -i stands for interactive mode
5724bdc2 x/tools/godoc: fix redirect to Gerrit
ac1e4b19 cmd/getgo: initial commit

Change-Id: Ie58293a2621bbabbadea4f9431c6fe7bc4aa329f
diff --git a/cmd/getgo/.dockerignore b/cmd/getgo/.dockerignore
new file mode 100644
index 0000000..2b87ad9
--- /dev/null
+++ b/cmd/getgo/.dockerignore
@@ -0,0 +1,5 @@
+.git
+.dockerignore
+LICENSE
+README.md
+.gitignore
diff --git a/cmd/getgo/.gitignore b/cmd/getgo/.gitignore
new file mode 100644
index 0000000..d4984ab
--- /dev/null
+++ b/cmd/getgo/.gitignore
@@ -0,0 +1,3 @@
+build
+testgetgo
+getgo
diff --git a/cmd/getgo/Dockerfile b/cmd/getgo/Dockerfile
new file mode 100644
index 0000000..78fd956
--- /dev/null
+++ b/cmd/getgo/Dockerfile
@@ -0,0 +1,20 @@
+FROM golang:latest
+
+ENV SHELL /bin/bash
+ENV HOME /root
+WORKDIR $HOME
+
+COPY . /go/src/golang.org/x/tools/cmd/getgo
+
+RUN ( \
+		cd /go/src/golang.org/x/tools/cmd/getgo \
+		&& go build \
+		&& mv getgo /usr/local/bin/getgo \
+	)
+
+# undo the adding of GOPATH to env for testing
+ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+ENV GOPATH ""
+
+# delete /go and /usr/local/go for testing
+RUN rm -rf /go /usr/local/go
diff --git a/cmd/getgo/LICENSE b/cmd/getgo/LICENSE
new file mode 100644
index 0000000..32017f8
--- /dev/null
+++ b/cmd/getgo/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2017 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/cmd/getgo/README.md b/cmd/getgo/README.md
new file mode 100644
index 0000000..e62a6c2
--- /dev/null
+++ b/cmd/getgo/README.md
@@ -0,0 +1,71 @@
+# getgo
+
+A proof-of-concept command-line installer for Go.
+
+This installer is designed to both install Go as well as do the initial configuration
+of setting up the right environment variables and paths.
+
+It will install the Go distribution (tools & stdlib) to "/.go" inside your home directory by default.
+
+It will setup "$HOME/go" as your GOPATH.
+This is where third party libraries and apps will be installed as well as where you will write your Go code.
+
+If Go is already installed via this installer it will upgrade it to the latest version of Go.
+
+Currently the installer supports Windows, \*nix and macOS on x86 & x64.
+It supports Bash and Zsh on all of these platforms as well as powershell & cmd.exe on Windows.
+
+## Usage
+
+Windows Powershell/cmd.exe:
+
+`(New-Object System.Net.WebClient).DownloadFile('https://get.golang.org/installer.exe', 'installer.exe'); Start-Process -Wait -NonewWindow installer.exe; Remove-Item installer.exe`
+
+Shell (Linux/macOS/Windows):
+
+`curl -LO https://get.golang.org/$(uname)/go_installer && chmod +x go_installer && ./go_installer && rm go_installer`
+
+## To Do
+
+* Check if Go is already installed (via a different method) and update it in place or at least notify the user
+* Lots of testing. It's only had limited testing so far.
+* Add support for additional shells.
+
+## Development instructions
+
+### Testing
+
+There are integration tests in [`main_test.go`](main_test.go). Please add more
+tests there.
+
+#### On unix/linux with the Dockerfile
+
+The Dockerfile automatically builds the binary, moves it to
+`/usr/local/bin/getgo` and then unsets `$GOPATH` and removes all `$GOPATH` from
+`$PATH`.
+
+```bash
+$ docker build --rm --force-rm -t getgo .
+...
+$ docker run --rm -it getgo bash
+root@78425260fad0:~# getgo -v
+Welcome to the Go installer!
+Downloading Go version go1.8.3 to /usr/local/go
+This may take a bit of time...
+Adding "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin" to /root/.bashrc
+Downloaded!
+Setting up GOPATH
+Adding "export GOPATH=/root/go" to /root/.bashrc
+Adding "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin" to /root/.bashrc
+GOPATH has been setup!
+root@78425260fad0:~# which go
+/usr/local/go/bin/go
+root@78425260fad0:~# echo $GOPATH
+/root/go
+root@78425260fad0:~# echo $PATH
+/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin
+```
+
+## Release instructions
+
+To upload a new release of getgo, run `./make.bash && ./upload.bash`.
diff --git a/cmd/getgo/download.go b/cmd/getgo/download.go
new file mode 100644
index 0000000..ae46549
--- /dev/null
+++ b/cmd/getgo/download.go
@@ -0,0 +1,182 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"archive/tar"
+	"archive/zip"
+	"compress/gzip"
+	"crypto/sha256"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+const (
+	currentVersionURL = "https://golang.org/VERSION?m=text"
+	downloadURLPrefix = "https://storage.googleapis.com/golang"
+)
+
+// downloadGoVersion downloads and upacks the specific go version to dest/go.
+func downloadGoVersion(version, ops, arch, dest string) error {
+	suffix := "tar.gz"
+	if ops == "windows" {
+		suffix = "zip"
+	}
+	uri := fmt.Sprintf("%s/%s.%s-%s.%s", downloadURLPrefix, version, ops, arch, suffix)
+
+	verbosef("Downloading %s", uri)
+
+	req, err := http.NewRequest("GET", uri, nil)
+	if err != nil {
+		return err
+	}
+	req.Header.Add("User-Agent", fmt.Sprintf("golang.org-getgo/%s", version))
+
+	resp, err := http.DefaultClient.Do(req)
+	if err != nil {
+		return fmt.Errorf("Downloading Go from %s failed: %v", uri, err)
+	}
+	if resp.StatusCode > 299 {
+		return fmt.Errorf("Downloading Go from %s failed with HTTP status %s", resp.Status)
+	}
+	defer resp.Body.Close()
+
+	tmpf, err := ioutil.TempFile("", "go")
+	if err != nil {
+		return err
+	}
+	defer os.Remove(tmpf.Name())
+
+	h := sha256.New()
+
+	w := io.MultiWriter(tmpf, h)
+	if _, err := io.Copy(w, resp.Body); err != nil {
+		return err
+	}
+
+	verbosef("Downloading SHA %s.sha256", uri)
+
+	sresp, err := http.Get(uri + ".sha256")
+	if err != nil {
+		return fmt.Errorf("Downloading Go sha256 from %s.sha256 failed: %v", uri, err)
+	}
+	defer sresp.Body.Close()
+	if sresp.StatusCode > 299 {
+		return fmt.Errorf("Downloading Go sha256 from %s.sha256 failed with HTTP status %s", sresp.Status)
+	}
+
+	shasum, err := ioutil.ReadAll(sresp.Body)
+	if err != nil {
+		return err
+	}
+
+	// Check the shasum.
+	sum := fmt.Sprintf("%x", h.Sum(nil))
+	if sum != string(shasum) {
+		return fmt.Errorf("Shasum mismatch %s vs. %s", sum, string(shasum))
+	}
+
+	unpackFunc := unpackTar
+	if ops == "windows" {
+		unpackFunc = unpackZip
+	}
+	if err := unpackFunc(tmpf.Name(), dest); err != nil {
+		return fmt.Errorf("Unpacking Go to %s failed: %v", dest, err)
+	}
+	return nil
+}
+
+func unpack(dest, name string, fi os.FileInfo, r io.Reader) error {
+	if strings.HasPrefix(name, "go/") {
+		name = name[len("go/"):]
+	}
+
+	path := filepath.Join(dest, name)
+	if fi.IsDir() {
+		return os.MkdirAll(path, fi.Mode())
+	}
+
+	f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode())
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	_, err = io.Copy(f, r)
+	return err
+}
+
+func unpackTar(src, dest string) error {
+	r, err := os.Open(src)
+	if err != nil {
+		return err
+	}
+	defer r.Close()
+
+	archive, err := gzip.NewReader(r)
+	if err != nil {
+		return err
+	}
+	defer archive.Close()
+
+	tarReader := tar.NewReader(archive)
+
+	for {
+		header, err := tarReader.Next()
+		if err == io.EOF {
+			break
+		} else if err != nil {
+			return err
+		}
+
+		if err := unpack(dest, header.Name, header.FileInfo(), tarReader); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func unpackZip(src, dest string) error {
+	zr, err := zip.OpenReader(src)
+	if err != nil {
+		return err
+	}
+
+	for _, f := range zr.File {
+		fr, err := f.Open()
+		if err != nil {
+			return err
+		}
+		if err := unpack(dest, f.Name, f.FileInfo(), fr); err != nil {
+			return err
+		}
+		fr.Close()
+	}
+
+	return nil
+}
+
+func getLatestGoVersion() (string, error) {
+	resp, err := http.Get(currentVersionURL)
+	if err != nil {
+		return "", fmt.Errorf("Getting current Go version failed: %v", err)
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode > 299 {
+		b, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 1024))
+		return "", fmt.Errorf("Could not get current Go version: HTTP %d: %q", resp.StatusCode, b)
+	}
+	version, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+	return strings.TrimSpace(string(version)), nil
+}
diff --git a/cmd/getgo/download_test.go b/cmd/getgo/download_test.go
new file mode 100644
index 0000000..1a47823
--- /dev/null
+++ b/cmd/getgo/download_test.go
@@ -0,0 +1,34 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+func TestDownloadGoVersion(t *testing.T) {
+	if testing.Short() {
+		t.Skipf("Skipping download in short mode")
+	}
+
+	tmpd, err := ioutil.TempDir("", "go")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpd)
+
+	if err := downloadGoVersion("go1.8.1", "linux", "amd64", filepath.Join(tmpd, "go")); err != nil {
+		t.Fatal(err)
+	}
+
+	// Ensure the VERSION file exists.
+	vf := filepath.Join(tmpd, "go", "VERSION")
+	if _, err := os.Stat(vf); os.IsNotExist(err) {
+		t.Fatalf("file %s does not exist and should", vf)
+	}
+}
diff --git a/cmd/getgo/main.go b/cmd/getgo/main.go
new file mode 100644
index 0000000..8d9fbd4
--- /dev/null
+++ b/cmd/getgo/main.go
@@ -0,0 +1,115 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The getgo command installs Go to the user's system.
+package main
+
+import (
+	"bufio"
+	"context"
+	"errors"
+	"flag"
+	"fmt"
+	"os"
+	"os/exec"
+	"strings"
+)
+
+var (
+	interactive = flag.Bool("i", false, "Interactive mode, prompt for inputs.")
+	verbose     = flag.Bool("v", false, "Verbose.")
+	setupOnly   = flag.Bool("skip-dl", false, "Don't download - only set up environment variables")
+	goVersion   = flag.String("version", "", `Version of Go to install (e.g. "1.8.3"). If empty, uses the latest version.`)
+
+	version = "devel"
+)
+
+var exitCleanly error = errors.New("exit cleanly sentinel value")
+
+func main() {
+	flag.Parse()
+	if *goVersion != "" && !strings.HasPrefix(*goVersion, "go") {
+		*goVersion = "go" + *goVersion
+	}
+
+	ctx := context.Background()
+
+	verbosef("version " + version)
+
+	runStep := func(s step) {
+		err := s(ctx)
+		if err == exitCleanly {
+			os.Exit(0)
+		}
+		if err != nil {
+			fmt.Fprintln(os.Stderr, err)
+			os.Exit(2)
+		}
+	}
+
+	if !*setupOnly {
+		runStep(welcome)
+		runStep(checkOthers)
+		runStep(chooseVersion)
+		runStep(downloadGo)
+	}
+
+	runStep(setupGOPATH)
+}
+
+func verbosef(format string, v ...interface{}) {
+	if !*verbose {
+		return
+	}
+
+	fmt.Printf(format+"\n", v...)
+}
+
+func prompt(ctx context.Context, query, defaultAnswer string) (string, error) {
+	if !*interactive {
+		return defaultAnswer, nil
+	}
+
+	fmt.Printf("%s [%s]: ", query, defaultAnswer)
+
+	type result struct {
+		answer string
+		err    error
+	}
+	ch := make(chan result, 1)
+	go func() {
+		s := bufio.NewScanner(os.Stdin)
+		if !s.Scan() {
+			ch <- result{"", s.Err()}
+			return
+		}
+		answer := s.Text()
+		if answer == "" {
+			answer = defaultAnswer
+		}
+		ch <- result{answer, nil}
+	}()
+
+	select {
+	case r := <-ch:
+		return r.answer, r.err
+	case <-ctx.Done():
+		return "", ctx.Err()
+	}
+}
+
+func runCommand(ctx context.Context, prog string, args ...string) ([]byte, error) {
+	verbosef("Running command: %s %v", prog, args)
+
+	cmd := exec.CommandContext(ctx, prog, args...)
+	out, err := cmd.CombinedOutput()
+	if err != nil {
+		return nil, fmt.Errorf("running cmd '%s %s' failed: %s err: %v", prog, strings.Join(args, " "), string(out), err)
+	}
+	if out != nil && err == nil && len(out) != 0 {
+		verbosef("%s", out)
+	}
+
+	return out, nil
+}
diff --git a/cmd/getgo/main_test.go b/cmd/getgo/main_test.go
new file mode 100644
index 0000000..932a739
--- /dev/null
+++ b/cmd/getgo/main_test.go
@@ -0,0 +1,171 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"runtime"
+	"testing"
+)
+
+const (
+	testbin = "testgetgo"
+)
+
+var (
+	exeSuffix string // ".exe" on Windows
+)
+
+func init() {
+	if runtime.GOOS == "windows" {
+		exeSuffix = ".exe"
+	}
+}
+
+// TestMain creates a getgo command for testing purposes and
+// deletes it after the tests have been run.
+func TestMain(m *testing.M) {
+	if os.Getenv("GOGET_INTEGRATION") == "" {
+		fmt.Fprintln(os.Stderr, "main_test: Skipping integration tests with GOGET_INTEGRATION unset")
+		return
+	}
+
+	args := []string{"build", "-tags", testbin, "-o", testbin + exeSuffix}
+	out, err := exec.Command("go", args...).CombinedOutput()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "building %s failed: %v\n%s", testbin, err, out)
+		os.Exit(2)
+	}
+
+	// Don't let these environment variables confuse the test.
+	os.Unsetenv("GOBIN")
+	os.Unsetenv("GOPATH")
+	os.Unsetenv("GIT_ALLOW_PROTOCOL")
+	os.Unsetenv("PATH")
+
+	r := m.Run()
+
+	os.Remove(testbin + exeSuffix)
+
+	os.Exit(r)
+}
+
+func createTmpHome(t *testing.T) string {
+	tmpd, err := ioutil.TempDir("", "testgetgo")
+	if err != nil {
+		t.Fatalf("creating test tempdir failed: %v", err)
+	}
+
+	os.Setenv("HOME", tmpd)
+	return tmpd
+}
+
+// doRun runs the test getgo command, recording stdout and stderr and
+// returning exit status.
+func doRun(t *testing.T, args ...string) error {
+	var stdout, stderr bytes.Buffer
+	t.Logf("running %s %v", testbin, args)
+	cmd := exec.Command("./"+testbin+exeSuffix, args...)
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+	cmd.Env = os.Environ()
+	status := cmd.Run()
+	if stdout.Len() > 0 {
+		t.Log("standard output:")
+		t.Log(stdout.String())
+	}
+	if stderr.Len() > 0 {
+		t.Log("standard error:")
+		t.Log(stderr.String())
+	}
+	return status
+}
+
+func TestCommandVerbose(t *testing.T) {
+	tmpd := createTmpHome(t)
+	defer os.RemoveAll(tmpd)
+
+	err := doRun(t, "-v")
+	if err != nil {
+		t.Fatal(err)
+	}
+	// make sure things are in path
+	shellConfig, err := shellConfigFile()
+	if err != nil {
+		t.Fatal(err)
+	}
+	b, err := ioutil.ReadFile(shellConfig)
+	if err != nil {
+		t.Fatal(err)
+	}
+	home, err := getHomeDir()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := fmt.Sprintf(`
+export PATH=$PATH:%s/.go/bin
+
+export GOPATH=%s/go
+
+export PATH=$PATH:%s/go/bin
+`, home, home, home)
+
+	if string(b) != expected {
+		t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
+	}
+}
+
+func TestCommandPathExists(t *testing.T) {
+	tmpd := createTmpHome(t)
+	defer os.RemoveAll(tmpd)
+
+	// run once
+	err := doRun(t, "-skip-dl")
+	if err != nil {
+		t.Fatal(err)
+	}
+	// make sure things are in path
+	shellConfig, err := shellConfigFile()
+	if err != nil {
+		t.Fatal(err)
+	}
+	b, err := ioutil.ReadFile(shellConfig)
+	if err != nil {
+		t.Fatal(err)
+	}
+	home, err := getHomeDir()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := fmt.Sprintf(`
+export GOPATH=%s/go
+
+export PATH=$PATH:%s/go/bin
+`, home, home)
+
+	if string(b) != expected {
+		t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
+	}
+
+	// run twice
+	if err := doRun(t, "-skip-dl"); err != nil {
+		t.Fatal(err)
+	}
+
+	b, err = ioutil.ReadFile(shellConfig)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if string(b) != expected {
+		t.Fatalf("%s expected %q, got %q", shellConfig, expected, string(b))
+	}
+}
diff --git a/cmd/getgo/make.bash b/cmd/getgo/make.bash
new file mode 100755
index 0000000..cbc3685
--- /dev/null
+++ b/cmd/getgo/make.bash
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Copyright 2017 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+set -e -o -x
+
+LDFLAGS="-X main.version=$(git describe --always --dirty='*')"
+
+GOOS=windows GOARCH=386 go build -o build/installer.exe    -ldflags="$LDFLAGS"
+GOOS=linux GOARCH=386   go build -o build/installer_linux  -ldflags="$LDFLAGS"
+GOOS=darwin GOARCH=386  go build -o build/installer_darwin -ldflags="$LDFLAGS"
diff --git a/cmd/getgo/path.go b/cmd/getgo/path.go
new file mode 100644
index 0000000..551ac42
--- /dev/null
+++ b/cmd/getgo/path.go
@@ -0,0 +1,153 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"bufio"
+	"context"
+	"fmt"
+	"os"
+	"os/user"
+	"path/filepath"
+	"runtime"
+	"strings"
+)
+
+const (
+	bashConfig = ".bash_profile"
+	zshConfig  = ".zshrc"
+)
+
+// appendToPATH adds the given path to the PATH environment variable and
+// persists it for future sessions.
+func appendToPATH(value string) error {
+	if isInPATH(value) {
+		return nil
+	}
+	return persistEnvVar("PATH", pathVar+envSeparator+value)
+}
+
+func isInPATH(dir string) bool {
+	p := os.Getenv("PATH")
+
+	paths := strings.Split(p, envSeparator)
+	for _, d := range paths {
+		if d == dir {
+			return true
+		}
+	}
+
+	return false
+}
+
+func getHomeDir() (string, error) {
+	home := os.Getenv(homeKey)
+	if home != "" {
+		return home, nil
+	}
+
+	u, err := user.Current()
+	if err != nil {
+		return "", err
+	}
+	return u.HomeDir, nil
+}
+
+func checkStringExistsFile(filename, value string) (bool, error) {
+	file, err := os.OpenFile(filename, os.O_RDONLY, 0600)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return false, nil
+		}
+		return false, err
+	}
+	defer file.Close()
+
+	scanner := bufio.NewScanner(file)
+	for scanner.Scan() {
+		line := scanner.Text()
+		if line == value {
+			return true, nil
+		}
+	}
+
+	return false, scanner.Err()
+}
+
+func appendToFile(filename, value string) error {
+	verbosef("Adding %q to %s", value, filename)
+
+	ok, err := checkStringExistsFile(filename, value)
+	if err != nil {
+		return err
+	}
+	if ok {
+		// Nothing to do.
+		return nil
+	}
+
+	f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	_, err = f.WriteString(lineEnding + value + lineEnding)
+	return err
+}
+
+func isShell(name string) bool {
+	return strings.Contains(currentShell(), name)
+}
+
+// persistEnvVarWindows sets an environment variable in the Windows
+// registry.
+func persistEnvVarWindows(name, value string) error {
+	_, err := runCommand(context.Background(), "powershell", "-command",
+		fmt.Sprintf(`[Environment]::SetEnvironmentVariable("%s", "%s", "User")`, name, value))
+	return err
+}
+
+func persistEnvVar(name, value string) error {
+	if runtime.GOOS == "windows" {
+		if err := persistEnvVarWindows(name, value); err != nil {
+			return err
+		}
+
+		if isShell("cmd.exe") || isShell("powershell.exe") {
+			return os.Setenv(strings.ToUpper(name), value)
+		}
+		// User is in bash, zsh, etc.
+		// Also set the environment variable in their shell config.
+	}
+
+	rc, err := shellConfigFile()
+	if err != nil {
+		return err
+	}
+
+	line := fmt.Sprintf("export %s=%s", strings.ToUpper(name), value)
+	if err := appendToFile(rc, line); err != nil {
+		return err
+	}
+
+	return os.Setenv(strings.ToUpper(name), value)
+}
+
+func shellConfigFile() (string, error) {
+	home, err := getHomeDir()
+	if err != nil {
+		return "", err
+	}
+
+	switch {
+	case isShell("bash"):
+		return filepath.Join(home, bashConfig), nil
+	case isShell("zsh"):
+		return filepath.Join(home, zshConfig), nil
+	default:
+		return "", fmt.Errorf("%q is not a supported shell", currentShell())
+	}
+}
diff --git a/cmd/getgo/path_test.go b/cmd/getgo/path_test.go
new file mode 100644
index 0000000..4cf6647
--- /dev/null
+++ b/cmd/getgo/path_test.go
@@ -0,0 +1,56 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+)
+
+func TestAppendPath(t *testing.T) {
+	tmpd, err := ioutil.TempDir("", "go")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpd)
+
+	if err := os.Setenv("HOME", tmpd); err != nil {
+		t.Fatal(err)
+	}
+
+	GOPATH := os.Getenv("GOPATH")
+	if err := appendToPATH(filepath.Join(GOPATH, "bin")); err != nil {
+		t.Fatal(err)
+	}
+
+	shellConfig, err := shellConfigFile()
+	if err != nil {
+		t.Fatal(err)
+	}
+	b, err := ioutil.ReadFile(shellConfig)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := "export PATH=" + pathVar + envSeparator + filepath.Join(GOPATH, "bin")
+	if strings.TrimSpace(string(b)) != expected {
+		t.Fatalf("expected: %q, got %q", expected, strings.TrimSpace(string(b)))
+	}
+
+	// Check that appendToPATH is idempotent.
+	if err := appendToPATH(filepath.Join(GOPATH, "bin")); err != nil {
+		t.Fatal(err)
+	}
+	b, err = ioutil.ReadFile(shellConfig)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if strings.TrimSpace(string(b)) != expected {
+		t.Fatalf("expected: %q, got %q", expected, strings.TrimSpace(string(b)))
+	}
+}
diff --git a/cmd/getgo/server/README.md b/cmd/getgo/server/README.md
new file mode 100644
index 0000000..0cf629d
--- /dev/null
+++ b/cmd/getgo/server/README.md
@@ -0,0 +1,7 @@
+# getgo server
+
+## Deployment
+
+```
+gcloud app deploy --promote --project golang-org
+```
diff --git a/cmd/getgo/server/app.yaml b/cmd/getgo/server/app.yaml
new file mode 100644
index 0000000..0502c4e
--- /dev/null
+++ b/cmd/getgo/server/app.yaml
@@ -0,0 +1,7 @@
+runtime: go
+service: get
+api_version: go1
+
+handlers:
+- url: /.*
+  script: _go_app
diff --git a/cmd/getgo/server/main.go b/cmd/getgo/server/main.go
new file mode 100644
index 0000000..b599506
--- /dev/null
+++ b/cmd/getgo/server/main.go
@@ -0,0 +1,61 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Command server serves get.golang.org, redirecting users to the appropriate
+// getgo installer based on the request path.
+package server
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+	"time"
+)
+
+const (
+	base             = "https://storage.googleapis.com/golang/getgo/"
+	windowsInstaller = base + "installer.exe"
+	linuxInstaller   = base + "installer_linux"
+	macInstaller     = base + "installer_darwin"
+)
+
+// substring-based redirects.
+var stringMatch = map[string]string{
+	// via uname, from bash
+	"MINGW":  windowsInstaller, // Reported as MINGW64_NT-10.0 in git bash
+	"Linux":  linuxInstaller,
+	"Darwin": macInstaller,
+}
+
+func init() {
+	http.HandleFunc("/", handler)
+}
+
+func handler(w http.ResponseWriter, r *http.Request) {
+	if containsIgnoreCase(r.URL.Path, "installer.exe") {
+		// cache bust
+		http.Redirect(w, r, windowsInstaller+cacheBust(), http.StatusFound)
+		return
+	}
+
+	for match, redirect := range stringMatch {
+		if containsIgnoreCase(r.URL.Path, match) {
+			http.Redirect(w, r, redirect, http.StatusFound)
+			return
+		}
+	}
+
+	http.NotFound(w, r)
+}
+
+func containsIgnoreCase(s, substr string) bool {
+	return strings.Contains(
+		strings.ToLower(s),
+		strings.ToLower(substr),
+	)
+}
+
+func cacheBust() string {
+	return fmt.Sprintf("?%d", time.Now().Nanosecond())
+}
diff --git a/cmd/getgo/steps.go b/cmd/getgo/steps.go
new file mode 100644
index 0000000..eac6517
--- /dev/null
+++ b/cmd/getgo/steps.go
@@ -0,0 +1,131 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strings"
+)
+
+type step func(context.Context) error
+
+func welcome(ctx context.Context) error {
+	fmt.Println("Welcome to the Go installer!")
+	answer, err := prompt(ctx, "Would you like to install Go? Y/n", "Y")
+	if err != nil {
+		return err
+	}
+	if strings.ToLower(answer) != "y" {
+		fmt.Println("Exiting install.")
+		return exitCleanly
+	}
+
+	return nil
+}
+
+func checkOthers(ctx context.Context) error {
+	// TODO: if go is currently installed install new version over that
+	path, err := whichGo(ctx)
+	if err != nil {
+		fmt.Printf("Cannot check if Go is already installed:\n%v\n", err)
+	}
+	if path == "" {
+		return nil
+	}
+	if path != installPath {
+		fmt.Printf("Go is already installed at %v; remove it from your PATH.\n", path)
+	}
+	return nil
+}
+
+func chooseVersion(ctx context.Context) error {
+	if *goVersion != "" {
+		return nil
+	}
+
+	var err error
+	*goVersion, err = getLatestGoVersion()
+	if err != nil {
+		return err
+	}
+
+	answer, err := prompt(ctx, fmt.Sprintf("The latest Go version is %s, install that? Y/n", *goVersion), "Y")
+	if err != nil {
+		return err
+	}
+
+	if strings.ToLower(answer) != "y" {
+		// TODO: handle passing a version
+		fmt.Println("Aborting install.")
+		return exitCleanly
+	}
+
+	return nil
+}
+
+func downloadGo(ctx context.Context) error {
+	answer, err := prompt(ctx, fmt.Sprintf("Download Go version %s to %s? Y/n", *goVersion, installPath), "Y")
+	if err != nil {
+		return err
+	}
+
+	if strings.ToLower(answer) != "y" {
+		fmt.Println("Aborting install.")
+		return exitCleanly
+	}
+
+	fmt.Printf("Downloading Go version %s to %s\n", *goVersion, installPath)
+	fmt.Println("This may take a bit of time...")
+
+	if err := downloadGoVersion(*goVersion, runtime.GOOS, arch, installPath); err != nil {
+		return err
+	}
+
+	if err := appendToPATH(filepath.Join(installPath, "bin")); err != nil {
+		return err
+	}
+
+	fmt.Println("Downloaded!")
+	return nil
+}
+
+func setupGOPATH(ctx context.Context) error {
+	answer, err := prompt(ctx, "Would you like us to setup your GOPATH? Y/n", "Y")
+	if err != nil {
+		return err
+	}
+
+	if strings.ToLower(answer) != "y" {
+		fmt.Println("Exiting and not setting up GOPATH.")
+		return exitCleanly
+	}
+
+	fmt.Println("Setting up GOPATH")
+	home, err := getHomeDir()
+	if err != nil {
+		return err
+	}
+
+	gopath := os.Getenv("GOPATH")
+	if gopath == "" {
+		// set $GOPATH
+		gopath = filepath.Join(home, "go")
+		if err := persistEnvVar("GOPATH", gopath); err != nil {
+			return err
+		}
+		fmt.Println("GOPATH has been set up!")
+	} else {
+		verbosef("GOPATH is already set to %s", gopath)
+	}
+
+	if err := appendToPATH(filepath.Join(gopath, "bin")); err != nil {
+		return err
+	}
+	return persistEnvChangesForSession()
+}
diff --git a/cmd/getgo/system.go b/cmd/getgo/system.go
new file mode 100644
index 0000000..8b5b024
--- /dev/null
+++ b/cmd/getgo/system.go
@@ -0,0 +1,36 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"bytes"
+	"context"
+	"os/exec"
+	"runtime"
+	"strings"
+)
+
+// arch contains either amd64 or 386.
+var arch = func() string {
+	cmd := exec.Command("uname", "-m") // "x86_64"
+	if runtime.GOOS == "windows" {
+		cmd = exec.Command("powershell", "-command", "(Get-WmiObject -Class Win32_ComputerSystem).SystemType") // "x64-based PC"
+	}
+
+	out, err := cmd.Output()
+	if err != nil {
+		// a sensible default?
+		return "amd64"
+	}
+	if bytes.Contains(out, []byte("64")) {
+		return "amd64"
+	}
+	return "386"
+}()
+
+func findGo(ctx context.Context, cmd string) (string, error) {
+	out, err := exec.CommandContext(ctx, cmd, "go").CombinedOutput()
+	return strings.TrimSpace(string(out)), err
+}
diff --git a/cmd/getgo/system_unix.go b/cmd/getgo/system_unix.go
new file mode 100644
index 0000000..d2405c5
--- /dev/null
+++ b/cmd/getgo/system_unix.go
@@ -0,0 +1,55 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+)
+
+const (
+	envSeparator = ":"
+	homeKey      = "HOME"
+	lineEnding   = "\n"
+	pathVar      = "$PATH"
+)
+
+var installPath = func() string {
+	home, err := getHomeDir()
+	if err != nil {
+		return "/usr/local/go"
+	}
+
+	return filepath.Join(home, ".go")
+}()
+
+func whichGo(ctx context.Context) (string, error) {
+	return findGo(ctx, "which")
+}
+
+func isWindowsXP() bool {
+	return false
+}
+
+func currentShell() string {
+	return os.Getenv("SHELL")
+}
+
+func persistEnvChangesForSession() error {
+	shellConfig, err := shellConfigFile()
+	if err != nil {
+		return err
+	}
+	fmt.Println()
+	fmt.Printf("One more thing! Run `source %s` to persist the\n", shellConfig)
+	fmt.Println("new environment variables to your current session, or open a")
+	fmt.Println("new shell prompt.")
+
+	return nil
+}
diff --git a/cmd/getgo/system_windows.go b/cmd/getgo/system_windows.go
new file mode 100644
index 0000000..d8a6191
--- /dev/null
+++ b/cmd/getgo/system_windows.go
@@ -0,0 +1,86 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+package main
+
+import (
+	"context"
+	"log"
+	"os"
+	"syscall"
+	"unsafe"
+)
+
+const (
+	envSeparator = ";"
+	homeKey      = "USERPROFILE"
+	lineEnding   = "/r/n"
+	pathVar      = "$env:Path"
+)
+
+var installPath = `c:\go`
+
+func isWindowsXP() bool {
+	v, err := syscall.GetVersion()
+	if err != nil {
+		log.Fatalf("GetVersion failed: %v", err)
+	}
+	major := byte(v)
+	return major < 6
+}
+
+func whichGo(ctx context.Context) (string, error) {
+	return findGo(ctx, "where")
+}
+
+// currentShell reports the current shell.
+// It might be "powershell.exe", "cmd.exe" or any of the *nix shells.
+//
+// Returns empty string if the shell is unknown.
+func currentShell() string {
+	shell := os.Getenv("SHELL")
+	if shell != "" {
+		return shell
+	}
+
+	pid := os.Getppid()
+	pe, err := getProcessEntry(pid)
+	if err != nil {
+		verbosef("getting shell from process entry failed: %v", err)
+		return ""
+	}
+
+	return syscall.UTF16ToString(pe.ExeFile[:])
+}
+
+func getProcessEntry(pid int) (*syscall.ProcessEntry32, error) {
+	// From https://go.googlesource.com/go/+/go1.8.3/src/syscall/syscall_windows.go#941
+	snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
+	if err != nil {
+		return nil, err
+	}
+	defer syscall.CloseHandle(snapshot)
+
+	var procEntry syscall.ProcessEntry32
+	procEntry.Size = uint32(unsafe.Sizeof(procEntry))
+	if err = syscall.Process32First(snapshot, &procEntry); err != nil {
+		return nil, err
+	}
+
+	for {
+		if procEntry.ProcessID == uint32(pid) {
+			return &procEntry, nil
+		}
+
+		if err := syscall.Process32Next(snapshot, &procEntry); err != nil {
+			return nil, err
+		}
+	}
+}
+
+func persistEnvChangesForSession() error {
+	return nil
+}
diff --git a/cmd/getgo/upload.bash b/cmd/getgo/upload.bash
new file mode 100755
index 0000000..f52bb23
--- /dev/null
+++ b/cmd/getgo/upload.bash
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# Copyright 2017 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+if ! command -v gsutil 2>&1 > /dev/null; then
+  echo "Install gsutil:"
+  echo
+  echo "   https://cloud.google.com/storage/docs/gsutil_install#sdk-install"
+fi
+
+if [ ! -d build ]; then
+  echo "Run make.bash first"
+fi
+
+set -e -o -x
+
+gsutil -m cp -a public-read build/* gs://golang/getgo
diff --git a/cmd/tip/Dockerfile b/cmd/tip/Dockerfile
index 9c80137..86dfe36 100644
--- a/cmd/tip/Dockerfile
+++ b/cmd/tip/Dockerfile
@@ -5,11 +5,128 @@
 # golang puts its go install here (weird but true)
 ENV GOROOT_BOOTSTRAP /usr/local/go
 
-RUN go get -d golang.org/x/crypto/acme/autocert
+# BEGIN deps (run `make update-deps` to update)
+
+# Repo cloud.google.com/go at 76d607c (2017-07-20)
+ENV REV=76d607c4e7a2b9df49f1d1a58a3f3d2dd2614704
+RUN go get -d cloud.google.com/go/compute/metadata `#and 6 other pkgs` &&\
+    (cd /go/src/cloud.google.com/go && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo github.com/golang/protobuf at 0a4f71a (2017-07-11)
+ENV REV=0a4f71a498b7c4812f64969510bcb4eca251e33a
+RUN go get -d github.com/golang/protobuf/proto `#and 6 other pkgs` &&\
+    (cd /go/src/github.com/golang/protobuf && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo github.com/googleapis/gax-go at 84ed267 (2017-06-10)
+ENV REV=84ed26760e7f6f80887a2fbfb50db3cc415d2cea
+RUN go get -d github.com/googleapis/gax-go &&\
+    (cd /go/src/github.com/googleapis/gax-go && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo golang.org/x/build at da1460b (2017-07-31)
+ENV REV=da1460b7c9c9b65383d1336593ed9ad346f6a1c5
+RUN go get -d golang.org/x/build/autocertcache &&\
+    (cd /go/src/golang.org/x/build && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo golang.org/x/crypto at 6914964 (2017-07-20)
+ENV REV=6914964337150723782436d56b3f21610a74ce7b
+RUN go get -d golang.org/x/crypto/acme `#and 2 other pkgs` &&\
+    (cd /go/src/golang.org/x/crypto && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo golang.org/x/net at ab54850 (2017-07-21)
+ENV REV=ab5485076ff3407ad2d02db054635913f017b0ed
+RUN go get -d golang.org/x/net/context `#and 8 other pkgs` &&\
+    (cd /go/src/golang.org/x/net && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo golang.org/x/oauth2 at b53b38a (2017-07-19)
+ENV REV=b53b38ad8a6435bd399ea76d0fa74f23149cca4e
+RUN go get -d golang.org/x/oauth2 `#and 5 other pkgs` &&\
+    (cd /go/src/golang.org/x/oauth2 && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo golang.org/x/text at 836efe4 (2017-07-14)
+ENV REV=836efe42bb4aa16aaa17b9c155d8813d336ed720
+RUN go get -d golang.org/x/text/secure/bidirule `#and 4 other pkgs` &&\
+    (cd /go/src/golang.org/x/text && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo google.golang.org/api at 295e4bb (2017-07-18)
+ENV REV=295e4bb0ade057ae2cfb9876ab0b54635dbfcea4
+RUN go get -d google.golang.org/api/gensupport `#and 9 other pkgs` &&\
+    (cd /go/src/google.golang.org/api && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo google.golang.org/genproto at b0a3dcf (2017-07-12)
+ENV REV=b0a3dcfcd1a9bd48e63634bd8802960804cf8315
+RUN go get -d google.golang.org/genproto/googleapis/api/annotations `#and 3 other pkgs` &&\
+    (cd /go/src/google.golang.org/genproto && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Repo google.golang.org/grpc at fa1cb32 (2017-07-31)
+ENV REV=fa1cb32dc4f81e23ab862dd5e7ac4f2920a33088
+RUN go get -d google.golang.org/grpc `#and 14 other pkgs` &&\
+    (cd /go/src/google.golang.org/grpc && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV)
+
+# Optimization to speed up iterative development, not necessary for correctness:
+RUN go install cloud.google.com/go/compute/metadata \
+	cloud.google.com/go/iam \
+	cloud.google.com/go/internal \
+	cloud.google.com/go/internal/optional \
+	cloud.google.com/go/internal/version \
+	cloud.google.com/go/storage \
+	github.com/golang/protobuf/proto \
+	github.com/golang/protobuf/protoc-gen-go/descriptor \
+	github.com/golang/protobuf/ptypes \
+	github.com/golang/protobuf/ptypes/any \
+	github.com/golang/protobuf/ptypes/duration \
+	github.com/golang/protobuf/ptypes/timestamp \
+	github.com/googleapis/gax-go \
+	golang.org/x/build/autocertcache \
+	golang.org/x/crypto/acme \
+	golang.org/x/crypto/acme/autocert \
+	golang.org/x/net/context \
+	golang.org/x/net/context/ctxhttp \
+	golang.org/x/net/http2 \
+	golang.org/x/net/http2/hpack \
+	golang.org/x/net/idna \
+	golang.org/x/net/internal/timeseries \
+	golang.org/x/net/lex/httplex \
+	golang.org/x/net/trace \
+	golang.org/x/oauth2 \
+	golang.org/x/oauth2/google \
+	golang.org/x/oauth2/internal \
+	golang.org/x/oauth2/jws \
+	golang.org/x/oauth2/jwt \
+	golang.org/x/text/secure/bidirule \
+	golang.org/x/text/transform \
+	golang.org/x/text/unicode/bidi \
+	golang.org/x/text/unicode/norm \
+	google.golang.org/api/gensupport \
+	google.golang.org/api/googleapi \
+	google.golang.org/api/googleapi/internal/uritemplates \
+	google.golang.org/api/googleapi/transport \
+	google.golang.org/api/internal \
+	google.golang.org/api/iterator \
+	google.golang.org/api/option \
+	google.golang.org/api/storage/v1 \
+	google.golang.org/api/transport/http \
+	google.golang.org/genproto/googleapis/api/annotations \
+	google.golang.org/genproto/googleapis/iam/v1 \
+	google.golang.org/genproto/googleapis/rpc/status \
+	google.golang.org/grpc \
+	google.golang.org/grpc/codes \
+	google.golang.org/grpc/credentials \
+	google.golang.org/grpc/grpclb/grpc_lb_v1 \
+	google.golang.org/grpc/grpclog \
+	google.golang.org/grpc/internal \
+	google.golang.org/grpc/keepalive \
+	google.golang.org/grpc/metadata \
+	google.golang.org/grpc/naming \
+	google.golang.org/grpc/peer \
+	google.golang.org/grpc/stats \
+	google.golang.org/grpc/status \
+	google.golang.org/grpc/tap \
+	google.golang.org/grpc/transport
+# END deps.
 
 # golang sets GOPATH=/go
 ADD . /go/src/tip
-RUN go install tip
+RUN go install --tags=autocert tip
 ENTRYPOINT ["/go/bin/tip"]
 # App Engine expects us to listen on port 8080
 EXPOSE 8080
diff --git a/cmd/tip/Makefile b/cmd/tip/Makefile
index 5844307..7d2f6ed 100644
--- a/cmd/tip/Makefile
+++ b/cmd/tip/Makefile
@@ -2,7 +2,11 @@
 # Use of this source code is governed by a BSD-style
 # license that can be found in the LICENSE file.
 
-VERSION=v1
+VERSION=v2
+
+update-deps:
+	go install golang.org/x/build/cmd/gitlock
+	gitlock --update=Dockerfile --ignore=NONE golang.org/x/tools/cmd/tip
 
 docker-prod: Dockerfile
 	docker build -f Dockerfile --tag=gcr.io/symbolic-datum-552/tip:$(VERSION) .
@@ -10,6 +14,6 @@
 	docker build -f Dockerfile --tag=gcr.io/go-dashboard-dev/tip:$(VERSION) .
 
 push-prod: docker-prod
-	gcloud docker push -- gcr.io/symbolic-datum-552/tip:$(VERSION)
+	gcloud docker -- push gcr.io/symbolic-datum-552/tip:$(VERSION)
 push-dev: docker-dev
-	gcloud docker push -- gcr.io/go-dashboard-dev/tip:$(VERSION)
+	gcloud docker -- push gcr.io/go-dashboard-dev/tip:$(VERSION)
diff --git a/cmd/tip/cert.go b/cmd/tip/cert.go
new file mode 100644
index 0000000..e00777b
--- /dev/null
+++ b/cmd/tip/cert.go
@@ -0,0 +1,50 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+// +build autocert
+
+// This file contains autocert and cloud.google.com/go/storage
+// dependencies we want to hide by default from the Go build system,
+// which currently doesn't know how to fetch non-golang.org/x/*
+// dependencies. The Dockerfile builds the production binary
+// with this code using --tags=autocert.
+
+package main
+
+import (
+	"context"
+	"crypto/tls"
+	"log"
+	"net/http"
+
+	"cloud.google.com/go/storage"
+	"golang.org/x/build/autocertcache"
+	"golang.org/x/crypto/acme/autocert"
+)
+
+func init() {
+	runHTTPS = runHTTPSAutocert
+}
+
+func runHTTPSAutocert(h http.Handler) error {
+	var cache autocert.Cache
+	if b := *autoCertCacheBucket; b != "" {
+		sc, err := storage.NewClient(context.Background())
+		if err != nil {
+			log.Fatalf("storage.NewClient: %v", err)
+		}
+		cache = autocertcache.NewGoogleCloudStorageCache(sc, b)
+	}
+	m := autocert.Manager{
+		Prompt:     autocert.AcceptTOS,
+		HostPolicy: autocert.HostWhitelist(*autoCertDomain),
+		Cache:      cache,
+	}
+	s := &http.Server{
+		Addr:      ":https",
+		Handler:   h,
+		TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
+	}
+	return s.ListenAndServeTLS("", "")
+}
diff --git a/cmd/tip/tip-rc.yaml b/cmd/tip/tip-rc.yaml
index 139e614..82af3b7 100644
--- a/cmd/tip/tip-rc.yaml
+++ b/cmd/tip/tip-rc.yaml
@@ -1,7 +1,7 @@
 apiVersion: v1
 kind: ReplicationController
 metadata:
-  name: tipgodoc-v1
+  name: tipgodoc
 spec:
   replicas: 1
   selector:
@@ -17,9 +17,9 @@
         emptyDir: {}
       containers:
       - name: gitmirror
-        image: gcr.io/symbolic-datum-552/tip:v1
+        image: gcr.io/symbolic-datum-552/tip:v2
         imagePullPolicy: Always
-        command: ["/go/bin/tip", "--autocert=tip.golang.org"]
+        command: ["/go/bin/tip", "--autocert=tip.golang.org", "--autocert-bucket=golang-tip-autocert"]
         env:
         - name: TMPDIR
           value: /build
diff --git a/cmd/tip/tip.go b/cmd/tip/tip.go
index 428bcf0..6e0fd47 100644
--- a/cmd/tip/tip.go
+++ b/cmd/tip/tip.go
@@ -8,7 +8,6 @@
 
 import (
 	"bufio"
-	"crypto/tls"
 	"encoding/json"
 	"errors"
 	"flag"
@@ -24,8 +23,6 @@
 	"path/filepath"
 	"sync"
 	"time"
-
-	"golang.org/x/crypto/acme/autocert"
 )
 
 const (
@@ -37,9 +34,14 @@
 var startTime = time.Now()
 
 var (
-	autoCertDomain = flag.String("autocert", "", "if non-empty, listen on port 443 and serve a LetsEncrypt cert for this hostname")
+	autoCertDomain      = flag.String("autocert", "", "if non-empty, listen on port 443 and serve a LetsEncrypt cert for this hostname")
+	autoCertCacheBucket = flag.String("autocert-bucket", "", "if non-empty, the Google Cloud Storage bucket in which to store the LetsEncrypt cache")
 )
 
+// runHTTPS, if non-nil, specifies the function to serve HTTPS.
+// It is set non-nil in cert.go with the "autocert" build tag.
+var runHTTPS func(http.Handler) error
+
 func main() {
 	flag.Parse()
 
@@ -60,25 +62,20 @@
 
 	log.Printf("Starting up tip server for builder %q", os.Getenv(k))
 
-	errc := make(chan error)
+	errc := make(chan error, 1)
 
 	go func() {
 		errc <- http.ListenAndServe(":8080", mux)
 	}()
 	if *autoCertDomain != "" {
+		if runHTTPS == nil {
+			errc <- errors.New("can't use --autocert without building binary with the autocert build tag")
+		} else {
+			go func() {
+				errc <- runHTTPS(mux)
+			}()
+		}
 		log.Printf("Listening on port 443 with LetsEncrypt support on domain %q", *autoCertDomain)
-		m := autocert.Manager{
-			Prompt:     autocert.AcceptTOS,
-			HostPolicy: autocert.HostWhitelist(*autoCertDomain),
-		}
-		s := &http.Server{
-			Addr:      ":https",
-			Handler:   mux,
-			TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
-		}
-		go func() {
-			errc <- s.ListenAndServeTLS("", "")
-		}()
 	}
 	if err := <-errc; err != nil {
 		p.stop()
diff --git a/godoc/README.md b/godoc/README.md
index 1c495c8..52bc8a4 100644
--- a/godoc/README.md
+++ b/godoc/README.md
@@ -10,7 +10,7 @@
 flag to load CSS/JS/templates from disk every time a page loads:
 
 ```
-godoc --templates=$GOPATH/src/golang.org/x/tools/godoc/static --http=:6060
+godoc -templates=$GOPATH/src/golang.org/x/tools/godoc/static -http=:6060
 ```
 
 ## Recompiling static assets
diff --git a/godoc/dl/dl.go b/godoc/dl/dl.go
index b4230b6..3d2f6c4 100644
--- a/godoc/dl/dl.go
+++ b/godoc/dl/dl.go
@@ -32,7 +32,6 @@
 	"google.golang.org/appengine/datastore"
 	"google.golang.org/appengine/log"
 	"google.golang.org/appengine/memcache"
-	"google.golang.org/appengine/user"
 )
 
 const (
@@ -183,7 +182,6 @@
 type listTemplateData struct {
 	Featured                  []Feature
 	Stable, Unstable, Archive []Release
-	LoginURL                  string
 }
 
 var (
@@ -218,11 +216,6 @@
 			d.Featured = filesToFeatured(d.Stable[0].Files)
 		}
 
-		d.LoginURL, _ = user.LoginURL(c, "/dl")
-		if user.Current(c) != nil {
-			d.LoginURL, _ = user.LogoutURL(c, "/dl")
-		}
-
 		item := &memcache.Item{Key: cacheKey, Object: &d, Expiration: cacheDuration}
 		if err := memcache.Gob.Set(c, item); err != nil {
 			log.Errorf(c, "cache set error: %v", err)
diff --git a/godoc/dl/tmpl.go b/godoc/dl/tmpl.go
index e92f9b7..440917a 100644
--- a/godoc/dl/tmpl.go
+++ b/godoc/dl/tmpl.go
@@ -106,7 +106,7 @@
 </p>
 
 <p>
-If you are building from source, 
+If you are building from source,
 follow the <a href="/doc/install/source">source installation instructions</a>.
 </p>
 
@@ -146,13 +146,6 @@
 </div>
 {{end}}
 
-
-<!-- Disabled for now; there's no admin functionality yet.
-<p>
-<small><a href="{{.LoginURL}}">&pi;</a></small>
-</p>
--->
-
 <div id="footer">
         <p>
         Except as
diff --git a/godoc/godoc.go b/godoc/godoc.go
index a9e8b3b..d6c27d0 100644
--- a/godoc/godoc.go
+++ b/godoc/godoc.go
@@ -421,9 +421,9 @@
 }
 
 type PageInfo struct {
-	Dirname string // directory containing the package
-	Err     error  // error or nil
-	Share   bool   // show share button on examples
+	Dirname  string // directory containing the package
+	Err      error  // error or nil
+	GoogleCN bool   // page is being served from golang.google.cn
 
 	Mode PageInfoMode // display metadata from query string
 
@@ -461,17 +461,14 @@
 	return "pkg/" + path
 }
 
-// srcToPkgLinkFunc builds an <a> tag linking to
-// the package documentation of relpath.
+// srcToPkgLinkFunc builds an <a> tag linking to the package
+// documentation of relpath.
 func srcToPkgLinkFunc(relpath string) string {
 	relpath = pkgLinkFunc(relpath)
-	if relpath == "pkg/" {
+	relpath = pathpkg.Dir(relpath)
+	if relpath == "pkg" {
 		return `<a href="/pkg">Index</a>`
 	}
-	if i := strings.LastIndex(relpath, "/"); i != -1 {
-		// Remove filename after last slash.
-		relpath = relpath[:i]
-	}
 	return fmt.Sprintf(`<a href="/%s">%s</a>`, relpath, relpath[len("pkg/"):])
 }
 
@@ -683,8 +680,8 @@
 
 		err := p.ExampleHTML.Execute(&buf, struct {
 			Name, Doc, Code, Play, Output string
-			Share                         bool
-		}{eg.Name, eg.Doc, code, play, out, info.Share})
+			GoogleCN                      bool
+		}{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
 		if err != nil {
 			log.Print(err)
 		}
diff --git a/godoc/godoc_test.go b/godoc/godoc_test.go
index dca1c95..c1d631c 100644
--- a/godoc/godoc_test.go
+++ b/godoc/godoc_test.go
@@ -313,6 +313,8 @@
 	}{
 		{"src/", `<a href="/pkg">Index</a>`},
 		{"src/fmt/", `<a href="/pkg/fmt">fmt</a>`},
+		{"pkg/", `<a href="/pkg">Index</a>`},
+		{"pkg/LICENSE", `<a href="/pkg">Index</a>`},
 	} {
 		if got := srcToPkgLinkFunc(tc.path); got != tc.want {
 			t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
diff --git a/godoc/page.go b/godoc/page.go
index 0c7bf00..10e86e5 100644
--- a/godoc/page.go
+++ b/godoc/page.go
@@ -9,6 +9,7 @@
 	"os"
 	"path/filepath"
 	"runtime"
+	"strings"
 )
 
 // Page describes the contents of the top-level godoc webpage.
@@ -19,7 +20,7 @@
 	SrcPath  string
 	Query    string
 	Body     []byte
-	Share    bool
+	GoogleCN bool // page is being served from golang.google.cn
 
 	// filled in by servePage
 	SearchBox  bool
@@ -51,19 +52,25 @@
 		Title:    "File " + relpath,
 		Subtitle: relpath,
 		Body:     applyTemplate(p.ErrorHTML, "errorHTML", err),
-		Share:    allowShare(r),
+		GoogleCN: googleCN(r),
 	})
 }
 
-var onAppengine = false // overriden in appengine.go when on app engine
+var onAppengine = false // overridden in appengine.go when on app engine
 
-func allowShare(r *http.Request) bool {
+func googleCN(r *http.Request) bool {
+	if r.FormValue("googlecn") != "" {
+		return true
+	}
 	if !onAppengine {
+		return false
+	}
+	if strings.HasSuffix(r.Host, ".cn") {
 		return true
 	}
 	switch r.Header.Get("X-AppEngine-Country") {
 	case "", "ZZ", "CN":
-		return false
+		return true
 	}
-	return true
+	return false
 }
diff --git a/godoc/proxy/proxy.go b/godoc/proxy/proxy.go
index e8cb18e..0ddaa00 100644
--- a/godoc/proxy/proxy.go
+++ b/godoc/proxy/proxy.go
@@ -19,6 +19,7 @@
 	"net/http"
 	"net/http/httputil"
 	"net/url"
+	"strings"
 	"time"
 
 	"golang.org/x/net/context"
@@ -147,8 +148,8 @@
 }
 
 func share(w http.ResponseWriter, r *http.Request) {
-	if !allowShare(r) {
-		http.Error(w, "Forbidden", http.StatusForbidden)
+	if googleCN(r) {
+		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
 		return
 	}
 	target, _ := url.Parse(playgroundURL)
@@ -157,13 +158,19 @@
 	p.ServeHTTP(w, r)
 }
 
-func allowShare(r *http.Request) bool {
+func googleCN(r *http.Request) bool {
+	if r.FormValue("googlecn") != "" {
+		return true
+	}
 	if appengine.IsDevAppServer() {
+		return false
+	}
+	if strings.HasSuffix(r.Host, ".cn") {
 		return true
 	}
 	switch r.Header.Get("X-AppEngine-Country") {
 	case "", "ZZ", "CN":
-		return false
+		return true
 	}
-	return true
+	return false
 }
diff --git a/godoc/redirect/redirect.go b/godoc/redirect/redirect.go
index c81d05f..f86e0ad 100644
--- a/godoc/redirect/redirect.go
+++ b/godoc/redirect/redirect.go
@@ -196,7 +196,7 @@
 	if n, err := strconv.Atoi(id); err == nil && n > 150000 {
 		target = "https://codereview.appspot.com/" + id
 	} else {
-		target = "https://go-review.googlesource.com/r/" + id
+		target = "https://go-review.googlesource.com/" + id
 	}
 	http.Redirect(w, r, target, http.StatusFound)
 }
diff --git a/godoc/redirect/redirect_test.go b/godoc/redirect/redirect_test.go
index 8a02de3..cfc2f97 100644
--- a/godoc/redirect/redirect_test.go
+++ b/godoc/redirect/redirect_test.go
@@ -58,8 +58,8 @@
 		"/design/123-foo":      {302, "https://github.com/golang/proposal/blob/master/design/123-foo.md"},
 		"/design/text/123-foo": {302, "https://github.com/golang/proposal/blob/master/design/text/123-foo.md"},
 
-		"/cl/1":          {302, "https://go-review.googlesource.com/r/1"},
-		"/cl/1/":         {302, "https://go-review.googlesource.com/r/1"},
+		"/cl/1":          {302, "https://go-review.googlesource.com/1"},
+		"/cl/1/":         {302, "https://go-review.googlesource.com/1"},
 		"/cl/267120043":  {302, "https://codereview.appspot.com/267120043"},
 		"/cl/267120043/": {302, "https://codereview.appspot.com/267120043"},
 	}
diff --git a/godoc/search.go b/godoc/search.go
index 63d9e76..d61193f 100644
--- a/godoc/search.go
+++ b/godoc/search.go
@@ -126,7 +126,7 @@
 		Tabtitle: query,
 		Query:    query,
 		Body:     body.Bytes(),
-		Share:    allowShare(r),
+		GoogleCN: googleCN(r),
 	})
 }
 
diff --git a/godoc/server.go b/godoc/server.go
index c9b4056..3b452e5 100644
--- a/godoc/server.go
+++ b/godoc/server.go
@@ -312,13 +312,13 @@
 		info.TypeInfoIndex[ti.Name] = i
 	}
 
-	info.Share = allowShare(r)
+	info.GoogleCN = googleCN(r)
 	h.p.ServePage(w, Page{
 		Title:    title,
 		Tabtitle: tabtitle,
 		Subtitle: subtitle,
 		Body:     applyTemplate(h.p.PackageHTML, "packageHTML", info),
-		Share:    info.Share,
+		GoogleCN: info.GoogleCN,
 	})
 }
 
@@ -583,7 +583,7 @@
 		SrcPath:  relpath,
 		Tabtitle: relpath,
 		Body:     buf.Bytes(),
-		Share:    allowShare(r),
+		GoogleCN: googleCN(r),
 	})
 }
 
@@ -654,7 +654,7 @@
 		SrcPath:  relpath,
 		Tabtitle: relpath,
 		Body:     applyTemplate(p.DirlistHTML, "dirlistHTML", list),
-		Share:    allowShare(r),
+		GoogleCN: googleCN(r),
 	})
 }
 
@@ -683,7 +683,7 @@
 	page := Page{
 		Title:    meta.Title,
 		Subtitle: meta.Subtitle,
-		Share:    allowShare(r),
+		GoogleCN: googleCN(r),
 	}
 
 	// evaluate as template if indicated
diff --git a/godoc/static/example.html b/godoc/static/example.html
index 3bc0eb9..1e86b25 100644
--- a/godoc/static/example.html
+++ b/godoc/static/example.html
@@ -13,7 +13,7 @@
 				<div class="buttons">
 					<a class="run" title="Run this code [shift-enter]">Run</a>
 					<a class="fmt" title="Format this code">Format</a>
-					{{if $.Share}}
+					{{if not $.GoogleCN}}
 					<a class="share" title="Share this code">Share</a>
 					{{end}}
 				</div>
diff --git a/godoc/static/godoc.html b/godoc/static/godoc.html
index 92b10aa..b7f6c11 100644
--- a/godoc/static/godoc.html
+++ b/godoc/static/godoc.html
@@ -55,7 +55,7 @@
 	<div class="buttons">
 		<a class="run" title="Run this code [shift-enter]">Run</a>
 		<a class="fmt" title="Format this code">Format</a>
-		{{if $.Share}}
+		{{if not $.GoogleCN}}
 		<a class="share" title="Share this code">Share</a>
 		{{end}}
 	</div>
@@ -95,7 +95,7 @@
 the content of this page is licensed under the
 Creative Commons Attribution 3.0 License,
 and code is licensed under a <a href="/LICENSE">BSD license</a>.<br>
-<a href="/doc/tos.html">Terms of Service</a> | 
+<a href="/doc/tos.html">Terms of Service</a> |
 <a href="http://www.google.com/intl/en/policies/privacy/">Privacy Policy</a>
 </div>
 
diff --git a/imports/fix.go b/imports/fix.go
index ac7f4b0..e246084 100644
--- a/imports/fix.go
+++ b/imports/fix.go
@@ -776,7 +776,7 @@
 	sort.Sort(byImportPathShortLength(candidates))
 	if Debug {
 		for i, pkg := range candidates {
-			log.Printf("%s candidate %d/%d: %v", pkgName, i+1, len(candidates), pkg.importPathShort)
+			log.Printf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), pkg.importPathShort, pkg.dir)
 		}
 	}