Skip to content

refactor: replace moat-init.sh with compiled Go init binary #340

@dpup

Description

@dpup

Summary

internal/deps/scripts/moat-init.sh has grown to ~440 lines of POSIX shell handling host entries, SSH agent bridging, agent config setup (Claude/Codex/Gemini), Docker-in-Docker, clipboard bridging, git config, pre-run hooks, and privilege dropping. Each new feature adds conditional blocks with retry loops and error handling that's hard to test and brittle across distros.

Replace it with a small, statically-linked Go binary that gets bind-mounted into the container at runtime.

Motivation

  • Testability: Shell logic is tested only via E2E. A Go binary can be unit-tested for each init phase (host entry writing, SSH bridging, file staging, privilege dropping).
  • Portability: Shell assumptions (e.g., getent ahostsv4 availability, GNU stat -c format, socat presence) vary across base images. Go's stdlib handles DNS, file ops, and user lookup natively.
  • Error handling: set -e provides coarse-grained error handling. Go gives structured errors per phase with clear reporting.
  • Complexity ceiling: The script is manageable today but each new feature (most recently DNS-resolved host entries in fix(proxy): reach host from custom networks on Docker Desktop #337) adds conditional paths that compound. A compiled binary provides a better foundation for future growth.

Approach

  1. Build cmd/moat-init/main.go — reads the same MOAT_* env vars, performs the same setup phases in order.
  2. Cross-compile as a static binary (CGO_ENABLED=0) during moat run image build or cache it.
  3. Bind-mount the binary into the container (e.g., /usr/local/bin/moat-init) and use it as the entrypoint wrapper.
  4. Each init phase becomes a function with its own tests — host entries, SSH bridge, agent config copy, DinD, git config, pre-run hook, privilege drop.
  5. Remove the shell script once the binary is stable.

Considerations

  • Binary size: static Go binary will be ~5-10MB. Acceptable for a bind mount.
  • Architecture: need to match the container's target arch (typically linux/amd64 or linux/arm64). May need to cross-compile or detect at runtime.
  • gosu replacement: Go can drop privileges natively via syscall.Setuid/syscall.Setgid, eliminating the gosu dependency.
  • Backward compat: existing moat-init.sh keeps working until the binary is validated across all runtimes.

Signal to start

This becomes urgent when the next feature needs conditional logic in moat-init.sh. Until then, the shell script is functional.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions