scripts/setup-dev-alias.sh261 of 261 statements covered (100.00%).
coveredmissednever traced by Bash (not counted)A line ending in … continues the statement above it and shares its fate.
| 1 | #!/usr/bin/env bash | |
| 2 | # | |
| 3 | # setup-dev-alias.sh — install a `gco` shell function that runs the GCO CLI | |
| 4 | # inside the dev container against your current working directory. | |
| 5 | # | |
| 6 | # Why a function instead of a bare alias? A function forwards arguments and | |
| 7 | # pipes correctly, attaches a TTY only when one is present (so it also works | |
| 8 | # in scripts and CI), and bakes in the correct container socket for the | |
| 9 | # runtime you actually have — which a copy-pasted `docker run ...` alias does | |
| 10 | # not. The block is written between marker lines, so re-running this script | |
| 11 | # updates it in place instead of appending duplicates. | |
| 12 | # | |
| 13 | # By default it also builds (or refreshes) the dev image from Dockerfile.dev | |
| 14 | # with the detected runtime before installing the function, so a single run | |
| 15 | # takes a fresh clone all the way to a working `gco`. Re-running always rebuilds | |
| 16 | # (cached layers make that cheap), which transparently replaces a stale local | |
| 17 | # image. Pass --no-build to skip the build when you manage the image yourself. | |
| 18 | # | |
| 19 | 77 | set -euo pipefail |
| 20 | ||
| 21 | 77 | MARKER_BEGIN="# >>> gco >>>" |
| 22 | 77 | MARKER_END="# <<< gco <<<" |
| 23 | 77 | IMAGE="gco-dev" |
| 24 | 77 | FORCED_RUNTIME="" |
| 25 | 77 | RC_FILE="" |
| 26 | 77 | PRINT_ONLY=0 |
| 27 | 77 | NO_BUILD=0 |
| 28 | 77 | AWS_WRITABLE=0 |
| 29 | 77 | UNINSTALL=0 |
| 30 | ||
| 31 | # Host environment variables forwarded into the container in bare `-e NAME` | |
| 32 | # form. Every runtime (Docker, Finch/nerdctl, and Podman) passes a variable only | |
| 33 | # when it is set in the caller's environment, so an unset variable never becomes | |
| 34 | # an empty override. The AWS entries preserve the host credential/Region chain; | |
| 35 | # the GCO_AUTOPILOT entries preserve engine, model, and generated-config choices. | |
| 36 | # | |
| 37 | # Values naming files outside ~/.aws are forwarded but require a matching mount. | |
| 38 | # Likewise, GCO_AUTOPILOT_CONFIG_DIR must be a writable path as seen *inside* | |
| 39 | # the container (normally under /root/.gco or /workspace), not an arbitrary host | |
| 40 | # path. Filesystem-bearing plugin paths are intentionally not forwarded. | |
| 41 | 77 | FORWARDED_ENV_VARS=( |
| 42 | AWS_PROFILE | |
| 43 | AWS_DEFAULT_PROFILE | |
| 44 | AWS_REGION | |
| 45 | AWS_DEFAULT_REGION | |
| 46 | AWS_ACCESS_KEY_ID | |
| 47 | AWS_SECRET_ACCESS_KEY | |
| 48 | AWS_SESSION_TOKEN | |
| 49 | AWS_CREDENTIAL_EXPIRATION | |
| 50 | AWS_ROLE_ARN | |
| 51 | AWS_ROLE_SESSION_NAME | |
| 52 | AWS_WEB_IDENTITY_TOKEN_FILE | |
| 53 | AWS_CONFIG_FILE | |
| 54 | AWS_SHARED_CREDENTIALS_FILE | |
| 55 | AWS_CA_BUNDLE | |
| 56 | AWS_ENDPOINT_URL | |
| 57 | AWS_USE_FIPS_ENDPOINT | |
| 58 | AWS_USE_DUALSTACK_ENDPOINT | |
| 59 | AWS_RETRY_MODE | |
| 60 | AWS_MAX_ATTEMPTS | |
| 61 | AWS_EC2_METADATA_DISABLED | |
| 62 | GCO_DEFAULT_REGION | |
| 63 | GCO_AUTOPILOT_ENGINE | |
| 64 | GCO_AUTOPILOT_MODEL | |
| 65 | GCO_AUTOPILOT_CODEX_MODEL | |
| 66 | GCO_AUTOPILOT_SMALL_FAST_MODEL | |
| 67 | GCO_AUTOPILOT_CONFIG_DIR | |
| 68 | ) | |
| 69 | ||
| 70 | # The dev image is built from Dockerfile.dev at the repository root. Resolve it | |
| 71 | # from this script's own location so the build works from any working directory. | |
| 72 | 308 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" |
| 73 | 231 | REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" |
| 74 | 77 | DOCKERFILE="$REPO_ROOT/Dockerfile.dev" |
| 75 | ||
| 76 | 505 | log() { printf '%s\n' "$*"; } |
| 77 | 5 | warn() { printf 'warning: %s\n' "$*" >&2; } |
| 78 | 22 | die() { printf 'error: %s\n' "$*" >&2; exit 1; } |
| 79 | ||
| 80 | # Values supplied through --runtime, --image, CDK_DOCKER, or | |
| 81 | # XDG_RUNTIME_DIR are persisted into a shell profile and therefore cross a | |
| 82 | # second shell-parsing boundary. Emit simple image/runtime names unchanged for | |
| 83 | # readability, but POSIX-single-quote anything containing shell syntax. The | |
| 84 | # sed replacement turns each embedded apostrophe into the safe '\'' sequence. | |
| 85 | shell_quote() { | |
| 86 | 65 | printf "'" |
| 87 | 130 | printf '%s' "$1" | sed "s/'/'\\\\''/g" |
| 88 | 65 | printf "'" |
| 89 | } | |
| 90 | ||
| 91 | shell_word() { | |
| 92 | 138 | case "$1" in |
| 93 | 2 | ""|*[!A-Za-z0-9_./:@+-]*) shell_quote "$1" ;; |
| 94 | 136 | *) printf '%s' "$1" ;; |
| 95 | esac | |
| 96 | } | |
| 97 | ||
| 98 | require_single_line() { | |
| 99 | 151 | local label="$1" value="$2" |
| 100 | 229 | case "$value" in |
| 101 | 2 | *$'\n'*|*$'\r'*) die "$label must not contain line breaks" ;; |
| 102 | esac | |
| 103 | } | |
| 104 | ||
| 105 | usage() { | |
| 106 | 1 | cat <<'EOF' |
| 107 | setup-dev-alias.sh — install a `gco` shell function for the dev container. | |
| 108 | ||
| 109 | Usage: scripts/setup-dev-alias.sh [options] | |
| 110 | ||
| 111 | -p, --print Print the shell function to stdout and exit (no build, no writes). | |
| 112 | -r, --runtime NAME Force a runtime (docker|finch|podman) vs auto-detecting. | |
| 113 | --rc PATH Target this rc file instead of the one inferred from $SHELL. | |
| 114 | --image NAME Dev image to build and run (default: gco-dev). | |
| 115 | --no-build Skip building the dev image; assume it already exists. | |
| 116 | --aws-writable Mount ~/.aws read-write so `aws sso login` can run in | |
| 117 | the container and cache its token for the host (default: | |
| 118 | read-only). | |
| 119 | --uninstall Remove the managed block from the rc file and exit. | |
| 120 | -h, --help Show this help and exit. | |
| 121 | ||
| 122 | By default the script builds (or refreshes) the dev image from Dockerfile.dev | |
| 123 | with the detected runtime, then installs the `gco` function. Detection prefers | |
| 124 | docker, then finch, then podman (the first whose daemon answers `<rt> info`). | |
| 125 | GCO_CONTAINER_RUNTIME or CDK_DOCKER override detection. | |
| 126 | ||
| 127 | AWS credentials: the function mounts ~/.aws and forwards the standard AWS | |
| 128 | environment variables (AWS_PROFILE, AWS_REGION, static keys, session tokens, | |
| 129 | role/web-identity settings, endpoint and retry overrides) only when the calling | |
| 130 | shell has them set. So `AWS_PROFILE=prod gco status`, an exported SSO or | |
| 131 | assume-role session, static keys, and a plain ~/.aws/config all work the same | |
| 132 | way inside the container as they do on the host. | |
| 133 | ||
| 134 | Autopilot controls are also forwarded by name: GCO_AUTOPILOT_ENGINE, | |
| 135 | GCO_AUTOPILOT_MODEL, GCO_AUTOPILOT_CODEX_MODEL, | |
| 136 | GCO_AUTOPILOT_SMALL_FAST_MODEL, and GCO_AUTOPILOT_CONFIG_DIR. Config-directory | |
| 137 | values must name a writable path as seen inside the container. | |
| 138 | EOF | |
| 139 | 1 | } |
| 140 | 1 | |
| 141 | 261 | while [ "$#" -gt 0 ]; do |
| 142 | 185 | case "$1" in |
| 143 | 78 | -p|--print) PRINT_ONLY=1; shift ;; |
| 144 | 198 | -r|--runtime) [ "$#" -ge 2 ] || die "--runtime needs a value"; FORCED_RUNTIME="$2"; shift 2 ;; |
| 145 | 1 | --runtime=*) FORCED_RUNTIME="${1#*=}"; shift ;; |
| 146 | 111 | --rc) [ "$#" -ge 2 ] || die "--rc needs a value"; RC_FILE="$2"; shift 2 ;; |
| 147 | 1 | --rc=*) RC_FILE="${1#*=}"; shift ;; |
| 148 | 57 | --image) [ "$#" -ge 2 ] || die "--image needs a value"; IMAGE="$2"; shift 2 ;; |
| 149 | 1 | --image=*) IMAGE="${1#*=}"; shift ;; |
| 150 | 38 | --no-build) NO_BUILD=1; shift ;; |
| 151 | 2 | --aws-writable) AWS_WRITABLE=1; shift ;; |
| 152 | 6 | --uninstall) UNINSTALL=1; shift ;; |
| 153 | 2 | -h|--help) usage; exit 0 ;; |
| 154 | 1 | *) die "unknown option: $1 (try --help)" ;; |
| 155 | 1 | esac |
| 156 | 1 | done |
| 157 | 1 | |
| 158 | 1 | # Detection mirrors cli/_container_runtime.py: only accept a runtime whose |
| 159 | 1 | # daemon actually answers `<rt> info`. |
| 160 | 1 | runtime_responds() { |
| 161 | 85 | command -v "$1" >/dev/null 2>&1 || return 1 |
| 162 | 75 | "$1" info >/dev/null 2>&1 |
| 163 | 1 | } |
| 164 | 1 | |
| 165 | 1 | detect_runtime() { |
| 166 | 5 | local rt |
| 167 | 12 | for rt in docker finch podman; do |
| 168 | 12 | if runtime_responds "$rt"; then |
| 169 | 3 | printf '%s\n' "$rt" |
| 170 | 3 | return 0 |
| 171 | 1 | fi |
| 172 | 1 | done |
| 173 | 2 | return 1 |
| 174 | 1 | } |
| 175 | 1 | |
| 176 | 1 | resolve_runtime() { |
| 177 | 73 | local override="" |
| 178 | 73 | if [ -n "$FORCED_RUNTIME" ]; then |
| 179 | 66 | override="$FORCED_RUNTIME" |
| 180 | 7 | elif [ -n "${GCO_CONTAINER_RUNTIME:-}" ]; then |
| 181 | 1 | override="$GCO_CONTAINER_RUNTIME" |
| 182 | 6 | elif [ -n "${CDK_DOCKER:-}" ]; then |
| 183 | 1 | override="$CDK_DOCKER" |
| 184 | 1 | fi |
| 185 | 73 | if [ -n "$override" ]; then |
| 186 | 73 | runtime_responds "$override" || warn "runtime '$override' is not answering '$override info' yet; using it anyway" |
| 187 | 68 | printf '%s\n' "$override" |
| 188 | 68 | return 0 |
| 189 | 1 | fi |
| 190 | 5 | detect_runtime |
| 191 | 1 | } |
| 192 | 1 | |
| 193 | 1 | socket_args_for() { |
| 194 | 71 | local mount="" |
| 195 | 71 | case "$1" in |
| 196 | 54 | docker) mount="/var/run/docker.sock:/var/run/docker.sock" ;; |
| 197 | 9 | podman) mount="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/podman/podman.sock:/var/run/docker.sock" ;; |
| 198 | 8 | *) return 0 ;; |
| 199 | 1 | esac |
| 200 | 126 | printf -- '-v %s ' "$(shell_quote "$mount")" |
| 201 | 1 | } |
| 202 | 1 | |
| 203 | 1 | socket_desc_for() { |
| 204 | 24 | case "$1" in |
| 205 | 23 | docker) printf '%s' "host Docker socket -> /var/run/docker.sock" ;; |
| 206 | 1 | podman) printf '%s' "Podman socket (${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/podman/podman.sock) -> /var/run/docker.sock" ;; |
| 207 | 1 | *) printf '%s' "none ($1 has no host socket to share)" ;; |
| 208 | 1 | esac |
| 209 | 1 | } |
| 210 | 1 | |
| 211 | 1 | choose_rc_file() { |
| 212 | 28 | if [ -n "$RC_FILE" ]; then |
| 213 | 27 | printf '%s\n' "$RC_FILE" |
| 214 | 27 | return |
| 215 | 1 | fi |
| 216 | 3 | case "$(basename "${SHELL:-sh}")" in |
| 217 | 1 | zsh) printf '%s\n' "$HOME/.zshrc" ;; |
| 218 | 1 | bash) printf '%s\n' "$HOME/.bashrc" ;; |
| 219 | 1 | # fish cannot parse POSIX function syntax, so writing this block into a |
| 220 | 1 | # fish config would produce a file fish errors on at every new shell — |
| 221 | 1 | # and writing it to ~/.profile (which fish does not read) would look |
| 222 | 1 | # like a successful install that silently never provides `gco`. Fail |
| 223 | 1 | # loudly with the two real options instead. |
| 224 | 1 | fish) die "fish shell cannot source this POSIX function. |
| 225 | Either run the CLI through a POSIX shell: | |
| 226 | bash -lc 'gco --help' | |
| 227 | or add a fish wrapper of your own using the printed command as the body: | |
| 228 | scripts/setup-dev-alias.sh --print | |
| 229 | Pass --rc PATH to install into a specific file anyway." ;; | |
| 230 | 1 | *) printf '%s\n' "$HOME/.profile" ;; |
| 231 | 1 | esac |
| 232 | 1 | } |
| 233 | 1 | |
| 234 | 1 | # Remove the managed block, leaving any surrounding rc content untouched. |
| 235 | 1 | remove_block() { |
| 236 | 3 | local rc="$1" tmp |
| 237 | 3 | [ -f "$rc" ] || { log "Nothing to remove: $rc does not exist."; return 0; } |
| 238 | 3 | if ! grep -qF "$MARKER_BEGIN" "$rc"; then |
| 239 | 1 | log "Nothing to remove: no gco block found in $rc." |
| 240 | 1 | return 0 |
| 241 | 1 | fi |
| 242 | 4 | tmp="$(mktemp)" |
| 243 | 2 | awk -v b="$MARKER_BEGIN" -v e="$MARKER_END" ' |
| 244 | $0 == b { skip = 1 } | |
| 245 | skip != 1 { print } | |
| 246 | $0 == e { skip = 0 } | |
| 247 | ' "$rc" > "$tmp" | |
| 248 | 2 | mv "$tmp" "$rc" |
| 249 | 2 | log "Removed the 'gco' function block from $rc." |
| 250 | 2 | log "Open a new shell (or unset it with: unset -f gco) to finish." |
| 251 | 1 | } |
| 252 | 1 | |
| 253 | 1 | # SELinux-enforcing hosts (Fedora, RHEL, CentOS Stream and friends) deny a |
| 254 | 1 | # container access to every bind mount unless the mount carries a relabel |
| 255 | 1 | # option. Without it `gco` starts and then fails on "permission denied" for |
| 256 | 1 | # /workspace and ~/.aws, which reads like a GCO bug rather than a host policy. |
| 257 | 1 | # `z` (lowercase) applies a shared label so several containers — and the host — |
| 258 | 1 | # keep access; `Z` would relabel exclusively and break other consumers of |
| 259 | 1 | # ~/.aws. Non-SELinux hosts get no suffix, keeping their command lines clean. |
| 260 | 1 | mount_suffix_for_host() { |
| 261 | 70 | if command -v selinuxenabled >/dev/null 2>&1 && selinuxenabled 2>/dev/null; then |
| 262 | 1 | printf ',z' |
| 263 | 1 | fi |
| 264 | 1 | } |
| 265 | 1 | |
| 266 | 1 | # Emit one bare `-e NAME` flag per forwarded host variable. |
| 267 | 1 | forwarded_env_args() { |
| 268 | 69 | local name |
| 269 | 1794 | for name in "${FORWARDED_ENV_VARS[@]}"; do |
| 270 | 1794 | printf -- '-e %s ' "$name" |
| 271 | 1 | done |
| 272 | 1 | } |
| 273 | 1 | |
| 274 | 1 | emit_block() { |
| 275 | 69 | local rt="$1" socket="$2" image="$3" forwarded_env="$4" mount_opts="$5" rt_word image_word |
| 276 | 138 | rt_word="$(shell_word "$rt")" |
| 277 | 138 | image_word="$(shell_word "$image")" |
| 278 | 1 | # Three persistence mounts make both `gco autopilot` engines (and anything |
| 279 | 1 | # else that keeps state under ~/.gco) survive the --rm container lifecycle: |
| 280 | 1 | # gco-dev-tools -> /root/.npm-global named volume; pinned Claude Code |
| 281 | 1 | # and Codex lazy installs persist |
| 282 | 1 | # ~/.claude -> /root/.claude host dir; CLAUDE_CONFIG_DIR keeps |
| 283 | 1 | # Claude onboarding and transcripts |
| 284 | 1 | # ~/.gco -> /root/.gco host dir; GCO CLI state plus the |
| 285 | 1 | # generated MCP/Codex configs and |
| 286 | 1 | # isolated Codex session state |
| 287 | 1 | # The host dirs are pre-created so a root-owned mount point is never |
| 288 | 1 | # created on Linux hosts. |
| 289 | 69 | cat <<EOF |
| 290 | $MARKER_BEGIN | |
| 291 | # Run the \`gco\` CLI inside the GCO dev container, against \$PWD. | |
| 292 | # Managed by scripts/setup-dev-alias.sh — re-run that script to regenerate | |
| 293 | # after switching container runtimes or image names. | |
| 294 | # ~/.aws is created (empty is fine) so hosts that authenticate purely through | |
| 295 | # environment variables, an OIDC/web-identity file, or instance metadata still | |
| 296 | # get a valid mount source instead of the runtime materialising a root-owned | |
| 297 | # directory on the host. Forwarded host variables use bare \`-e NAME\`, so | |
| 298 | # each is passed only when the calling shell actually has it set. | |
| 299 | gco() { | |
| 300 | mkdir -p "\$HOME/.aws" "\$HOME/.claude" "\$HOME/.gco" | |
| 301 | if [ -t 0 ] && [ -t 1 ]; then | |
| 302 | $rt_word run --rm -it -v "\$HOME/.aws:/root/.aws:${mount_opts}" -v "\$HOME/.claude:/root/.claude" -v "\$HOME/.gco:/root/.gco" -v gco-dev-tools:/root/.npm-global -e CLAUDE_CONFIG_DIR=/root/.claude ${forwarded_env}-v "\$PWD:/workspace" ${socket}-w /workspace $image_word gco "\$@" | |
| 303 | else | |
| 304 | $rt_word run --rm -i -v "\$HOME/.aws:/root/.aws:${mount_opts}" -v "\$HOME/.claude:/root/.claude" -v "\$HOME/.gco:/root/.gco" -v gco-dev-tools:/root/.npm-global -e CLAUDE_CONFIG_DIR=/root/.claude ${forwarded_env}-v "\$PWD:/workspace" ${socket}-w /workspace $image_word gco "\$@" | |
| 305 | fi | |
| 306 | } | |
| 307 | $MARKER_END | |
| 308 | EOF | |
| 309 | } | |
| 310 | ||
| 311 | # Number of times to try the image build, and the backoff between tries. | |
| 312 | # The build's first act is resolving the Dockerfile.dev base image from Docker | |
| 313 | # Hub, which is a public registry the build does not control: a single DNS or | |
| 314 | # TCP timeout there ("failed to resolve source metadata ... i/o timeout") fails | |
| 315 | # an otherwise healthy build. Retrying makes that transient class self-healing | |
| 316 | # while a genuine build error still fails on the last attempt with its own | |
| 317 | # output. Override the count to 1 to disable retries. | |
| 318 | 76 | BUILD_ATTEMPTS="${GCO_DEV_IMAGE_BUILD_ATTEMPTS:-3}" |
| 319 | 76 | BUILD_RETRY_DELAY="${GCO_DEV_IMAGE_BUILD_RETRY_DELAY:-15}" |
| 320 | ||
| 321 | build_image() { | |
| 322 | 14 | local rt="$1" |
| 323 | 14 | [ -f "$DOCKERFILE" ] || die "cannot build '$IMAGE': $DOCKERFILE not found." |
| 324 | 14 | log "Building the '$IMAGE' image from Dockerfile.dev with $rt ..." |
| 325 | 14 | log "(the first build can take a few minutes; re-runs reuse cached layers and just refresh what changed)" |
| 326 | ||
| 327 | 14 | local attempt=1 |
| 328 | 24 | while true; do |
| 329 | 24 | if "$rt" build -f "$DOCKERFILE" -t "$IMAGE" "$REPO_ROOT"; then |
| 330 | 8 | break |
| 331 | fi | |
| 332 | 16 | if [ "$attempt" -ge "$BUILD_ATTEMPTS" ]; then |
| 333 | 6 | die "$rt failed to build '$IMAGE' from $DOCKERFILE." |
| 334 | fi | |
| 335 | 10 | log "build attempt $attempt/$BUILD_ATTEMPTS failed; retrying in ${BUILD_RETRY_DELAY}s ..." |
| 336 | 10 | log "(usually a transient registry/network error pulling the base image)" |
| 337 | 10 | sleep "$BUILD_RETRY_DELAY" |
| 338 | 10 | attempt=$((attempt + 1)) |
| 339 | done | |
| 340 | ||
| 341 | 8 | log "Image '$IMAGE' is ready." |
| 342 | 8 | log "" |
| 343 | } | |
| 344 | ||
| 345 | install_block() { | |
| 346 | 24 | local rc="$1" block="$2" tmp |
| 347 | 48 | tmp="$(mktemp)" |
| 348 | 24 | if [ -f "$rc" ]; then |
| 349 | 5 | awk -v b="$MARKER_BEGIN" -v e="$MARKER_END" ' |
| 350 | $0 == b { skip = 1 } | |
| 351 | skip != 1 { print } | |
| 352 | $0 == e { skip = 0 } | |
| 353 | ' "$rc" > "$tmp" | |
| 354 | 8 | if [ -s "$tmp" ]; then printf '\n' >> "$tmp"; fi |
| 355 | fi | |
| 356 | 24 | printf '%s\n' "$block" >> "$tmp" |
| 357 | 24 | mv "$tmp" "$rc" |
| 358 | } | |
| 359 | ||
| 360 | # Uninstall is pure rc-file surgery: it must work on a machine whose container | |
| 361 | # runtime is gone or broken, so it runs before any runtime detection or build. | |
| 362 | 76 | if [ "$UNINSTALL" -eq 1 ]; then |
| 363 | 6 | remove_block "$(choose_rc_file)" |
| 364 | 3 | exit 0 |
| 365 | fi | |
| 366 | ||
| 367 | 148 | runtime="$(resolve_runtime || true)" |
| 368 | 75 | [ -n "$runtime" ] || die "no container runtime found. Install Docker, Finch, or Podman and start it, then re-run (or force one with --runtime NAME)." |
| 369 | 71 | require_single_line "container runtime" "$runtime" |
| 370 | 71 | if [ "$runtime" = "podman" ]; then |
| 371 | 9 | require_single_line "XDG_RUNTIME_DIR" "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" |
| 372 | fi | |
| 373 | ||
| 374 | 142 | socket="$(socket_args_for "$runtime")" |
| 375 | ||
| 376 | # Podman does not resolve a bare, locally-built image name: an image built with | |
| 377 | # `podman build -t gco-dev` is stored as `localhost/gco-dev`, but `podman run | |
| 378 | # gco-dev` treats the unqualified name as remote and searches the configured | |
| 379 | # registries (docker.io, quay.io, ...) instead of local storage. Prefix | |
| 380 | # `localhost/` so the emitted `podman run` finds the image you built locally. | |
| 381 | # Names that already carry a registry/namespace (contain a `/`) are untouched, | |
| 382 | # and docker/finch — which do resolve bare local names — keep the plain name. | |
| 383 | 71 | image_ref="$IMAGE" |
| 384 | 71 | if [ "$runtime" = "podman" ]; then |
| 385 | 9 | case "$IMAGE" in |
| 386 | 1 | */*) : ;; |
| 387 | 8 | *) image_ref="localhost/$IMAGE" ;; |
| 388 | esac | |
| 389 | fi | |
| 390 | 71 | require_single_line "image name" "$image_ref" |
| 391 | ||
| 392 | 138 | mount_suffix="$(mount_suffix_for_host)" |
| 393 | 69 | if [ "$AWS_WRITABLE" -eq 1 ]; then |
| 394 | # Writable ~/.aws lets `aws sso login` / `aws configure` run INSIDE the | |
| 395 | # container and cache their tokens where the host can reuse them. Opt-in: | |
| 396 | # the read-only default keeps a container that runs third-party tooling | |
| 397 | # from rewriting the operator's credential files. | |
| 398 | 1 | aws_mount_opts="rw${mount_suffix}" |
| 399 | else | |
| 400 | 68 | aws_mount_opts="ro${mount_suffix}" |
| 401 | fi | |
| 402 | ||
| 403 | 207 | block="$(emit_block "$runtime" "$socket" "$image_ref" "$(forwarded_env_args)" "$aws_mount_opts")" |
| 404 | ||
| 405 | 69 | if [ "$PRINT_ONLY" -eq 1 ]; then |
| 406 | 38 | printf '%s\n' "$block" |
| 407 | 38 | exit 0 |
| 408 | fi | |
| 409 | ||
| 410 | # Build (or refresh) the dev image before wiring up the function, so a single | |
| 411 | # run takes a fresh clone all the way to a working `gco`. Always rebuilding also | |
| 412 | # means a stale local image is transparently replaced. Skipped with --no-build. | |
| 413 | 31 | if [ "$NO_BUILD" -eq 1 ]; then |
| 414 | 17 | log "Skipping the image build (--no-build); assuming '$image_ref' already exists." |
| 415 | 17 | log "" |
| 416 | else | |
| 417 | 14 | build_image "$runtime" |
| 418 | fi | |
| 419 | ||
| 420 | 50 | rc="$(choose_rc_file)" |
| 421 | 24 | install_block "$rc" "$block" |
| 422 | ||
| 423 | 24 | log "Installed the 'gco' dev-container function." |
| 424 | 24 | log "" |
| 425 | 24 | log " Container runtime : $runtime" |
| 426 | 48 | log " Socket mount : $(socket_desc_for "$runtime")" |
| 427 | 24 | log " Dev image : $image_ref" |
| 428 | 24 | log " Shell profile : $rc" |
| 429 | 72 | log " AWS credentials : ~/.aws mounted $([ "$AWS_WRITABLE" -eq 1 ] && printf 'read-write' || printf 'read-only') + AWS_PROFILE/AWS_REGION/keys/session" |
| 430 | 24 | log " forwarded from your shell when set" |
| 431 | 24 | log " Autopilot env : engine/model/config controls forwarded when set" |
| 432 | 24 | if [ -n "$mount_suffix" ]; then |
| 433 | 1 | log " SELinux : enforcing host detected; bind mounts carry the ',z' shared label" |
| 434 | fi | |
| 435 | 24 | log "" |
| 436 | 24 | if [ "$AWS_WRITABLE" -eq 0 ]; then |
| 437 | 24 | log "Note: ~/.aws is mounted read-only, so an SSO/session token that expires must be" |
| 438 | 24 | log "refreshed on the host ('aws sso login'); re-run with --aws-writable to allow the" |
| 439 | 24 | log "container to refresh and cache it instead." |
| 440 | fi | |
| 441 | 24 | if [ -n "${AWS_CONFIG_FILE:-}${AWS_SHARED_CREDENTIALS_FILE:-}${AWS_WEB_IDENTITY_TOKEN_FILE:-}" ]; then |
| 442 | 1 | log "" |
| 443 | 1 | log "Note: you have an AWS file-path variable set. It is forwarded, but the file is" |
| 444 | 1 | log "only readable in the container when it lives under ~/.aws (the mounted path)." |
| 445 | 1 | log "Copy or symlink it under ~/.aws, or add your own -v mount to the function." |
| 446 | fi | |
| 447 | 24 | log "" |
| 448 | 24 | log "Activate it in this shell: source \"$rc\"" |
| 449 | 24 | log "Then try: gco --help" |
| 450 | 24 | if [ -z "$socket" ]; then |
| 451 | 1 | log "" |
| 452 | 1 | log "Note: $runtime runs containers inside a VM and exposes no host daemon socket the" |
| 453 | 1 | log "container can reach (a bind-mounted socket connects to nothing across the VM" |
| 454 | 1 | log "boundary), so the function omits the socket mount. Everyday commands — jobs," |
| 455 | 1 | log "status, costs, inference, and non-build stacks operations — work as-is." |
| 456 | 1 | log "" |
| 457 | 1 | log "Build-heavy commands ('gco stacks deploy-all', image builds) need a container" |
| 458 | 1 | log "daemon at CDK synth time, so run them on the host with $runtime as the builder:" |
| 459 | 1 | log "" |
| 460 | 1 | log " CDK_DOCKER=$runtime gco stacks deploy-all -y" |
| 461 | 1 | log "" |
| 462 | 1 | log "Run that against a host install of the GCO CLI whose deps match the lockfile" |
| 463 | 1 | log "(e.g. a project virtualenv), so the pinned aws-cdk-lib / cdk-nag are used." |
| 464 | fi |