# Add Docker SSH end-to-end test

`dec0ec0`→[main](/content/gh/entireio/git-sync/commits/main/index.html)·

Soph·2mo ago·3 files·+332 added/-0 removed

## Sessions

6f3629569e1eView transcript

## Changes

3

- docs
  
  - Mtesting.md+11

- internal/syncer
  
  - Assh\_docker\_test.go+317

- Mmise.toml+4

````
31 unmodified lines

## Docker SSH End-To-End Test

Optional end-to-end sync against a real `sshd` running in Docker:

```bash
env GOCACHE=/tmp/go-build GITSYNC_E2E_SSH_DOCKER=1 go test ./internal/syncer -run TestRun_SSHDockerSync -v
```

That path builds a disposable Docker image with `openssh-server` and `git`, mounts local bare source/target repositories into the container, configures a temporary SSH keypair plus an isolated SSH config passed via a wrapper `ssh` binary, and runs a real SSH-based sync through `git-upload-pack` / `git-receive-pack`.

## Live Linux Smokes

Optional live Linux bootstrap smoke:

- `mise run test` — default suite
- `mise run test:ci` — default suite with race detection
- `mise run test:git-http-backend` — `git-http-backend` end-to-end
- `mise run test:ssh-docker` — Docker-based SSH end-to-end
- `mise run test:linux-smoke` — live Linux bootstrap smoke
- `mise run test:linux-smoke:batched` — live Linux batched bootstrap smoke
````

```go
package syncer

import (
	"context"
	"encoding/base64"
	"fmt"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
	"time"

"entire.io/entire/git-sync/internal/gitproto"
	"github.com/go-git/go-git/v6/plumbing"
)

const sshDockerEnv = "GITSYNC_E2E_SSH_DOCKER"

func TestRun_SSHDockerSync(t *testing.T) {
	if os.Getenv(sshDockerEnv) == "" {
		t.Skip("set GITSYNC_E2E_SSH_DOCKER=1 to run docker-based SSH integration test")
	}

for _, tool := range []string{"docker", "git", "ssh", "ssh-keygen"} {
		if _, err := exec.LookPath(tool); err != nil {
			t.Skipf("%s not available: %%v", tool, err)
		}
	}

root := dockerBindMountTempDir(t)
	reposDir := filepath.Join(root, "repos")
	worktree := filepath.Join(root, "worktree")
	sourceBare := filepath.Join(reposDir, "source.git")
	targetBare := filepath.Join(reposDir, "target.git")

if err := os.MkdirAll(reposDir, 0o755); err != nil {
		t.Fatalf("mkdir repos: %%v", err)
	}

runGit(t, root, "init", "--bare", sourceBare)
	runGit(t, root, "init", "--bare", targetBare)
	runGit(t, root, "init", "-b", testBranch, worktree)
	runGit(t, worktree, "config", "user.name", "git-sync test")
	runGit(t, worktree, "config", "user.email", "git-sync@example.com")

writeFile(t, filepath.Join(worktree, "README.md"), "hello over docker ssh\n")
	runGit(t, worktree, "add", "README.md")
	runGit(t, worktree, "commit", "-m", "initial")
	runGit(t, worktree, "remote", "add", "origin", sourceBare)
	runGit(t, worktree, "push", "origin", "HEAD:refs/heads/"+testBranch)

keyPath := filepath.Join(root, "id_ed25519")
	runCommand(t, root, nil, "ssh-keygen", "-t", "ed25519", "-N", "", "-f", keyPath)
	pubKey, err := os.ReadFile(keyPath + ".pub")
	if err != nil {
		t.Fatalf("read public key: %%v", err)
	}
	pubKeyB64 := base64.StdEncoding.EncodeToString([]byte(strings.TrimSpace(string(pubKey))))

imageCtx := filepath.Join(root, "image")
	writeSSHServerDockerContext(t, imageCtx)

imageTag := fmt.Sprintf("git-sync-ssh-e2e:%%d", rand.New(rand.NewSource(time.Now().UnixNano())).Int63())
	runCommand(t, imageCtx, nil, "docker", "build", "-t", imageTag, ".")

t.Cleanup(func() {
		_ = runCommandBestEffort(root, nil, "docker", "image", "rm", "-f", imageTag)
	})

containerID := strings.TrimSpace(runCommand(t, root, nil,
		"docker", "run", "-d",
		"-e", "AUTHORIZED_KEY_B64="+pubKeyB64,
		"-P",
		imageTag,
	))

t.Cleanup(func() {
		_ = runCommandBestEffort(root, nil, "docker", "rm", "-f", containerID)
	})

runCommand(t, root, nil, "docker", "cp", sourceBare, containerID+":/srv/git/source.git")
	runCommand(t, root, nil, "docker", "cp", targetBare, containerID+":/srv/git/target.git")
	runCommand(t, root, nil, "docker", "exec", containerID, "chown", "-R", "git:git", "/srv/git")

portOutput := strings.TrimSpace(runCommandWithDiagnostics(t, root, nil, containerID,
		"docker", "port", containerID, "22/tcp",
	))
	port, err := parseDockerPort(portOutput)
	if err != nil {
		t.Fatalf("parse docker port %%q: %%v", portOutput, err)
	}
	if port == "" {
		t.Fatal("docker inspect returned empty SSH host port")
	}

sshConfigPath := filepath.Join(root, "ssh_config")
	writeFile(t, sshConfigPath, strings.Join([]string{
		"Host gitsync-ssh-docker",
		"  HostName 127.0.0.1",
		"  Port " + port,
		"  User git",
		"  IdentityFile " + keyPath,
		"  IdentitiesOnly yes",
		"  StrictHostKeyChecking no",
		"  UserKnownHostsFile /dev/null",
		"  BatchMode yes",
		"  ",
	}, "\n"))

sshPath, err := exec.LookPath("ssh")
	if err != nil {
		t.Fatalf("locate ssh: %%v", err)
	}
	sshWrapper := filepath.Join(root, "ssh-wrapper.sh")
	writeFile(t, sshWrapper, strings.Join([]string{
		"#!/bin/sh",
		"exec " + shSingleQuote(sshPath) + " -F " + shSingleQuote(sshConfigPath) + ` "$@"`, 
		"",
	}, "\n"))
	if err := os.Chmod(sshWrapper, 0o755); err != nil {
		t.Fatalf("chmod ssh wrapper: %%v", err)
	}

orig := gitproto.SSHLookPath
	t.Cleanup(func() { gitproto.SSHLookPath = orig })
	gitproto.SSHLookPath = func(string) (string, error) { return sshWrapper, nil }

waitForSSHReady(t, sshWrapper, containerID)

result, err := Run(context.Background(), Config{
		Source:       Endpoint{URL: "ssh://gitsync-ssh-docker/srv/git/source.git"},
		Target:       Endpoint{URL: "ssh://gitsync-ssh-docker/srv/git/target.git"},
		ProtocolMode: protocolModeAuto,
	})
	if err != nil {
		t.Fatalf("Run over Docker SSH failed: %%v", err)
	}
	if result.Protocol != protocolModeV2 {
		t.Fatalf("result.Protocol = %%q, want %%q", result.Protocol, protocolModeV2)
	}
	if !result.Relay || result.RelayMode != relayModeBootstrap {
		t.Fatalf("expected bootstrap relay over SSH, got %+v", result)
	}

syncedTargetBare := filepath.Join(root, "synced-target.git")
	runCommand(t, root, nil, "docker", "cp", containerID+":/srv/git/target.git", syncedTargetBare)
	assertGitRefEqual(t, sourceBare, syncedTargetBare, plumbing.NewBranchReferenceName(testBranch))
}

func writeSSHServerDockerContext(t *testing.T, dir string) {
	t.Helper()
	if err := os.MkdirAll(dir, 0o755); err != nil {
		t.Fatalf("mkdir docker context: %%v", err)
	}

writeFile(t, filepath.Join(dir, "Dockerfile"), strings.Join([]string{
		"FROM alpine:3.21",
		"RUN apk add --no-cache git openssh-server",
		"RUN adduser -D -h /home/git -s /bin/sh git",
		`RUN sed -i 's/^git:!:/git::/' /etc/shadow`,
		"EXPOSE 22",
		"COPY sshd_config /etc/ssh/sshd_config",
		"COPY entrypoint.sh /entrypoint.sh",
		"RUN chmod +x /entrypoint.sh",
		`ENTRYPOINT ["/entrypoint.sh"]`,
		"",
	}, "\n"))

writeFile(t, filepath.Join(dir, "sshd_config"), strings.Join([]string{
		"Port 22",
		"ListenAddress 0.0.0.0",
		"PasswordAuthentication no",
		"KbdInteractiveAuthentication no",
		"ChallengeResponseAuthentication no",
		"UsePAM no",
		"PermitRootLogin no",
		"PubkeyAuthentication yes",
		"AuthorizedKeysFile .ssh/authorized_keys",
		"AllowUsers git",
		"LogLevel VERBOSE",
		"Subsystem sftp internal-sftp",
		"PidFile /run/sshd.pid",
		"",
	}, "\n"))

writeFile(t, filepath.Join(dir, "entrypoint.sh"), strings.Join([]string{
		"#!/bin/sh",
		"set -eu",
		"mkdir -p /run/sshd /home/git/.ssh /srv/git",
		`printf '%s' "$AUTHORIZED_KEY_B64" | base64 -d > /home/git/.ssh/authorized_keys`,
		"chmod 700 /home/git/.ssh",
		"chmod 600 /home/git/.ssh/authorized_keys",
		"chown -R git:git /home/git /srv/git",
		"ssh-keygen -A >/dev/null 2>&1",
		"exec /usr/sbin/sshd -D -e",
		"",
	}, "\n"))
}

// Additional helper functions omitted for brevity.
